From c8c4271aeb33250c13b287b51c97e9c95ca9a872 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Fri, 9 Dec 2022 16:09:53 +0100 Subject: [PATCH] Fixes #164: Rewrote all CM view plugins to statefields --- plugs/directive/directives.ts | 2 +- web/cm_plugins/block.ts | 41 ++----- web/cm_plugins/block_quote.ts | 59 ++++------ web/cm_plugins/clean.ts | 16 +-- web/cm_plugins/command_link.ts | 142 ++++++++++------------- web/cm_plugins/directive.ts | 100 ++++++---------- web/cm_plugins/hide_mark.ts | 108 +++++------------ web/cm_plugins/inline_image.ts | 47 ++------ web/cm_plugins/line_wrapper.ts | 54 ++------- web/cm_plugins/link.ts | 159 +++++++++++-------------- web/cm_plugins/list.ts | 38 ++---- web/cm_plugins/table.ts | 51 ++++----- web/cm_plugins/task.ts | 113 +++++++----------- web/cm_plugins/util.ts | 37 +++--- web/cm_plugins/wiki_link.ts | 204 ++++++++++++++------------------- web/editor.tsx | 1 - 16 files changed, 433 insertions(+), 739 deletions(-) diff --git a/plugs/directive/directives.ts b/plugs/directive/directives.ts index eea5b7a9..a8f5e1e5 100644 --- a/plugs/directive/directives.ts +++ b/plugs/directive/directives.ts @@ -1,4 +1,4 @@ -import { nodeAtPos, ParseTree, renderToText } from "$sb/lib/tree.ts"; +import { nodeAtPos, ParseTree } from "$sb/lib/tree.ts"; import { replaceAsync } from "$sb/lib/util.ts"; import { markdown } from "$sb/silverbullet-syscall/mod.ts"; diff --git a/web/cm_plugins/block.ts b/web/cm_plugins/block.ts index 73693977..daba2186 100644 --- a/web/cm_plugins/block.ts +++ b/web/cm_plugins/block.ts @@ -1,23 +1,17 @@ +import { Decoration, EditorState, syntaxTree } from "../deps.ts"; import { - Decoration, - DecorationSet, - EditorView, - ViewPlugin, - ViewUpdate, -} from "../deps.ts"; -import { + decoratorStateField, invisibleDecoration, isCursorInRange, - iterateTreeInVisibleRanges, } from "./util.ts"; -function hideNodes(view: EditorView) { +function hideNodes(state: EditorState) { const widgets: any[] = []; - iterateTreeInVisibleRanges(view, { + syntaxTree(state).iterate({ enter(node) { if ( node.name === "HorizontalRule" && - !isCursorInRange(view.state, [node.from, node.to]) + !isCursorInRange(state, [node.from, node.to]) ) { widgets.push(invisibleDecoration.range(node.from, node.to)); widgets.push( @@ -29,7 +23,7 @@ function hideNodes(view: EditorView) { if ( node.name === "Image" && - !isCursorInRange(view.state, [node.from, node.to]) + !isCursorInRange(state, [node.from, node.to]) ) { widgets.push(invisibleDecoration.range(node.from, node.to)); } @@ -38,7 +32,7 @@ function hideNodes(view: EditorView) { node.name === "FrontMatterMarker" ) { const parent = node.node.parent!; - if (!isCursorInRange(view.state, [parent.from, parent.to])) { + if (!isCursorInRange(state, [parent.from, parent.to])) { widgets.push( Decoration.line({ class: "sb-line-frontmatter-outside", @@ -54,7 +48,7 @@ function hideNodes(view: EditorView) { // Hide ONLY if CodeMark is not insine backticks (InlineCode) and the cursor is placed outside if ( parent.node.name !== "InlineCode" && - !isCursorInRange(view.state, [parent.from, parent.to]) + !isCursorInRange(state, [parent.from, parent.to]) ) { widgets.push( Decoration.line({ @@ -68,19 +62,6 @@ function hideNodes(view: EditorView) { return Decoration.set(widgets, true); } -export const cleanBlockPlugin = ViewPlugin.fromClass( - class { - decorations: DecorationSet; - - constructor(view: EditorView) { - this.decorations = hideNodes(view); - } - - update(update: ViewUpdate) { - if (update.docChanged || update.selectionSet) { - this.decorations = hideNodes(update.view); - } - } - }, - { decorations: (v) => v.decorations }, -); +export function cleanBlockPlugin() { + return decoratorStateField(hideNodes); +} diff --git a/web/cm_plugins/block_quote.ts b/web/cm_plugins/block_quote.ts index 6cb2e57b..1ed2cd11 100644 --- a/web/cm_plugins/block_quote.ts +++ b/web/cm_plugins/block_quote.ts @@ -1,45 +1,26 @@ +import { Decoration, EditorState, syntaxTree } from "../deps.ts"; import { - Decoration, - DecorationSet, - EditorView, - ViewPlugin, - ViewUpdate, -} from "../deps.ts"; -import { + decoratorStateField, invisibleDecoration, isCursorInRange, - iterateTreeInVisibleRanges, } from "./util.ts"; -class BlockquotePlugin { - decorations: DecorationSet = Decoration.none; - constructor(view: EditorView) { - this.decorations = this.decorateLists(view); - } - update(update: ViewUpdate) { - if (update.docChanged || update.viewportChanged || update.selectionSet) { - this.decorations = this.decorateLists(update.view); - } - } - private decorateLists(view: EditorView) { - const widgets: any[] = []; - iterateTreeInVisibleRanges(view, { - enter: ({ type, from, to }) => { - if (isCursorInRange(view.state, [from, to])) return; - if (type.name === "QuoteMark") { - widgets.push(invisibleDecoration.range(from, to)); - widgets.push( - Decoration.line({ class: "sb-blockquote-outside" }).range(from), - ); - } - }, - }); - return Decoration.set(widgets, true); - } +function decorateBlockQuote(state: EditorState) { + const widgets: any[] = []; + syntaxTree(state).iterate({ + enter: ({ type, from, to }) => { + if (isCursorInRange(state, [from, to])) return; + if (type.name === "QuoteMark") { + widgets.push(invisibleDecoration.range(from, to)); + widgets.push( + Decoration.line({ class: "sb-blockquote-outside" }).range(from), + ); + } + }, + }); + return Decoration.set(widgets, true); +} + +export function blockquotePlugin() { + return decoratorStateField(decorateBlockQuote); } -export const blockquotePlugin = ViewPlugin.fromClass( - BlockquotePlugin, - { - decorations: (v) => v.decorations, - }, -); diff --git a/web/cm_plugins/clean.ts b/web/cm_plugins/clean.ts index 410b5a5a..db0c61ad 100644 --- a/web/cm_plugins/clean.ts +++ b/web/cm_plugins/clean.ts @@ -3,7 +3,7 @@ import type { Extension } from "../deps.ts"; import { Editor } from "../editor.tsx"; import { blockquotePlugin } from "./block_quote.ts"; import { directivePlugin } from "./directive.ts"; -import { hideHeaderMarkPlugin, hideMarks } from "./hide_mark.ts"; +import { hideHeaderMarkPlugin, hideMarksPlugin } from "./hide_mark.ts"; import { cleanBlockPlugin } from "./block.ts"; import { linkPlugin } from "./link.ts"; import { listBulletPlugin } from "./list.ts"; @@ -15,11 +15,11 @@ import { cleanCommandLinkPlugin } from "./command_link.ts"; export function cleanModePlugins(editor: Editor) { return [ linkPlugin(editor), - directivePlugin, - blockquotePlugin, - hideMarks(), - hideHeaderMarkPlugin, - cleanBlockPlugin, + directivePlugin(), + blockquotePlugin(), + hideMarksPlugin(), + hideHeaderMarkPlugin(), + cleanBlockPlugin(), taskListPlugin({ // TODO: Move this logic elsewhere? onCheckboxClick: (pos) => { @@ -34,8 +34,8 @@ export function cleanModePlugins(editor: Editor) { editor.dispatchAppEvent("page:click", clickEvent); }, }), - listBulletPlugin, - tablePlugin, + listBulletPlugin(), + tablePlugin(editor), cleanWikiLinkPlugin(editor), cleanCommandLinkPlugin(editor), ] as Extension[]; diff --git a/web/cm_plugins/command_link.ts b/web/cm_plugins/command_link.ts index fb719485..51f67213 100644 --- a/web/cm_plugins/command_link.ts +++ b/web/cm_plugins/command_link.ts @@ -1,99 +1,75 @@ -import { commandLinkRegex, pageLinkRegex } from "../../common/parser.ts"; -import { ClickEvent } from "../../plug-api/app_event.ts"; -import { - Decoration, - DecorationSet, - EditorView, - ViewPlugin, - ViewUpdate, -} from "../deps.ts"; +import { commandLinkRegex } from "../../common/parser.ts"; +import { ClickEvent } from "$sb/app_event.ts"; +import { Decoration, syntaxTree } from "../deps.ts"; import { Editor } from "../editor.tsx"; import { ButtonWidget, + decoratorStateField, invisibleDecoration, isCursorInRange, - iterateTreeInVisibleRanges, } from "./util.ts"; /** * Plugin to hide path prefix when the cursor is not inside. */ export function cleanCommandLinkPlugin(editor: Editor) { - return ViewPlugin.fromClass( - class { - decorations: DecorationSet; - constructor(view: EditorView) { - this.decorations = this.compute(view); - } - update(update: ViewUpdate) { - if ( - update.docChanged || update.viewportChanged || update.selectionSet - ) { - this.decorations = this.compute(update.view); + return decoratorStateField((state) => { + const widgets: any[] = []; + // let parentRange: [number, number]; + syntaxTree(state).iterate({ + enter: ({ type, from, to }) => { + if (type.name !== "CommandLink") { + return; + } + if (isCursorInRange(state, [from, to])) { + return; } - } - compute(view: EditorView): DecorationSet { - const widgets: any[] = []; - // let parentRange: [number, number]; - iterateTreeInVisibleRanges(view, { - enter: ({ type, from, to }) => { - if (type.name !== "CommandLink") { - return; - } - if (isCursorInRange(view.state, [from, to])) { - return; - } - const text = view.state.sliceDoc(from, to); - const match = commandLinkRegex.exec(text); - if (!match) return; - const [_fullMatch, command, _pipePart, alias] = match; + const text = state.sliceDoc(from, to); + const match = commandLinkRegex.exec(text); + if (!match) return; + const [_fullMatch, command, _pipePart, alias] = match; - // Hide the whole thing - widgets.push( - invisibleDecoration.range( - from, - to, - ), - ); + // Hide the whole thing + widgets.push( + invisibleDecoration.range( + from, + to, + ), + ); - const linkText = alias || command; - // And replace it with a widget - widgets.push( - Decoration.widget({ - widget: new ButtonWidget( - linkText, - `Run command: ${command}`, - "sb-command-button", - (e) => { - if (e.altKey) { - // Move cursor into the link - return view.dispatch({ - selection: { anchor: from + 2 }, - }); - } - // Dispatch click event to navigate there without moving the cursor - const clickEvent: ClickEvent = { - page: editor.currentPage!, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - pos: from, - }; - editor.dispatchAppEvent("page:click", clickEvent).catch( - console.error, - ); - }, - ), - }).range(from), - ); - }, - }); - return Decoration.set(widgets, true); - } - }, - { - decorations: (v) => v.decorations, - }, - ); + const linkText = alias || command; + // And replace it with a widget + widgets.push( + Decoration.widget({ + widget: new ButtonWidget( + linkText, + `Run command: ${command}`, + "sb-command-button", + (e) => { + if (e.altKey) { + // Move cursor into the link + return editor.editorView!.dispatch({ + selection: { anchor: from + 2 }, + }); + } + // Dispatch click event to navigate there without moving the cursor + const clickEvent: ClickEvent = { + page: editor.currentPage!, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + altKey: e.altKey, + pos: from, + }; + editor.dispatchAppEvent("page:click", clickEvent).catch( + console.error, + ); + }, + ), + }).range(from), + ); + }, + }); + return Decoration.set(widgets, true); + }); } diff --git a/web/cm_plugins/directive.ts b/web/cm_plugins/directive.ts index 3223c61a..a895a9e3 100644 --- a/web/cm_plugins/directive.ts +++ b/web/cm_plugins/directive.ts @@ -1,70 +1,44 @@ -import { - Decoration, - DecorationSet, - EditorView, - syntaxTree, - ViewPlugin, - ViewUpdate, -} from "../deps.ts"; -import { isCursorInRange } from "./util.ts"; +import { Decoration, EditorState, syntaxTree } from "../deps.ts"; +import { decoratorStateField, isCursorInRange } from "./util.ts"; -function getDirectives(view: EditorView) { +function getDirectives(state: EditorState) { const widgets: any[] = []; - for (const { from, to } of view.visibleRanges) { - syntaxTree(view.state).iterate({ - from, - to, - enter: ({ type, from, to }) => { - if (type.name !== "CommentBlock") { - return; - } - const text = view.state.sliceDoc(from, to); - if (/