diff --git a/assets/icons/drag-vertical-dark.svg b/assets/icons/drag-vertical-dark.svg new file mode 100644 index 0000000..a425050 --- /dev/null +++ b/assets/icons/drag-vertical-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/drag-vertical-light.svg b/assets/icons/drag-vertical-light.svg new file mode 100644 index 0000000..3d785fa --- /dev/null +++ b/assets/icons/drag-vertical-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/electron/config.js b/electron/config.js index a306131..0436060 100644 --- a/electron/config.js +++ b/electron/config.js @@ -25,12 +25,15 @@ const schema = { "keymap": { "enum": ["default", "emacs"], default:"default" }, "emacsMetaKey": { "enum": [null, "alt", "meta"], default: null }, "keyBindings": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" + "type": "array", + "items": { + "type": "object", + "required": ["key", "command"], + "properties": { + "key": { "type": "string" }, + "command": { "type": "string" } + }, + "additionalProperties": false } }, @@ -71,7 +74,7 @@ const defaults = { settings: { keymap: "default", emacsMetaKey: isMac ? "meta" : "alt", - keyBindings: {}, + keyBindings: [], showLineNumberGutter: true, showFoldGutter: true, autoUpdate: true, diff --git a/package-lock.json b/package-lock.json index 60cd913..d635f9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "electron-log": "^5.0.1", "fuzzysort": "^3.0.2", "pinia": "^2.1.7", - "semver": "^7.6.3" + "semver": "^7.6.3", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@codemirror/autocomplete": "^6.11.1", @@ -5565,6 +5566,12 @@ "npm": ">= 3.0.0" } }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6472,6 +6479,18 @@ "typescript": "*" } }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "license": "MIT", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index e88ff2b..4a45b58 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "electron-log": "^5.0.1", "fuzzysort": "^3.0.2", "pinia": "^2.1.7", - "semver": "^7.6.3" + "semver": "^7.6.3", + "vuedraggable": "^4.1.0" } } diff --git a/src/components/settings/KeyBindRow.vue b/src/components/settings/KeyBindRow.vue index 64d8110..f4e304d 100644 --- a/src/components/settings/KeyBindRow.vue +++ b/src/components/settings/KeyBindRow.vue @@ -4,7 +4,7 @@ "keys", "command", "isDefault", - "isOverridden", + "source", ], computed: { @@ -19,9 +19,9 @@ @@ -57,6 +60,19 @@ .unbound font-style: italic color: #999 + &.drag-handle + width: 24px + padding: 0 + cursor: ns-resize + background-color: rgba(0,0,0, 0.02) + background-size: 20px + background-repeat: no-repeat + background-position: center center + background-image: url(@/assets/icons/drag-vertical-light.svg) + + +dark-mode + background-color: rgba(0,0,0, 0.08) + background-image: url(@/assets/icons/drag-vertical-dark.svg) button padding: 0 10px height: 22px diff --git a/src/components/settings/KeyboardBindings.vue b/src/components/settings/KeyboardBindings.vue index a4a584e..81adfe8 100644 --- a/src/components/settings/KeyboardBindings.vue +++ b/src/components/settings/KeyboardBindings.vue @@ -1,5 +1,6 @@ @@ -82,19 +88,41 @@

Keyboard Bindings

+ - + + + + + +
Source Key Command
@@ -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, + ) +}