From ff87717c7255ea067f00c68eb6321586cbbdd704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 13 Jul 2024 22:42:53 -0700 Subject: [PATCH] add deletion support to mechanics blocks and individual nodes (#416) Fixes: https://github.com/iron-vault-plugin/iron-vault/issues/407 --- src/mechanics/mechanics-blocks.ts | 365 ++++++++++++++++++++---------- 1 file changed, 249 insertions(+), 116 deletions(-) diff --git a/src/mechanics/mechanics-blocks.ts b/src/mechanics/mechanics-blocks.ts index 9178a996..5a1d6258 100644 --- a/src/mechanics/mechanics-blocks.ts +++ b/src/mechanics/mechanics-blocks.ts @@ -1,5 +1,11 @@ -import { Node as KdlNodeBare, Document as KdlDocument, parse } from "kdljs"; -import { ButtonComponent, MarkdownPostProcessorContext } from "obsidian"; +import { Node as KdlNodeBare, parse, format } from "kdljs"; +import { + ButtonComponent, + MarkdownPostProcessorContext, + MarkdownPreviewView, + MarkdownView, + Menu, +} from "obsidian"; import { render, html, TemplateResult } from "lit-html"; import { styleMap } from "lit-html/directives/style-map.js"; import { ref } from "lit-html/directives/ref.js"; @@ -8,6 +14,7 @@ import { ProgressTrack } from "tracks/progress"; import IronVaultPlugin from "../index"; import { md } from "utils/ui/directives"; import { map } from "lit-html/directives/map.js"; +import { node } from "utils/kdl"; interface KdlNode extends KdlNodeBare { parent?: KdlNode; @@ -22,12 +29,7 @@ function makeHandler(plugin: IronVaultPlugin) { // We can't render blocks until datastore is ready. await plugin.datastore.waitForReady; if (!el.mechanicsRenderer) { - el.mechanicsRenderer = new MechanicsRenderer( - el, - source, - plugin, - ctx.sourcePath, - ); + el.mechanicsRenderer = new MechanicsRenderer(el, source, plugin, ctx); } el.mechanicsRenderer.render(); }; @@ -46,27 +48,26 @@ interface MechanicsContainerEl extends HTMLElement { } export class MechanicsRenderer { - plugin: IronVaultPlugin; sourcePath: string; lastRoll: KdlNode | undefined; moveEl: HTMLElement | undefined; - doc?: KdlDocument; + doc?: KdlNode; details: string[] = []; - contentEl: HTMLElement; - source: string; mechNode?: HTMLElement; hideMechanics = false; constructor( - contentEl: HTMLElement, - source: string, - plugin: IronVaultPlugin, - sourcePath: string, + public contentEl: HTMLElement, + public source: string, + public plugin: IronVaultPlugin, + public ctx: MarkdownPostProcessorContext, ) { - this.contentEl = contentEl; - this.source = source; - this.plugin = plugin; - this.sourcePath = sourcePath; + this.sourcePath = ctx.sourcePath; + const res = parse(this.source); + if (res.output) { + this.doc = node("doc", { children: res.output }); + this.fillParents(this.doc.children, this.doc); + } plugin.register( plugin.settings.on("change", ({ key, oldValue, newValue }) => { if ( @@ -93,8 +94,7 @@ export class MechanicsRenderer { render(html``, this.contentEl); return; } - const res = parse(this.source); - if (!res.output) { + if (!this.doc) { // TODO: give line/column information for errors. render( html`
@@ -105,16 +105,14 @@ See https://kdl.dev for syntax.
(this.mechNode = el as HTMLElement | undefined)} class="iron-vault-mechanics" style=${styleMap({ "--vs1-color": this.plugin.settings.challengeDie1Color, "--vs2-color": this.plugin.settings.challengeDie2Color, })} + @contextmenu=${this.makeMenuHandler(this.doc)} ${ref((el?: Element) => { if ( !el || @@ -138,12 +136,91 @@ See https://kdl.dev for syntax. - ${this.hideMechanics ? null : this.renderChildren(this.doc)} + ${this.hideMechanics ? null : this.renderChildren(this.doc.children)} `, this.contentEl, ); } + makeMenuHandler(node: KdlNode) { + return (ev: MouseEvent) => { + const view = this.plugin.app.workspace.getActiveViewOfType(MarkdownView); + if (!view || view.currentMode instanceof MarkdownPreviewView) { + return; + } + + ev.preventDefault(); + ev.stopPropagation(); + + const menu = new Menu(); + + menu.addItem((item) => { + item + .setTitle( + `Delete ${node.name === "-" ? "comment" : node === this.doc ? "mechanics block" : node.name}`, + ) + .setIcon("trash") + .onClick(() => { + if (node.parent) { + const idx = node.parent.children.indexOf(node); + if (idx >= 0) { + node.parent.children.splice(idx, 1); + this.updateBlock(); + } + } else if (node === this.doc) { + // We probably don't need this if, but just to make sure. + node.children = []; + this.updateBlock(); + } + }); + }); + + menu.showAtMouseEvent(ev); + }; + } + + updateBlock() { + const view = this.plugin.app.workspace.getActiveViewOfType(MarkdownView); + const editor = this.plugin.app.workspace.activeEditor?.editor; + const sectionInfo = this.ctx.getSectionInfo(this.contentEl as HTMLElement); + if ( + !editor || + !sectionInfo || + !this.doc || + !view || + view.currentMode instanceof MarkdownPreviewView + ) { + return; + } + const editorRange = { + from: { + ch: 0, + line: sectionInfo.lineStart, + }, + to: { + ch: 0, + line: sectionInfo.lineEnd, + }, + }; + const to = { + line: editorRange.to.line, + ch: + editor.getLine(editorRange.to.line).length + + (this.doc.children.length ? 0 : 1), + }; + editor.replaceRange( + this.doc.children.length + ? `\`\`\`iron-vault-mechanics\n${format(this.doc.children)}\`\`\`` + : "", + editorRange.from, + to, + ); + editor.focus(); + const moveTo = editorRange.from.line - (this.doc.children.length ? 1 : 0); + // NB(@zkat): This prevents the editor jumping around. + editor.setCursor({ ch: 0, line: moveTo >= 0 ? moveTo : 0 }); + } + renderChildren(nodes: KdlNode[]): TemplateResult { return html`${map( nodes.reduce((acc, node) => { @@ -179,7 +256,6 @@ See https://kdl.dev for syntax. (this.moveEl = el as HTMLElement | undefined))} > ${md(this.plugin, moveName)} @@ -268,7 +343,10 @@ See https://kdl.dev for syntax. + return html``; } @@ -286,7 +364,7 @@ See https://kdl.dev for syntax. - ${this.renderDlist("oracle", data)} + return html`
+ ${this.renderDlist("oracle", data, node)} ${node.children.length ? html`
${this.renderChildren(node.children)}
` : null} @@ -486,7 +595,10 @@ ${result.error.toString()} + return html`
${md(this.plugin, name)} ${node.children.length ? html`
${this.renderChildren(node.children)}
` @@ -522,7 +634,7 @@ ${result.error.toString()} + return html`
${md(this.plugin, name)}
${this.renderChildren(node.children)}
`; } - renderUnknown(name: string) { - return html`

Unknown node: "${name}"

`; + renderUnknown(node: KdlNode) { + return html`

+ Unknown node: "${node.name}" +

`; } - renderDlist(cls: string, data: DataList): TemplateResult { - return html`
+ renderDlist(cls: string, data: DataList, node: KdlNode): TemplateResult { + return html`
${map( Object.entries(data), ([key, { cls, value, dataProp, md: renderMd }]) => {