Files
plainleaf/web/cm_plugins/top_bottom_panels.ts
T
Zef HemelandGitHub dc2987a3f9 Sync engine rearchitecture (#1516)
* Don't require auth for .ping
* Finally introducing loggers
* Remove file list caching from disk_space_primitives
I don't think this is required anymore with the sync model
* Simplification of space primitives and more tests
* More reliable online status
* Add .zed settings
* Remove selfUpdate argument from `writeFile` no longer used
- Add `sync` config schema and document sync options
- Move sync config handling to client boot logic
- Move BootConfig type to ui_types.ts
- Remove sync env vars from server
- Update documentation for sync and architecture
* Improve sync logic and proxy routing for full sync state
* Add BoxProxy for late-bound client and improve Lua boot
- Add isMobile syscall for editor
* Add concurrency to sync process and log indexing duration
* Better service worker lifecycle management
2025-09-12 14:43:57 +02:00

156 lines
4.1 KiB
TypeScript

import type { EditorState } from "@codemirror/state";
import { Decoration, WidgetType } from "@codemirror/view";
import type { Client } from "../client.ts";
import { decoratorStateField } from "./util.ts";
import { LuaWidget, type LuaWidgetContent } from "./lua_widget.ts";
import { activeWidgets } from "./code_widget.ts";
class ArrayWidget extends WidgetType {
public dom?: HTMLElement;
constructor(
readonly client: Client,
readonly cacheKey: string,
readonly callback: (
pageName: string,
) => Promise<LuaWidgetContent[] | null>,
readonly childClass: string,
) {
super();
}
override get estimatedHeight(): number {
const cacheItem = this.client.getWidgetCache(this.cacheKey);
return cacheItem ? cacheItem.height : -1;
}
toDOM(): HTMLElement {
activeWidgets.add(this);
const div = document.createElement("div");
div.className = "sb-widget-array";
// This doesn't do that much, but it also doesn't really hurt
const cacheItem = this.client.getWidgetCache(this.cacheKey);
if (cacheItem) {
div.innerHTML = cacheItem.html;
}
// Async kick-off of content renderer
this.renderContent(div).catch(console.error);
this.dom = div;
return div;
}
async renderContent(
div: HTMLElement,
) {
const content = await this.callback(this.client.currentName());
if (!content) return;
const renderedWidgets: HTMLElement[] = [];
for (const [i, widgetContent] of content.entries()) {
// Filter out any "empty" widgets. Leaving the content empty, but
// returning a valid widgets, seems to be a common pattern
if (
!widgetContent ||
widgetContent === "" ||
(widgetContent instanceof Object &&
!widgetContent.markdown &&
!widgetContent.html)
) continue;
const widget = new LuaWidget(
this.client,
`${this.cacheKey}:${i}`,
"",
() => Promise.resolve(widgetContent),
false,
false,
);
// Throw away the wrapper, as it only causes trouble and we are rewrapping
// anyways
const html = widget.toDOM().querySelector<HTMLDivElement>(":scope > div");
if (!html) {
// This should never really happen, just in case
console.log("There was an error rendering one of the panel widgets");
continue;
}
html.classList.add(this.childClass);
renderedWidgets.push(html);
}
if (renderedWidgets.length === 0) {
div.style.display = "none";
return;
}
div.replaceChildren(...renderedWidgets);
// Wait for the clientHeight to settle
setTimeout(() => {
this.client.setWidgetCache(this.cacheKey, {
height: div.clientHeight,
block: true,
html: div.innerHTML,
});
});
}
override eq(other: WidgetType): boolean {
// This class isn't really used for stuff that's updated. If that's
// needed in the future, one could e.g. add a `bodyText` property again
return other instanceof ArrayWidget && other.cacheKey === this.cacheKey;
}
}
export function postScriptPrefacePlugin(
editor: Client,
) {
return decoratorStateField((state: EditorState) => {
if (!editor.clientSystem.scriptsLoaded) {
// console.info("System not yet ready, not rendering panel widgets.");
return Decoration.none;
}
const widgets: any[] = [];
widgets.push(
Decoration.widget({
widget: new ArrayWidget(
editor,
`top:lua:${editor.currentPath()}`,
async () =>
await client.dispatchAppEvent(
"hooks:renderTopWidgets",
),
"sb-lua-top-widget",
),
side: -1,
block: true,
}).range(0),
);
widgets.push(
Decoration.widget({
widget: new ArrayWidget(
editor,
`bottom:lua:${editor.currentPath()}`,
async () =>
await client.dispatchAppEvent(
"hooks:renderBottomWidgets",
),
"sb-lua-bottom-widget",
),
side: 1,
block: true,
}).range(state.doc.length),
);
return Decoration.set(widgets);
});
}