From da3e4f5f75d2012bc4e0eaac358a196d99ab3fbb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 11 Apr 2023 11:48:58 +0100 Subject: [PATCH] Got md shortcuts working, marked actions for update --- resources/js/code/index.mjs | 16 ++---- resources/js/components/markdown-editor.js | 4 +- resources/js/markdown/actions.js | 35 +++++++++++- resources/js/markdown/codemirror.js | 12 ++-- resources/js/markdown/shortcuts.js | 65 ++++++++++++---------- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 6cad052f4..3fe4a6d86 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,4 +1,4 @@ -import {EditorView} from "@codemirror/view" +import {EditorView, keymap} from "@codemirror/view" import Clipboard from "clipboard/dist/clipboard.min"; // Modes @@ -182,9 +182,10 @@ export function updateLayout(cmInstance) { * @param {HTMLElement} elem * @param {function} onChange * @param {object} domEventHandlers + * @param {Array} keyBindings * @returns {*} */ -export function markdownEditor(elem, onChange, domEventHandlers) { +export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { const content = elem.textContent; // TODO - Change to pass something else that's useful, probably extension array? @@ -199,20 +200,11 @@ export function markdownEditor(elem, onChange, domEventHandlers) { onChange(v); }), EditorView.domEventHandlers(domEventHandlers), + keymap.of(keyBindings), ], }); elem.style.display = 'none'; return ev; -} - -/** - * Get the 'meta' key dependent on the user's system. - * @returns {string} - */ -export function getMetaKey() { - // TODO - Redo, Is needed? No CodeMirror instance to use. - const mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; - return mac ? "Cmd" : "Ctrl"; } \ No newline at end of file diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index 6b4682d1e..922916701 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -45,7 +45,7 @@ export class MarkdownEditor extends Component { window.$events.emitPublic(this.elem, 'editor-markdown::setup', { markdownIt: this.editor.markdown.getRenderer(), displayEl: this.display, - // TODO + // TODO - change to codeMirrorView? // codeMirrorInstance: this.editor.cm, }); } @@ -58,7 +58,7 @@ export class MarkdownEditor extends Component { if (button === null) return; const action = button.getAttribute('data-action'); - if (action === 'insertImage') this.editor.actions.insertImage(); + if (action === 'insertImage') this.editor.actions.showImageInsert(); if (action === 'insertLink') this.editor.actions.showLinkSelector(); if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) { this.editor.actions.showImageManager(); diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index 666998723..dfbe89c5a 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -28,7 +28,8 @@ export class Actions { return this.lastContent; } - insertImage() { + showImageInsert() { + // TODO const cursorPos = this.editor.cm.getCursor('from'); /** @type {ImageManager} **/ const imageManager = window.$components.first('image-manager'); @@ -42,7 +43,17 @@ export class Actions { }, 'gallery'); } + insertImage() { + // TODO + const selectedText = this.editor.cm.getSelection(); + const newText = `![${selectedText}](http://)`; + const cursorPos = this.editor.cm.getCursor('from'); + this.editor.cm.replaceSelection(newText); + this.editor.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length -1); + } + insertLink() { + // TODO const cursorPos = this.editor.cm.getCursor('from'); const selectedText = this.editor.cm.getSelection() || ''; const newText = `[${selectedText}]()`; @@ -53,6 +64,7 @@ export class Actions { } showImageManager() { + // TODO const cursorPos = this.editor.cm.getCursor('from'); /** @type {ImageManager} **/ const imageManager = window.$components.first('image-manager'); @@ -63,6 +75,7 @@ export class Actions { // Show the popup link selector and insert a link when finished showLinkSelector() { + // TODO const cursorPos = this.editor.cm.getCursor('from'); /** @type {EntitySelectorPopup} **/ const selector = window.$components.first('entity-selector-popup'); @@ -77,6 +90,7 @@ export class Actions { // Show draw.io if enabled and handle save. startDrawing() { + // TODO const url = this.editor.config.drawioUrl; if (!url) return; @@ -101,6 +115,7 @@ export class Actions { } insertDrawing(image, originalCursor) { + // TODO const newText = `
`; this.editor.cm.focus(); this.editor.cm.replaceSelection(newText); @@ -109,6 +124,7 @@ export class Actions { // Show draw.io if enabled and handle save. editDrawing(imgContainer) { + // TODO const drawioUrl = this.editor.config.drawioUrl; if (!drawioUrl) { return; @@ -145,6 +161,7 @@ export class Actions { } handleDrawingUploadError(error) { + // TODO if (error.status === 413) { window.$events.emit('error', this.editor.config.text.serverUploadLimit); } else { @@ -155,6 +172,7 @@ export class Actions { // Make the editor full screen fullScreen() { + // TODO const container = this.editor.config.container; const alreadyFullscreen = container.classList.contains('fullscreen'); container.classList.toggle('fullscreen', !alreadyFullscreen); @@ -163,6 +181,7 @@ export class Actions { // Scroll to a specified text scrollToText(searchText) { + // TODO if (!searchText) { return; } @@ -189,6 +208,7 @@ export class Actions { } focus() { + // TODO this.editor.cm.focus(); } @@ -197,6 +217,7 @@ export class Actions { * @param {String} content */ insertContent(content) { + // TODO this.editor.cm.replaceSelection(content); } @@ -205,6 +226,7 @@ export class Actions { * @param {String} content */ prependContent(content) { + // TODO const cursorPos = this.editor.cm.getCursor('from'); const newContent = content + '\n' + this.editor.cm.getValue(); this.editor.cm.setValue(newContent); @@ -217,6 +239,7 @@ export class Actions { * @param {String} content */ appendContent(content) { + // TODO const cursorPos = this.editor.cm.getCursor('from'); const newContent = this.editor.cm.getValue() + '\n' + content; this.editor.cm.setValue(newContent); @@ -228,6 +251,7 @@ export class Actions { * @param {String} content */ replaceContent(content) { + // TODO this.editor.cm.setValue(content); } @@ -236,6 +260,7 @@ export class Actions { * @param {String} replace */ findAndReplaceContent(search, replace) { + // TODO const text = this.editor.cm.getValue(); const cursor = this.editor.cm.listSelections(); this.editor.cm.setValue(text.replace(search, replace)); @@ -247,6 +272,7 @@ export class Actions { * @param {String} newStart */ replaceLineStart(newStart) { + // TODO const cursor = this.editor.cm.getCursor(); let lineContent = this.editor.cm.getLine(cursor.line); const lineLen = lineContent.length; @@ -279,6 +305,7 @@ export class Actions { * @param {String} end */ wrapLine(start, end) { + // TODO const cursor = this.editor.cm.getCursor(); const lineContent = this.editor.cm.getLine(cursor.line); const lineLen = lineContent.length; @@ -300,6 +327,7 @@ export class Actions { * @param {String} end */ wrapSelection(start, end) { + // TODO const selection = this.editor.cm.getSelection(); if (selection === '') return this.wrapLine(start, end); @@ -324,6 +352,7 @@ export class Actions { } replaceLineStartForOrderedList() { + // TODO const cursor = this.editor.cm.getCursor(); const prevLineContent = this.editor.cm.getLine(cursor.line - 1) || ''; const listMatch = prevLineContent.match(/^(\s*)(\d)([).])\s/) || []; @@ -341,6 +370,7 @@ export class Actions { * Creates a callout block if none existing, and removes it if cycling past the danger type. */ cycleCalloutTypeAtSelection() { + // TODO const selectionRange = this.editor.cm.listSelections()[0]; const lineContent = this.editor.cm.getLine(selectionRange.anchor.line); const lineLength = lineContent.length; @@ -379,6 +409,7 @@ export class Actions { * @param {File} file */ uploadImage(file) { + // TODO if (file === null || file.type.indexOf('image') !== 0) return; let ext = 'png'; @@ -436,6 +467,7 @@ export class Actions { * @param {Number} posY */ insertTemplate(templateId, posX, posY) { + // TODO const cursorPos = this.editor.cm.coordsChar({left: posX, top: posY}); this.editor.cm.setCursor(cursorPos); window.$http.get(`/templates/${templateId}`).then(resp => { @@ -449,6 +481,7 @@ export class Actions { * @param {File[]} images */ insertClipboardImages(images) { + // TODO const cursorPos = this.editor.cm.coordsChar({left: event.pageX, top: event.pageY}); this.editor.cm.setCursor(cursorPos); for (const image of images) { diff --git a/resources/js/markdown/codemirror.js b/resources/js/markdown/codemirror.js index dad999e7a..cd620137d 100644 --- a/resources/js/markdown/codemirror.js +++ b/resources/js/markdown/codemirror.js @@ -1,4 +1,4 @@ -import {provide as provideShortcuts} from "./shortcuts"; +import {provideKeyBindings} from "./shortcuts"; import {debounce} from "../services/util"; import Clipboard from "../services/clipboard"; @@ -28,15 +28,17 @@ export async function init(editor) { scroll: (event) => syncActive && onScrollDebounced(event) } - const cm = Code.markdownEditor(editor.config.inputEl, onViewUpdate, domEventHandlers); + const cm = Code.markdownEditor( + editor.config.inputEl, + onViewUpdate, + domEventHandlers, + provideKeyBindings(editor), + ); window.cm = cm; // Will force to remain as ltr for now due to issues when HTML is in editor. // TODO // cm.setOption('direction', 'ltr'); - // Register shortcuts - // TODO - // cm.setOption('extraKeys', provideShortcuts(editor, Code.getMetaKey())); // Handle image paste diff --git a/resources/js/markdown/shortcuts.js b/resources/js/markdown/shortcuts.js index 17ffe2fb3..08841e6c2 100644 --- a/resources/js/markdown/shortcuts.js +++ b/resources/js/markdown/shortcuts.js @@ -1,48 +1,57 @@ /** - * Provide shortcuts for the given codemirror instance. + * Provide shortcuts for the editor instance. * @param {MarkdownEditor} editor - * @param {String} metaKey * @returns {Object} */ -export function provide(editor, metaKey) { +function provide(editor) { const shortcuts = {}; // Insert Image shortcut - shortcuts[`${metaKey}-Alt-I`] = function(cm) { - const selectedText = cm.getSelection(); - const newText = `![${selectedText}](http://)`; - const cursorPos = cm.getCursor('from'); - cm.replaceSelection(newText); - cm.setCursor(cursorPos.line, cursorPos.ch + newText.length -1); - }; + shortcuts['Mod-Alt-i'] = () => editor.actions.insertImage(); // Save draft - shortcuts[`${metaKey}-S`] = cm => window.$events.emit('editor-save-draft'); + shortcuts['Mod-s'] = cm => window.$events.emit('editor-save-draft'); // Save page - shortcuts[`${metaKey}-Enter`] = cm => window.$events.emit('editor-save-page'); + shortcuts['Mod-Enter'] = cm => window.$events.emit('editor-save-page'); // Show link selector - shortcuts[`Shift-${metaKey}-K`] = cm => editor.actions.showLinkSelector(); + shortcuts['Shift-Mod-k'] = cm => editor.actions.showLinkSelector(); // Insert Link - shortcuts[`${metaKey}-K`] = cm => editor.actions.insertLink(); + shortcuts['Mod-k'] = cm => editor.actions.insertLink(); // FormatShortcuts - shortcuts[`${metaKey}-1`] = cm => editor.actions.replaceLineStart('##'); - shortcuts[`${metaKey}-2`] = cm => editor.actions.replaceLineStart('###'); - shortcuts[`${metaKey}-3`] = cm => editor.actions.replaceLineStart('####'); - shortcuts[`${metaKey}-4`] = cm => editor.actions.replaceLineStart('#####'); - shortcuts[`${metaKey}-5`] = cm => editor.actions.replaceLineStart(''); - shortcuts[`${metaKey}-D`] = cm => editor.actions.replaceLineStart(''); - shortcuts[`${metaKey}-6`] = cm => editor.actions.replaceLineStart('>'); - shortcuts[`${metaKey}-Q`] = cm => editor.actions.replaceLineStart('>'); - shortcuts[`${metaKey}-7`] = cm => editor.actions.wrapSelection('\n```\n', '\n```'); - shortcuts[`${metaKey}-8`] = cm => editor.actions.wrapSelection('`', '`'); - shortcuts[`Shift-${metaKey}-E`] = cm => editor.actions.wrapSelection('`', '`'); - shortcuts[`${metaKey}-9`] = cm => editor.actions.cycleCalloutTypeAtSelection(); - shortcuts[`${metaKey}-P`] = cm => editor.actions.replaceLineStart('-') - shortcuts[`${metaKey}-O`] = cm => editor.actions.replaceLineStartForOrderedList() + shortcuts['Mod-1'] = cm => editor.actions.replaceLineStart('##'); + shortcuts['Mod-2'] = cm => editor.actions.replaceLineStart('###'); + shortcuts['Mod-3'] = cm => editor.actions.replaceLineStart('####'); + shortcuts['Mod-4'] = cm => editor.actions.replaceLineStart('#####'); + shortcuts['Mod-5'] = cm => editor.actions.replaceLineStart(''); + shortcuts['Mod-d'] = cm => editor.actions.replaceLineStart(''); + shortcuts['Mod-6'] = cm => editor.actions.replaceLineStart('>'); + shortcuts['Mod-q'] = cm => editor.actions.replaceLineStart('>'); + shortcuts['Mod-7'] = cm => editor.actions.wrapSelection('\n```\n', '\n```'); + shortcuts['Mod-8'] = cm => editor.actions.wrapSelection('`', '`'); + shortcuts['Shift-Mod-e'] = cm => editor.actions.wrapSelection('`', '`'); + shortcuts['Mod-9'] = cm => editor.actions.cycleCalloutTypeAtSelection(); + shortcuts['Mod-p'] = cm => editor.actions.replaceLineStart('-') + shortcuts['Mod-o'] = cm => editor.actions.replaceLineStartForOrderedList() return shortcuts; +} + +/** + * Get the editor shortcuts in CodeMirror keybinding format. + * @param {MarkdownEditor} editor + * @return {{key: String, run: function, preventDefault: boolean}[]} + */ +export function provideKeyBindings(editor) { + const shortcuts= provide(editor); + const keyBindings = []; + + for (const [shortcut, action] of Object.entries(shortcuts)) { + keyBindings.push({key: shortcut, run: action, preventDefault: true}); + } + + return keyBindings; } \ No newline at end of file