No longer cache widget HTML content just widget heights, either way the widgets will briefly flash as the widget is recalculated and this saves storage space and reduces issues in Webkit
144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
import type { EditorState } from "@codemirror/state";
|
|
import { syntaxTree } from "@codemirror/language";
|
|
import { Decoration, WidgetType } from "@codemirror/view";
|
|
import {
|
|
decoratorStateField,
|
|
invisibleDecoration,
|
|
isCursorInRange,
|
|
} from "./util.ts";
|
|
|
|
import { renderMarkdownToHtml } from "../markdown_renderer/markdown_render.ts";
|
|
import {
|
|
type ParseTree,
|
|
renderToText,
|
|
} from "@silverbulletmd/silverbullet/lib/tree";
|
|
import { lezerToParseTree } from "../markdown_parser/parse_tree.ts";
|
|
import type { Client } from "../client.ts";
|
|
import { expandMarkdown } from "../markdown_renderer/inline.ts";
|
|
import {
|
|
attachWidgetEventHandlers,
|
|
buildTranslateUrls,
|
|
} from "./widget_util.ts";
|
|
|
|
class TableViewWidget extends WidgetType {
|
|
tableBodyText: string;
|
|
|
|
constructor(
|
|
readonly client: Client,
|
|
readonly t: ParseTree,
|
|
) {
|
|
super();
|
|
this.tableBodyText = renderToText(t);
|
|
}
|
|
|
|
override get estimatedHeight(): number {
|
|
return this.client.widgetCache.getCachedWidgetHeight(
|
|
`table:${this.tableBodyText}`,
|
|
);
|
|
}
|
|
|
|
toDOM(): HTMLElement {
|
|
const dom = document.createElement("span");
|
|
dom.classList.add("sb-table-widget");
|
|
dom.addEventListener("click", (e) => {
|
|
// Pulling data-pos to put the cursor in the right place, falling back
|
|
// to the start of the table.
|
|
const dataAttributes = (e.target as any).dataset;
|
|
const fallbackPos = this.client.editorView.posAtDOM(dom, 0);
|
|
this.client.editorView.dispatch({
|
|
selection: {
|
|
anchor: dataAttributes.pos ? +dataAttributes.pos : fallbackPos,
|
|
},
|
|
});
|
|
});
|
|
|
|
void expandMarkdown(
|
|
this.client.space,
|
|
this.client.currentName(),
|
|
this.t,
|
|
this.client.clientSystem.spaceLuaEnv,
|
|
{
|
|
syntaxExtensions: this.client.config.get("syntaxExtensions", {}),
|
|
},
|
|
).then((t) => {
|
|
dom.innerHTML = renderMarkdownToHtml(t, {
|
|
// Annotate every element with its position so we can use it to put
|
|
// the cursor there when the user clicks on the table.
|
|
annotationPositions: true,
|
|
shortWikiLinks: this.client.config.get("shortWikiLinks", false),
|
|
translateUrls: buildTranslateUrls(this.client),
|
|
});
|
|
setTimeout(() => {
|
|
// Give it a tick to render
|
|
attachWidgetEventHandlers(dom, this.client, this.tableBodyText);
|
|
|
|
this.client.widgetCache.setCachedWidgetMeta(
|
|
`table:${this.tableBodyText}`,
|
|
{ height: dom.clientHeight, block: true },
|
|
);
|
|
});
|
|
});
|
|
return dom;
|
|
}
|
|
|
|
override eq(other: WidgetType): boolean {
|
|
return (
|
|
other instanceof TableViewWidget &&
|
|
other.tableBodyText === this.tableBodyText
|
|
);
|
|
}
|
|
}
|
|
|
|
export function tablePlugin(editor: Client) {
|
|
return decoratorStateField((state: EditorState) => {
|
|
const widgets: any[] = [];
|
|
syntaxTree(state).iterate({
|
|
enter: (node) => {
|
|
const { from, to, name } = node;
|
|
if (name !== "Table") return;
|
|
if (isCursorInRange(state, [from, to])) return;
|
|
|
|
const tableText = state.sliceDoc(from, to);
|
|
const lineStrings = tableText.split("\n");
|
|
|
|
const lines: { from: number; to: number }[] = [];
|
|
let fromIt = from;
|
|
for (const line of lineStrings) {
|
|
lines.push({
|
|
from: fromIt,
|
|
to: fromIt + line.length,
|
|
});
|
|
fromIt += line.length + 1;
|
|
}
|
|
|
|
const firstLine = lines[0],
|
|
lastLine = lines[lines.length - 1];
|
|
|
|
// In case of doubt, back out
|
|
if (!firstLine || !lastLine) return;
|
|
|
|
widgets.push(invisibleDecoration.range(firstLine.from, firstLine.to));
|
|
widgets.push(invisibleDecoration.range(lastLine.from, lastLine.to));
|
|
|
|
lines.slice(1, lines.length - 1).forEach((line) => {
|
|
widgets.push(
|
|
Decoration.line({ class: "sb-line-table-outside" }).range(
|
|
line.from,
|
|
),
|
|
);
|
|
});
|
|
const text = state.sliceDoc(0, to);
|
|
widgets.push(
|
|
Decoration.widget({
|
|
widget: new TableViewWidget(
|
|
editor,
|
|
lezerToParseTree(text, node.node),
|
|
),
|
|
}).range(from),
|
|
);
|
|
},
|
|
});
|
|
return Decoration.set(widgets, true);
|
|
});
|
|
}
|