mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-28 17:02:04 +03:00
MD Editor: Added plaintext/cm switching
Also aligned the construction of the inputs where possible.
This commit is contained in:
@ -1,25 +1,48 @@
|
|||||||
import {provideKeyBindings} from './shortcuts';
|
import {EditorView, KeyBinding, ViewUpdate} from "@codemirror/view";
|
||||||
import {EditorView, ViewUpdate} from "@codemirror/view";
|
|
||||||
import {MarkdownEditor} from "./index.mjs";
|
|
||||||
import {CodeModule} from "../global";
|
import {CodeModule} from "../global";
|
||||||
import {MarkdownEditorEventMap} from "./dom-handlers";
|
import {MarkdownEditorEventMap} from "./dom-handlers";
|
||||||
|
import {MarkdownEditorShortcutMap} from "./shortcuts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert editor shortcuts to CodeMirror keybinding format.
|
||||||
|
*/
|
||||||
|
export function shortcutsToKeyBindings(shortcuts: MarkdownEditorShortcutMap): KeyBinding[] {
|
||||||
|
const keyBindings = [];
|
||||||
|
|
||||||
|
const wrapAction = (action: () => void) => () => {
|
||||||
|
action();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [shortcut, action] of Object.entries(shortcuts)) {
|
||||||
|
keyBindings.push({key: shortcut, run: wrapAction(action), preventDefault: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBindings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate the codemirror instance for the Markdown editor.
|
* Initiate the codemirror instance for the Markdown editor.
|
||||||
*/
|
*/
|
||||||
export function init(editor: MarkdownEditor, Code: CodeModule, domEventHandlers: MarkdownEditorEventMap): EditorView {
|
export async function init(
|
||||||
|
input: HTMLTextAreaElement,
|
||||||
|
shortcuts: MarkdownEditorShortcutMap,
|
||||||
|
domEventHandlers: MarkdownEditorEventMap,
|
||||||
|
onChange: () => void
|
||||||
|
): Promise<EditorView> {
|
||||||
|
const Code = await window.importVersioned('code') as CodeModule;
|
||||||
|
|
||||||
function onViewUpdate(v: ViewUpdate) {
|
function onViewUpdate(v: ViewUpdate) {
|
||||||
if (v.docChanged) {
|
if (v.docChanged) {
|
||||||
editor.actions.updateAndRender();
|
onChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const cm = Code.markdownEditor(
|
const cm = Code.markdownEditor(
|
||||||
editor.config.inputEl,
|
input,
|
||||||
onViewUpdate,
|
onViewUpdate,
|
||||||
domEventHandlers,
|
domEventHandlers,
|
||||||
provideKeyBindings(editor),
|
shortcutsToKeyBindings(shortcuts),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add editor view to the window for easy access/debugging.
|
// Add editor view to the window for easy access/debugging.
|
||||||
|
@ -4,7 +4,6 @@ import {Actions} from './actions';
|
|||||||
import {Settings} from './settings';
|
import {Settings} from './settings';
|
||||||
import {listenToCommonEvents} from './common-events';
|
import {listenToCommonEvents} from './common-events';
|
||||||
import {init as initCodemirror} from './codemirror';
|
import {init as initCodemirror} from './codemirror';
|
||||||
import {CodeModule} from "../global";
|
|
||||||
import {MarkdownEditorInput} from "./inputs/interface";
|
import {MarkdownEditorInput} from "./inputs/interface";
|
||||||
import {CodemirrorInput} from "./inputs/codemirror";
|
import {CodemirrorInput} from "./inputs/codemirror";
|
||||||
import {TextareaInput} from "./inputs/textarea";
|
import {TextareaInput} from "./inputs/textarea";
|
||||||
@ -34,8 +33,6 @@ export interface MarkdownEditor {
|
|||||||
* Initiate a new Markdown editor instance.
|
* Initiate a new Markdown editor instance.
|
||||||
*/
|
*/
|
||||||
export async function init(config: MarkdownEditorConfig): Promise<MarkdownEditor> {
|
export async function init(config: MarkdownEditorConfig): Promise<MarkdownEditor> {
|
||||||
// const Code = await window.importVersioned('code') as CodeModule;
|
|
||||||
|
|
||||||
const editor: MarkdownEditor = {
|
const editor: MarkdownEditor = {
|
||||||
config,
|
config,
|
||||||
markdown: new Markdown(),
|
markdown: new Markdown(),
|
||||||
@ -46,15 +43,25 @@ export async function init(config: MarkdownEditorConfig): Promise<MarkdownEditor
|
|||||||
editor.display = new Display(editor);
|
editor.display = new Display(editor);
|
||||||
|
|
||||||
const eventHandlers = getMarkdownDomEventHandlers(editor);
|
const eventHandlers = getMarkdownDomEventHandlers(editor);
|
||||||
// TODO - Switching
|
const shortcuts = provideShortcutMap(editor);
|
||||||
// const codeMirror = initCodemirror(editor, Code);
|
const onInputChange = () => editor.actions.updateAndRender();
|
||||||
// editor.input = new CodemirrorInput(codeMirror);
|
|
||||||
editor.input = new TextareaInput(
|
|
||||||
config.inputEl,
|
|
||||||
provideShortcutMap(editor),
|
|
||||||
eventHandlers
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const initCodemirrorInput: () => Promise<MarkdownEditorInput> = async () => {
|
||||||
|
const codeMirror = await initCodemirror(config.inputEl, shortcuts, eventHandlers, onInputChange);
|
||||||
|
return new CodemirrorInput(codeMirror);
|
||||||
|
};
|
||||||
|
const initTextAreaInput: () => Promise<MarkdownEditorInput> = async () => {
|
||||||
|
return new TextareaInput(config.inputEl, shortcuts, eventHandlers, onInputChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPlainEditor = Boolean(editor.settings.get('plainEditor'));
|
||||||
|
editor.input = await (isPlainEditor ? initTextAreaInput() : initCodemirrorInput());
|
||||||
|
editor.settings.onChange('plainEditor', async (value) => {
|
||||||
|
const isPlain = Boolean(value);
|
||||||
|
const newInput = await (isPlain ? initTextAreaInput() : initCodemirrorInput());
|
||||||
|
editor.input.teardown();
|
||||||
|
editor.input = newInput;
|
||||||
|
});
|
||||||
// window.devinput = editor.input;
|
// window.devinput = editor.input;
|
||||||
|
|
||||||
listenToCommonEvents(editor);
|
listenToCommonEvents(editor);
|
||||||
|
@ -10,6 +10,10 @@ export class CodemirrorInput implements MarkdownEditorInput {
|
|||||||
this.cm = cm;
|
this.cm = cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teardown(): void {
|
||||||
|
this.cm.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
focus(): void {
|
focus(): void {
|
||||||
if (!this.cm.hasFocus) {
|
if (!this.cm.hasFocus) {
|
||||||
this.cm.focus();
|
this.cm.focus();
|
||||||
|
@ -73,4 +73,9 @@ export interface MarkdownEditorInput {
|
|||||||
* Search and return a line range which includes the provided text.
|
* Search and return a line range which includes the provided text.
|
||||||
*/
|
*/
|
||||||
searchForLineContaining(text: string): MarkdownEditorInputSelection|null;
|
searchForLineContaining(text: string): MarkdownEditorInputSelection|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tear down the input.
|
||||||
|
*/
|
||||||
|
teardown(): void;
|
||||||
}
|
}
|
@ -8,23 +8,43 @@ export class TextareaInput implements MarkdownEditorInput {
|
|||||||
protected input: HTMLTextAreaElement;
|
protected input: HTMLTextAreaElement;
|
||||||
protected shortcuts: MarkdownEditorShortcutMap;
|
protected shortcuts: MarkdownEditorShortcutMap;
|
||||||
protected events: MarkdownEditorEventMap;
|
protected events: MarkdownEditorEventMap;
|
||||||
|
protected onChange: () => void;
|
||||||
|
protected eventController = new AbortController();
|
||||||
|
|
||||||
constructor(input: HTMLTextAreaElement, shortcuts: MarkdownEditorShortcutMap, events: MarkdownEditorEventMap) {
|
constructor(
|
||||||
|
input: HTMLTextAreaElement,
|
||||||
|
shortcuts: MarkdownEditorShortcutMap,
|
||||||
|
events: MarkdownEditorEventMap,
|
||||||
|
onChange: () => void
|
||||||
|
) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.shortcuts = shortcuts;
|
this.shortcuts = shortcuts;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
|
this.onChange = onChange;
|
||||||
|
|
||||||
this.onKeyDown = this.onKeyDown.bind(this);
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
this.configureListeners();
|
this.configureListeners();
|
||||||
|
|
||||||
|
this.input.style.removeProperty("display");
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
this.eventController.abort('teardown');
|
||||||
}
|
}
|
||||||
|
|
||||||
configureListeners(): void {
|
configureListeners(): void {
|
||||||
// TODO - Teardown handling
|
// Keyboard shortcuts
|
||||||
this.input.addEventListener('keydown', this.onKeyDown);
|
this.input.addEventListener('keydown', this.onKeyDown, {signal: this.eventController.signal});
|
||||||
|
|
||||||
|
// Shared event listeners
|
||||||
for (const [name, listener] of Object.entries(this.events)) {
|
for (const [name, listener] of Object.entries(this.events)) {
|
||||||
this.input.addEventListener(name, listener);
|
this.input.addEventListener(name, listener, {signal: this.eventController.signal});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Input change handling
|
||||||
|
this.input.addEventListener('input', () => {
|
||||||
|
this.onChange();
|
||||||
|
}, {signal: this.eventController.signal});
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e: KeyboardEvent) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {MarkdownEditor} from "./index.mjs";
|
import {MarkdownEditor} from "./index.mjs";
|
||||||
import {KeyBinding} from "@codemirror/view";
|
|
||||||
|
|
||||||
export type MarkdownEditorShortcutMap = Record<string, () => void>;
|
export type MarkdownEditorShortcutMap = Record<string, () => void>;
|
||||||
|
|
||||||
@ -42,22 +41,3 @@ export function provideShortcutMap(editor: MarkdownEditor): MarkdownEditorShortc
|
|||||||
|
|
||||||
return shortcuts;
|
return shortcuts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the editor shortcuts in CodeMirror keybinding format.
|
|
||||||
*/
|
|
||||||
export function provideKeyBindings(editor: MarkdownEditor): KeyBinding[] {
|
|
||||||
const shortcuts = provideShortcutMap(editor);
|
|
||||||
const keyBindings = [];
|
|
||||||
|
|
||||||
const wrapAction = (action: ()=>void) => () => {
|
|
||||||
action();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [shortcut, action] of Object.entries(shortcuts)) {
|
|
||||||
keyBindings.push({key: shortcut, run: wrapAction(action), preventDefault: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyBindings;
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user