diff --git a/assets/icons/arrow-right-black.svg b/assets/icons/arrow-right-black.svg new file mode 100644 index 0000000..27ad162 --- /dev/null +++ b/assets/icons/arrow-right-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right-grey.svg similarity index 100% rename from assets/icons/arrow-right.svg rename to assets/icons/arrow-right-grey.svg diff --git a/assets/icons/arrow-right-white.svg b/assets/icons/arrow-right-white.svg new file mode 100644 index 0000000..9130299 --- /dev/null +++ b/assets/icons/arrow-right-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index bef7a83..52f7c2f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,6 +36,8 @@ Available for Mac, Windows, and Linux. ⌥ + Shift + Enter Add new block at the start of the buffer ⌘ + ⌥ + Enter Split the current block at cursor position ⌘ + L Change block language +⌘ + S Create a new note from the current block +⌘ + P Open note selector ⌘ + Down Goto next block ⌘ + Up Goto previous block ⌘ + 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 Ctrl + Alt + Enter Split the current block at cursor position 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 + Up Goto previous block Ctrl + A Select all text in a note block. Press again to select the whole buffer diff --git a/electron/initial-content.ts b/electron/initial-content.ts index d120afe..374f812 100644 --- a/electron/initial-content.ts +++ b/electron/initial-content.ts @@ -1,9 +1,8 @@ import os from "os"; import { keyHelpStr } from "../shared-utils/key-helper"; -export const eraseInitialContent = !!process.env.ERASE_INITIAL_CONTENT - export const initialContent = ` +{"formatVersion":"1.0.0","name":"Scratch"} ∞∞∞markdown Welcome to Heynote! 👋 diff --git a/electron/main/buffer.js b/electron/main/buffer.js deleted file mode 100644 index 0ecc5a7..0000000 --- a/electron/main/buffer.js +++ /dev/null @@ -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 -}) diff --git a/electron/main/file-library.js b/electron/main/file-library.js index bc06a18..6b3649d 100644 --- a/electron/main/file-library.js +++ b/electron/main/file-library.js @@ -5,6 +5,16 @@ import { join, dirname, basename } from "path" import * as jetpack from "fs-jetpack"; 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 homeDir = os.homedir() @@ -42,6 +52,11 @@ export class FileLibrary { this.watcher = null; this.contentSaved = false 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) { @@ -82,7 +97,7 @@ export class FileLibrary { } async getList() { - console.log("Loading notes") + //console.log("Listing notes") const notes = {} const files = await this.jetpack.findAsync(".", { 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) => { - console.log("buffer:load", path) + //console.log("buffer:load", path) return await library.load(path) }); @@ -244,5 +262,71 @@ export function setupFileLibraryEventHandlers(library, win) { 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) + } + } +} \ No newline at end of file diff --git a/electron/main/index.ts b/electron/main/index.ts index 93a24f5..c78a57f 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -9,8 +9,13 @@ import CONFIG from "../config" import { isDev, isLinux, isMac, isWindows } from '../detect-platform'; import { initializeAutoUpdate, checkForUpdates } from './auto-update'; import { fixElectronCors } from './cors'; -import { loadBuffer, contentSaved } from './buffer'; -import { FileLibrary, setupFileLibraryEventHandlers } from './file-library'; +import { + FileLibrary, + setupFileLibraryEventHandlers, + setCurrentFileLibrary, + migrateBufferFileToLibrary, + NOTES_DIR_NAME +} from './file-library'; // The built directory structure @@ -310,7 +315,9 @@ function registerAlwaysOnTop() { } app.whenReady().then(createWindow).then(async () => { - setupFileLibraryEventHandlers(fileLibrary, win) + initFileLibrary(win).then(() => { + setupFileLibraryEventHandlers(win) + }) initializeAutoUpdate(win) registerGlobalHotkey() registerShowInDock() @@ -352,14 +359,28 @@ ipcMain.handle('dark-mode:set', (event, mode) => { ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource) + // Initialize note/file library -const customLibraryPath = CONFIG.get("settings.bufferPath") -const libraryPath = customLibraryPath ? customLibraryPath : join(app.getPath("userData"), "notes") -console.log("libraryPath", libraryPath) -try { - fileLibrary = new FileLibrary(libraryPath) -} catch (error) { - initErrors.push(`Error: ${error.message}`) +async function initFileLibrary(win) { + await migrateBufferFileToLibrary(app) + + const customLibraryPath = CONFIG.get("settings.bufferPath") + const defaultLibraryPath = join(app.getPath("userData"), NOTES_DIR_NAME) + const libraryPath = customLibraryPath ? customLibraryPath : defaultLibraryPath + //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", () => { @@ -393,9 +414,10 @@ ipcMain.handle('settings:set', async (event, settings) => { registerAlwaysOnTop() } if (bufferPathChanged) { - const buffer = loadBuffer() - if (buffer.exists()) { - win?.webContents.send("buffer-content:change", await buffer.load()) - } + console.log("bufferPath changed, closing existing file library") + fileLibrary.close() + console.log("initializing new file library") + initFileLibrary(win) + await win.webContents.send("library:pathChanged") } }) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index bb1e553..9e5da29 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -108,12 +108,11 @@ contextBridge.exposeInMainWorld("heynote", { }, async selectLocation() { - return await ipcRenderer.invoke("buffer-content:selectLocation") + return await ipcRenderer.invoke("library:selectLocation") }, - callbacks(callbacks) { - ipcRenderer.on("buffer:noteMetadataChanged", (event, path, info) => callbacks?.noteMetadataChanged(path, info)) - ipcRenderer.on("buffer:noteRemoved", (event, path) => callbacks?.noteRemoved(path)) + setLibraryPathChangeCallback(callback) { + ipcRenderer.on("library:pathChanged", callback) }, }, diff --git a/shared-utils/key-helper.ts b/shared-utils/key-helper.ts index 9930042..900a78f 100644 --- a/shared-utils/key-helper.ts +++ b/shared-utils/key-helper.ts @@ -9,6 +9,8 @@ export const keyHelpStr = (platform: string) => { [`${altChar} + Shift + Enter`, "Add new block at the start of the buffer"], [`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"], [`${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} + Up`, "Goto previous block"], [`${modChar} + A`, "Select all text in a note block. Press again to select the whole buffer"], diff --git a/src/common/constants.js b/src/common/constants.js new file mode 100644 index 0000000..42c1766 --- /dev/null +++ b/src/common/constants.js @@ -0,0 +1 @@ +export const SCRATCH_FILE_NAME = "scratch.txt" diff --git a/src/editor/note-format.js b/src/common/note-format.js similarity index 100% rename from src/editor/note-format.js rename to src/common/note-format.js diff --git a/src/components/App.vue b/src/components/App.vue index 982b80a..b9683b0 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -137,6 +137,11 @@ this.$refs.editor.focus() }, + setTheme(theme) { + window.heynote.themeMode.set(theme) + this.themeSetting = theme + }, + onSelectLanguage(language) { this.closeDialog() this.$refs.editor.setLanguage(language) @@ -170,11 +175,8 @@ ref="editor" />
- + +
@@ -256,7 +273,7 @@ padding: 10px padding-top: 0 display: flex - justify-content: flex-end + justify-content: space-between button font-size: 12px height: 28px @@ -270,5 +287,9 @@ background: #444 border: none color: rgba(255,255,255, 0.75) + &[type="submit"] + order: 1 + &.cancel + order: 0 diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 93c52af..5358d16 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -86,9 +86,9 @@ }, watch: { - currentNotePath(path) { + loadNewEditor() { //console.log("currentNotePath changed to", path) - this.loadBuffer(path) + this.loadBuffer(this.currentNotePath) }, theme(newTheme) { @@ -152,11 +152,16 @@ computed: { ...mapState(useNotesStore, [ "currentNotePath", + "libraryId", ]), ...mapWritableState(useNotesStore, [ "currentEditor", "currentNoteName", ]), + + loadNewEditor() { + return `${this.currentNotePath}|${this.libraryId}` + }, }, methods: { diff --git a/src/components/NewNote.vue b/src/components/NewNote.vue index 7cf8f20..224b9e5 100644 --- a/src/components/NewNote.vue +++ b/src/components/NewNote.vue @@ -96,6 +96,18 @@ } }, + onCancelKeydown(event) { + if (event.key === "Enter") { + event.preventDefault() + event.stopPropagation() + this.cancel() + } + }, + + cancel() { + this.$emit("close") + }, + onInputKeydown(event) { // redirect arrow keys and page up/down to folder selector const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"] @@ -162,6 +174,11 @@
+
@@ -246,7 +263,7 @@ padding: 10px padding-top: 0 display: flex - justify-content: flex-end + justify-content: space-between button font-size: 12px height: 28px @@ -260,5 +277,9 @@ background: #444 border: none color: rgba(255,255,255, 0.75) + &[type="submit"] + order: 1 + &.cancel + order: 0 diff --git a/src/components/NoteSelector.vue b/src/components/NoteSelector.vue index a08ac5e..a5d6e4b 100644 --- a/src/components/NoteSelector.vue +++ b/src/components/NoteSelector.vue @@ -3,7 +3,8 @@ import { mapState, mapActions } from 'pinia' 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 { data() { @@ -12,7 +13,8 @@ actionButton: 0, filter: "", items: [], - SCRATCH_FILE: SCRATCH_FILE, + SCRATCH_FILE_NAME: SCRATCH_FILE_NAME, + deleteConfirm: false, } }, @@ -25,7 +27,7 @@ "path": path, "name": metadata?.name || path, "folder": path.split("/").slice(0, -1).join("/"), - "scratch": path === SCRATCH_FILE, + "scratch": path === SCRATCH_FILE_NAME, } }) if (this.items.length > 1) { @@ -120,25 +122,30 @@ this.$refs.item[this.selected].scrollIntoView({block: "nearest"}) } this.actionButton = 0 - } else if (event.key === "ArrowRight" && path !== SCRATCH_FILE) { + } else if (event.key === "ArrowRight" && path !== SCRATCH_FILE_NAME) { event.preventDefault() 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() this.actionButton = Math.max(0, this.actionButton - 1) + this.deleteConfirm = false } else if (event.key === "Enter") { event.preventDefault() if (this.actionButton === 1) { console.log("edit file:", path) this.editNote(path) } else if (this.actionButton === 2) { - console.log("delete file:", path) + this.deleteConfirmNote(path) } else { this.selectItem(path) } } else if (event.key === "Escape") { - this.$emit("close") event.preventDefault() + if (this.actionButton !== 0) { + this.hideActionButtons() + } else { + this.$emit("close") + } } }, @@ -169,8 +176,24 @@ showActionButtons(idx) { this.selected = idx this.actionButton = 1 + this.deleteConfirm = false 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() + } + }, } } @@ -195,18 +218,27 @@ > - + + :class="{'delete':true, 'selected':actionButton === 2, 'confirm':deleteConfirm}" + @click.stop.prevent="deleteConfirmNote(item.path)" + > + + + @@ -273,31 +305,40 @@ > li position: relative border-radius: 3px - padding: 5px 12px + padding: 3px 12px + line-height: 18px display: flex align-items: center &:hover background: #e2e2e2 .action-buttons .show-actions display: inline-block - &.selected - background: #48b57e - color: #fff - .action-buttons .show-actions - display: inline-block - &.scratch - font-weight: 600 + background-image: url(@/assets/icons/arrow-right-black.svg) + &.selected .action-buttons .show-actions + background-image: url(@/assets/icons/arrow-right-white.svg) +dark-mode color: rgba(255,255,255, 0.65) &:hover 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 color: rgba(255,255,255, 0.87) &.action-buttons-visible background: none border: 1px solid #1b6540 - padding: 4px 11px + color: rgba(255,255,255, 0.65) + &.scratch + font-weight: 600 .name margin-right: 12px flex-shrink: 0 @@ -318,9 +359,15 @@ .action-buttons position: absolute top: 1px - right: 1px + right: 0px + padding: 0 1px + &.visible + background: #efefef + +dark-mode + background: #151516 button - padding: 1px 10px + padding: 0 10px + height: 20px font-size: 12px background: none border: none @@ -330,29 +377,41 @@ &:last-child margin-right: 0 &:hover - background: rgba(255,255,255, 0.1) + background: rgba(0,0,0, 0.1) +dark-mode - //background: #1b6540 - //&:hover - // background: - &.selected - background: #1b6540 &:hover - background: #1f7449 + background-color: rgba(255,255,255, 0.1) + &.selected + background: #48b57e + color: #fff + &:hover + background: #3ea471 &.delete - background: #ae1e1e + background: #e95050 &:hover - background: #bf2222 + background: #ce4848 + +dark-mode + background: #1b6540 + &:hover + background: #1f7449 + &.delete + background: #ae1e1e + &:hover + background: #bf2222 + &.confirm + font-weight: 600 &.show-actions display: none position: relative top: 1px padding: 1px 8px //cursor: default - background-image: url(@/assets/icons/arrow-right.svg) + background-image: url(@/assets/icons/arrow-right-white.svg) width: 22px height: 19px background-size: 19px background-position: center center background-repeat: no-repeat + +dark-mode + background-image: url(@/assets/icons/arrow-right-grey.svg) diff --git a/src/components/StatusBar.vue b/src/components/StatusBar.vue index a6b4df1..4c7cdef 100644 --- a/src/components/StatusBar.vue +++ b/src/components/StatusBar.vue @@ -9,8 +9,6 @@ export default { props: [ - "theme", - "themeSetting", "autoUpdate", "allowBetaVersions", ], @@ -113,9 +111,6 @@ :autoUpdate="autoUpdate" :allowBetaVersions="allowBetaVersions" /> -
- -
+
+
+

Color Theme

+ +
+

Gutters

diff --git a/src/editor/editor.js b/src/editor/editor.js index 754e5c8..fb23d9c 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -21,7 +21,7 @@ import { languageDetection } from "./language-detection/autodetect.js" import { autoSaveContent } from "./save.js" import { todoCheckboxPlugin} from "./todo-checkbox.ts" 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"; @@ -140,7 +140,7 @@ export class HeynoteEditor { if (content === this.diskContent) { return } - console.log("saving:", this.path) + //console.log("saving:", this.path) this.diskContent = content await window.heynote.buffer.save(this.path, content) } @@ -158,7 +158,7 @@ export class HeynoteEditor { } async loadContent() { - console.log("loading content", this.path) + //console.log("loading content", this.path) const content = await window.heynote.buffer.load(this.path) this.diskContent = content this.contentLoaded = true @@ -328,11 +328,13 @@ export class HeynoteEditor { triggerCurrenciesLoaded(this.view.state, this.view.dispatch) } - destroy() { + destroy(save=true) { if (this.onChange) { window.heynote.buffer.removeOnChangeCallback(this.path, this.onChange) } - this.save() + if (save) { + this.save() + } this.view.destroy() window.heynote.buffer.close(this.path) } diff --git a/src/stores/editor-cache.js b/src/stores/editor-cache.js index 5cf492c..a8b41e9 100644 --- a/src/stores/editor-cache.js +++ b/src/stores/editor-cache.js @@ -1,6 +1,6 @@ import { toRaw } from 'vue'; import { defineStore } from "pinia" -import { NoteFormat } from "../editor/note-format" +import { NoteFormat } from "../common/note-format" const NUM_EDITOR_INSTANCES = 5 @@ -44,5 +44,14 @@ export const useEditorCacheStore = defineStore("editorCache", { eachEditor(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 = [] + }, }, }) diff --git a/src/stores/notes-store.js b/src/stores/notes-store.js index bdd1187..5ac1c11 100644 --- a/src/stores/notes-store.js +++ b/src/stores/notes-store.js @@ -1,22 +1,23 @@ import { toRaw } from 'vue'; import { defineStore } from "pinia" -import { NoteFormat } from "../editor/note-format" +import { NoteFormat } from "../common/note-format" 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", { state: () => ({ notes: {}, - recentNotePaths: [SCRATCH_FILE], + recentNotePaths: [SCRATCH_FILE_NAME], currentEditor: null, - currentNotePath: SCRATCH_FILE, + currentNotePath: SCRATCH_FILE_NAME, currentNoteName: null, currentLanguage: null, currentLanguageAuto: null, currentCursorLine: null, currentSelectionSize: null, + libraryId: 0, showNoteSelector: false, showLanguageSelector: false, @@ -119,10 +120,22 @@ export const useNotesStore = defineStore("notes", { 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() { const notesStore = useNotesStore() + window.heynote.buffer.setLibraryPathChangeCallback(() => { + notesStore.reloadLibrary() + }) await notesStore.updateNotes() } diff --git a/tests/note-format.spec.js b/tests/note-format.spec.js index f9acfb9..9822d03 100644 --- a/tests/note-format.spec.js +++ b/tests/note-format.spec.js @@ -1,6 +1,6 @@ import { test, expect } from "@playwright/test"; import { HeynotePage } from "./test-utils.js"; -import { NoteFormat } from "../src/editor/note-format.js"; +import { NoteFormat } from "../src/common/note-format.js"; let heynotePage diff --git a/tests/test-utils.js b/tests/test-utils.js index b2760cf..aa59fb6 100644 --- a/tests/test-utils.js +++ b/tests/test-utils.js @@ -1,5 +1,5 @@ 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) { let messages = [];