import {listenToCommonEvents} from './common-events';
import {init as initCodemirror} from './codemirror';
import {EditorView} from "@codemirror/view";
+import {importVersioned} from "../services/util";
+import {CodeModule} from "../global";
export interface MarkdownEditorConfig {
pageId: string;
* Initiate a new Markdown editor instance.
*/
export async function init(config: MarkdownEditorConfig): Promise<MarkdownEditor> {
+ const Code = await window.importVersioned('code') as CodeModule;
+
const editor: MarkdownEditor = {
config,
markdown: new Markdown(),
editor.actions = new Actions(editor);
editor.display = new Display(editor);
- editor.cm = await initCodemirror(editor);
+ editor.cm = initCodemirror(editor, Code);
listenToCommonEvents(editor);
--- /dev/null
+import {MarkdownEditorInput, MarkdownEditorInputSelection} from "./interface";
+import {MarkdownEditor} from "../index.mjs";
+import {EditorView} from "@codemirror/view";
+import {ChangeSpec, SelectionRange, TransactionSpec} from "@codemirror/state";
+
+
+export class CodemirrorInput implements MarkdownEditorInput {
+
+ protected editor: MarkdownEditor;
+ protected cm: EditorView;
+
+ constructor(cm: EditorView) {
+ this.cm = cm;
+ }
+
+ focus(): void {
+ if (!this.editor.cm.hasFocus) {
+ this.editor.cm.focus();
+ }
+ }
+
+ getSelection(): MarkdownEditorInputSelection {
+ return this.editor.cm.state.selection.main;
+ }
+
+ getSelectionText(selection: MarkdownEditorInputSelection|null = null): string {
+ selection = selection || this.getSelection();
+ return this.editor.cm.state.sliceDoc(selection.from, selection.to);
+ }
+
+ setSelection(selection: MarkdownEditorInputSelection, scrollIntoView: boolean = false) {
+ this.editor.cm.dispatch({
+ selection: {anchor: selection.from, head: selection.to},
+ scrollIntoView,
+ });
+ }
+
+ getText(): string {
+ return this.editor.cm.state.doc.toString();
+ }
+
+ getTextAboveView(): string {
+ const blockInfo = this.editor.cm.lineBlockAtHeight(scrollEl.scrollTop);
+ return this.editor.cm.state.sliceDoc(0, blockInfo.from);
+ }
+
+ setText(text: string, selection: MarkdownEditorInputSelection | null = null) {
+ selection = selection || this.getSelection();
+ const newDoc = this.editor.cm.state.toText(text);
+ const newSelectFrom = Math.min(selection.from, newDoc.length);
+ const scrollTop = this.editor.cm.scrollDOM.scrollTop;
+ this.dispatchChange(0, this.editor.cm.state.doc.length, text, newSelectFrom);
+ this.focus();
+ window.requestAnimationFrame(() => {
+ this.editor.cm.scrollDOM.scrollTop = scrollTop;
+ });
+ }
+
+ spliceText(from: number, to: number, newText: string, selection: MarkdownEditorInputSelection | null = null) {
+ const end = (selection?.from === selection?.to) ? null : selection?.to;
+ this.dispatchChange(from, to, newText, selection?.from, end)
+ }
+
+ appendText(text: string) {
+ const end = this.editor.cm.state.doc.length;
+ this.dispatchChange(end, end, `\n${text}`);
+ }
+
+ getLineText(lineIndex: number = -1): string {
+ const index = lineIndex > -1 ? lineIndex : this.getSelection().from;
+ return this.editor.cm.state.doc.lineAt(index).text;
+ }
+
+ wrapLine(start: string, end: string) {
+ const selectionRange = this.getSelection();
+ const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
+ const lineContent = line.text;
+ let newLineContent;
+ let lineOffset = 0;
+
+ if (lineContent.startsWith(start) && lineContent.endsWith(end)) {
+ newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
+ lineOffset = -(start.length);
+ } else {
+ newLineContent = `${start}${lineContent}${end}`;
+ lineOffset = start.length;
+ }
+
+ this.dispatchChange(line.from, line.to, newLineContent, selectionRange.from + lineOffset);
+ }
+
+ coordsToSelection(x: number, y: number): MarkdownEditorInputSelection {
+ const cursorPos = this.editor.cm.posAtCoords({x, y}, false);
+ return {from: cursorPos, to: cursorPos};
+ }
+
+ /**
+ * Dispatch changes to the editor.
+ */
+ protected dispatchChange(from: number, to: number|null = null, text: string|null = null, selectFrom: number|null = null, selectTo: number|null = null): void {
+ const change: ChangeSpec = {from};
+ if (to) {
+ change.to = to;
+ }
+ if (text) {
+ change.insert = text;
+ }
+ const tr: TransactionSpec = {changes: change};
+
+ if (selectFrom) {
+ tr.selection = {anchor: selectFrom};
+ if (selectTo) {
+ tr.selection.head = selectTo;
+ }
+ }
+
+ this.cm.dispatch(tr);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+
+export interface MarkdownEditorInputSelection {
+ from: number;
+ to: number;
+}
+
+export interface MarkdownEditorInput {
+ /**
+ * Focus on the editor.
+ */
+ focus(): void;
+
+ /**
+ * Get the current selection range.
+ */
+ getSelection(): MarkdownEditorInputSelection;
+
+ /**
+ * Get the text of the given (or current) selection range.
+ */
+ getSelectionText(selection: MarkdownEditorInputSelection|null = null): string;
+
+ /**
+ * Set the selection range of the editor.
+ */
+ setSelection(selection: MarkdownEditorInputSelection, scrollIntoView: boolean = false): void;
+
+ /**
+ * Get the full text of the input.
+ */
+ getText(): string;
+
+ /**
+ * Get just the text which is above (out) the current view range.
+ * This is used for position estimation.
+ */
+ getTextAboveView(): string;
+
+ /**
+ * Set the full text of the input.
+ * Optionally can provide a selection to restore after setting text.
+ */
+ setText(text: string, selection: MarkdownEditorInputSelection|null = null): void;
+
+ /**
+ * Splice in/out text within the input.
+ * Optionally can provide a selection to restore after setting text.
+ */
+ spliceText(from: number, to: number, newText: string, selection: MarkdownEditorInputSelection|null = null): void;
+
+ /**
+ * Append text to the end of the editor.
+ */
+ appendText(text: string): void;
+
+ /**
+ * Get the text of the given line number otherwise the text
+ * of the current selected line.
+ */
+ getLineText(lineIndex:number = -1): string;
+
+ /**
+ * Wrap the current line in the given start/end contents.
+ */
+ wrapLine(start: string, end: string): void;
+
+ /**
+ * Convert the given screen coords to a selection position within the input.
+ */
+ coordsToSelection(x: number, y: number): MarkdownEditorInputSelection;
+}
\ No newline at end of file