Got md shortcuts working, marked actions for update

This commit is contained in:
Dan Brown
2023-04-11 11:48:58 +01:00
parent 572037ef1f
commit da3e4f5f75
5 changed files with 84 additions and 48 deletions

View File

@ -1,4 +1,4 @@
import {EditorView} from "@codemirror/view" import {EditorView, keymap} from "@codemirror/view"
import Clipboard from "clipboard/dist/clipboard.min"; import Clipboard from "clipboard/dist/clipboard.min";
// Modes // Modes
@ -182,9 +182,10 @@ export function updateLayout(cmInstance) {
* @param {HTMLElement} elem * @param {HTMLElement} elem
* @param {function} onChange * @param {function} onChange
* @param {object} domEventHandlers * @param {object} domEventHandlers
* @param {Array} keyBindings
* @returns {*} * @returns {*}
*/ */
export function markdownEditor(elem, onChange, domEventHandlers) { export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) {
const content = elem.textContent; const content = elem.textContent;
// TODO - Change to pass something else that's useful, probably extension array? // TODO - Change to pass something else that's useful, probably extension array?
@ -199,20 +200,11 @@ export function markdownEditor(elem, onChange, domEventHandlers) {
onChange(v); onChange(v);
}), }),
EditorView.domEventHandlers(domEventHandlers), EditorView.domEventHandlers(domEventHandlers),
keymap.of(keyBindings),
], ],
}); });
elem.style.display = 'none'; elem.style.display = 'none';
return ev; 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";
} }

View File

@ -45,7 +45,7 @@ export class MarkdownEditor extends Component {
window.$events.emitPublic(this.elem, 'editor-markdown::setup', { window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
markdownIt: this.editor.markdown.getRenderer(), markdownIt: this.editor.markdown.getRenderer(),
displayEl: this.display, displayEl: this.display,
// TODO // TODO - change to codeMirrorView?
// codeMirrorInstance: this.editor.cm, // codeMirrorInstance: this.editor.cm,
}); });
} }
@ -58,7 +58,7 @@ export class MarkdownEditor extends Component {
if (button === null) return; if (button === null) return;
const action = button.getAttribute('data-action'); 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 === 'insertLink') this.editor.actions.showLinkSelector();
if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) { if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
this.editor.actions.showImageManager(); this.editor.actions.showImageManager();

View File

@ -28,7 +28,8 @@ export class Actions {
return this.lastContent; return this.lastContent;
} }
insertImage() { showImageInsert() {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
/** @type {ImageManager} **/ /** @type {ImageManager} **/
const imageManager = window.$components.first('image-manager'); const imageManager = window.$components.first('image-manager');
@ -42,7 +43,17 @@ export class Actions {
}, 'gallery'); }, '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() { insertLink() {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
const selectedText = this.editor.cm.getSelection() || ''; const selectedText = this.editor.cm.getSelection() || '';
const newText = `[${selectedText}]()`; const newText = `[${selectedText}]()`;
@ -53,6 +64,7 @@ export class Actions {
} }
showImageManager() { showImageManager() {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
/** @type {ImageManager} **/ /** @type {ImageManager} **/
const imageManager = window.$components.first('image-manager'); 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 // Show the popup link selector and insert a link when finished
showLinkSelector() { showLinkSelector() {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
/** @type {EntitySelectorPopup} **/ /** @type {EntitySelectorPopup} **/
const selector = window.$components.first('entity-selector-popup'); const selector = window.$components.first('entity-selector-popup');
@ -77,6 +90,7 @@ export class Actions {
// Show draw.io if enabled and handle save. // Show draw.io if enabled and handle save.
startDrawing() { startDrawing() {
// TODO
const url = this.editor.config.drawioUrl; const url = this.editor.config.drawioUrl;
if (!url) return; if (!url) return;
@ -101,6 +115,7 @@ export class Actions {
} }
insertDrawing(image, originalCursor) { insertDrawing(image, originalCursor) {
// TODO
const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`; const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
this.editor.cm.focus(); this.editor.cm.focus();
this.editor.cm.replaceSelection(newText); this.editor.cm.replaceSelection(newText);
@ -109,6 +124,7 @@ export class Actions {
// Show draw.io if enabled and handle save. // Show draw.io if enabled and handle save.
editDrawing(imgContainer) { editDrawing(imgContainer) {
// TODO
const drawioUrl = this.editor.config.drawioUrl; const drawioUrl = this.editor.config.drawioUrl;
if (!drawioUrl) { if (!drawioUrl) {
return; return;
@ -145,6 +161,7 @@ export class Actions {
} }
handleDrawingUploadError(error) { handleDrawingUploadError(error) {
// TODO
if (error.status === 413) { if (error.status === 413) {
window.$events.emit('error', this.editor.config.text.serverUploadLimit); window.$events.emit('error', this.editor.config.text.serverUploadLimit);
} else { } else {
@ -155,6 +172,7 @@ export class Actions {
// Make the editor full screen // Make the editor full screen
fullScreen() { fullScreen() {
// TODO
const container = this.editor.config.container; const container = this.editor.config.container;
const alreadyFullscreen = container.classList.contains('fullscreen'); const alreadyFullscreen = container.classList.contains('fullscreen');
container.classList.toggle('fullscreen', !alreadyFullscreen); container.classList.toggle('fullscreen', !alreadyFullscreen);
@ -163,6 +181,7 @@ export class Actions {
// Scroll to a specified text // Scroll to a specified text
scrollToText(searchText) { scrollToText(searchText) {
// TODO
if (!searchText) { if (!searchText) {
return; return;
} }
@ -189,6 +208,7 @@ export class Actions {
} }
focus() { focus() {
// TODO
this.editor.cm.focus(); this.editor.cm.focus();
} }
@ -197,6 +217,7 @@ export class Actions {
* @param {String} content * @param {String} content
*/ */
insertContent(content) { insertContent(content) {
// TODO
this.editor.cm.replaceSelection(content); this.editor.cm.replaceSelection(content);
} }
@ -205,6 +226,7 @@ export class Actions {
* @param {String} content * @param {String} content
*/ */
prependContent(content) { prependContent(content) {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
const newContent = content + '\n' + this.editor.cm.getValue(); const newContent = content + '\n' + this.editor.cm.getValue();
this.editor.cm.setValue(newContent); this.editor.cm.setValue(newContent);
@ -217,6 +239,7 @@ export class Actions {
* @param {String} content * @param {String} content
*/ */
appendContent(content) { appendContent(content) {
// TODO
const cursorPos = this.editor.cm.getCursor('from'); const cursorPos = this.editor.cm.getCursor('from');
const newContent = this.editor.cm.getValue() + '\n' + content; const newContent = this.editor.cm.getValue() + '\n' + content;
this.editor.cm.setValue(newContent); this.editor.cm.setValue(newContent);
@ -228,6 +251,7 @@ export class Actions {
* @param {String} content * @param {String} content
*/ */
replaceContent(content) { replaceContent(content) {
// TODO
this.editor.cm.setValue(content); this.editor.cm.setValue(content);
} }
@ -236,6 +260,7 @@ export class Actions {
* @param {String} replace * @param {String} replace
*/ */
findAndReplaceContent(search, replace) { findAndReplaceContent(search, replace) {
// TODO
const text = this.editor.cm.getValue(); const text = this.editor.cm.getValue();
const cursor = this.editor.cm.listSelections(); const cursor = this.editor.cm.listSelections();
this.editor.cm.setValue(text.replace(search, replace)); this.editor.cm.setValue(text.replace(search, replace));
@ -247,6 +272,7 @@ export class Actions {
* @param {String} newStart * @param {String} newStart
*/ */
replaceLineStart(newStart) { replaceLineStart(newStart) {
// TODO
const cursor = this.editor.cm.getCursor(); const cursor = this.editor.cm.getCursor();
let lineContent = this.editor.cm.getLine(cursor.line); let lineContent = this.editor.cm.getLine(cursor.line);
const lineLen = lineContent.length; const lineLen = lineContent.length;
@ -279,6 +305,7 @@ export class Actions {
* @param {String} end * @param {String} end
*/ */
wrapLine(start, end) { wrapLine(start, end) {
// TODO
const cursor = this.editor.cm.getCursor(); const cursor = this.editor.cm.getCursor();
const lineContent = this.editor.cm.getLine(cursor.line); const lineContent = this.editor.cm.getLine(cursor.line);
const lineLen = lineContent.length; const lineLen = lineContent.length;
@ -300,6 +327,7 @@ export class Actions {
* @param {String} end * @param {String} end
*/ */
wrapSelection(start, end) { wrapSelection(start, end) {
// TODO
const selection = this.editor.cm.getSelection(); const selection = this.editor.cm.getSelection();
if (selection === '') return this.wrapLine(start, end); if (selection === '') return this.wrapLine(start, end);
@ -324,6 +352,7 @@ export class Actions {
} }
replaceLineStartForOrderedList() { replaceLineStartForOrderedList() {
// TODO
const cursor = this.editor.cm.getCursor(); const cursor = this.editor.cm.getCursor();
const prevLineContent = this.editor.cm.getLine(cursor.line - 1) || ''; const prevLineContent = this.editor.cm.getLine(cursor.line - 1) || '';
const listMatch = prevLineContent.match(/^(\s*)(\d)([).])\s/) || []; 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. * Creates a callout block if none existing, and removes it if cycling past the danger type.
*/ */
cycleCalloutTypeAtSelection() { cycleCalloutTypeAtSelection() {
// TODO
const selectionRange = this.editor.cm.listSelections()[0]; const selectionRange = this.editor.cm.listSelections()[0];
const lineContent = this.editor.cm.getLine(selectionRange.anchor.line); const lineContent = this.editor.cm.getLine(selectionRange.anchor.line);
const lineLength = lineContent.length; const lineLength = lineContent.length;
@ -379,6 +409,7 @@ export class Actions {
* @param {File} file * @param {File} file
*/ */
uploadImage(file) { uploadImage(file) {
// TODO
if (file === null || file.type.indexOf('image') !== 0) return; if (file === null || file.type.indexOf('image') !== 0) return;
let ext = 'png'; let ext = 'png';
@ -436,6 +467,7 @@ export class Actions {
* @param {Number} posY * @param {Number} posY
*/ */
insertTemplate(templateId, posX, posY) { insertTemplate(templateId, posX, posY) {
// TODO
const cursorPos = this.editor.cm.coordsChar({left: posX, top: posY}); const cursorPos = this.editor.cm.coordsChar({left: posX, top: posY});
this.editor.cm.setCursor(cursorPos); this.editor.cm.setCursor(cursorPos);
window.$http.get(`/templates/${templateId}`).then(resp => { window.$http.get(`/templates/${templateId}`).then(resp => {
@ -449,6 +481,7 @@ export class Actions {
* @param {File[]} images * @param {File[]} images
*/ */
insertClipboardImages(images) { insertClipboardImages(images) {
// TODO
const cursorPos = this.editor.cm.coordsChar({left: event.pageX, top: event.pageY}); const cursorPos = this.editor.cm.coordsChar({left: event.pageX, top: event.pageY});
this.editor.cm.setCursor(cursorPos); this.editor.cm.setCursor(cursorPos);
for (const image of images) { for (const image of images) {

View File

@ -1,4 +1,4 @@
import {provide as provideShortcuts} from "./shortcuts"; import {provideKeyBindings} from "./shortcuts";
import {debounce} from "../services/util"; import {debounce} from "../services/util";
import Clipboard from "../services/clipboard"; import Clipboard from "../services/clipboard";
@ -28,15 +28,17 @@ export async function init(editor) {
scroll: (event) => syncActive && onScrollDebounced(event) 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; window.cm = cm;
// Will force to remain as ltr for now due to issues when HTML is in editor. // Will force to remain as ltr for now due to issues when HTML is in editor.
// TODO // TODO
// cm.setOption('direction', 'ltr'); // cm.setOption('direction', 'ltr');
// Register shortcuts
// TODO
// cm.setOption('extraKeys', provideShortcuts(editor, Code.getMetaKey()));
// Handle image paste // Handle image paste

View File

@ -1,48 +1,57 @@
/** /**
* Provide shortcuts for the given codemirror instance. * Provide shortcuts for the editor instance.
* @param {MarkdownEditor} editor * @param {MarkdownEditor} editor
* @param {String} metaKey
* @returns {Object<String, Function>} * @returns {Object<String, Function>}
*/ */
export function provide(editor, metaKey) { function provide(editor) {
const shortcuts = {}; const shortcuts = {};
// Insert Image shortcut // Insert Image shortcut
shortcuts[`${metaKey}-Alt-I`] = function(cm) { shortcuts['Mod-Alt-i'] = () => editor.actions.insertImage();
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);
};
// Save draft // Save draft
shortcuts[`${metaKey}-S`] = cm => window.$events.emit('editor-save-draft'); shortcuts['Mod-s'] = cm => window.$events.emit('editor-save-draft');
// Save page // 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 // Show link selector
shortcuts[`Shift-${metaKey}-K`] = cm => editor.actions.showLinkSelector(); shortcuts['Shift-Mod-k'] = cm => editor.actions.showLinkSelector();
// Insert Link // Insert Link
shortcuts[`${metaKey}-K`] = cm => editor.actions.insertLink(); shortcuts['Mod-k'] = cm => editor.actions.insertLink();
// FormatShortcuts // FormatShortcuts
shortcuts[`${metaKey}-1`] = cm => editor.actions.replaceLineStart('##'); shortcuts['Mod-1'] = cm => editor.actions.replaceLineStart('##');
shortcuts[`${metaKey}-2`] = cm => editor.actions.replaceLineStart('###'); shortcuts['Mod-2'] = cm => editor.actions.replaceLineStart('###');
shortcuts[`${metaKey}-3`] = cm => editor.actions.replaceLineStart('####'); shortcuts['Mod-3'] = cm => editor.actions.replaceLineStart('####');
shortcuts[`${metaKey}-4`] = cm => editor.actions.replaceLineStart('#####'); shortcuts['Mod-4'] = cm => editor.actions.replaceLineStart('#####');
shortcuts[`${metaKey}-5`] = cm => editor.actions.replaceLineStart(''); shortcuts['Mod-5'] = cm => editor.actions.replaceLineStart('');
shortcuts[`${metaKey}-D`] = cm => editor.actions.replaceLineStart(''); shortcuts['Mod-d'] = cm => editor.actions.replaceLineStart('');
shortcuts[`${metaKey}-6`] = cm => editor.actions.replaceLineStart('>'); shortcuts['Mod-6'] = cm => editor.actions.replaceLineStart('>');
shortcuts[`${metaKey}-Q`] = cm => editor.actions.replaceLineStart('>'); shortcuts['Mod-q'] = cm => editor.actions.replaceLineStart('>');
shortcuts[`${metaKey}-7`] = cm => editor.actions.wrapSelection('\n```\n', '\n```'); shortcuts['Mod-7'] = cm => editor.actions.wrapSelection('\n```\n', '\n```');
shortcuts[`${metaKey}-8`] = cm => editor.actions.wrapSelection('`', '`'); shortcuts['Mod-8'] = cm => editor.actions.wrapSelection('`', '`');
shortcuts[`Shift-${metaKey}-E`] = cm => editor.actions.wrapSelection('`', '`'); shortcuts['Shift-Mod-e'] = cm => editor.actions.wrapSelection('`', '`');
shortcuts[`${metaKey}-9`] = cm => editor.actions.cycleCalloutTypeAtSelection(); shortcuts['Mod-9'] = cm => editor.actions.cycleCalloutTypeAtSelection();
shortcuts[`${metaKey}-P`] = cm => editor.actions.replaceLineStart('-') shortcuts['Mod-p'] = cm => editor.actions.replaceLineStart('-')
shortcuts[`${metaKey}-O`] = cm => editor.actions.replaceLineStartForOrderedList() shortcuts['Mod-o'] = cm => editor.actions.replaceLineStartForOrderedList()
return shortcuts; 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;
} }