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", true),
|
|
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);
|
|
});
|
|
}
|