mirror of
https://github.com/heyman/heynote.git
synced 2024-12-22 14:40:37 +01:00
WIP: Multiple notes support
Add support for migrating old buffer file to new library. Add support for changing location for the notes library. Replace theme toggle in status bar with a dropdown in Appearance settings. Improve New Note and Update Note dialogs. Implement UI for confirming note delete (the actualal deltion is still to be implemented).
This commit is contained in:
parent
29facb4787
commit
7be0a304d0
1
assets/icons/arrow-right-black.svg
Normal file
1
assets/icons/arrow-right-black.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg baseProfile="tiny" height="24px" version="1.2" viewBox="0 0 24 24" width="24px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><path fill="#222" d="M13.293,7.293c-0.391,0.391-0.391,1.023,0,1.414L15.586,11H8c-0.552,0-1,0.448-1,1s0.448,1,1,1h7.586l-2.293,2.293 c-0.391,0.391-0.391,1.023,0,1.414C13.488,16.902,13.744,17,14,17s0.512-0.098,0.707-0.293L19.414,12l-4.707-4.707 C14.316,6.902,13.684,6.902,13.293,7.293z"/></g></svg>
|
After Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |
1
assets/icons/arrow-right-white.svg
Normal file
1
assets/icons/arrow-right-white.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg baseProfile="tiny" height="24px" version="1.2" viewBox="0 0 24 24" width="24px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><path fill="#fff" d="M13.293,7.293c-0.391,0.391-0.391,1.023,0,1.414L15.586,11H8c-0.552,0-1,0.448-1,1s0.448,1,1,1h7.586l-2.293,2.293 c-0.391,0.391-0.391,1.023,0,1.414C13.488,16.902,13.744,17,14,17s0.512-0.098,0.707-0.293L19.414,12l-4.707-4.707 C14.316,6.902,13.684,6.902,13.293,7.293z"/></g></svg>
|
After Width: | Height: | Size: 522 B |
@ -36,6 +36,8 @@ Available for Mac, Windows, and Linux.
|
|||||||
⌥ + Shift + Enter Add new block at the start of the buffer
|
⌥ + Shift + Enter Add new block at the start of the buffer
|
||||||
⌘ + ⌥ + Enter Split the current block at cursor position
|
⌘ + ⌥ + Enter Split the current block at cursor position
|
||||||
⌘ + L Change block language
|
⌘ + L Change block language
|
||||||
|
⌘ + S Create a new note from the current block
|
||||||
|
⌘ + P Open note selector
|
||||||
⌘ + Down Goto next block
|
⌘ + Down Goto next block
|
||||||
⌘ + Up Goto previous block
|
⌘ + Up Goto previous block
|
||||||
⌘ + A Select all text in a note block. Press again to select the whole buffer
|
⌘ + A Select all text in a note block. Press again to select the whole buffer
|
||||||
@ -52,6 +54,8 @@ Ctrl + Shift + Enter Add new block at the end of the buffer
|
|||||||
Alt + Shift + Enter Add new block at the start of the buffer
|
Alt + Shift + Enter Add new block at the start of the buffer
|
||||||
Ctrl + Alt + Enter Split the current block at cursor position
|
Ctrl + Alt + Enter Split the current block at cursor position
|
||||||
Ctrl + L Change block language
|
Ctrl + L Change block language
|
||||||
|
Ctrl + S Create a new note from the current block
|
||||||
|
Ctrl + P Open note selector
|
||||||
Ctrl + Down Goto next block
|
Ctrl + Down Goto next block
|
||||||
Ctrl + Up Goto previous block
|
Ctrl + Up Goto previous block
|
||||||
Ctrl + A Select all text in a note block. Press again to select the whole buffer
|
Ctrl + A Select all text in a note block. Press again to select the whole buffer
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import os from "os";
|
import os from "os";
|
||||||
import { keyHelpStr } from "../shared-utils/key-helper";
|
import { keyHelpStr } from "../shared-utils/key-helper";
|
||||||
|
|
||||||
export const eraseInitialContent = !!process.env.ERASE_INITIAL_CONTENT
|
|
||||||
|
|
||||||
export const initialContent = `
|
export const initialContent = `
|
||||||
|
{"formatVersion":"1.0.0","name":"Scratch"}
|
||||||
∞∞∞markdown
|
∞∞∞markdown
|
||||||
Welcome to Heynote! 👋
|
Welcome to Heynote! 👋
|
||||||
|
|
||||||
|
@ -1,172 +0,0 @@
|
|||||||
import fs from "fs"
|
|
||||||
import os from "node:os"
|
|
||||||
import { join, dirname, basename } from "path"
|
|
||||||
import { app, ipcMain, dialog } from "electron"
|
|
||||||
import * as jetpack from "fs-jetpack";
|
|
||||||
|
|
||||||
import CONFIG from "../config"
|
|
||||||
import { isDev } from "../detect-platform"
|
|
||||||
import { win } from "./index"
|
|
||||||
import { eraseInitialContent, initialContent, initialDevContent } from '../initial-content'
|
|
||||||
|
|
||||||
const untildify = (pathWithTilde) => {
|
|
||||||
const homeDirectory = os.homedir();
|
|
||||||
return homeDirectory
|
|
||||||
? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory)
|
|
||||||
: pathWithTilde;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function constructBufferFilePath(directoryPath, path) {
|
|
||||||
return join(untildify(directoryPath), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFullBufferFilePath(path) {
|
|
||||||
let defaultPath = app.getPath("userData")
|
|
||||||
let configPath = CONFIG.get("settings.bufferPath")
|
|
||||||
let bufferPath = configPath.length ? configPath : defaultPath
|
|
||||||
let bufferFilePath = constructBufferFilePath(bufferPath, path)
|
|
||||||
try {
|
|
||||||
// use realpathSync to resolve a potential symlink
|
|
||||||
return fs.realpathSync(bufferFilePath)
|
|
||||||
} catch (err) {
|
|
||||||
// realpathSync will fail if the file does not exist, but that doesn't matter since the file will be created
|
|
||||||
if (err.code !== "ENOENT") {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
return bufferFilePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Buffer {
|
|
||||||
constructor({filePath, onChange}) {
|
|
||||||
this.filePath = filePath
|
|
||||||
this.onChange = onChange
|
|
||||||
this.watcher = null
|
|
||||||
this.setupWatcher()
|
|
||||||
this._lastSavedContent = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
const content = await jetpack.read(this.filePath, 'utf8')
|
|
||||||
this.setupWatcher()
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(content) {
|
|
||||||
this._lastSavedContent = content
|
|
||||||
const saveResult = await jetpack.write(this.filePath, content, {
|
|
||||||
atomic: true,
|
|
||||||
mode: '600',
|
|
||||||
})
|
|
||||||
return saveResult
|
|
||||||
}
|
|
||||||
|
|
||||||
exists() {
|
|
||||||
return jetpack.exists(this.filePath) === "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
setupWatcher() {
|
|
||||||
if (!this.watcher && this.exists()) {
|
|
||||||
this.watcher = fs.watch(
|
|
||||||
dirname(this.filePath),
|
|
||||||
{
|
|
||||||
persistent: true,
|
|
||||||
recursive: false,
|
|
||||||
encoding: "utf8",
|
|
||||||
},
|
|
||||||
async (eventType, filename) => {
|
|
||||||
if (filename !== basename(this.filePath)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the file content and compare it to the last saved content
|
|
||||||
// (if the content is the same, then we can ignore the event)
|
|
||||||
const content = await jetpack.read(this.filePath, 'utf8')
|
|
||||||
|
|
||||||
if (this._lastSavedContent !== content) {
|
|
||||||
// file has changed on disk, trigger onChange
|
|
||||||
this.onChange(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
if (this.watcher) {
|
|
||||||
this.watcher.close()
|
|
||||||
this.watcher = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Buffer
|
|
||||||
let buffers = {}
|
|
||||||
export function loadBuffer(path) {
|
|
||||||
if (buffers[path]) {
|
|
||||||
buffers[path].close()
|
|
||||||
}
|
|
||||||
buffers[path] = new Buffer({
|
|
||||||
filePath: getFullBufferFilePath(path),
|
|
||||||
onChange: (content) => {
|
|
||||||
console.log("Old buffer.js onChange")
|
|
||||||
win?.webContents.send("buffer-content:change", path, content)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return buffers[path]
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle('buffer-content:load', async (event, path) => {
|
|
||||||
if (!buffers[path]) {
|
|
||||||
loadBuffer(path)
|
|
||||||
}
|
|
||||||
if (buffers[path].exists() && !(eraseInitialContent && isDev)) {
|
|
||||||
return await buffers[path].load()
|
|
||||||
} else {
|
|
||||||
return isDev ? initialDevContent : initialContent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function save(path, content) {
|
|
||||||
return await buffers[path].save(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle('buffer-content:save', async (event, path, content) => {
|
|
||||||
return await save(path, content)
|
|
||||||
});
|
|
||||||
|
|
||||||
export let contentSaved = false
|
|
||||||
ipcMain.handle('buffer-content:saveAndQuit', async (event, contents) => {
|
|
||||||
for (const [path, content] of contents) {
|
|
||||||
await save(path, content)
|
|
||||||
}
|
|
||||||
contentSaved = true
|
|
||||||
app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle("buffer-content:selectLocation", async () => {
|
|
||||||
let result = await dialog.showOpenDialog({
|
|
||||||
title: "Select directory to store buffer",
|
|
||||||
properties: [
|
|
||||||
"openDirectory",
|
|
||||||
"createDirectory",
|
|
||||||
"noResolveAliases",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (result.canceled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const filePath = result.filePaths[0]
|
|
||||||
if (fs.existsSync(constructBufferFilePath(filePath))) {
|
|
||||||
if (dialog.showMessageBoxSync({
|
|
||||||
type: "question",
|
|
||||||
message: "The selected directory already contains a buffer file. It will be loaded. Do you want to continue?",
|
|
||||||
buttons: ["Cancel", "Continue"],
|
|
||||||
}) === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filePath
|
|
||||||
})
|
|
@ -5,6 +5,16 @@ import { join, dirname, basename } from "path"
|
|||||||
import * as jetpack from "fs-jetpack";
|
import * as jetpack from "fs-jetpack";
|
||||||
import { app, ipcMain, dialog } from "electron"
|
import { app, ipcMain, dialog } from "electron"
|
||||||
|
|
||||||
|
import CONFIG from "../config"
|
||||||
|
import { SCRATCH_FILE_NAME } from "../../src/common/constants"
|
||||||
|
import { NoteFormat } from "../../src/common/note-format"
|
||||||
|
import { isDev } from '../detect-platform';
|
||||||
|
import { initialContent, initialDevContent } from '../initial-content'
|
||||||
|
|
||||||
|
export const NOTES_DIR_NAME = isDev ? "notes-dev" : "notes"
|
||||||
|
|
||||||
|
|
||||||
|
let library
|
||||||
|
|
||||||
const untildify = (pathWithTilde) => {
|
const untildify = (pathWithTilde) => {
|
||||||
const homeDir = os.homedir()
|
const homeDir = os.homedir()
|
||||||
@ -42,6 +52,11 @@ export class FileLibrary {
|
|||||||
this.watcher = null;
|
this.watcher = null;
|
||||||
this.contentSaved = false
|
this.contentSaved = false
|
||||||
this.onChangeCallback = null
|
this.onChangeCallback = null
|
||||||
|
|
||||||
|
// create scratch.txt if it doesn't exist
|
||||||
|
if (!this.jetpack.exists(SCRATCH_FILE_NAME)) {
|
||||||
|
this.jetpack.write(SCRATCH_FILE_NAME, isDev ? initialDevContent : initialContent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists(path) {
|
async exists(path) {
|
||||||
@ -82,7 +97,7 @@ export class FileLibrary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getList() {
|
async getList() {
|
||||||
console.log("Loading notes")
|
//console.log("Listing notes")
|
||||||
const notes = {}
|
const notes = {}
|
||||||
const files = await this.jetpack.findAsync(".", {
|
const files = await this.jetpack.findAsync(".", {
|
||||||
matching: "*.txt",
|
matching: "*.txt",
|
||||||
@ -199,10 +214,13 @@ export class NoteBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setCurrentFileLibrary(lib) {
|
||||||
|
library = lib
|
||||||
|
}
|
||||||
|
|
||||||
export function setupFileLibraryEventHandlers(library, win) {
|
export function setupFileLibraryEventHandlers(win) {
|
||||||
ipcMain.handle('buffer:load', async (event, path) => {
|
ipcMain.handle('buffer:load', async (event, path) => {
|
||||||
console.log("buffer:load", path)
|
//console.log("buffer:load", path)
|
||||||
return await library.load(path)
|
return await library.load(path)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -244,5 +262,71 @@ export function setupFileLibraryEventHandlers(library, win) {
|
|||||||
return await library.move(path, newPath)
|
return await library.move(path, newPath)
|
||||||
});
|
});
|
||||||
|
|
||||||
library.setupWatcher(win)
|
ipcMain.handle("library:selectLocation", async () => {
|
||||||
|
let result = await dialog.showOpenDialog({
|
||||||
|
title: "Select directory to store buffer",
|
||||||
|
properties: [
|
||||||
|
"openDirectory",
|
||||||
|
"createDirectory",
|
||||||
|
"noResolveAliases",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
if (result.canceled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const filePath = result.filePaths[0]
|
||||||
|
return filePath
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function migrateBufferFileToLibrary(app) {
|
||||||
|
async function ensureBufferFileMetadata(filePath) {
|
||||||
|
const metadata = await readNoteMetadata(filePath)
|
||||||
|
//console.log("Metadata", metadata)
|
||||||
|
if (!metadata || !metadata.name) {
|
||||||
|
console.log("Adding metadata to", filePath)
|
||||||
|
const note = NoteFormat.load(jetpack.read(filePath))
|
||||||
|
note.metadata.name = "Scratch"
|
||||||
|
jetpack.write(filePath, note.serialize())
|
||||||
|
} else {
|
||||||
|
console.log("Metadata already exists for", filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLibraryPath = join(app.getPath("userData"), NOTES_DIR_NAME)
|
||||||
|
const customBufferPath = CONFIG.get("settings.bufferPath")
|
||||||
|
const oldBufferFile = isDev ? "buffer-dev.txt" : "buffer.txt"
|
||||||
|
if (customBufferPath) {
|
||||||
|
// if the new buffer file exists, no need to migrate
|
||||||
|
if (jetpack.exists(join(customBufferPath, SCRATCH_FILE_NAME)) === "file") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const oldBufferFileFullPath = join(customBufferPath, oldBufferFile)
|
||||||
|
if (jetpack.exists(oldBufferFileFullPath) === "file") {
|
||||||
|
const newFileFullPath = join(customBufferPath, SCRATCH_FILE_NAME);
|
||||||
|
console.log(`Migrating file ${oldBufferFileFullPath} to ${newFileFullPath}`)
|
||||||
|
// rename buffer file to scratch.txt
|
||||||
|
jetpack.move(oldBufferFileFullPath, newFileFullPath)
|
||||||
|
// add metadata to scratch.txt (just to be sure, we'll double check that it's needed first)
|
||||||
|
await ensureBufferFileMetadata(newFileFullPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the new buffer file exists, no need to migrate
|
||||||
|
if (jetpack.exists(join(defaultLibraryPath, SCRATCH_FILE_NAME)) === "file") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if the old buffer file exists, while the default *library* path doesn't exist
|
||||||
|
const oldBufferFileFullPath = join(app.getPath("userData"), oldBufferFile)
|
||||||
|
if (jetpack.exists(oldBufferFileFullPath) === "file" && jetpack.exists(defaultLibraryPath) !== "dir") {
|
||||||
|
const newFileFullPath = join(defaultLibraryPath, SCRATCH_FILE_NAME);
|
||||||
|
console.log(`Migrating buffer file ${oldBufferFileFullPath} to ${newFileFullPath}`)
|
||||||
|
// create the default library path
|
||||||
|
jetpack.dir(defaultLibraryPath)
|
||||||
|
// move the buffer file to the library path
|
||||||
|
jetpack.move(oldBufferFileFullPath, newFileFullPath)
|
||||||
|
// add metadata to scratch.txt
|
||||||
|
await ensureBufferFileMetadata(newFileFullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,8 +9,13 @@ import CONFIG from "../config"
|
|||||||
import { isDev, isLinux, isMac, isWindows } from '../detect-platform';
|
import { isDev, isLinux, isMac, isWindows } from '../detect-platform';
|
||||||
import { initializeAutoUpdate, checkForUpdates } from './auto-update';
|
import { initializeAutoUpdate, checkForUpdates } from './auto-update';
|
||||||
import { fixElectronCors } from './cors';
|
import { fixElectronCors } from './cors';
|
||||||
import { loadBuffer, contentSaved } from './buffer';
|
import {
|
||||||
import { FileLibrary, setupFileLibraryEventHandlers } from './file-library';
|
FileLibrary,
|
||||||
|
setupFileLibraryEventHandlers,
|
||||||
|
setCurrentFileLibrary,
|
||||||
|
migrateBufferFileToLibrary,
|
||||||
|
NOTES_DIR_NAME
|
||||||
|
} from './file-library';
|
||||||
|
|
||||||
|
|
||||||
// The built directory structure
|
// The built directory structure
|
||||||
@ -310,7 +315,9 @@ function registerAlwaysOnTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(createWindow).then(async () => {
|
app.whenReady().then(createWindow).then(async () => {
|
||||||
setupFileLibraryEventHandlers(fileLibrary, win)
|
initFileLibrary(win).then(() => {
|
||||||
|
setupFileLibraryEventHandlers(win)
|
||||||
|
})
|
||||||
initializeAutoUpdate(win)
|
initializeAutoUpdate(win)
|
||||||
registerGlobalHotkey()
|
registerGlobalHotkey()
|
||||||
registerShowInDock()
|
registerShowInDock()
|
||||||
@ -352,14 +359,28 @@ ipcMain.handle('dark-mode:set', (event, mode) => {
|
|||||||
|
|
||||||
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
|
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
|
||||||
|
|
||||||
|
|
||||||
// Initialize note/file library
|
// Initialize note/file library
|
||||||
const customLibraryPath = CONFIG.get("settings.bufferPath")
|
async function initFileLibrary(win) {
|
||||||
const libraryPath = customLibraryPath ? customLibraryPath : join(app.getPath("userData"), "notes")
|
await migrateBufferFileToLibrary(app)
|
||||||
console.log("libraryPath", libraryPath)
|
|
||||||
try {
|
const customLibraryPath = CONFIG.get("settings.bufferPath")
|
||||||
fileLibrary = new FileLibrary(libraryPath)
|
const defaultLibraryPath = join(app.getPath("userData"), NOTES_DIR_NAME)
|
||||||
} catch (error) {
|
const libraryPath = customLibraryPath ? customLibraryPath : defaultLibraryPath
|
||||||
initErrors.push(`Error: ${error.message}`)
|
//console.log("libraryPath", libraryPath)
|
||||||
|
|
||||||
|
// if we're using the default library path, and it doesn't exist (e.g. first time run), create it
|
||||||
|
if (!customLibraryPath && !fs.existsSync(defaultLibraryPath)) {
|
||||||
|
fs.mkdirSync(defaultLibraryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileLibrary = new FileLibrary(libraryPath)
|
||||||
|
fileLibrary.setupWatcher(win)
|
||||||
|
} catch (error) {
|
||||||
|
initErrors.push(`Error: ${error.message}`)
|
||||||
|
}
|
||||||
|
setCurrentFileLibrary(fileLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle("getInitErrors", () => {
|
ipcMain.handle("getInitErrors", () => {
|
||||||
@ -393,9 +414,10 @@ ipcMain.handle('settings:set', async (event, settings) => {
|
|||||||
registerAlwaysOnTop()
|
registerAlwaysOnTop()
|
||||||
}
|
}
|
||||||
if (bufferPathChanged) {
|
if (bufferPathChanged) {
|
||||||
const buffer = loadBuffer()
|
console.log("bufferPath changed, closing existing file library")
|
||||||
if (buffer.exists()) {
|
fileLibrary.close()
|
||||||
win?.webContents.send("buffer-content:change", await buffer.load())
|
console.log("initializing new file library")
|
||||||
}
|
initFileLibrary(win)
|
||||||
|
await win.webContents.send("library:pathChanged")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -108,12 +108,11 @@ contextBridge.exposeInMainWorld("heynote", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async selectLocation() {
|
async selectLocation() {
|
||||||
return await ipcRenderer.invoke("buffer-content:selectLocation")
|
return await ipcRenderer.invoke("library:selectLocation")
|
||||||
},
|
},
|
||||||
|
|
||||||
callbacks(callbacks) {
|
setLibraryPathChangeCallback(callback) {
|
||||||
ipcRenderer.on("buffer:noteMetadataChanged", (event, path, info) => callbacks?.noteMetadataChanged(path, info))
|
ipcRenderer.on("library:pathChanged", callback)
|
||||||
ipcRenderer.on("buffer:noteRemoved", (event, path) => callbacks?.noteRemoved(path))
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ export const keyHelpStr = (platform: string) => {
|
|||||||
[`${altChar} + Shift + Enter`, "Add new block at the start of the buffer"],
|
[`${altChar} + Shift + Enter`, "Add new block at the start of the buffer"],
|
||||||
[`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"],
|
[`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"],
|
||||||
[`${modChar} + L`, "Change block language"],
|
[`${modChar} + L`, "Change block language"],
|
||||||
|
[`${modChar} + S`, "Create a new note from the current block"],
|
||||||
|
[`${modChar} + P`, "Open note selector"],
|
||||||
[`${modChar} + Down`, "Goto next block"],
|
[`${modChar} + Down`, "Goto next block"],
|
||||||
[`${modChar} + Up`, "Goto previous block"],
|
[`${modChar} + Up`, "Goto previous block"],
|
||||||
[`${modChar} + A`, "Select all text in a note block. Press again to select the whole buffer"],
|
[`${modChar} + A`, "Select all text in a note block. Press again to select the whole buffer"],
|
||||||
|
1
src/common/constants.js
Normal file
1
src/common/constants.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const SCRATCH_FILE_NAME = "scratch.txt"
|
@ -137,6 +137,11 @@
|
|||||||
this.$refs.editor.focus()
|
this.$refs.editor.focus()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setTheme(theme) {
|
||||||
|
window.heynote.themeMode.set(theme)
|
||||||
|
this.themeSetting = theme
|
||||||
|
},
|
||||||
|
|
||||||
onSelectLanguage(language) {
|
onSelectLanguage(language) {
|
||||||
this.closeDialog()
|
this.closeDialog()
|
||||||
this.$refs.editor.setLanguage(language)
|
this.$refs.editor.setLanguage(language)
|
||||||
@ -170,11 +175,8 @@
|
|||||||
ref="editor"
|
ref="editor"
|
||||||
/>
|
/>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
:theme="theme"
|
|
||||||
:themeSetting="themeSetting"
|
|
||||||
:autoUpdate="settings.autoUpdate"
|
:autoUpdate="settings.autoUpdate"
|
||||||
:allowBetaVersions="settings.allowBetaVersions"
|
:allowBetaVersions="settings.allowBetaVersions"
|
||||||
@toggleTheme="toggleTheme"
|
|
||||||
@openNoteSelector="openNoteSelector"
|
@openNoteSelector="openNoteSelector"
|
||||||
@openLanguageSelector="openLanguageSelector"
|
@openLanguageSelector="openLanguageSelector"
|
||||||
@formatCurrentBlock="formatCurrentBlock"
|
@formatCurrentBlock="formatCurrentBlock"
|
||||||
@ -196,7 +198,9 @@
|
|||||||
<Settings
|
<Settings
|
||||||
v-if="showSettings"
|
v-if="showSettings"
|
||||||
:initialSettings="settings"
|
:initialSettings="settings"
|
||||||
|
:themeSetting="themeSetting"
|
||||||
@closeSettings="closeSettings"
|
@closeSettings="closeSettings"
|
||||||
|
@setTheme="setTheme"
|
||||||
/>
|
/>
|
||||||
<NewNote
|
<NewNote
|
||||||
v-if="showCreateNote"
|
v-if="showCreateNote"
|
||||||
|
@ -97,14 +97,26 @@
|
|||||||
|
|
||||||
onKeydown(event) {
|
onKeydown(event) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.$emit("close")
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
this.cancel()
|
||||||
} if (event.key === "Enter") {
|
} if (event.key === "Enter") {
|
||||||
this.submit()
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
this.submit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onCancelKeydown(event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.$emit("close")
|
||||||
|
},
|
||||||
|
|
||||||
onInputKeydown(event) {
|
onInputKeydown(event) {
|
||||||
// redirect arrow keys and page up/down to folder selector
|
// redirect arrow keys and page up/down to folder selector
|
||||||
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
||||||
@ -171,7 +183,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<button type="submit">Create Note</button>
|
<button type="submit">Update Note</button>
|
||||||
|
<button
|
||||||
|
class="cancel"
|
||||||
|
@keydown="onCancelKeydown"
|
||||||
|
@click.stop.prevent="cancel"
|
||||||
|
>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -256,7 +273,7 @@
|
|||||||
padding: 10px
|
padding: 10px
|
||||||
padding-top: 0
|
padding-top: 0
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: flex-end
|
justify-content: space-between
|
||||||
button
|
button
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
height: 28px
|
height: 28px
|
||||||
@ -270,5 +287,9 @@
|
|||||||
background: #444
|
background: #444
|
||||||
border: none
|
border: none
|
||||||
color: rgba(255,255,255, 0.75)
|
color: rgba(255,255,255, 0.75)
|
||||||
|
&[type="submit"]
|
||||||
|
order: 1
|
||||||
|
&.cancel
|
||||||
|
order: 0
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -86,9 +86,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
currentNotePath(path) {
|
loadNewEditor() {
|
||||||
//console.log("currentNotePath changed to", path)
|
//console.log("currentNotePath changed to", path)
|
||||||
this.loadBuffer(path)
|
this.loadBuffer(this.currentNotePath)
|
||||||
},
|
},
|
||||||
|
|
||||||
theme(newTheme) {
|
theme(newTheme) {
|
||||||
@ -152,11 +152,16 @@
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useNotesStore, [
|
...mapState(useNotesStore, [
|
||||||
"currentNotePath",
|
"currentNotePath",
|
||||||
|
"libraryId",
|
||||||
]),
|
]),
|
||||||
...mapWritableState(useNotesStore, [
|
...mapWritableState(useNotesStore, [
|
||||||
"currentEditor",
|
"currentEditor",
|
||||||
"currentNoteName",
|
"currentNoteName",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
loadNewEditor() {
|
||||||
|
return `${this.currentNotePath}|${this.libraryId}`
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -96,6 +96,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onCancelKeydown(event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.$emit("close")
|
||||||
|
},
|
||||||
|
|
||||||
onInputKeydown(event) {
|
onInputKeydown(event) {
|
||||||
// redirect arrow keys and page up/down to folder selector
|
// redirect arrow keys and page up/down to folder selector
|
||||||
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
|
||||||
@ -162,6 +174,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<button type="submit">Create Note</button>
|
<button type="submit">Create Note</button>
|
||||||
|
<button
|
||||||
|
class="cancel"
|
||||||
|
@keydown="onCancelKeydown"
|
||||||
|
@click.stop.prevent="cancel"
|
||||||
|
>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -246,7 +263,7 @@
|
|||||||
padding: 10px
|
padding: 10px
|
||||||
padding-top: 0
|
padding-top: 0
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: flex-end
|
justify-content: space-between
|
||||||
button
|
button
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
height: 28px
|
height: 28px
|
||||||
@ -260,5 +277,9 @@
|
|||||||
background: #444
|
background: #444
|
||||||
border: none
|
border: none
|
||||||
color: rgba(255,255,255, 0.75)
|
color: rgba(255,255,255, 0.75)
|
||||||
|
&[type="submit"]
|
||||||
|
order: 1
|
||||||
|
&.cancel
|
||||||
|
order: 0
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
import { mapState, mapActions } from 'pinia'
|
import { mapState, mapActions } from 'pinia'
|
||||||
import { toRaw } from 'vue';
|
import { toRaw } from 'vue';
|
||||||
import { useNotesStore, SCRATCH_FILE } from "../stores/notes-store"
|
import { SCRATCH_FILE_NAME } from "../common/constants"
|
||||||
|
import { useNotesStore } from "../stores/notes-store"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@ -12,7 +13,8 @@
|
|||||||
actionButton: 0,
|
actionButton: 0,
|
||||||
filter: "",
|
filter: "",
|
||||||
items: [],
|
items: [],
|
||||||
SCRATCH_FILE: SCRATCH_FILE,
|
SCRATCH_FILE_NAME: SCRATCH_FILE_NAME,
|
||||||
|
deleteConfirm: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -25,7 +27,7 @@
|
|||||||
"path": path,
|
"path": path,
|
||||||
"name": metadata?.name || path,
|
"name": metadata?.name || path,
|
||||||
"folder": path.split("/").slice(0, -1).join("/"),
|
"folder": path.split("/").slice(0, -1).join("/"),
|
||||||
"scratch": path === SCRATCH_FILE,
|
"scratch": path === SCRATCH_FILE_NAME,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (this.items.length > 1) {
|
if (this.items.length > 1) {
|
||||||
@ -120,25 +122,30 @@
|
|||||||
this.$refs.item[this.selected].scrollIntoView({block: "nearest"})
|
this.$refs.item[this.selected].scrollIntoView({block: "nearest"})
|
||||||
}
|
}
|
||||||
this.actionButton = 0
|
this.actionButton = 0
|
||||||
} else if (event.key === "ArrowRight" && path !== SCRATCH_FILE) {
|
} else if (event.key === "ArrowRight" && path !== SCRATCH_FILE_NAME) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.actionButton = Math.min(2, this.actionButton + 1)
|
this.actionButton = Math.min(2, this.actionButton + 1)
|
||||||
} else if (event.key === "ArrowLeft" && path !== SCRATCH_FILE) {
|
} else if (event.key === "ArrowLeft" && path !== SCRATCH_FILE_NAME) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.actionButton = Math.max(0, this.actionButton - 1)
|
this.actionButton = Math.max(0, this.actionButton - 1)
|
||||||
|
this.deleteConfirm = false
|
||||||
} else if (event.key === "Enter") {
|
} else if (event.key === "Enter") {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (this.actionButton === 1) {
|
if (this.actionButton === 1) {
|
||||||
console.log("edit file:", path)
|
console.log("edit file:", path)
|
||||||
this.editNote(path)
|
this.editNote(path)
|
||||||
} else if (this.actionButton === 2) {
|
} else if (this.actionButton === 2) {
|
||||||
console.log("delete file:", path)
|
this.deleteConfirmNote(path)
|
||||||
} else {
|
} else {
|
||||||
this.selectItem(path)
|
this.selectItem(path)
|
||||||
}
|
}
|
||||||
} else if (event.key === "Escape") {
|
} else if (event.key === "Escape") {
|
||||||
this.$emit("close")
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
if (this.actionButton !== 0) {
|
||||||
|
this.hideActionButtons()
|
||||||
|
} else {
|
||||||
|
this.$emit("close")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -169,8 +176,24 @@
|
|||||||
showActionButtons(idx) {
|
showActionButtons(idx) {
|
||||||
this.selected = idx
|
this.selected = idx
|
||||||
this.actionButton = 1
|
this.actionButton = 1
|
||||||
|
this.deleteConfirm = false
|
||||||
this.$refs.input.focus()
|
this.$refs.input.focus()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hideActionButtons() {
|
||||||
|
this.actionButton = 0
|
||||||
|
this.deleteConfirm = false
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteConfirmNote(path) {
|
||||||
|
if (this.deleteConfirm) {
|
||||||
|
console.log("delete file:", path)
|
||||||
|
} else {
|
||||||
|
this.deleteConfirm = true
|
||||||
|
this.actionButton = 2
|
||||||
|
this.$refs.input.focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -195,18 +218,27 @@
|
|||||||
>
|
>
|
||||||
<span class="name" v-html="item.name" />
|
<span class="name" v-html="item.name" />
|
||||||
<span class="path" v-html="item.folder" />
|
<span class="path" v-html="item.folder" />
|
||||||
<span class="action-buttons">
|
<span :class="{'action-buttons':true, 'visible':actionButton > 0 && idx === selected}">
|
||||||
<button
|
<button
|
||||||
v-if="actionButton > 0 && idx === selected"
|
v-if="actionButton > 0 && idx === selected"
|
||||||
:class="{'selected':actionButton === 1}"
|
:class="{'selected':actionButton === 1}"
|
||||||
|
@click.stop.prevent="editNote(item.path)"
|
||||||
>Edit</button>
|
>Edit</button>
|
||||||
<button
|
<button
|
||||||
v-if="actionButton > 0 && idx === selected"
|
v-if="actionButton > 0 && idx === selected"
|
||||||
:class="{'delete':true, 'selected':actionButton === 2}"
|
:class="{'delete':true, 'selected':actionButton === 2, 'confirm':deleteConfirm}"
|
||||||
>Delete</button>
|
@click.stop.prevent="deleteConfirmNote(item.path)"
|
||||||
|
>
|
||||||
|
<template v-if="deleteConfirm">
|
||||||
|
Really Delete?
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
Delete
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="show-actions"
|
class="show-actions"
|
||||||
v-if="item.path !== SCRATCH_FILE && (actionButton === 0 || idx !== selected)"
|
v-if="item.path !== SCRATCH_FILE_NAME && (actionButton === 0 || idx !== selected)"
|
||||||
@click.stop.prevent="showActionButtons(idx)"
|
@click.stop.prevent="showActionButtons(idx)"
|
||||||
></button>
|
></button>
|
||||||
</span>
|
</span>
|
||||||
@ -273,31 +305,40 @@
|
|||||||
> li
|
> li
|
||||||
position: relative
|
position: relative
|
||||||
border-radius: 3px
|
border-radius: 3px
|
||||||
padding: 5px 12px
|
padding: 3px 12px
|
||||||
|
line-height: 18px
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
&:hover
|
&:hover
|
||||||
background: #e2e2e2
|
background: #e2e2e2
|
||||||
.action-buttons .show-actions
|
.action-buttons .show-actions
|
||||||
display: inline-block
|
display: inline-block
|
||||||
&.selected
|
background-image: url(@/assets/icons/arrow-right-black.svg)
|
||||||
background: #48b57e
|
&.selected .action-buttons .show-actions
|
||||||
color: #fff
|
background-image: url(@/assets/icons/arrow-right-white.svg)
|
||||||
.action-buttons .show-actions
|
|
||||||
display: inline-block
|
|
||||||
&.scratch
|
|
||||||
font-weight: 600
|
|
||||||
+dark-mode
|
+dark-mode
|
||||||
color: rgba(255,255,255, 0.65)
|
color: rgba(255,255,255, 0.65)
|
||||||
&:hover
|
&:hover
|
||||||
background: #29292a
|
background: #29292a
|
||||||
&.selected
|
&.selected
|
||||||
|
background: #48b57e
|
||||||
|
color: #fff
|
||||||
|
&.action-buttons-visible
|
||||||
|
background: none
|
||||||
|
border: 1px solid #48b57e
|
||||||
|
padding: 2px 11px
|
||||||
|
color: #444
|
||||||
|
.action-buttons .show-actions
|
||||||
|
display: inline-block
|
||||||
|
+dark-mode
|
||||||
background: #1b6540
|
background: #1b6540
|
||||||
color: rgba(255,255,255, 0.87)
|
color: rgba(255,255,255, 0.87)
|
||||||
&.action-buttons-visible
|
&.action-buttons-visible
|
||||||
background: none
|
background: none
|
||||||
border: 1px solid #1b6540
|
border: 1px solid #1b6540
|
||||||
padding: 4px 11px
|
color: rgba(255,255,255, 0.65)
|
||||||
|
&.scratch
|
||||||
|
font-weight: 600
|
||||||
.name
|
.name
|
||||||
margin-right: 12px
|
margin-right: 12px
|
||||||
flex-shrink: 0
|
flex-shrink: 0
|
||||||
@ -318,9 +359,15 @@
|
|||||||
.action-buttons
|
.action-buttons
|
||||||
position: absolute
|
position: absolute
|
||||||
top: 1px
|
top: 1px
|
||||||
right: 1px
|
right: 0px
|
||||||
|
padding: 0 1px
|
||||||
|
&.visible
|
||||||
|
background: #efefef
|
||||||
|
+dark-mode
|
||||||
|
background: #151516
|
||||||
button
|
button
|
||||||
padding: 1px 10px
|
padding: 0 10px
|
||||||
|
height: 20px
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
background: none
|
background: none
|
||||||
border: none
|
border: none
|
||||||
@ -330,29 +377,41 @@
|
|||||||
&:last-child
|
&:last-child
|
||||||
margin-right: 0
|
margin-right: 0
|
||||||
&:hover
|
&:hover
|
||||||
background: rgba(255,255,255, 0.1)
|
background: rgba(0,0,0, 0.1)
|
||||||
+dark-mode
|
+dark-mode
|
||||||
//background: #1b6540
|
|
||||||
//&:hover
|
|
||||||
// background:
|
|
||||||
&.selected
|
|
||||||
background: #1b6540
|
|
||||||
&:hover
|
&:hover
|
||||||
background: #1f7449
|
background-color: rgba(255,255,255, 0.1)
|
||||||
|
&.selected
|
||||||
|
background: #48b57e
|
||||||
|
color: #fff
|
||||||
|
&:hover
|
||||||
|
background: #3ea471
|
||||||
&.delete
|
&.delete
|
||||||
background: #ae1e1e
|
background: #e95050
|
||||||
&:hover
|
&:hover
|
||||||
background: #bf2222
|
background: #ce4848
|
||||||
|
+dark-mode
|
||||||
|
background: #1b6540
|
||||||
|
&:hover
|
||||||
|
background: #1f7449
|
||||||
|
&.delete
|
||||||
|
background: #ae1e1e
|
||||||
|
&:hover
|
||||||
|
background: #bf2222
|
||||||
|
&.confirm
|
||||||
|
font-weight: 600
|
||||||
&.show-actions
|
&.show-actions
|
||||||
display: none
|
display: none
|
||||||
position: relative
|
position: relative
|
||||||
top: 1px
|
top: 1px
|
||||||
padding: 1px 8px
|
padding: 1px 8px
|
||||||
//cursor: default
|
//cursor: default
|
||||||
background-image: url(@/assets/icons/arrow-right.svg)
|
background-image: url(@/assets/icons/arrow-right-white.svg)
|
||||||
width: 22px
|
width: 22px
|
||||||
height: 19px
|
height: 19px
|
||||||
background-size: 19px
|
background-size: 19px
|
||||||
background-position: center center
|
background-position: center center
|
||||||
background-repeat: no-repeat
|
background-repeat: no-repeat
|
||||||
|
+dark-mode
|
||||||
|
background-image: url(@/assets/icons/arrow-right-grey.svg)
|
||||||
</style>
|
</style>
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: [
|
props: [
|
||||||
"theme",
|
|
||||||
"themeSetting",
|
|
||||||
"autoUpdate",
|
"autoUpdate",
|
||||||
"allowBetaVersions",
|
"allowBetaVersions",
|
||||||
],
|
],
|
||||||
@ -113,9 +111,6 @@
|
|||||||
:autoUpdate="autoUpdate"
|
:autoUpdate="autoUpdate"
|
||||||
:allowBetaVersions="allowBetaVersions"
|
:allowBetaVersions="allowBetaVersions"
|
||||||
/>
|
/>
|
||||||
<div class="status-block theme clickable" @click="$emit('toggleTheme')" title="Toggle dark/light mode">
|
|
||||||
<span :class="'icon ' + themeSetting"></span>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
@click.stop="$emit('openSettings')"
|
@click.stop="$emit('openSettings')"
|
||||||
class="status-block settings clickable"
|
class="status-block settings clickable"
|
||||||
@ -176,19 +171,6 @@
|
|||||||
color: rgba(255, 255, 255, 0.7)
|
color: rgba(255, 255, 255, 0.7)
|
||||||
+dark-mode
|
+dark-mode
|
||||||
color: rgba(255, 255, 255, 0.55)
|
color: rgba(255, 255, 255, 0.55)
|
||||||
.theme
|
|
||||||
padding-top: 0
|
|
||||||
padding-bottom: 0
|
|
||||||
.icon
|
|
||||||
background-size: 14px
|
|
||||||
background-repeat: no-repeat
|
|
||||||
background-position: center center
|
|
||||||
&.dark
|
|
||||||
background-image: url("@/assets/icons/dark-mode.png")
|
|
||||||
&.light
|
|
||||||
background-image: url("@/assets/icons/light-mode.png")
|
|
||||||
&.system
|
|
||||||
background-image: url("@/assets/icons/both-mode.png")
|
|
||||||
|
|
||||||
.format
|
.format
|
||||||
padding-top: 0
|
padding-top: 0
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
props: {
|
props: {
|
||||||
initialKeymap: String,
|
initialKeymap: String,
|
||||||
initialSettings: Object,
|
initialSettings: Object,
|
||||||
|
themeSetting: String,
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
KeyboardHotkey,
|
KeyboardHotkey,
|
||||||
@ -60,6 +61,7 @@
|
|||||||
systemFonts: [[defaultFontFamily, defaultFontFamily + " (default)"]],
|
systemFonts: [[defaultFontFamily, defaultFontFamily + " (default)"]],
|
||||||
defaultFontSize: defaultFontSize,
|
defaultFontSize: defaultFontSize,
|
||||||
appVersion: "",
|
appVersion: "",
|
||||||
|
theme: this.themeSetting,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -109,6 +111,9 @@
|
|||||||
if (!this.showInDock) {
|
if (!this.showInDock) {
|
||||||
this.showInMenu = true
|
this.showInMenu = true
|
||||||
}
|
}
|
||||||
|
if (this.theme != this.themeSetting) {
|
||||||
|
this.$emit("setTheme", this.theme)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async selectBufferLocation() {
|
async selectBufferLocation() {
|
||||||
@ -293,6 +298,16 @@
|
|||||||
</TabContent>
|
</TabContent>
|
||||||
|
|
||||||
<TabContent tab="appearance" :activeTab="activeTab">
|
<TabContent tab="appearance" :activeTab="activeTab">
|
||||||
|
<div class="row">
|
||||||
|
<div class="entry">
|
||||||
|
<h2>Color Theme</h2>
|
||||||
|
<select v-model="theme" @change="updateSettings" class="theme">
|
||||||
|
<option :selected="theme === 'system'" value="system">System</option>
|
||||||
|
<option :selected="theme === 'light'" value="light">Light</option>
|
||||||
|
<option :selected="theme === 'dark'" value="dark">Dark</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<h2>Gutters</h2>
|
<h2>Gutters</h2>
|
||||||
|
@ -21,7 +21,7 @@ import { languageDetection } from "./language-detection/autodetect.js"
|
|||||||
import { autoSaveContent } from "./save.js"
|
import { autoSaveContent } from "./save.js"
|
||||||
import { todoCheckboxPlugin} from "./todo-checkbox.ts"
|
import { todoCheckboxPlugin} from "./todo-checkbox.ts"
|
||||||
import { links } from "./links.js"
|
import { links } from "./links.js"
|
||||||
import { NoteFormat } from "./note-format.js"
|
import { NoteFormat } from "../common/note-format.js"
|
||||||
import { useNotesStore } from "../stores/notes-store.js";
|
import { useNotesStore } from "../stores/notes-store.js";
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ export class HeynoteEditor {
|
|||||||
if (content === this.diskContent) {
|
if (content === this.diskContent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log("saving:", this.path)
|
//console.log("saving:", this.path)
|
||||||
this.diskContent = content
|
this.diskContent = content
|
||||||
await window.heynote.buffer.save(this.path, content)
|
await window.heynote.buffer.save(this.path, content)
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ export class HeynoteEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadContent() {
|
async loadContent() {
|
||||||
console.log("loading content", this.path)
|
//console.log("loading content", this.path)
|
||||||
const content = await window.heynote.buffer.load(this.path)
|
const content = await window.heynote.buffer.load(this.path)
|
||||||
this.diskContent = content
|
this.diskContent = content
|
||||||
this.contentLoaded = true
|
this.contentLoaded = true
|
||||||
@ -328,11 +328,13 @@ export class HeynoteEditor {
|
|||||||
triggerCurrenciesLoaded(this.view.state, this.view.dispatch)
|
triggerCurrenciesLoaded(this.view.state, this.view.dispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy(save=true) {
|
||||||
if (this.onChange) {
|
if (this.onChange) {
|
||||||
window.heynote.buffer.removeOnChangeCallback(this.path, this.onChange)
|
window.heynote.buffer.removeOnChangeCallback(this.path, this.onChange)
|
||||||
}
|
}
|
||||||
this.save()
|
if (save) {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
this.view.destroy()
|
this.view.destroy()
|
||||||
window.heynote.buffer.close(this.path)
|
window.heynote.buffer.close(this.path)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { toRaw } from 'vue';
|
import { toRaw } from 'vue';
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { NoteFormat } from "../editor/note-format"
|
import { NoteFormat } from "../common/note-format"
|
||||||
|
|
||||||
const NUM_EDITOR_INSTANCES = 5
|
const NUM_EDITOR_INSTANCES = 5
|
||||||
|
|
||||||
@ -44,5 +44,14 @@ export const useEditorCacheStore = defineStore("editorCache", {
|
|||||||
eachEditor(fn) {
|
eachEditor(fn) {
|
||||||
Object.values(toRaw(this.editorCache.cache)).forEach(fn)
|
Object.values(toRaw(this.editorCache.cache)).forEach(fn)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearCache(save=true) {
|
||||||
|
console.log("Clearing editor cache")
|
||||||
|
this.eachEditor((editor) => {
|
||||||
|
editor.destroy(save=save)
|
||||||
|
})
|
||||||
|
this.editorCache.cache = {}
|
||||||
|
this.editorCache.lru = []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
import { toRaw } from 'vue';
|
import { toRaw } from 'vue';
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { NoteFormat } from "../editor/note-format"
|
import { NoteFormat } from "../common/note-format"
|
||||||
import { useEditorCacheStore } from "./editor-cache"
|
import { useEditorCacheStore } from "./editor-cache"
|
||||||
|
import { SCRATCH_FILE_NAME } from "../common/constants"
|
||||||
|
|
||||||
export const SCRATCH_FILE = window.heynote.isDev ? "buffer-dev.txt" : "buffer.txt"
|
|
||||||
|
|
||||||
export const useNotesStore = defineStore("notes", {
|
export const useNotesStore = defineStore("notes", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
notes: {},
|
notes: {},
|
||||||
recentNotePaths: [SCRATCH_FILE],
|
recentNotePaths: [SCRATCH_FILE_NAME],
|
||||||
|
|
||||||
currentEditor: null,
|
currentEditor: null,
|
||||||
currentNotePath: SCRATCH_FILE,
|
currentNotePath: SCRATCH_FILE_NAME,
|
||||||
currentNoteName: null,
|
currentNoteName: null,
|
||||||
currentLanguage: null,
|
currentLanguage: null,
|
||||||
currentLanguageAuto: null,
|
currentLanguageAuto: null,
|
||||||
currentCursorLine: null,
|
currentCursorLine: null,
|
||||||
currentSelectionSize: null,
|
currentSelectionSize: null,
|
||||||
|
libraryId: 0,
|
||||||
|
|
||||||
showNoteSelector: false,
|
showNoteSelector: false,
|
||||||
showLanguageSelector: false,
|
showLanguageSelector: false,
|
||||||
@ -119,10 +120,22 @@ export const useNotesStore = defineStore("notes", {
|
|||||||
this.updateNotes()
|
this.updateNotes()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async reloadLibrary() {
|
||||||
|
const editorCacheStore = useEditorCacheStore()
|
||||||
|
await this.updateNotes()
|
||||||
|
editorCacheStore.clearCache(false)
|
||||||
|
this.currentEditor = null
|
||||||
|
this.currentNotePath = SCRATCH_FILE_NAME
|
||||||
|
this.libraryId++
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function initNotesStore() {
|
export async function initNotesStore() {
|
||||||
const notesStore = useNotesStore()
|
const notesStore = useNotesStore()
|
||||||
|
window.heynote.buffer.setLibraryPathChangeCallback(() => {
|
||||||
|
notesStore.reloadLibrary()
|
||||||
|
})
|
||||||
await notesStore.updateNotes()
|
await notesStore.updateNotes()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { test, expect } from "@playwright/test";
|
import { test, expect } from "@playwright/test";
|
||||||
import { HeynotePage } from "./test-utils.js";
|
import { HeynotePage } from "./test-utils.js";
|
||||||
import { NoteFormat } from "../src/editor/note-format.js";
|
import { NoteFormat } from "../src/common/note-format.js";
|
||||||
|
|
||||||
let heynotePage
|
let heynotePage
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
import { NoteFormat } from '../src/editor/note-format.js';
|
import { NoteFormat } from '../src/common/note-format.js';
|
||||||
|
|
||||||
export function pageErrorGetter(page) {
|
export function pageErrorGetter(page) {
|
||||||
let messages = [];
|
let messages = [];
|
||||||
|
Loading…
Reference in New Issue
Block a user