diff --git a/plug-api/silverbullet-syscall/editor.ts b/plug-api/silverbullet-syscall/editor.ts index 8d400666..7d2b8468 100644 --- a/plug-api/silverbullet-syscall/editor.ts +++ b/plug-api/silverbullet-syscall/editor.ts @@ -121,3 +121,11 @@ export function confirm( export function enableReadOnlyMode(enabled: boolean) { return syscall("editor.enableReadOnlyMode", enabled); } + +export function setDirectiveBodyEditingEnabled(enabled: boolean) { + return syscall("editor.setDirectiveBodyEditingEnabled", enabled); +} + +export function getDirectiveBodyEditingEnabled(): Promise { + return syscall("editor.getDirectiveBodyEditingEnabled"); +} diff --git a/plugs/directive/command.ts b/plugs/directive/command.ts index 8d8f9cb3..45be7703 100644 --- a/plugs/directive/command.ts +++ b/plugs/directive/command.ts @@ -110,3 +110,16 @@ export function serverRenderDirective( ): Promise { return renderDirectives(pageName, text); } + +export async function toggleEditDirectiveBodyCommand() { + const directiveBodyEditingEnabled = await editor + .getDirectiveBodyEditingEnabled(); + await editor.setDirectiveBodyEditingEnabled( + !directiveBodyEditingEnabled, + ); + await editor.flashNotification( + directiveBodyEditingEnabled + ? "Editing of directive bodies now disabled" + : "Editing of directive bodies now enabled", + ); +} diff --git a/plugs/directive/directive.plug.yaml b/plugs/directive/directive.plug.yaml index be14fe65..1a76e5fd 100644 --- a/plugs/directive/directive.plug.yaml +++ b/plugs/directive/directive.plug.yaml @@ -24,6 +24,11 @@ functions: events: - page:complete + toggleEditDirectiveBodyCommand: + path: ./command.ts:toggleEditDirectiveBodyCommand + command: + name: "Directives: Toggle Body Editing" + # Templates insertQuery: redirect: core.insertTemplateText 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/readonly_directives.ts b/web/cm_plugins/readonly_directives.ts new file mode 100644 index 00000000..75a6ff9f --- /dev/null +++ b/web/cm_plugins/readonly_directives.ts @@ -0,0 +1,31 @@ +import { EditorState } from "../deps.ts"; +import { directiveRegex } from "../../plugs/directive/directives.ts"; +import type { Editor } from "../editor.tsx"; + +// Prevents edits inside blocks. +// Possible performance concern: on every edit (every character typed), this pulls the whole document and applies a regex to it +export function readonlyDirectives(editor: Editor) { + return EditorState.changeFilter.of((tr): boolean => { + // Only act on actual edits triggered by the user (so 'changes' and 'selection' are set) + if (tr.docChanged && tr.selection) { + const text = tr.state.sliceDoc(0); + const allMatches = text.matchAll(directiveRegex); + for (const match of allMatches) { + const [_fullMatch, startInst, _type, _args, body] = match; + const from = match.index! + startInst.length; + const to = match.index! + startInst.length + body.length; + for (const sel of tr.selection.ranges) { + if (from <= sel.from && sel.to <= to) { + // In range: BLOCK + editor.flashNotification( + "Cannot edit inside directive bodies (run `Directives: Update` to update instead)", + "error", + ); + return false; + } + } + } + } + return true; + }); +} diff --git a/web/editor.tsx b/web/editor.tsx index 8eb557a5..1286ea18 100644 --- a/web/editor.tsx +++ b/web/editor.tsx @@ -105,6 +105,7 @@ import customMarkdownStyle from "./style.ts"; // Real-time collaboration import { CollabState } from "./cm_plugins/collab.ts"; import { collabSyscalls } from "./syscalls/collab.ts"; +import { readonlyDirectives } from "./cm_plugins/readonly_directives.ts"; const frontMatterRegex = /^---\s*$(.*?)---\s*$/ms; @@ -138,6 +139,7 @@ export class Editor { urlPrefix: string; indexPage: string; collabState?: CollabState; + enableDirectiveBodyEditing = false; constructor( space: Space, @@ -560,6 +562,9 @@ export class Editor { pasteLinkExtension, attachmentExtension(this), closeBrackets(), + ...[ + this.enableDirectiveBodyEditing ? [] : readonlyDirectives(editor), + ], ...[this.collabState ? this.collabState.collabExtension() : []], ], }); diff --git a/web/syscalls/editor.ts b/web/syscalls/editor.ts index e8899fa3..0005f693 100644 --- a/web/syscalls/editor.ts +++ b/web/syscalls/editor.ts @@ -197,6 +197,13 @@ export function editorSyscalls(editor: Editor): SysCallMapping { enabled, }); }, + "editor.setDirectiveBodyEditingEnabled": (_ctx, enabled: boolean) => { + editor.enableDirectiveBodyEditing = enabled; + editor.rebuildEditorState(); + }, + "editor.getDirectiveBodyEditingEnabled": (_ctx): boolean => { + return editor.enableDirectiveBodyEditing; + }, }; return syscalls; diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 621a102b..de2c576d 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -7,6 +7,7 @@ release. * Replaced the `--password` flag with `--user` taking a basic auth combination of username and password, e.g. `--user pete:1234`. Authentication now uses standard basic auth. This should fix attachments not working with password protected setups. * Added support for ~~strikethrough~~ syntax. +* Disabled editing of text inside `` and `` blocks to avoid accidental edits (later overridden when the body is updated via {[Directives: Update]}). If you need to temporarily disable this, use the {[Directives: Toggle Body Editing]} command. ---