From: Dan Brown Date: Mon, 21 Jul 2025 17:53:22 +0000 (+0100) Subject: MD Editor: Added plaintext input implementation X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/6b4b500a3313f30c92a8a6ffa1d8427fdf4d2aaa MD Editor: Added plaintext input implementation --- diff --git a/resources/js/markdown/codemirror.ts b/resources/js/markdown/codemirror.ts index 1b54c5819..82aeb1141 100644 --- a/resources/js/markdown/codemirror.ts +++ b/resources/js/markdown/codemirror.ts @@ -1,72 +1,19 @@ import {provideKeyBindings} from './shortcuts'; -import {debounce} from '../services/util'; -import {Clipboard} from '../services/clipboard'; import {EditorView, ViewUpdate} from "@codemirror/view"; import {MarkdownEditor} from "./index.mjs"; import {CodeModule} from "../global"; +import {MarkdownEditorEventMap} from "./dom-handlers"; /** - * Initiate the codemirror instance for the MarkDown editor. + * Initiate the codemirror instance for the Markdown editor. */ -export function init(editor: MarkdownEditor, Code: CodeModule): EditorView { +export function init(editor: MarkdownEditor, Code: CodeModule, domEventHandlers: MarkdownEditorEventMap): EditorView { function onViewUpdate(v: ViewUpdate) { if (v.docChanged) { editor.actions.updateAndRender(); } } - const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false); - let syncActive = editor.settings.get('scrollSync'); - editor.settings.onChange('scrollSync', val => { - syncActive = val; - }); - - const domEventHandlers = { - // Handle scroll to sync display view - scroll: (event: Event) => syncActive && onScrollDebounced(event), - // Handle image & content drag n drop - drop: (event: DragEvent) => { - if (!event.dataTransfer) { - return; - } - - const templateId = event.dataTransfer.getData('bookstack/template'); - if (templateId) { - event.preventDefault(); - editor.actions.insertTemplate(templateId, event.pageX, event.pageY); - } - - const clipboard = new Clipboard(event.dataTransfer); - const clipboardImages = clipboard.getImages(); - if (clipboardImages.length > 0) { - event.stopPropagation(); - event.preventDefault(); - editor.actions.insertClipboardImages(clipboardImages, event.pageX, event.pageY); - } - }, - // Handle dragover event to allow as drop-target in chrome - dragover: (event: DragEvent) => { - event.preventDefault(); - }, - // Handle image paste - paste: (event: ClipboardEvent) => { - if (!event.clipboardData) { - return; - } - - const clipboard = new Clipboard(event.clipboardData); - - // Don't handle the event ourselves if no items exist of contains table-looking data - if (!clipboard.hasItems() || clipboard.containsTabularData()) { - return; - } - - const images = clipboard.getImages(); - for (const image of images) { - editor.actions.uploadImage(image); - } - }, - }; const cm = Code.markdownEditor( editor.config.inputEl, diff --git a/resources/js/markdown/dom-handlers.ts b/resources/js/markdown/dom-handlers.ts new file mode 100644 index 000000000..db3f2b576 --- /dev/null +++ b/resources/js/markdown/dom-handlers.ts @@ -0,0 +1,62 @@ +import {Clipboard} from "../services/clipboard"; +import {MarkdownEditor} from "./index.mjs"; +import {debounce} from "../services/util"; + + +export type MarkdownEditorEventMap = Record void>; + +export function getMarkdownDomEventHandlers(editor: MarkdownEditor): MarkdownEditorEventMap { + + const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false); + let syncActive = editor.settings.get('scrollSync'); + editor.settings.onChange('scrollSync', val => { + syncActive = val; + }); + + return { + // Handle scroll to sync display view + scroll: (event: Event) => syncActive && onScrollDebounced(event), + // Handle image & content drag n drop + drop: (event: DragEvent) => { + if (!event.dataTransfer) { + return; + } + + const templateId = event.dataTransfer.getData('bookstack/template'); + if (templateId) { + event.preventDefault(); + editor.actions.insertTemplate(templateId, event.pageX, event.pageY); + } + + const clipboard = new Clipboard(event.dataTransfer); + const clipboardImages = clipboard.getImages(); + if (clipboardImages.length > 0) { + event.stopPropagation(); + event.preventDefault(); + editor.actions.insertClipboardImages(clipboardImages, event.pageX, event.pageY); + } + }, + // Handle dragover event to allow as drop-target in chrome + dragover: (event: DragEvent) => { + event.preventDefault(); + }, + // Handle image paste + paste: (event: ClipboardEvent) => { + if (!event.clipboardData) { + return; + } + + const clipboard = new Clipboard(event.clipboardData); + + // Don't handle the event ourselves if no items exist of contains table-looking data + if (!clipboard.hasItems() || clipboard.containsTabularData()) { + return; + } + + const images = clipboard.getImages(); + for (const image of images) { + editor.actions.uploadImage(image); + } + }, + }; +} \ No newline at end of file diff --git a/resources/js/markdown/index.mts b/resources/js/markdown/index.mts index 5385e27cc..7edf80d4f 100644 --- a/resources/js/markdown/index.mts +++ b/resources/js/markdown/index.mts @@ -7,6 +7,9 @@ import {init as initCodemirror} from './codemirror'; import {CodeModule} from "../global"; import {MarkdownEditorInput} from "./inputs/interface"; import {CodemirrorInput} from "./inputs/codemirror"; +import {TextareaInput} from "./inputs/textarea"; +import {provideShortcutMap} from "./shortcuts"; +import {getMarkdownDomEventHandlers} from "./dom-handlers"; export interface MarkdownEditorConfig { pageId: string; @@ -31,7 +34,7 @@ export interface MarkdownEditor { * Initiate a new Markdown editor instance. */ export async function init(config: MarkdownEditorConfig): Promise { - const Code = await window.importVersioned('code') as CodeModule; + // const Code = await window.importVersioned('code') as CodeModule; const editor: MarkdownEditor = { config, @@ -42,8 +45,17 @@ export async function init(config: MarkdownEditorConfig): Promise -1) { + return this.getLineRangeFromPosition(textPosition); + } + + return null; + } + + setSelection(selection: MarkdownEditorInputSelection, scrollIntoView: boolean): void { + this.input.selectionStart = selection.from; + this.input.selectionEnd = selection.to; + } + + setText(text: string, selection?: MarkdownEditorInputSelection): void { + this.input.value = text; + if (selection) { + this.setSelection(selection, false); + } + } + + spliceText(from: number, to: number, newText: string, selection: Partial | null): void { + const text = this.getText(); + const updatedText = text.slice(0, from) + newText + text.slice(to); + this.setText(updatedText); + if (selection && selection.from) { + const newSelection = {from: selection.from, to: selection.to || selection.from}; + this.setSelection(newSelection, false); + } + } +} \ No newline at end of file diff --git a/resources/js/markdown/shortcuts.ts b/resources/js/markdown/shortcuts.ts index c746b52e7..734160f29 100644 --- a/resources/js/markdown/shortcuts.ts +++ b/resources/js/markdown/shortcuts.ts @@ -1,11 +1,13 @@ import {MarkdownEditor} from "./index.mjs"; import {KeyBinding} from "@codemirror/view"; +export type MarkdownEditorShortcutMap = Record void>; + /** * Provide shortcuts for the editor instance. */ -function provide(editor: MarkdownEditor): Record void> { - const shortcuts: Record void> = {}; +export function provideShortcutMap(editor: MarkdownEditor): MarkdownEditorShortcutMap { + const shortcuts: MarkdownEditorShortcutMap = {}; // Insert Image shortcut shortcuts['Shift-Mod-i'] = () => editor.actions.insertImage(); @@ -45,7 +47,7 @@ function provide(editor: MarkdownEditor): Record void> { * Get the editor shortcuts in CodeMirror keybinding format. */ export function provideKeyBindings(editor: MarkdownEditor): KeyBinding[] { - const shortcuts = provide(editor); + const shortcuts = provideShortcutMap(editor); const keyBindings = []; const wrapAction = (action: ()=>void) => () => { diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index b66688f8d..e71edc1d7 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -57,6 +57,9 @@ padding: vars.$xs vars.$m; color: #444; border-radius: 0; + height: 100%; + font-size: 14px; + line-height: 1.2; max-height: 100%; flex: 1; border: 0;