Fixed a lot of bugs with new widget rendering
This commit is contained in:
parent
4d66f23391
commit
d43dbcacec
@ -133,6 +133,13 @@ export type CodeWidgetContent = {
|
|||||||
html?: string;
|
html?: string;
|
||||||
markdown?: string;
|
markdown?: string;
|
||||||
script?: string;
|
script?: string;
|
||||||
|
buttons?: CodeWidgetButton[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CodeWidgetButton = {
|
||||||
|
description: string;
|
||||||
|
svg: string;
|
||||||
|
invokeFunction: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LintDiagnostic = {
|
export type LintDiagnostic = {
|
||||||
|
@ -178,6 +178,9 @@ functions:
|
|||||||
env: client
|
env: client
|
||||||
panelWidget: top
|
panelWidget: top
|
||||||
|
|
||||||
|
refreshTOC:
|
||||||
|
path: toc.ts:refreshTOC
|
||||||
|
|
||||||
lintYAML:
|
lintYAML:
|
||||||
path: lint.ts:lintYAML
|
path: lint.ts:lintYAML
|
||||||
events:
|
events:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { clientStore, editor, system } from "$sb/silverbullet-syscall/mod.ts";
|
import { clientStore, codeWidget, editor, system } from "$sb/syscalls.ts";
|
||||||
import { CodeWidgetContent } from "$sb/types.ts";
|
import { CodeWidgetContent } from "$sb/types.ts";
|
||||||
import { queryObjects } from "./api.ts";
|
import { queryObjects } from "./api.ts";
|
||||||
import { LinkObject } from "./page_links.ts";
|
import { LinkObject } from "./page_links.ts";
|
||||||
@ -9,14 +9,16 @@ export async function toggleMentions() {
|
|||||||
let hideMentions = await clientStore.get(hideMentionsKey);
|
let hideMentions = await clientStore.get(hideMentionsKey);
|
||||||
hideMentions = !hideMentions;
|
hideMentions = !hideMentions;
|
||||||
await clientStore.set(hideMentionsKey, hideMentions);
|
await clientStore.set(hideMentionsKey, hideMentions);
|
||||||
if (!hideMentions) {
|
await codeWidget.refreshAll();
|
||||||
await renderMentions();
|
|
||||||
} else {
|
|
||||||
await editor.dispatch({});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderMentions(): Promise<CodeWidgetContent | null> {
|
export async function renderMentions(): Promise<CodeWidgetContent | null> {
|
||||||
|
console.log("Hide mentions", await clientStore.get(hideMentionsKey));
|
||||||
|
if (await clientStore.get(hideMentionsKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Stil here");
|
||||||
const page = await editor.getCurrentPage();
|
const page = await editor.getCurrentPage();
|
||||||
const linksResult = await queryObjects<LinkObject>("link", {
|
const linksResult = await queryObjects<LinkObject>("link", {
|
||||||
// Query all links that point to this page, excluding those that are inside directives and self pointers.
|
// Query all links that point to this page, excluding those that are inside directives and self pointers.
|
||||||
@ -41,6 +43,12 @@ export async function renderMentions(): Promise<CodeWidgetContent | null> {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
markdown: renderedMd,
|
markdown: renderedMd,
|
||||||
|
buttons: [{
|
||||||
|
description: "Hide",
|
||||||
|
svg:
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>`,
|
||||||
|
invokeFunction: "index.toggleMentions",
|
||||||
|
}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
clientStore,
|
clientStore,
|
||||||
|
codeWidget,
|
||||||
editor,
|
editor,
|
||||||
markdown,
|
markdown,
|
||||||
system,
|
|
||||||
} from "$sb/silverbullet-syscall/mod.ts";
|
} from "$sb/silverbullet-syscall/mod.ts";
|
||||||
import { renderToText, traverseTree, traverseTreeAsync } from "$sb/lib/tree.ts";
|
import { renderToText, traverseTree } from "$sb/lib/tree.ts";
|
||||||
import { CodeWidgetContent } from "$sb/types.ts";
|
import { CodeWidgetContent } from "$sb/types.ts";
|
||||||
|
|
||||||
const hideTOCKey = "hideTOC";
|
const hideTOCKey = "hideTOC";
|
||||||
@ -16,23 +16,18 @@ type Header = {
|
|||||||
level: number;
|
level: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let cachedTOC: string | undefined;
|
|
||||||
|
|
||||||
export async function toggleTOC() {
|
export async function toggleTOC() {
|
||||||
cachedTOC = undefined;
|
|
||||||
let hideTOC = await clientStore.get(hideTOCKey);
|
let hideTOC = await clientStore.get(hideTOCKey);
|
||||||
hideTOC = !hideTOC;
|
hideTOC = !hideTOC;
|
||||||
await clientStore.set(hideTOCKey, hideTOC);
|
await clientStore.set(hideTOCKey, hideTOC);
|
||||||
await renderTOC(); // This will hide it if needed
|
await codeWidget.refreshAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markdownToHtml(text: string): Promise<string> {
|
export async function refreshTOC() {
|
||||||
return system.invokeFunction("markdown.markdownToHtml", text);
|
await codeWidget.refreshAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderTOC(
|
export async function renderTOC(): Promise<CodeWidgetContent | null> {
|
||||||
reload = false,
|
|
||||||
): Promise<CodeWidgetContent | null> {
|
|
||||||
if (await clientStore.get(hideTOCKey)) {
|
if (await clientStore.get(hideTOCKey)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -40,7 +35,7 @@ export async function renderTOC(
|
|||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const tree = await markdown.parseMarkdown(text);
|
const tree = await markdown.parseMarkdown(text);
|
||||||
const headers: Header[] = [];
|
const headers: Header[] = [];
|
||||||
await traverseTreeAsync(tree, async (n) => {
|
traverseTree(tree, (n) => {
|
||||||
if (n.type?.startsWith("ATXHeading")) {
|
if (n.type?.startsWith("ATXHeading")) {
|
||||||
headers.push({
|
headers.push({
|
||||||
name: n.children!.slice(1).map(renderToText).join("").trim(),
|
name: n.children!.slice(1).map(renderToText).join("").trim(),
|
||||||
@ -66,5 +61,19 @@ export async function renderTOC(
|
|||||||
// console.log("Markdown", renderedMd);
|
// console.log("Markdown", renderedMd);
|
||||||
return {
|
return {
|
||||||
markdown: renderedMd,
|
markdown: renderedMd,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
description: "Reload",
|
||||||
|
svg:
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-cw"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`,
|
||||||
|
invokeFunction: "index.refreshTOC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Hide",
|
||||||
|
svg:
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>`,
|
||||||
|
invokeFunction: "index.toggleTOC",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,15 @@ functions:
|
|||||||
- editor:complete
|
- editor:complete
|
||||||
|
|
||||||
refreshAllWidgets:
|
refreshAllWidgets:
|
||||||
path: widget.ts:refreshAll
|
path: widget.ts:refreshAllWidgets
|
||||||
command:
|
command:
|
||||||
name: "Live Queries and Templates: Refresh All"
|
name: "Live Queries and Templates: Refresh All"
|
||||||
key: "Alt-q"
|
key: "Alt-q"
|
||||||
|
|
||||||
|
# Query widget buttons
|
||||||
|
editButton:
|
||||||
|
path: widget.ts:editButton
|
||||||
|
|
||||||
# Slash commands
|
# Slash commands
|
||||||
insertQuery:
|
insertQuery:
|
||||||
redirect: template.insertTemplateText
|
redirect: template.insertTemplateText
|
||||||
|
@ -9,12 +9,12 @@ import { astToKvQuery } from "$sb/lib/parse-query.ts";
|
|||||||
import { jsonToMDTable, renderQueryTemplate } from "../directive/util.ts";
|
import { jsonToMDTable, renderQueryTemplate } from "../directive/util.ts";
|
||||||
import { loadPageObject, replaceTemplateVars } from "../template/template.ts";
|
import { loadPageObject, replaceTemplateVars } from "../template/template.ts";
|
||||||
import { cleanPageRef, resolvePath } from "$sb/lib/resolve.ts";
|
import { cleanPageRef, resolvePath } from "$sb/lib/resolve.ts";
|
||||||
import { LintDiagnostic } from "$sb/types.ts";
|
import { CodeWidgetContent, LintDiagnostic } from "$sb/types.ts";
|
||||||
|
|
||||||
export async function widget(
|
export async function widget(
|
||||||
bodyText: string,
|
bodyText: string,
|
||||||
pageName: string,
|
pageName: string,
|
||||||
): Promise<WidgetContent> {
|
): Promise<CodeWidgetContent> {
|
||||||
const pageObject = await loadPageObject(pageName);
|
const pageObject = await loadPageObject(pageName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -72,6 +72,20 @@ export async function widget(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
markdown: resultMarkdown,
|
markdown: resultMarkdown,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
description: "Edit",
|
||||||
|
svg:
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>`,
|
||||||
|
invokeFunction: "query.editButton",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Reload",
|
||||||
|
svg:
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-cw"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`,
|
||||||
|
invokeFunction: "query.refreshAllWidgets",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return { markdown: `**Error:** ${e.message}` };
|
return { markdown: `**Error:** ${e.message}` };
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { codeWidget } from "$sb/syscalls.ts";
|
import { codeWidget, editor } from "$sb/syscalls.ts";
|
||||||
|
|
||||||
export function refreshAll() {
|
export function refreshAllWidgets() {
|
||||||
codeWidget.refreshAll();
|
codeWidget.refreshAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function editButton(pos: number) {
|
||||||
|
await editor.moveCursor(pos);
|
||||||
|
}
|
||||||
|
@ -98,7 +98,7 @@ export class ServerSystem {
|
|||||||
),
|
),
|
||||||
eventHook,
|
eventHook,
|
||||||
);
|
);
|
||||||
const space = new Space(this.spacePrimitives, this.ds, eventHook);
|
const space = new Space(this.spacePrimitives, eventHook);
|
||||||
|
|
||||||
// Add syscalls
|
// Add syscalls
|
||||||
this.system.registerSyscalls(
|
this.system.registerSyscalls(
|
||||||
|
@ -38,7 +38,7 @@ import { OpenPages } from "./open_pages.ts";
|
|||||||
import { MainUI } from "./editor_ui.tsx";
|
import { MainUI } from "./editor_ui.tsx";
|
||||||
import { cleanPageRef } from "$sb/lib/resolve.ts";
|
import { cleanPageRef } from "$sb/lib/resolve.ts";
|
||||||
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||||
import { FileMeta, PageMeta } from "$sb/types.ts";
|
import { CodeWidgetButton, FileMeta, PageMeta } from "$sb/types.ts";
|
||||||
import { DataStore } from "../plugos/lib/datastore.ts";
|
import { DataStore } from "../plugos/lib/datastore.ts";
|
||||||
import { IndexedDBKvPrimitives } from "../plugos/lib/indexeddb_kv_primitives.ts";
|
import { IndexedDBKvPrimitives } from "../plugos/lib/indexeddb_kv_primitives.ts";
|
||||||
import { DataStoreMQ } from "../plugos/lib/mq.datastore.ts";
|
import { DataStoreMQ } from "../plugos/lib/mq.datastore.ts";
|
||||||
@ -188,13 +188,7 @@ export class Client {
|
|||||||
// Load settings
|
// Load settings
|
||||||
this.settings = await ensureSettingsAndIndex(localSpacePrimitives);
|
this.settings = await ensureSettingsAndIndex(localSpacePrimitives);
|
||||||
|
|
||||||
// Load widget cache
|
await this.loadCaches();
|
||||||
this.widgetCache = new LimitedMap(
|
|
||||||
100,
|
|
||||||
await this.stateDataStore.get(["cache", "widgets"]) ||
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pinging a remote space to ensure we're authenticated properly, if not will result in a redirect to auth page
|
// Pinging a remote space to ensure we're authenticated properly, if not will result in a redirect to auth page
|
||||||
try {
|
try {
|
||||||
await this.httpSpacePrimitives.ping();
|
await this.httpSpacePrimitives.ping();
|
||||||
@ -484,7 +478,6 @@ export class Client {
|
|||||||
|
|
||||||
this.space = new Space(
|
this.space = new Space(
|
||||||
localSpacePrimitives,
|
localSpacePrimitives,
|
||||||
this.stateDataStore,
|
|
||||||
this.eventHook,
|
this.eventHook,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1013,7 +1006,56 @@ export class Client {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private widgetCache = new LimitedMap<WidgetCacheItem>(100);
|
// Widget and image height caching
|
||||||
|
private widgetCache = new LimitedMap<WidgetCacheItem>(100); // bodyText -> WidgetCacheItem
|
||||||
|
private imageHeightCache = new LimitedMap<number>(100); // url -> height
|
||||||
|
private widgetHeightCache = new LimitedMap<number>(100); // bodytext -> height
|
||||||
|
|
||||||
|
async loadCaches() {
|
||||||
|
const [imageHeightCache, widgetHeightCache, widgetCache] = await this
|
||||||
|
.stateDataStore.batchGet([["cache", "imageHeight"], [
|
||||||
|
"cache",
|
||||||
|
"widgetHeight",
|
||||||
|
], ["cache", "widgets"]]);
|
||||||
|
this.imageHeightCache = new LimitedMap(100, imageHeightCache || {});
|
||||||
|
this.widgetHeightCache = new LimitedMap(100, widgetHeightCache || {});
|
||||||
|
this.widgetCache = new LimitedMap(100, widgetCache || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncedImageCacheFlush = throttle(() => {
|
||||||
|
this.stateDataStore.set(["cache", "imageHeight"], this.imageHeightCache)
|
||||||
|
.catch(
|
||||||
|
console.error,
|
||||||
|
);
|
||||||
|
console.log("Flushed image height cache to store");
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
setCachedImageHeight(url: string, height: number) {
|
||||||
|
this.imageHeightCache.set(url, height);
|
||||||
|
this.debouncedImageCacheFlush();
|
||||||
|
}
|
||||||
|
getCachedImageHeight(url: string): number {
|
||||||
|
return this.imageHeightCache.get(url) ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncedWidgetHeightCacheFlush = throttle(() => {
|
||||||
|
this.stateDataStore.set(
|
||||||
|
["cache", "widgetHeight"],
|
||||||
|
this.widgetHeightCache.toJSON(),
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
console.error,
|
||||||
|
);
|
||||||
|
// console.log("Flushed widget height cache to store");
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
setCachedWidgetHeight(bodyText: string, height: number) {
|
||||||
|
this.widgetHeightCache.set(bodyText, height);
|
||||||
|
this.debouncedWidgetHeightCacheFlush();
|
||||||
|
}
|
||||||
|
getCachedWidgetHeight(bodyText: string): number {
|
||||||
|
return this.widgetHeightCache.get(bodyText) ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
debouncedWidgetCacheFlush = throttle(() => {
|
debouncedWidgetCacheFlush = throttle(() => {
|
||||||
this.stateDataStore.set(["cache", "widgets"], this.widgetCache.toJSON())
|
this.stateDataStore.set(["cache", "widgets"], this.widgetCache.toJSON())
|
||||||
@ -1023,8 +1065,8 @@ export class Client {
|
|||||||
console.log("Flushed widget cache to store");
|
console.log("Flushed widget cache to store");
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
setWidgetCache(key: string, height: number, html: string) {
|
setWidgetCache(key: string, cacheItem: WidgetCacheItem) {
|
||||||
this.widgetCache.set(key, { height, html });
|
this.widgetCache.set(key, cacheItem);
|
||||||
this.debouncedWidgetCacheFlush();
|
this.debouncedWidgetCacheFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1036,4 +1078,5 @@ export class Client {
|
|||||||
type WidgetCacheItem = {
|
type WidgetCacheItem = {
|
||||||
height: number;
|
height: number;
|
||||||
html: string;
|
html: string;
|
||||||
|
buttons?: CodeWidgetButton[];
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,7 @@ export class IFrameWidget extends WidgetType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get estimatedHeight(): number {
|
get estimatedHeight(): number {
|
||||||
const cachedHeight = this.client.space.getCachedWidgetHeight(this.bodyText);
|
const cachedHeight = this.client.getCachedWidgetHeight(this.bodyText);
|
||||||
// console.log("Calling estimated height", cachedHeight);
|
// console.log("Calling estimated height", cachedHeight);
|
||||||
return cachedHeight > 0 ? cachedHeight : 150;
|
return cachedHeight > 0 ? cachedHeight : 150;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class InlineImageWidget extends WidgetType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get estimatedHeight(): number {
|
get estimatedHeight(): number {
|
||||||
const cachedHeight = this.client.space.getCachedImageHeight(this.url);
|
const cachedHeight = this.client.getCachedImageHeight(this.url);
|
||||||
// console.log("Estimated height requested", this.url, cachedHeight);
|
// console.log("Estimated height requested", this.url, cachedHeight);
|
||||||
return cachedHeight;
|
return cachedHeight;
|
||||||
}
|
}
|
||||||
@ -35,11 +35,11 @@ class InlineImageWidget extends WidgetType {
|
|||||||
let url = this.url;
|
let url = this.url;
|
||||||
url = resolvePath(this.client.currentPage!, url, true);
|
url = resolvePath(this.client.currentPage!, url, true);
|
||||||
// console.log("Creating DOM", this.url);
|
// console.log("Creating DOM", this.url);
|
||||||
const cachedImageHeight = this.client.space.getCachedImageHeight(url);
|
const cachedImageHeight = this.client.getCachedImageHeight(url);
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// console.log("Loaded", this.url, "with height", img.height);
|
// console.log("Loaded", this.url, "with height", img.height);
|
||||||
if (img.height !== cachedImageHeight) {
|
if (img.height !== cachedImageHeight) {
|
||||||
this.client.space.setCachedImageHeight(url, img.height);
|
this.client.setCachedImageHeight(url, img.height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
img.src = url;
|
img.src = url;
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { WidgetType } from "../deps.ts";
|
import { WidgetType } from "../deps.ts";
|
||||||
import type { Client } from "../client.ts";
|
import type { Client } from "../client.ts";
|
||||||
import type { CodeWidgetCallback } from "$sb/types.ts";
|
import type { CodeWidgetButton, CodeWidgetCallback } from "$sb/types.ts";
|
||||||
import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
|
import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
|
||||||
import { resolveAttachmentPath } from "$sb/lib/resolve.ts";
|
import { resolveAttachmentPath } from "$sb/lib/resolve.ts";
|
||||||
import { parse } from "../../common/markdown_parser/parse_tree.ts";
|
import { parse } from "../../common/markdown_parser/parse_tree.ts";
|
||||||
import buildMarkdown from "../../common/markdown_parser/parser.ts";
|
import buildMarkdown from "../../common/markdown_parser/parser.ts";
|
||||||
import { renderToText } from "$sb/lib/tree.ts";
|
import { renderToText } from "$sb/lib/tree.ts";
|
||||||
|
|
||||||
|
const activeWidgets = new Set<MarkdownWidget>();
|
||||||
|
|
||||||
export class MarkdownWidget extends WidgetType {
|
export class MarkdownWidget extends WidgetType {
|
||||||
renderedMarkdown?: string;
|
renderedMarkdown?: string;
|
||||||
|
public dom?: HTMLElement;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly from: number | undefined,
|
readonly from: number | undefined,
|
||||||
@ -27,19 +30,20 @@ export class MarkdownWidget extends WidgetType {
|
|||||||
if (cacheItem) {
|
if (cacheItem) {
|
||||||
div.innerHTML = this.wrapHtml(
|
div.innerHTML = this.wrapHtml(
|
||||||
cacheItem.html,
|
cacheItem.html,
|
||||||
this.from !== undefined,
|
cacheItem.buttons,
|
||||||
this.from !== undefined,
|
|
||||||
);
|
);
|
||||||
this.attachListeners(div);
|
this.attachListeners(div, cacheItem.buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Async kick-off of content renderer
|
// Async kick-off of content renderer
|
||||||
this.renderContent(div, cacheItem?.html).catch(console.error);
|
this.renderContent(div, cacheItem?.html).catch(console.error);
|
||||||
|
|
||||||
|
this.dom = div;
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async renderContent(
|
async renderContent(
|
||||||
div: HTMLElement,
|
div: HTMLElement,
|
||||||
cachedHtml: string | undefined,
|
cachedHtml: string | undefined,
|
||||||
) {
|
) {
|
||||||
@ -47,6 +51,7 @@ export class MarkdownWidget extends WidgetType {
|
|||||||
this.bodyText,
|
this.bodyText,
|
||||||
this.client.currentPage!,
|
this.client.currentPage!,
|
||||||
);
|
);
|
||||||
|
activeWidgets.add(this);
|
||||||
if (!widgetContent) {
|
if (!widgetContent) {
|
||||||
div.innerHTML = "";
|
div.innerHTML = "";
|
||||||
// div.style.display = "none";
|
// div.style.display = "none";
|
||||||
@ -89,42 +94,30 @@ export class MarkdownWidget extends WidgetType {
|
|||||||
// HTML still same as in cache, no need to re-render
|
// HTML still same as in cache, no need to re-render
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
div.innerHTML = this.wrapHtml(
|
div.innerHTML = this.wrapHtml(html, widgetContent.buttons);
|
||||||
html,
|
this.attachListeners(div, widgetContent.buttons);
|
||||||
this.from !== undefined,
|
|
||||||
this.from !== undefined,
|
|
||||||
);
|
|
||||||
this.attachListeners(div);
|
|
||||||
|
|
||||||
// Let's give it a tick, then measure and cache
|
// Let's give it a tick, then measure and cache
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.client.setWidgetCache(
|
this.client.setWidgetCache(
|
||||||
this.bodyText,
|
this.bodyText,
|
||||||
div.clientHeight,
|
{ height: div.clientHeight, html, buttons: widgetContent.buttons },
|
||||||
html,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private wrapHtml(html: string, editButton = true, sourceButton = true) {
|
private wrapHtml(html: string, buttons?: CodeWidgetButton[]) {
|
||||||
return `
|
if (!buttons) {
|
||||||
<div class="button-bar">
|
return html;
|
||||||
${
|
|
||||||
sourceButton
|
|
||||||
? `<button class="source-button" title="Show Markdown source"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-code"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg></button>`
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
<button class="reload-button" title="Reload"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg></button>
|
return `<div class="button-bar">${
|
||||||
${
|
buttons.map((button, idx) =>
|
||||||
editButton
|
`<button data-button="${idx}" title="${button.description}">${button.svg}</button> `
|
||||||
? `<button class="edit-button" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button>`
|
).join("")
|
||||||
: ""
|
}</div>${html}`;
|
||||||
}
|
|
||||||
</div>
|
|
||||||
${html}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private attachListeners(div: HTMLElement) {
|
private attachListeners(div: HTMLElement, buttons?: CodeWidgetButton[]) {
|
||||||
div.querySelectorAll("a[data-ref]").forEach((el_) => {
|
div.querySelectorAll("a[data-ref]").forEach((el_) => {
|
||||||
const el = el_ as HTMLElement;
|
const el = el_ as HTMLElement;
|
||||||
// Override default click behavior with a local navigate (faster)
|
// Override default click behavior with a local navigate (faster)
|
||||||
@ -160,20 +153,30 @@ export class MarkdownWidget extends WidgetType {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.from !== undefined) {
|
if (!buttons) {
|
||||||
div.querySelector(".edit-button")!.addEventListener("click", () => {
|
buttons = [];
|
||||||
this.client.editorView.dispatch({
|
|
||||||
selection: { anchor: this.from! },
|
|
||||||
});
|
|
||||||
this.client.focus();
|
|
||||||
});
|
|
||||||
div.querySelector(".source-button")!.addEventListener("click", () => {
|
|
||||||
div.innerText = this.renderedMarkdown!;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
div.querySelector(".reload-button")!.addEventListener("click", () => {
|
|
||||||
this.renderContent(div, undefined).catch(console.error);
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
});
|
const button = buttons[i];
|
||||||
|
div.querySelector(`button[data-button="${i}"]`)!.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
this.client.system.localSyscall("system.invokeFunction", [
|
||||||
|
button.invokeFunction,
|
||||||
|
this.from,
|
||||||
|
]).then((newContent: string | undefined) => {
|
||||||
|
if (newContent) {
|
||||||
|
div.innerText = newContent;
|
||||||
|
}
|
||||||
|
this.client.focus();
|
||||||
|
}).catch(console.error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// div.querySelectorAll("ul > li").forEach((el) => {
|
||||||
|
// el.classList.add("sb-line-li-1", "sb-line-ul");
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
get estimatedHeight(): number {
|
get estimatedHeight(): number {
|
||||||
@ -189,3 +192,25 @@ export class MarkdownWidget extends WidgetType {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reloadAllMarkdownWidgets() {
|
||||||
|
for (const widget of activeWidgets) {
|
||||||
|
// Garbage collect as we go
|
||||||
|
if (!widget.dom || !widget.dom.parentNode) {
|
||||||
|
activeWidgets.delete(widget);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
widget.renderContent(widget.dom!, undefined).catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function garbageCollectWidgets() {
|
||||||
|
for (const widget of activeWidgets) {
|
||||||
|
if (!widget.dom || !widget.dom.parentNode) {
|
||||||
|
// console.log("Garbage collecting widget", widget.bodyText);
|
||||||
|
activeWidgets.delete(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(garbageCollectWidgets, 5000);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Decoration, EditorState, WidgetType } from "../deps.ts";
|
import { Decoration, EditorState } from "../deps.ts";
|
||||||
import type { Client } from "../client.ts";
|
import type { Client } from "../client.ts";
|
||||||
import { decoratorStateField } from "./util.ts";
|
import { decoratorStateField } from "./util.ts";
|
||||||
import { MarkdownWidget } from "./markdown_widget.ts";
|
import { MarkdownWidget } from "./markdown_widget.ts";
|
||||||
|
@ -149,7 +149,7 @@ export function mountIFrame(
|
|||||||
case "setHeight":
|
case "setHeight":
|
||||||
iframe.height = data.height + "px";
|
iframe.height = data.height + "px";
|
||||||
if (widgetHeightCacheKey) {
|
if (widgetHeightCacheKey) {
|
||||||
client.space.setCachedWidgetHeight(
|
client.setCachedWidgetHeight(
|
||||||
widgetHeightCacheKey,
|
widgetHeightCacheKey,
|
||||||
data.height,
|
data.height,
|
||||||
);
|
);
|
||||||
|
46
web/space.ts
46
web/space.ts
@ -11,40 +11,6 @@ import { LimitedMap } from "../common/limited_map.ts";
|
|||||||
const pageWatchInterval = 5000;
|
const pageWatchInterval = 5000;
|
||||||
|
|
||||||
export class Space {
|
export class Space {
|
||||||
imageHeightCache = new LimitedMap<number>(100); // url -> height
|
|
||||||
widgetHeightCache = new LimitedMap<number>(100); // bodytext -> height
|
|
||||||
|
|
||||||
debouncedImageCacheFlush = throttle(() => {
|
|
||||||
this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch(
|
|
||||||
console.error,
|
|
||||||
);
|
|
||||||
console.log("Flushed image height cache to store");
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
setCachedImageHeight(url: string, height: number) {
|
|
||||||
this.imageHeightCache.set(url, height);
|
|
||||||
this.debouncedImageCacheFlush();
|
|
||||||
}
|
|
||||||
getCachedImageHeight(url: string): number {
|
|
||||||
return this.imageHeightCache.get(url) ?? -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedWidgetCacheFlush = throttle(() => {
|
|
||||||
this.ds.set(["cache", "widgetHeight"], this.widgetHeightCache.toJSON())
|
|
||||||
.catch(
|
|
||||||
console.error,
|
|
||||||
);
|
|
||||||
// console.log("Flushed widget height cache to store");
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
setCachedWidgetHeight(bodyText: string, height: number) {
|
|
||||||
this.widgetHeightCache.set(bodyText, height);
|
|
||||||
this.debouncedWidgetCacheFlush();
|
|
||||||
}
|
|
||||||
getCachedWidgetHeight(bodyText: string): number {
|
|
||||||
return this.widgetHeightCache.get(bodyText) ?? -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do watch files in the background to detect changes
|
// We do watch files in the background to detect changes
|
||||||
// This set of pages should only ever contain 1 page
|
// This set of pages should only ever contain 1 page
|
||||||
watchedPages = new Set<string>();
|
watchedPages = new Set<string>();
|
||||||
@ -55,20 +21,8 @@ export class Space {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly spacePrimitives: SpacePrimitives,
|
readonly spacePrimitives: SpacePrimitives,
|
||||||
private ds: DataStore,
|
|
||||||
eventHook: EventHook,
|
eventHook: EventHook,
|
||||||
) {
|
) {
|
||||||
// super();
|
|
||||||
this.ds.batchGet([["cache", "imageHeight"], ["cache", "widgetHeight"]])
|
|
||||||
.then(([imageCache, widgetCache]) => {
|
|
||||||
if (imageCache) {
|
|
||||||
this.imageHeightCache = new LimitedMap(100, imageCache);
|
|
||||||
}
|
|
||||||
if (widgetCache) {
|
|
||||||
// console.log("Loaded widget cache from store", widgetCache);
|
|
||||||
this.widgetHeightCache = new LimitedMap(100, widgetCache);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
eventHook.addLocalListener("page:deleted", (pageName: string) => {
|
eventHook.addLocalListener("page:deleted", (pageName: string) => {
|
||||||
if (this.watchedPages.has(pageName)) {
|
if (this.watchedPages.has(pageName)) {
|
||||||
// Stop watching deleted pages already
|
// Stop watching deleted pages already
|
||||||
|
@ -451,13 +451,13 @@
|
|||||||
.sb-markdown-bottom-widget h1 {
|
.sb-markdown-bottom-widget h1 {
|
||||||
border-top-right-radius: 5px;
|
border-top-right-radius: 5px;
|
||||||
border-top-left-radius: 5px;
|
border-top-left-radius: 5px;
|
||||||
margin: 0;
|
margin: 0 0 5px 0;
|
||||||
padding: 10px !important;
|
padding: 10px !important;
|
||||||
background-color: var(--editor-directive-background-color);
|
background-color: var(--editor-directive-background-color);
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-markdown-top-widget {
|
.sb-markdown-top-widget:has(*) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +471,7 @@
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
border: 1px solid var(--editor-directive-background-color);
|
border: 1px solid var(--editor-directive-background-color);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
white-space: nowrap;
|
white-space: wrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
@ -482,7 +482,7 @@
|
|||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 1ch;
|
// padding-left: 1ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul li::before {
|
ul li::before {
|
||||||
@ -493,7 +493,7 @@
|
|||||||
/* Needed to add space between the bullet and the text */
|
/* Needed to add space between the bullet and the text */
|
||||||
width: 1em;
|
width: 1em;
|
||||||
/* Also needed for space (tweak if needed) */
|
/* Also needed for space (tweak if needed) */
|
||||||
// margin-left: -1em;
|
margin-left: -1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
@ -528,27 +528,24 @@
|
|||||||
|
|
||||||
.button-bar {
|
.button-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 6px;
|
right: 10px;
|
||||||
top: 6px;
|
top: 10px;
|
||||||
display: none;
|
display: none;
|
||||||
background: var(--editor-directive-background-color);
|
background: var(--editor-directive-background-color);
|
||||||
padding-inline: 3px;
|
padding-inline: 3px;
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--root-color);
|
color: var(--root-color);
|
||||||
|
margin-right: -8px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.edit-button,
|
|
||||||
.reload-button {
|
|
||||||
margin-left: -10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-fenced-code-iframe {
|
.sb-fenced-code-iframe {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { SysCallMapping } from "../../plugos/system.ts";
|
import { SysCallMapping } from "../../plugos/system.ts";
|
||||||
|
import { reloadAllMarkdownWidgets } from "../cm_plugins/markdown_widget.ts";
|
||||||
import { broadcastReload } from "../components/widget_sandbox_iframe.ts";
|
import { broadcastReload } from "../components/widget_sandbox_iframe.ts";
|
||||||
|
|
||||||
export function clientCodeWidgetSyscalls(): SysCallMapping {
|
export function clientCodeWidgetSyscalls(): SysCallMapping {
|
||||||
return {
|
return {
|
||||||
"codeWidget.refreshAll": () => {
|
"codeWidget.refreshAll": () => {
|
||||||
broadcastReload();
|
broadcastReload();
|
||||||
|
reloadAllMarkdownWidgets();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user