From 85f59661e9716d3efca79b86719cabd3488e370b Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 11:26:15 +0200 Subject: [PATCH 01/62] Fix issue with positioning and size of todo list checkboxes in Markdown blocks Use two different ways of positioning the checkbox depending on if the font is monospaced or not --- docs/changelog.md | 4 ++++ src/editor/annotation.js | 1 + src/editor/editor.js | 8 ++++++- src/editor/theme/font-theme.js | 40 +++++++++++++++++++++++++++----- src/editor/todo-checkbox.ts | 42 +++++++++++++++++++++++++--------- 5 files changed, 77 insertions(+), 18 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 9dc65cd..c8c65f3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ Here are the most notable changes in each release. For a more detailed list of changes, see the [Github Releases page](https://github.com/heyman/heynote/releases). +## 2.1.4 (not released yet) + +- Fix issue with positioning and size of todo list checkboxes in Markdown blocks when using a non-default font size, or a non-monospaced font. + ## 2.1.3 - Fix escaping issue in buffer selector (properly this time, hopefully) diff --git a/src/editor/annotation.js b/src/editor/annotation.js index 2f6dc46..09342c2 100644 --- a/src/editor/annotation.js +++ b/src/editor/annotation.js @@ -8,3 +8,4 @@ export const ADD_NEW_BLOCK = "heynote-add-new-block" export const DELETE_BLOCK = "heynote-delete-block" export const CURSOR_CHANGE = "heynote-cursor-change" export const APPEND_BLOCK = "heynote-append-block" +export const SET_FONT = "heynote-set-font" diff --git a/src/editor/editor.js b/src/editor/editor.js index 749328a..1ee2ab9 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -12,7 +12,7 @@ import { getFontTheme } from "./theme/font-theme.js"; import { customSetup } from "./setup.js" import { heynoteLang } from "./lang-heynote/heynote.js" import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js" -import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK } from "./annotation.js"; +import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK, SET_FONT } from "./annotation.js"; import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock, selectAll } from "./block/commands.js" import { formatBlockContent } from "./block/format-code.js" import { heynoteKeymap } from "./keymap.js" @@ -135,6 +135,11 @@ export class HeynoteEditor { if (focus) { this.view.focus() } + + // trigger setFont once the fonts has loaded + document.fonts.ready.then(() => { + this.setFont(fontFamily, fontSize) + }) } async save() { @@ -258,6 +263,7 @@ export class HeynoteEditor { setFont(fontFamily, fontSize) { this.view.dispatch({ effects: this.fontTheme.reconfigure(getFontTheme(fontFamily, fontSize)), + annotations: [heynoteEvent.of(SET_FONT), Transaction.addToHistory.of(false)], }) } diff --git a/src/editor/theme/font-theme.js b/src/editor/theme/font-theme.js index 0dc7879..2728fdc 100644 --- a/src/editor/theme/font-theme.js +++ b/src/editor/theme/font-theme.js @@ -1,11 +1,39 @@ import { EditorView } from "@codemirror/view" +import { Facet } from "@codemirror/state" + + +/** + * Check if the given font family is monospace by drawing test characters on a canvas + */ +function isMonospace(fontFamily) { + const testCharacters = ['i', 'W', 'm', ' '] + const testSize = '72px' + + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + context.font = `${testSize} ${fontFamily}` + + const widths = testCharacters.map(char => context.measureText(char).width) + return widths.every(width => width === widths[0]) +} + + +export const isMonospaceFont = Facet.define({ + combine: values => values.length ? values[0] : true, +}) export function getFontTheme(fontFamily, fontSize) { fontSize = fontSize || window.heynote.defaultFontSize - return EditorView.theme({ - '.cm-scroller': { - fontFamily: fontFamily || window.heynote.defaultFontFamily, - fontSize: (fontSize) + "px", - }, - }) + const computedFontFamily = fontFamily || window.heynote.defaultFontFamily + return [ + EditorView.theme({ + '.cm-scroller': { + fontFamily: computedFontFamily, + fontSize: (fontSize) + "px", + }, + }), + // in order to avoid a short flicker when the program is loaded with the default font (Hack), + // we hardcode Hack to be monospace + isMonospaceFont.of(computedFontFamily === "Hack" ? true : isMonospace(computedFontFamily)), + ] } diff --git a/src/editor/todo-checkbox.ts b/src/editor/todo-checkbox.ts index 0fd69f3..60d76a6 100644 --- a/src/editor/todo-checkbox.ts +++ b/src/editor/todo-checkbox.ts @@ -3,25 +3,44 @@ import { syntaxTree, ensureSyntaxTree } from "@codemirror/language" import { WidgetType } from "@codemirror/view" import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view" +import { isMonospaceFont } from "./theme/font-theme" +import { SET_FONT } from "./annotation" + class CheckboxWidget extends WidgetType { - constructor(readonly checked: boolean) { super() } + constructor(readonly checked: boolean, readonly monospace: boolean) { super() } - eq(other: CheckboxWidget) { return other.checked == this.checked } + eq(other: CheckboxWidget) { return other.checked == this.checked && other.monospace == this.monospace } toDOM() { let wrap = document.createElement("span") wrap.setAttribute("aria-hidden", "true") wrap.className = "cm-taskmarker-toggle" - wrap.style.position = "relative" - // Three spaces since it's the same width as [ ] and [x] - wrap.appendChild(document.createTextNode(" ")) - let box = wrap.appendChild(document.createElement("input")) + + let box = document.createElement("input") box.type = "checkbox" box.checked = this.checked - box.style.position = "absolute" - box.style.top = "-3px" - box.style.left = "0" + box.style.margin = "0" + box.style.padding = "0" + + if (this.monospace) { + // if the font is monospaced, we'll set the content of the wrapper to " " and the + // position of the checkbox to absolute, since three spaces will be the same width + // as "[ ]" and "[x]" so that characters on different lines will line up + wrap.appendChild(document.createTextNode(" ")) + wrap.style.position = "relative" + box.style.position = "absolute" + box.style.top = "0" + box.style.left = "0.25em" + box.style.width = "1.1em" + box.style.height = "1.1em" + } else { + // if the font isn't monospaced, we'll let the checkbox take up as much space as needed + box.style.position = "relative" + box.style.top = "0.1em" + box.style.marginRight = "0.5em" + } + wrap.appendChild(box) return wrap } @@ -52,7 +71,7 @@ function checkboxes(view: EditorView) { if (view.state.doc.sliceString(nodeRef.to, nodeRef.to+1) === " ") { let isChecked = view.state.doc.sliceString(nodeRef.from, nodeRef.to).toLowerCase() === "[x]" let deco = Decoration.replace({ - widget: new CheckboxWidget(isChecked), + widget: new CheckboxWidget(isChecked, view.state.facet(isMonospaceFont)), inclusive: false, }) widgets.push(deco.range(nodeRef.from, nodeRef.to)) @@ -92,8 +111,9 @@ export const todoCheckboxPlugin = [ } update(update: ViewUpdate) { - if (update.docChanged || update.viewportChanged) + if (update.docChanged || update.viewportChanged || update.transactions.some(tr => tr.annotations.some(a => a.value === SET_FONT))) { this.decorations = checkboxes(update.view) + } } }, { decorations: v => v.decorations, From fa83c50f445c38d728ab4603ed6fc6f42c48e2e7 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 11:44:52 +0200 Subject: [PATCH 02/62] Add tests that checks positioning of todo checkboxes depending on font type --- tests/custom-font.spec.js | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/custom-font.spec.js b/tests/custom-font.spec.js index cceae05..e834930 100644 --- a/tests/custom-font.spec.js +++ b/tests/custom-font.spec.js @@ -64,3 +64,51 @@ test("test custom font", async ({ page, browserName }) => { return el.clientHeight })).toBeGreaterThan(20) }) + +test("markdown todo checkbox position with monospaced font", async ({ page }) => { + await heynotePage.setContent(` +∞∞∞markdown +- [ ] Test +- [x] Test 2 +`) + expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]")).toHaveCount(2) + expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first()).toHaveCSS("position", "absolute") +}) + +test("markdown todo checkbox position with variable width font", async ({ page }) => { + await page.evaluate(() => { + window.queryLocalFonts = async () => { + return [ + { + family: "Arial", + style: "Regular", + }, + { + family: "Hack", + fullName: "Hack Regular", + style: "Regular", + postscriptName: "Hack-Regular", + }, + { + family: "Hack", + fullName: "Hack Italic", + style: "Italic", + postscriptName: "Hack-Italic", + }, + ] + } + }) + await page.locator("css=.status-block.settings").click() + await page.locator("css=li.tab-appearance").click() + await page.locator("css=select.font-family").selectOption("Arial") + await page.locator("css=select.font-size").selectOption("20") + await page.locator("body").press("Escape") + + await heynotePage.setContent(` +∞∞∞markdown +- [ ] Test +- [x] Test 2 +`) + expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]")).toHaveCount(2) + expect(await page.locator("css=.cm-taskmarker-toggle input[type=checkbox]").first()).toHaveCSS("position", "relative") +}) \ No newline at end of file From 15df9e5e5cbae5afda1daeb9f693677b5ae36b4c Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 13:01:22 +0200 Subject: [PATCH 03/62] Fix issue when pressing `Ctrl/Cmd+A` in a text input inside a modal dialog --- docs/changelog.md | 1 + src/components/App.vue | 7 +++++-- src/components/BufferSelector.vue | 6 ++++++ src/components/EditBuffer.vue | 6 ++++++ src/components/LanguageSelector.vue | 6 ++++++ src/components/NewBuffer.vue | 6 ++++++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index c8c65f3..709fea0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,7 @@ Here are the most notable changes in each release. For a more detailed list of c ## 2.1.4 (not released yet) - Fix issue with positioning and size of todo list checkboxes in Markdown blocks when using a non-default font size, or a non-monospaced font. +- Fix issue when pressing `Ctrl/Cmd+A` in a text input inside a modal dialog (e.g. the buffer selector). Previously the select all command would be sent to the editor. ## 2.1.3 diff --git a/src/components/App.vue b/src/components/App.vue index fa20814..5a762e9 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -85,11 +85,14 @@ "showCreateBuffer", "showEditBuffer", "showMoveToBufferSelector", - "openMoveToBufferSelector", ]), + dialogVisible() { + return this.showLanguageSelector || this.showBufferSelector || this.showCreateBuffer || this.showEditBuffer || this.showMoveToBufferSelector + }, + editorInert() { - return this.showCreateBuffer || this.showSettings || this.showEditBuffer + return this.dialogVisible }, }, diff --git a/src/components/BufferSelector.vue b/src/components/BufferSelector.vue index 2a4693b..3ba88be 100644 --- a/src/components/BufferSelector.vue +++ b/src/components/BufferSelector.vue @@ -133,6 +133,12 @@ return } + // support Ctrl/Cmd+A to select all + if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { + event.preventDefault() + event.srcElement.select() + } + if (this.filteredItems.length === 0) { return } diff --git a/src/components/EditBuffer.vue b/src/components/EditBuffer.vue index 2ef8c1c..7171aed 100644 --- a/src/components/EditBuffer.vue +++ b/src/components/EditBuffer.vue @@ -120,6 +120,12 @@ }, onInputKeydown(event) { + // support Ctrl/Cmd+A to select all + if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { + event.preventDefault() + event.srcElement.select() + } + // redirect arrow keys and page up/down to folder selector const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"] if (redirectKeys.includes(event.key)) { diff --git a/src/components/LanguageSelector.vue b/src/components/LanguageSelector.vue index cc08107..d9cc8f6 100644 --- a/src/components/LanguageSelector.vue +++ b/src/components/LanguageSelector.vue @@ -50,6 +50,12 @@ methods: { onKeydown(event) { + // support Ctrl/Cmd+A to select all + if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { + event.preventDefault() + event.srcElement.select() + } + if (event.key === "ArrowDown") { this.selected = Math.min(this.selected + 1, this.filteredItems.length - 1) event.preventDefault() diff --git a/src/components/NewBuffer.vue b/src/components/NewBuffer.vue index 27bb986..6eecfcc 100644 --- a/src/components/NewBuffer.vue +++ b/src/components/NewBuffer.vue @@ -128,6 +128,12 @@ }, onInputKeydown(event) { + // support Ctrl/Cmd+A to select all + if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { + event.preventDefault() + event.srcElement.select() + } + // redirect arrow keys and page up/down to folder selector const redirectKeys = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"] if (redirectKeys.includes(event.key)) { From 3f7671503d4ec51cbabdf6dae179d73376071c87 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 13:04:23 +0200 Subject: [PATCH 04/62] Don't perform editor Select All command if the editor isn't focused (e.g. the Settings modal is open). Fix Editor/Editor Cache teardown/cleanup. --- src/components/Editor.vue | 37 ++++++++++++++++++++++++------------- src/stores/editor-cache.js | 3 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/components/Editor.vue b/src/components/Editor.vue index aca4658..d0fc963 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -22,6 +22,10 @@ syntaxTreeDebugContent: null, editor: null, onWindowClose: null, + onUndo: null, + onRedo: null, + onDeleteBlock: null, + onSelectAll: null, } }, @@ -39,29 +43,36 @@ } window.heynote.mainProcess.on(WINDOW_CLOSE_EVENT, this.onWindowClose) - window.heynote.mainProcess.on(UNDO_EVENT, () => { + this.onUndo = () => { if (this.editor) { toRaw(this.editor).undo() } - }) + } + window.heynote.mainProcess.on(UNDO_EVENT, this.onUndo) - window.heynote.mainProcess.on(REDO_EVENT, () => { + this.onRedo = () => { if (this.editor) { toRaw(this.editor).redo() } - }) + } + window.heynote.mainProcess.on(REDO_EVENT, this.onRedo) - window.heynote.mainProcess.on(DELETE_BLOCK_EVENT, () => { + this.onDeleteBlock = () => { if (this.editor) { toRaw(this.editor).deleteActiveBlock() } - }) + } + window.heynote.mainProcess.on(DELETE_BLOCK_EVENT, this.onDeleteBlock) - window.heynote.mainProcess.on(SELECT_ALL_EVENT, () => { + this.onSelectAll = () => { if (this.editor) { - toRaw(this.editor).selectAll() + // make sure the editor is focused + if (this.$refs.editor.contains(document.activeElement)) { + toRaw(this.editor).selectAll() + } } - }) + } + window.heynote.mainProcess.on(SELECT_ALL_EVENT, this.onSelectAll) // if debugSyntaxTree prop is set, display syntax tree for debugging if (this.debugSyntaxTree) { @@ -85,10 +96,10 @@ beforeUnmount() { window.heynote.mainProcess.off(WINDOW_CLOSE_EVENT, this.onWindowClose) - window.heynote.mainProcess.off(UNDO_EVENT) - window.heynote.mainProcess.off(REDO_EVENT) - window.heynote.mainProcess.off(DELETE_BLOCK_EVENT) - window.heynote.mainProcess.off(SELECT_ALL_EVENT) + window.heynote.mainProcess.off(UNDO_EVENT, this.onUndo) + window.heynote.mainProcess.off(REDO_EVENT, this.onRedo) + window.heynote.mainProcess.off(DELETE_BLOCK_EVENT, this.onDeleteBlock) + window.heynote.mainProcess.off(SELECT_ALL_EVENT, this.onSelectAll) this.editorCacheStore.tearDown(); }, diff --git a/src/stores/editor-cache.js b/src/stores/editor-cache.js index 7badc2e..391bed5 100644 --- a/src/stores/editor-cache.js +++ b/src/stores/editor-cache.js @@ -164,6 +164,9 @@ export const useEditorCacheStore = defineStore("editorCache", { } window.document.removeEventListener("currenciesLoaded", this.onCurrenciesLoaded) + + this.editorCache.lru = [] + this.editorCache.cache = {} }, moveCurrentBlockToOtherEditor(targetPath) { From c397511bdac34ac9a9b9c50bfd1c0e09b2088373 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 13:15:09 +0200 Subject: [PATCH 05/62] Bump version to 2.1.4-beta --- docs/changelog.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 709fea0..7e623b1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ Here are the most notable changes in each release. For a more detailed list of changes, see the [Github Releases page](https://github.com/heyman/heynote/releases). -## 2.1.4 (not released yet) +## 2.1.4-beta - Fix issue with positioning and size of todo list checkboxes in Markdown blocks when using a non-default font size, or a non-monospaced font. - Fix issue when pressing `Ctrl/Cmd+A` in a text input inside a modal dialog (e.g. the buffer selector). Previously the select all command would be sent to the editor. diff --git a/package-lock.json b/package-lock.json index 615d33b..0574ad8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Heynote", - "version": "2.1.3", + "version": "2.1.4-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Heynote", - "version": "2.1.3", + "version": "2.1.4-beta", "license": "Commons Clause MIT", "dependencies": { "@sindresorhus/slugify": "^2.2.1", diff --git a/package.json b/package.json index 32223a5..3e9b679 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Heynote", - "version": "2.1.3", + "version": "2.1.4-beta", "main": "dist-electron/main/index.js", "description": "A dedicated scratch pad", "author": "Jonatan Heyman (https://heyman.info)", From 4e5a1139d9634679a13f1c8f3864ce1e539d9036 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 7 Apr 2025 13:34:03 +0200 Subject: [PATCH 06/62] Add transactionsHasAnnotation utility function --- src/editor/annotation.js | 6 ++++++ src/editor/block/math.js | 8 +------- src/editor/todo-checkbox.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/editor/annotation.js b/src/editor/annotation.js index 09342c2..5e46be7 100644 --- a/src/editor/annotation.js +++ b/src/editor/annotation.js @@ -9,3 +9,9 @@ export const DELETE_BLOCK = "heynote-delete-block" export const CURSOR_CHANGE = "heynote-cursor-change" export const APPEND_BLOCK = "heynote-append-block" export const SET_FONT = "heynote-set-font" + + +// This function checks if any of the transactions has the given Heynote annotation +export function transactionsHasAnnotation(transactions, annotation) { + return transactions.some(tr => tr.annotation(heynoteEvent) === annotation) +} diff --git a/src/editor/block/math.js b/src/editor/block/math.js index b76a73f..4de7061 100644 --- a/src/editor/block/math.js +++ b/src/editor/block/math.js @@ -4,7 +4,7 @@ import { RangeSetBuilder } from "@codemirror/state" import { WidgetType } from "@codemirror/view" import { getNoteBlockFromPos } from "./block" -import { CURRENCIES_LOADED } from "../annotation" +import { transactionsHasAnnotation, CURRENCIES_LOADED } from "../annotation" class MathResult extends WidgetType { @@ -107,12 +107,6 @@ function mathDeco(view) { return builder.finish() } - -// This function checks if any of the transactions has the given annotation -const transactionsHasAnnotation = (transactions, annotation) => { - return transactions.some(tr => tr.annotations.some(a => a.value === annotation)) -} - export const mathBlock = ViewPlugin.fromClass(class { decorations diff --git a/src/editor/todo-checkbox.ts b/src/editor/todo-checkbox.ts index 60d76a6..a25a1ee 100644 --- a/src/editor/todo-checkbox.ts +++ b/src/editor/todo-checkbox.ts @@ -4,7 +4,7 @@ import { WidgetType } from "@codemirror/view" import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view" import { isMonospaceFont } from "./theme/font-theme" -import { SET_FONT } from "./annotation" +import { transactionsHasAnnotation, SET_FONT } from "./annotation" class CheckboxWidget extends WidgetType { @@ -111,7 +111,7 @@ export const todoCheckboxPlugin = [ } update(update: ViewUpdate) { - if (update.docChanged || update.viewportChanged || update.transactions.some(tr => tr.annotations.some(a => a.value === SET_FONT))) { + if (update.docChanged || update.viewportChanged || transactionsHasAnnotation(update.transactions, SET_FONT)) { this.decorations = checkboxes(update.view) } } From 76df74fe6739800063511c012ee4e5d673c74c22 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Tue, 8 Apr 2025 13:35:06 +0200 Subject: [PATCH 07/62] Bump version to 2.1.4 --- docs/changelog.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 7e623b1..657c2bb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ Here are the most notable changes in each release. For a more detailed list of changes, see the [Github Releases page](https://github.com/heyman/heynote/releases). -## 2.1.4-beta +## 2.1.4 - Fix issue with positioning and size of todo list checkboxes in Markdown blocks when using a non-default font size, or a non-monospaced font. - Fix issue when pressing `Ctrl/Cmd+A` in a text input inside a modal dialog (e.g. the buffer selector). Previously the select all command would be sent to the editor. diff --git a/package-lock.json b/package-lock.json index 0574ad8..0f943a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Heynote", - "version": "2.1.4-beta", + "version": "2.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Heynote", - "version": "2.1.4-beta", + "version": "2.1.4", "license": "Commons Clause MIT", "dependencies": { "@sindresorhus/slugify": "^2.2.1", diff --git a/package.json b/package.json index 3e9b679..e3744c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Heynote", - "version": "2.1.4-beta", + "version": "2.1.4", "main": "dist-electron/main/index.js", "description": "A dedicated scratch pad", "author": "Jonatan Heyman (https://heyman.info)", From 863a721bef9be7190538e25d781c19d0817b902f Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Tue, 8 Apr 2025 14:05:50 +0200 Subject: [PATCH 08/62] Update link texts Signed-off-by: Jonatan Heyman --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae8cbc6..02cf15b 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ ## General Information -- Website: [heynote.com](https://heynote.com) -- Documentation: [heynote.com](https://heynote.com/docs/) -- Changelog: [heynote.com](https://heynote.com/docs/changelog/) +- [Website](https://heynote.com) +- [Documentation](https://heynote.com/docs/) +- [Changelog](https://heynote.com/docs/changelog/) Heynote is a dedicated scratchpad for developers. It functions as a large persistent text buffer where you can write down anything you like. Works great for that Slack message you don't want to accidentally send, a JSON response from an API you're working with, notes from a meeting, your daily to-do list, etc. From deedf24394ad1dbf5952cbf69427c09aba732fca Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 16:57:20 +0200 Subject: [PATCH 09/62] Fix issue where deleteLine command could fuck up the Heynote block syntax --- src/editor/block/delete-line.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/block/delete-line.js b/src/editor/block/delete-line.js index 9066231..31f82cc 100644 --- a/src/editor/block/delete-line.js +++ b/src/editor/block/delete-line.js @@ -1,5 +1,5 @@ import { EditorSelection } from "@codemirror/state" -import { getActiveNoteBlock } from "./block" +import { getNoteBlockFromPos } from "./block" function updateSel(sel, by) { return EditorSelection.create(sel.ranges.map(by), sel.mainIndex); @@ -28,10 +28,10 @@ export const deleteLine = (view) => { const { state } = view - const block = getActiveNoteBlock(view.state) const selectedLines = selectedLineBlocks(state) const changes = state.changes(selectedLines.map(({ from, to }) => { + const block = getNoteBlockFromPos(state, from) if(from !== block.content.from || to !== block.content.to) { if (from > 0) from-- else if (to < state.doc.length) to++ From 0de4710cf3fe5531b99315cb2d29e8c39558cea0 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 17:03:12 +0200 Subject: [PATCH 10/62] Add "block aware" transposeChars command --- src/editor/block/transpose-chars.js | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/editor/block/transpose-chars.js diff --git a/src/editor/block/transpose-chars.js b/src/editor/block/transpose-chars.js new file mode 100644 index 0000000..5896cd6 --- /dev/null +++ b/src/editor/block/transpose-chars.js @@ -0,0 +1,30 @@ +import { EditorSelection, findClusterBreak} from "@codemirror/state"; + +import { getNoteBlockFromPos } from "./block" + +/** +Flip the characters before and after the cursor(s). +*/ +export const transposeChars = ({ state, dispatch }) => { + if (state.readOnly) + return false; + let changes = state.changeByRange(range => { + // prevent transposing characters if we're at the start or end of a block, since it'll break the block syntax + const block = getNoteBlockFromPos(state, range.from) + if (range.from === block.content.from || range.from === block.content.to) { + return { range } + } + + if (!range.empty || range.from == 0 || range.from == state.doc.length) + return { range }; + let pos = range.from, line = state.doc.lineAt(pos); + let from = pos == line.from ? pos - 1 : findClusterBreak(line.text, pos - line.from, false) + line.from; + let to = pos == line.to ? pos + 1 : findClusterBreak(line.text, pos - line.from, true) + line.from; + return { changes: { from, to, insert: state.doc.slice(pos, to).append(state.doc.slice(from, pos)) }, + range: EditorSelection.cursor(to) }; + }); + if (changes.changes.empty) + return false; + dispatch(state.update(changes, { scrollIntoView: true, userEvent: "move.character" })); + return true; +}; From 813522cc0e7a8255e2d1a1c341a92aeab9f692fd Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 19:29:11 +0200 Subject: [PATCH 11/62] Set tab-index="-1" for todo checkboxes Otherwise they will be focusable when opening the search panel and pressing tab to cycle between the fields --- src/editor/todo-checkbox.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/todo-checkbox.ts b/src/editor/todo-checkbox.ts index a25a1ee..2a74a2b 100644 --- a/src/editor/todo-checkbox.ts +++ b/src/editor/todo-checkbox.ts @@ -20,6 +20,7 @@ class CheckboxWidget extends WidgetType { let box = document.createElement("input") box.type = "checkbox" box.checked = this.checked + box.tabIndex = -1 box.style.margin = "0" box.style.padding = "0" From ae4d86b9f3d5bd8bf83c66aabaa2daa9f8b8bd9c Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 19:34:26 +0200 Subject: [PATCH 12/62] Improve the way we handle SelectAll events (from the Menu shortcuts) when a text input (and not the editor) has focus --- src/components/BufferSelector.vue | 6 ------ src/components/Editor.vue | 8 ++++++-- src/components/LanguageSelector.vue | 6 ------ 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/components/BufferSelector.vue b/src/components/BufferSelector.vue index 3ba88be..2a4693b 100644 --- a/src/components/BufferSelector.vue +++ b/src/components/BufferSelector.vue @@ -133,12 +133,6 @@ return } - // support Ctrl/Cmd+A to select all - if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { - event.preventDefault() - event.srcElement.select() - } - if (this.filteredItems.length === 0) { return } diff --git a/src/components/Editor.vue b/src/components/Editor.vue index d0fc963..09b7461 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -65,9 +65,13 @@ window.heynote.mainProcess.on(DELETE_BLOCK_EVENT, this.onDeleteBlock) this.onSelectAll = () => { - if (this.editor) { + const activeEl = document.activeElement + if (activeEl && activeEl.tagName === "INPUT") { + // if the active element is an input, select all text in it + activeEl.select() + } else if (this.editor) { // make sure the editor is focused - if (this.$refs.editor.contains(document.activeElement)) { + if (this.$refs.editor.contains(activeEl)) { toRaw(this.editor).selectAll() } } diff --git a/src/components/LanguageSelector.vue b/src/components/LanguageSelector.vue index d9cc8f6..cc08107 100644 --- a/src/components/LanguageSelector.vue +++ b/src/components/LanguageSelector.vue @@ -50,12 +50,6 @@ methods: { onKeydown(event) { - // support Ctrl/Cmd+A to select all - if (event.key === "a" && event[window.heynote.platform.isMac ? "metaKey" : "ctrlKey"]) { - event.preventDefault() - event.srcElement.select() - } - if (event.key === "ArrowDown") { this.selected = Math.min(this.selected + 1, this.filteredItems.length - 1) event.preventDefault() From ffec1c498d29659a71731bf4b5136adcbeff80c5 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 19:36:33 +0200 Subject: [PATCH 13/62] Return true from delete block commands (to prevent any other commands to run) --- src/editor/block/commands.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index f67ccbe..0126356 100644 --- a/src/editor/block/commands.js +++ b/src/editor/block/commands.js @@ -360,6 +360,7 @@ export const deleteBlock = (editor) => ({state, dispatch}) => { selection: EditorSelection.cursor(newSelection), annotations: [heynoteEvent.of(DELETE_BLOCK)], })) + return true } export const deleteBlockSetCursorPreviousBlock = (editor) => ({state, dispatch}) => { @@ -380,4 +381,5 @@ export const deleteBlockSetCursorPreviousBlock = (editor) => ({state, dispatch}) selection: EditorSelection.cursor(newSelection), annotations: [heynoteEvent.of(DELETE_BLOCK)], })) + return true } From 9be328cbe4e379c2b1ae12f85dfec54618ac4d56 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 19:37:26 +0200 Subject: [PATCH 14/62] Remove old unused code --- electron/main/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 32effaa..31bad25 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -64,8 +64,6 @@ const preload = join(__dirname, '../preload/index.js') const url = process.env.VITE_DEV_SERVER_URL const indexHtml = join(process.env.DIST, 'index.html') -let currentKeymap = CONFIG.get("settings.keymap") - // if this version is a beta version, set the release channel to beta const isBetaVersion = app.getVersion().includes("beta") if (isBetaVersion) { @@ -409,9 +407,6 @@ ipcMain.handle("getInitErrors", () => { ipcMain.handle('settings:set', async (event, settings) => { - if (settings.keymap !== CONFIG.get("settings.keymap")) { - currentKeymap = settings.keymap - } let globalHotkeyChanged = settings.enableGlobalHotkey !== CONFIG.get("settings.enableGlobalHotkey") || settings.globalHotkey !== CONFIG.get("settings.globalHotkey") let showInDockChanged = settings.showInDock !== CONFIG.get("settings.showInDock"); let showInMenuChanged = settings.showInMenu !== CONFIG.get("settings.showInMenu"); From 94f553461167a76657b96a48d2bb52f649e99bbc Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Thu, 10 Apr 2025 20:04:44 +0200 Subject: [PATCH 15/62] Revamp key bindings This is a work in progress revamp of the key binding system. It implements a system, built on top of CodeMirror's key binding system, for defining key bindings. The system uses a dumb "KeyShortcut" -> "Command" mapping with a set of default keys (which will be different if Heynote's Emacs mode is used) that can be overridden by user key bindings. The key bindings are *displayed* in the Settings, and it's possible to set user defined key bindings in Heynote's config file, but it's not yet possible to define custom key bindings in the UI. Previously we Heynote on a bunch of default key bindings from CodeMirror (some of which was not "block aware"). This is no longer the case, and because of this, it's quite likely that there are key bindings that was previously working that is now missing (if so, these can easily be added later). --- electron/config.js | 11 + src/components/settings/KeyBindRow.vue | 85 ++++++++ src/components/settings/KeyboardBindings.vue | 126 +++++++++++ src/components/settings/Settings.vue | 59 +++-- src/components/settings/TabListItem.vue | 1 + src/editor/close-brackets.js | 12 + src/editor/commands.js | 122 +++++++++++ src/editor/copy-paste.js | 6 +- src/editor/editor.js | 31 +-- src/editor/emacs-mode.js | 37 ++++ src/editor/emacs.js | 119 ---------- src/editor/keymap.js | 217 +++++++++++++------ src/editor/setup.js | 9 +- src/stores/editor-cache.js | 4 +- tests/emacs-clipboard-keys.spec.js | 1 + 15 files changed, 615 insertions(+), 225 deletions(-) create mode 100644 src/components/settings/KeyBindRow.vue create mode 100644 src/components/settings/KeyboardBindings.vue create mode 100644 src/editor/close-brackets.js create mode 100644 src/editor/commands.js create mode 100644 src/editor/emacs-mode.js delete mode 100644 src/editor/emacs.js diff --git a/electron/config.js b/electron/config.js index f3ecd97..a306131 100644 --- a/electron/config.js +++ b/electron/config.js @@ -24,6 +24,16 @@ const schema = { properties: { "keymap": { "enum": ["default", "emacs"], default:"default" }, "emacsMetaKey": { "enum": [null, "alt", "meta"], default: null }, + "keyBindings": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "showLineNumberGutter": {type: "boolean", default:true}, "showFoldGutter": {type: "boolean", default:true}, "autoUpdate": {type: "boolean", default: true}, @@ -61,6 +71,7 @@ const defaults = { settings: { keymap: "default", emacsMetaKey: isMac ? "meta" : "alt", + keyBindings: {}, showLineNumberGutter: true, showFoldGutter: true, autoUpdate: true, diff --git a/src/components/settings/KeyBindRow.vue b/src/components/settings/KeyBindRow.vue new file mode 100644 index 0000000..64d8110 --- /dev/null +++ b/src/components/settings/KeyBindRow.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/settings/KeyboardBindings.vue b/src/components/settings/KeyboardBindings.vue new file mode 100644 index 0000000..a4a584e --- /dev/null +++ b/src/components/settings/KeyboardBindings.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/components/settings/Settings.vue b/src/components/settings/Settings.vue index f142b70..9e9e6aa 100644 --- a/src/components/settings/Settings.vue +++ b/src/components/settings/Settings.vue @@ -1,9 +1,11 @@ @@ -111,16 +139,31 @@ border: 2px solid #f1f1f1 +dark-mode background: #3c3c3c + background: #333 border: 2px solid #3c3c3c ::v-deep(tr) - &:nth-child(2n) - background: #fff + background: #fff + border-bottom: 2px solid #f1f1f1 + +dark-mode + background: #333 + border-bottom: 2px solid #3c3c3c + &.ghost + background: #48b57e + color: #fff +dark-mode - background: #333 + background: #1b6540 th text-align: left font-weight: 600 th, td padding: 8px + &.actions + padding: 6px + button + height: 20px + font-size: 11px + + tbody + margin-bottom: 20px diff --git a/src/components/settings/Settings.vue b/src/components/settings/Settings.vue index 84ed5fa..cb399c6 100644 --- a/src/components/settings/Settings.vue +++ b/src/components/settings/Settings.vue @@ -86,6 +86,12 @@ window.removeEventListener("keydown", this.onKeyDown); }, + watch: { + keyBindings(newKeyBindings) { + this.updateSettings() + } + }, + methods: { onKeyDown(event) { if (event.key === "Escape") { @@ -98,7 +104,7 @@ showLineNumberGutter: this.showLineNumberGutter, showFoldGutter: this.showFoldGutter, keymap: this.keymap, - keyBindings: toRaw(this.keyBindings), + keyBindings: this.keyBindings.map((kb) => toRaw(kb)), emacsMetaKey: window.heynote.platform.isMac ? this.metaKey : "alt", allowBetaVersions: this.allowBetaVersions, enableGlobalHotkey: this.enableGlobalHotkey, @@ -369,6 +375,7 @@ diff --git a/src/editor/editor.js b/src/editor/editor.js index 23d1535..b5484d1 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -16,7 +16,7 @@ import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, t import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK, SET_FONT } from "./annotation.js"; import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock, selectAll } from "./block/commands.js" import { formatBlockContent } from "./block/format-code.js" -import { heynoteKeymap, DEFAULT_KEYMAP, EMACS_KEYMAP } from "./keymap.js" +import { getKeymapExtensions } from "./keymap.js" import { heynoteCopyCut } from "./copy-paste" import { languageDetection } from "./language-detection/autodetect.js" import { autoSaveContent } from "./save.js" @@ -29,15 +29,6 @@ import { useHeynoteStore } from "../stores/heynote-store.js"; import { useErrorStore } from "../stores/error-store.js"; -function getKeymapExtensions(editor, keymap, keyBindings) { - return heynoteKeymap( - editor, - keymap === "emacs" ? EMACS_KEYMAP : DEFAULT_KEYMAP, - keyBindings, - ) -} - - export class HeynoteEditor { constructor({ element, diff --git a/src/editor/keymap.js b/src/editor/keymap.js index 8389d32..211a0d3 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -3,30 +3,6 @@ import { Prec } from "@codemirror/state" import { HEYNOTE_COMMANDS } from "./commands.js" - -function keymapFromSpec(specs, editor) { - return keymap.of(specs.map((spec) => { - let key = spec.key - if (key.indexOf("EmacsMeta") != -1) { - key = key.replace("EmacsMeta", editor.emacsMetaKey === "alt" ? "Alt" : "Meta") - } - return { - key: key, - //preventDefault: true, - preventDefault: false, - run: (view) => { - //console.log("run()", spec.key, spec.command) - const command = HEYNOTE_COMMANDS[spec.command] - if (!command) { - console.error(`Command not found: ${spec.command} (${spec.key})`) - return false - } - return command(editor)(view) - }, - } - })) -} - const cmd = (key, command) => ({key, command}) const cmdShift = (key, command, shiftCommand) => { return [ @@ -99,8 +75,6 @@ export const DEFAULT_KEYMAP = [ cmd("Shift-Tab", "indentLess"), //cmd("Alt-ArrowLeft", "cursorSubwordBackward"), //cmd("Alt-ArrowRight", "cursorSubwordForward"), - cmd("Ctrl-Space", "toggleEmacsMarkMode"), - cmd("Ctrl-g", "emacsCancel"), cmd("Mod-l", "openLanguageSelector"), cmd("Mod-p", "openBufferSelector"), @@ -146,27 +120,46 @@ export const EMACS_KEYMAP = [ ...cmdShift("Ctrl-f", "cursorCharRight", "selectCharRight"), ...cmdShift("Ctrl-a", "cursorLineStart", "selectLineStart"), ...cmdShift("Ctrl-e", "cursorLineEnd", "selectLineEnd"), - ...DEFAULT_KEYMAP, ] +function keymapFromSpec(specs, editor) { + return keymap.of(specs.map((spec) => { + let key = spec.key + if (key.indexOf("EmacsMeta") != -1) { + key = key.replace("EmacsMeta", editor.emacsMetaKey === "alt" ? "Alt" : "Meta") + } + return { + key: key, + //preventDefault: true, + preventDefault: false, + run: (view) => { + //console.log("run()", spec.key, spec.command) + const command = HEYNOTE_COMMANDS[spec.command] + if (!command) { + console.error(`Command not found: ${spec.command} (${spec.key})`) + return false + } + return command(editor)(view) + }, + } + })) +} + export function heynoteKeymap(editor, keymap, userKeymap) { - //return [ - // keymapFromSpec([ - // ...Object.entries(userKeymap).map(([key, command]) => cmd(key, command)), - // ...keymap, - // ], editor), - //] - - // merge the default keymap with the custom keymap - const defaultKeys = Object.fromEntries(keymap.map(km => [km.key, km.command])) - //let mergedKeys = Object.entries({...defaultKeys, ...Object.fromEntries(userKeymap.map(km => [km.key, km.command]))}).map(([key, command]) => cmd(key, command)) - let mergedKeys = Object.entries({...defaultKeys, ...userKeymap}).map(([key, command]) => cmd(key, command)) - //console.log("userKeys:", userKeymap) - //console.log("mergedKeys:", mergedKeys) - return [ - Prec.high(keymapFromSpec(mergedKeys, editor)), + keymapFromSpec([ + ...userKeymap, + ...keymap, + ], editor), ] } + +export function getKeymapExtensions(editor, keymap, keyBindings) { + return heynoteKeymap( + editor, + keymap === "emacs" ? EMACS_KEYMAP.concat(DEFAULT_KEYMAP) : DEFAULT_KEYMAP, + keyBindings, + ) +} From 89f883e5e9b18313a6048e881b13b44209489cf8 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Sun, 13 Apr 2025 12:39:50 +0200 Subject: [PATCH 28/62] Make emacs mark mode commands return true to stop other key bindings from executing --- src/editor/emacs-mode.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/emacs-mode.js b/src/editor/emacs-mode.js index d560b2f..7d957f5 100644 --- a/src/editor/emacs-mode.js +++ b/src/editor/emacs-mode.js @@ -26,6 +26,7 @@ export function emacsMoveCommand(defaultCmd, markModeCmd) { export function toggleEmacsMarkMode(editor) { return (view) => { editor.emacsMarkMode = !editor.emacsMarkMode + return true } } @@ -33,5 +34,6 @@ export function emacsCancel(editor) { return (view) => { simplifySelection(view) editor.emacsMarkMode = false + return true } } From 01aba1fb9f7ebc756b72f5bbe3aede2ec41f4813 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Sun, 13 Apr 2025 12:40:31 +0200 Subject: [PATCH 29/62] Add command that does nothing except preventing further commands to execute for a specific keybinding (can be used to remove a default key binding) --- src/editor/commands.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editor/commands.js b/src/editor/commands.js index d1d4772..94248ce 100644 --- a/src/editor/commands.js +++ b/src/editor/commands.js @@ -58,6 +58,9 @@ const openCreateNewBuffer = (editor) => () => { editor.openCreateBuffer("new") return true } +const nothing = (view) => { + return true +} const HEYNOTE_COMMANDS = { //undo, @@ -107,6 +110,7 @@ const NON_EDITOR_CONTEXT_COMMANDS = { selectPreviousParagraph, selectNextParagraph, selectPreviousBlock, selectNextBlock, paste: pasteCommand, + nothing, // directly from CodeMirror undo, redo, From 166855c6f5fd614d78fad070a34b8a1fd147f086 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 10:31:43 +0200 Subject: [PATCH 30/62] Fix tests and webapp --- src/editor/keymap.js | 2 +- webapp/bridge.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/keymap.js b/src/editor/keymap.js index 211a0d3..ca52817 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -160,6 +160,6 @@ export function getKeymapExtensions(editor, keymap, keyBindings) { return heynoteKeymap( editor, keymap === "emacs" ? EMACS_KEYMAP.concat(DEFAULT_KEYMAP) : DEFAULT_KEYMAP, - keyBindings, + keyBindings || [], ) } diff --git a/webapp/bridge.js b/webapp/bridge.js index 1d5f09f..48944bc 100644 --- a/webapp/bridge.js +++ b/webapp/bridge.js @@ -89,6 +89,7 @@ let initialSettings = { showLineNumberGutter: true, showFoldGutter: true, bracketClosing: false, + keyBindings: [], } if (settingsData !== null) { initialSettings = Object.assign(initialSettings, JSON.parse(settingsData)) From 4241a9d6ce34c5e626951f61191f68f5b5583f71 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 14:53:36 +0200 Subject: [PATCH 31/62] Rename Emacs mark mode -> Selection mark mode --- src/editor/commands.js | 16 ++++++++-------- src/editor/copy-paste.js | 4 ++-- src/editor/editor.js | 2 +- src/editor/emacs-mode.js | 12 ++++++------ src/editor/keymap.js | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/editor/commands.js b/src/editor/commands.js index 94248ce..890174d 100644 --- a/src/editor/commands.js +++ b/src/editor/commands.js @@ -29,13 +29,13 @@ import { transposeChars } from "./block/transpose-chars.js" import { cutCommand, copyCommand, pasteCommand } from "./copy-paste.js" -import { emacsMoveCommand, toggleEmacsMarkMode, emacsCancel } from "./emacs-mode.js" +import { markModeMoveCommand, toggleSelectionMarkMode, selectionMarkModeCancel } from "./emacs-mode.js" -const cursorPreviousBlock = emacsMoveCommand(gotoPreviousBlock, selectPreviousBlock) -const cursorNextBlock = emacsMoveCommand(gotoNextBlock, selectNextBlock) -const cursorPreviousParagraph = emacsMoveCommand(gotoPreviousParagraph, selectPreviousParagraph) -const cursorNextParagraph = emacsMoveCommand(gotoNextParagraph, selectNextParagraph) +const cursorPreviousBlock = markModeMoveCommand(gotoPreviousBlock, selectPreviousBlock) +const cursorNextBlock = markModeMoveCommand(gotoNextBlock, selectNextBlock) +const cursorPreviousParagraph = markModeMoveCommand(gotoPreviousParagraph, selectPreviousParagraph) +const cursorNextParagraph = markModeMoveCommand(gotoNextParagraph, selectNextParagraph) const openLanguageSelector = (editor) => () => { @@ -71,8 +71,8 @@ const HEYNOTE_COMMANDS = { cursorPreviousParagraph, cursorNextParagraph, deleteBlock, deleteBlockSetCursorPreviousBlock, - toggleEmacsMarkMode, - emacsCancel, + toggleSelectionMarkMode, + selectionMarkModeCancel, openLanguageSelector, openBufferSelector, @@ -97,7 +97,7 @@ for (let commandSuffix of [ "SubwordBackward", "SubwordForward", "LineBoundaryBackward", "LineBoundaryForward", ]) { - HEYNOTE_COMMANDS[`cursor${commandSuffix}`] = emacsMoveCommand(codeMirrorCommands[`cursor${commandSuffix}`], codeMirrorCommands[`select${commandSuffix}`]) + HEYNOTE_COMMANDS[`cursor${commandSuffix}`] = markModeMoveCommand(codeMirrorCommands[`cursor${commandSuffix}`], codeMirrorCommands[`select${commandSuffix}`]) HEYNOTE_COMMANDS[`select${commandSuffix}`] = (editor) => codeMirrorCommands[`select${commandSuffix}`] } diff --git a/src/editor/copy-paste.js b/src/editor/copy-paste.js index cfac1d1..1974a71 100644 --- a/src/editor/copy-paste.js +++ b/src/editor/copy-paste.js @@ -60,7 +60,7 @@ export const heynoteCopyCut = (editor) => { } // if we're in Emacs mode, we want to exit mark mode in case we're in it - editor.emacsMarkMode = false + editor.selectionMarkMode = false // if Editor.deselectOnCopy is set (e.g. we're in Emacs mode), we want to remove the selection after we've copied the text if (editor.deselectOnCopy && event.type == "copy") { @@ -94,7 +94,7 @@ const copyCut = (view, cut, editor) => { } // if we're in Emacs mode, we want to exit mark mode in case we're in it - editor.emacsMarkMode = false + editor.selectionMarkMode = false // if Editor.deselectOnCopy is set (e.g. we're in Emacs mode), we want to remove the selection after we've copied the text if (editor.deselectOnCopy && !cut) { diff --git a/src/editor/editor.js b/src/editor/editor.js index b5484d1..f08927d 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -64,7 +64,7 @@ export class HeynoteEditor { this.notesStore = useHeynoteStore() this.errorStore = useErrorStore() this.name = "" - this.emacsMarkMode = false + this.selectionMarkMode = false const state = EditorState.create({ diff --git a/src/editor/emacs-mode.js b/src/editor/emacs-mode.js index 7d957f5..03e48a2 100644 --- a/src/editor/emacs-mode.js +++ b/src/editor/emacs-mode.js @@ -6,9 +6,9 @@ import { * Takes a command that moves the cursor and a command that marks the selection, and returns a new command that * will run the mark command if we're in Emacs mark mode, or the move command otherwise. */ -export function emacsMoveCommand(defaultCmd, markModeCmd) { +export function markModeMoveCommand(defaultCmd, markModeCmd) { return (editor) => { - if (editor.emacsMarkMode) { + if (editor.selectionMarkMode) { return (view) => { markModeCmd(view) // we need to return true here instead of returning what the default command returns, since the default @@ -23,17 +23,17 @@ export function emacsMoveCommand(defaultCmd, markModeCmd) { } -export function toggleEmacsMarkMode(editor) { +export function toggleSelectionMarkMode(editor) { return (view) => { - editor.emacsMarkMode = !editor.emacsMarkMode + editor.selectionMarkMode = !editor.selectionMarkMode return true } } -export function emacsCancel(editor) { +export function selectionMarkModeCancel(editor) { return (view) => { simplifySelection(view) - editor.emacsMarkMode = false + editor.selectionMarkMode = false return true } } diff --git a/src/editor/keymap.js b/src/editor/keymap.js index ca52817..b2d104b 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -105,9 +105,9 @@ export const EMACS_KEYMAP = [ cmd("Ctrl-w", "cut"), cmd("Ctrl-y", "paste"), cmd("EmacsMeta-w", "copy"), - cmd("Ctrl-Space", "toggleEmacsMarkMode"), - cmd("Ctrl-g", "emacsCancel"), - cmd("Escape", "emacsCancel"), + cmd("Ctrl-Space", "toggleSelectionMarkMode"), + cmd("Ctrl-g", "selectionMarkModeCancel"), + cmd("Escape", "selectionMarkModeCancel"), cmd("Ctrl-o", "splitLine"), cmd("Ctrl-d", "deleteCharForward"), cmd("Ctrl-h", "deleteCharBackward"), From c6cae175228cce945651890db392a2f9b3f59d5e Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 14:55:11 +0200 Subject: [PATCH 32/62] Rename Emacs mark mode -> Selection mark mode --- src/editor/commands.js | 2 +- src/editor/{emacs-mode.js => mark-mode.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/editor/{emacs-mode.js => mark-mode.js} (100%) diff --git a/src/editor/commands.js b/src/editor/commands.js index 890174d..1844ec2 100644 --- a/src/editor/commands.js +++ b/src/editor/commands.js @@ -84,7 +84,7 @@ const HEYNOTE_COMMANDS = { copy: copyCommand, } -// emacs-mode:ify all cursor/select commands from CodeMirror +// selection mark-mode:ify all cursor/select commands from CodeMirror for (let commandSuffix of [ "CharLeft", "CharRight", "CharBackward", "CharForward", diff --git a/src/editor/emacs-mode.js b/src/editor/mark-mode.js similarity index 100% rename from src/editor/emacs-mode.js rename to src/editor/mark-mode.js From 28fb986250c0084c77919358efc0b25b573f5a6a Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 15:08:49 +0200 Subject: [PATCH 33/62] Rename Emacs mark mode -> Selection mark mode --- src/editor/commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/commands.js b/src/editor/commands.js index 1844ec2..b4df7df 100644 --- a/src/editor/commands.js +++ b/src/editor/commands.js @@ -29,7 +29,7 @@ import { transposeChars } from "./block/transpose-chars.js" import { cutCommand, copyCommand, pasteCommand } from "./copy-paste.js" -import { markModeMoveCommand, toggleSelectionMarkMode, selectionMarkModeCancel } from "./emacs-mode.js" +import { markModeMoveCommand, toggleSelectionMarkMode, selectionMarkModeCancel } from "./mark-mode.js" const cursorPreviousBlock = markModeMoveCommand(gotoPreviousBlock, selectPreviousBlock) From bcaa2d300657a82a0d5ae6cb6e8ceb47402d7a9f Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 16:18:23 +0200 Subject: [PATCH 34/62] Increase default window size slightly, and increase size of Settings dialog --- electron/main/index.ts | 4 ++-- src/components/settings/Settings.vue | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 31bad25..07dcb1a 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -83,8 +83,8 @@ export function quit() { async function createWindow() { // read any stored window settings from config, or use defaults let windowConfig = { - width: CONFIG.get("windowConfig.width", 900) as number, - height: CONFIG.get("windowConfig.height", 680) as number, + width: CONFIG.get("windowConfig.width", 940) as number, + height: CONFIG.get("windowConfig.height", 720) as number, isMaximized: CONFIG.get("windowConfig.isMaximized", false) as boolean, isFullScreen: CONFIG.get("windowConfig.isFullScreen", false) as boolean, x: CONFIG.get("windowConfig.x"), diff --git a/src/components/settings/Settings.vue b/src/components/settings/Settings.vue index cb399c6..7b21a6b 100644 --- a/src/components/settings/Settings.vue +++ b/src/components/settings/Settings.vue @@ -446,7 +446,7 @@ background: rgba(0, 0, 0, 0.5) .dialog - --dialog-height: 560px + --dialog-height: 600px --bottom-bar-height: 48px box-sizing: border-box z-index: 2 @@ -454,7 +454,7 @@ left: 50% top: 50% transform: translate(-50%, -50%) - width: 700px + width: 820px height: var(--dialog-height) max-width: 100% max-height: 100% From a080b627e0a97fbbc7ac0c6d6bb7a00bbaa2a317 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 14 Apr 2025 16:19:43 +0200 Subject: [PATCH 35/62] Add descriptions and categories to commands Use the descriptions and categories in the Command palette and Settings dialog --- src/components/BufferSelector.vue | 17 ++- src/components/settings/KeyBindRow.vue | 25 ++-- src/components/settings/KeyboardBindings.vue | 2 +- src/editor/commands.js | 132 ++++++++++++------- src/editor/editor.js | 2 +- src/editor/keymap.js | 2 +- 6 files changed, 115 insertions(+), 65 deletions(-) diff --git a/src/components/BufferSelector.vue b/src/components/BufferSelector.vue index 246c1d7..1a9b24a 100644 --- a/src/components/BufferSelector.vue +++ b/src/components/BufferSelector.vue @@ -52,9 +52,20 @@ ]), commands() { - return Object.keys(HEYNOTE_COMMANDS).map(cmd => ({ - name: cmd, - cmd: cmd, + const commands = Object.entries(HEYNOTE_COMMANDS) + // sort array first by category, then by description + commands.sort((a, b) => { + const aCategory = a[1].category || "" + const bCategory = b[1].category || "" + if (aCategory === bCategory) { + return a[1].description.localeCompare(b[1].description) + } else { + return aCategory.localeCompare(bCategory) + } + }) + return commands.map(([cmdKey, cmd]) => ({ + name: `${cmd.category}: ${cmd.description}`, + cmd: cmdKey, isCommand: true, })) }, diff --git a/src/components/settings/KeyBindRow.vue b/src/components/settings/KeyBindRow.vue index f4e304d..c436e43 100644 --- a/src/components/settings/KeyBindRow.vue +++ b/src/components/settings/KeyBindRow.vue @@ -1,4 +1,6 @@ @@ -29,8 +39,7 @@ - Unbound - {{ command }} + {{ commandLabel }}