From d486ddf4170f00375bdb57205c02ca7a7dfb18c7 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 18:13:43 -0800 Subject: [PATCH 1/6] backend ref updates and ref operations now working --- pkg/vdom/vdom.go | 14 ++++++++ pkg/vdom/vdom_root.go | 47 +++++++++++++++++++++++++++ pkg/vdom/vdom_types.go | 7 ++-- pkg/vdom/vdomclient/vdomclient.go | 6 ++-- pkg/vdom/vdomclient/vdomserverimpl.go | 4 +++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/pkg/vdom/vdom.go b/pkg/vdom/vdom.go index 945dbdcb5d..88d6be5496 100644 --- a/pkg/vdom/vdom.go +++ b/pkg/vdom/vdom.go @@ -346,6 +346,20 @@ func UseId(ctx context.Context) string { return vc.Comp.WaveId } +func QueueRefOp(ctx context.Context, ref *VDomRef, op VDomRefOperation) { + if ref == nil || !ref.HasCurrent { + return + } + vc := getRenderContext(ctx) + if vc == nil { + panic("QueueRefOp must be called within a component (no context)") + } + if op.RefId == "" { + op.RefId = ref.RefId + } + vc.Root.QueueRefOp(op) +} + func depsEqual(deps1 []any, deps2 []any) bool { if len(deps1) != len(deps2) { return false diff --git a/pkg/vdom/vdom_root.go b/pkg/vdom/vdom_root.go index 954edd9af1..5538aaa80a 100644 --- a/pkg/vdom/vdom_root.go +++ b/pkg/vdom/vdom_root.go @@ -6,7 +6,10 @@ package vdom import ( "context" "fmt" + "log" "reflect" + "strconv" + "strings" "github.com/google/uuid" "github.com/wavetermdev/waveterm/pkg/util/utilfn" @@ -41,6 +44,7 @@ type RootElem struct { EffectWorkQueue []*EffectWorkElem NeedsRenderMap map[string]bool Atoms map[string]*Atom + RefOperations []VDomRefOperation } const ( @@ -414,6 +418,49 @@ func (r *RootElem) renderComponent(cfunc any, elem *VDomElem, comp **ComponentIm r.render(rtnElem, &(*comp).Comp) } +func (r *RootElem) UpdateRef(updateRef VDomRefUpdate) { + refId := updateRef.RefId + split := strings.SplitN(refId, ":", 2) + if len(split) != 2 { + log.Printf("invalid ref id: %s\n", refId) + return + } + waveId := split[0] + hookIdx, err := strconv.Atoi(split[1]) + if err != nil { + log.Printf("invalid ref id (bad hook idx): %s\n", refId) + return + } + comp := r.CompMap[waveId] + if comp == nil { + return + } + if hookIdx < 0 || hookIdx >= len(comp.Hooks) { + return + } + hook := comp.Hooks[hookIdx] + if hook == nil { + return + } + ref, ok := hook.Val.(*VDomRef) + if !ok { + return + } + ref.HasCurrent = updateRef.HasCurrent + ref.Position = updateRef.Position + r.AddRenderWork(waveId) +} + +func (r *RootElem) QueueRefOp(op VDomRefOperation) { + r.RefOperations = append(r.RefOperations, op) +} + +func (r *RootElem) GetRefOperations() []VDomRefOperation { + ops := r.RefOperations + r.RefOperations = nil + return ops +} + func convertPropsToVDom(props map[string]any) map[string]any { if len(props) == 0 { return nil diff --git a/pkg/vdom/vdom_types.go b/pkg/vdom/vdom_types.go index ae77fcd8a3..a2d1ea6f97 100644 --- a/pkg/vdom/vdom_types.go +++ b/pkg/vdom/vdom_types.go @@ -188,9 +188,10 @@ type VDomRenderUpdate struct { } type VDomRefOperation struct { - RefId string `json:"refid"` - Op string `json:"op" tsype:"\"focus\""` - Params []any `json:"params,omitempty"` + RefId string `json:"refid"` + Op string `json:"op"` + Params []any `json:"params,omitempty"` + OutputRef string `json:"outputref,omitempty"` } type VDomMessage struct { diff --git a/pkg/vdom/vdomclient/vdomclient.go b/pkg/vdom/vdomclient/vdomclient.go index ee26e0eccb..47a79c7e63 100644 --- a/pkg/vdom/vdomclient/vdomclient.go +++ b/pkg/vdom/vdomclient/vdomclient.go @@ -211,7 +211,8 @@ func (c *Client) fullRender() (*vdom.VDomBackendUpdate, error) { RenderUpdates: []vdom.VDomRenderUpdate{ {UpdateType: "root", VDom: renderedVDom}, }, - StateSync: c.Root.GetStateSync(true), + RefOperations: c.Root.GetRefOperations(), + StateSync: c.Root.GetStateSync(true), }, nil } @@ -228,7 +229,8 @@ func (c *Client) incrementalRender() (*vdom.VDomBackendUpdate, error) { RenderUpdates: []vdom.VDomRenderUpdate{ {UpdateType: "root", VDom: renderedVDom}, }, - StateSync: c.Root.GetStateSync(false), + RefOperations: c.Root.GetRefOperations(), + StateSync: c.Root.GetStateSync(false), }, nil } diff --git a/pkg/vdom/vdomclient/vdomserverimpl.go b/pkg/vdom/vdomclient/vdomserverimpl.go index f79584e179..70a803cc4c 100644 --- a/pkg/vdom/vdomclient/vdomserverimpl.go +++ b/pkg/vdom/vdomclient/vdomserverimpl.go @@ -60,6 +60,10 @@ func (impl *VDomServerImpl) VDomRenderCommand(ctx context.Context, feUpdate vdom impl.Client.Root.Event(event.WaveId, event.EventType, event) } } + // update refs + for _, ref := range feUpdate.RefUpdates { + impl.Client.Root.UpdateRef(ref) + } var update *vdom.VDomBackendUpdate var err error From bf80c5e1ab4df1c395713ed998af878dbfca01f2 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 18:14:19 -0800 Subject: [PATCH 2/6] implement ref ops, and *canvas* ref operations --- frontend/app/view/vdom/vdom-model.tsx | 24 +++++++++++++-- frontend/app/view/vdom/vdom-utils.tsx | 42 +++++++++++++++++++++++++++ frontend/app/view/vdom/vdom.tsx | 7 ++++- frontend/types/gotypes.d.ts | 1 + 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/frontend/app/view/vdom/vdom-model.tsx b/frontend/app/view/vdom/vdom-model.tsx index 6e2ffe3f92..1d35206881 100644 --- a/frontend/app/view/vdom/vdom-model.tsx +++ b/frontend/app/view/vdom/vdom-model.tsx @@ -9,7 +9,7 @@ import { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; -import { mergeBackendUpdates, restoreVDomElems } from "@/app/view/vdom/vdom-utils"; +import { applyCanvasOp, mergeBackendUpdates, restoreVDomElems } from "@/app/view/vdom/vdom-utils"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import debug from "debug"; import * as jotai from "jotai"; @@ -94,7 +94,7 @@ class VDomWshClient extends WshClient { } handle_vdomasyncinitiation(rh: RpcResponseHelper, data: VDomAsyncInitiationRequest) { - console.log("async-initiation", rh.getSource(), data); + dlog("async-initiation", rh.getSource(), data); this.model.queueUpdate(true); } } @@ -130,6 +130,9 @@ export class VDomModel { persist: jotai.Atom; routeGoneUnsub: () => void; routeConfirmed: boolean = false; + refOutputStore: Map = new Map(); + globalVersion: jotai.PrimitiveAtom = jotai.atom(0); + hasBackendWork: boolean = false; constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewType = "vdom"; @@ -201,6 +204,9 @@ export class VDomModel { this.needsImmediateUpdate = false; this.lastUpdateTs = 0; this.queuedUpdate = null; + this.refOutputStore.clear(); + this.globalVersion = jotai.atom(0); + this.hasBackendWork = false; globalStore.set(this.contextActive, false); } @@ -537,6 +543,10 @@ export class VDomModel { this.addErrorMessage(`Could not find ref with id ${refOp.refid}`); continue; } + if (elem instanceof HTMLCanvasElement) { + applyCanvasOp(elem, refOp, this.refOutputStore); + continue; + } if (refOp.op == "focus") { if (elem == null) { this.addErrorMessage(`Could not focus ref with id ${refOp.refid}: elem is null`); @@ -575,7 +585,17 @@ export class VDomModel { } } } + globalStore.set(this.globalVersion, globalStore.get(this.globalVersion) + 1); if (update.haswork) { + this.hasBackendWork = true; + } + } + + renderDone(version: number) { + // called when the render is done + dlog("renderDone", version); + if (this.hasRefUpdates() || this.hasBackendWork) { + this.hasBackendWork = false; this.queueUpdate(true); } } diff --git a/frontend/app/view/vdom/vdom-utils.tsx b/frontend/app/view/vdom/vdom-utils.tsx index 17a8e44753..af48a79a3a 100644 --- a/frontend/app/view/vdom/vdom-utils.tsx +++ b/frontend/app/view/vdom/vdom-utils.tsx @@ -197,3 +197,45 @@ export function mergeBackendUpdates(baseUpdate: VDomBackendUpdate, nextUpdate: V baseUpdate.statesync.push(...nextUpdate.statesync); } } + +export function applyCanvasOp(canvas: HTMLCanvasElement, canvasOp: VDomRefOperation, refStore: Map) { + const ctx = canvas.getContext("2d"); + if (!ctx) { + console.error("Canvas 2D context not available."); + return; + } + + const { op, params, outputref } = canvasOp; + + // Resolve any reference parameters in params + const resolvedParams: any[] = []; + params.forEach((param) => { + if (typeof param === "string" && param.startsWith("#ref:")) { + const refId = param.slice(5); // Remove "#ref:" prefix + resolvedParams.push(refStore.get(refId)); + } else if (typeof param === "string" && param.startsWith("#spreadRef:")) { + const refId = param.slice(11); // Remove "#spreadRef:" prefix + const arrayRef = refStore.get(refId); + if (Array.isArray(arrayRef)) { + resolvedParams.push(...arrayRef); // Spread array elements + } else { + console.error(`Reference ${refId} is not an array and cannot be spread.`); + } + } else { + resolvedParams.push(param); + } + }); + + // Apply the operation on the canvas context + if (op === "dropRef" && params.length > 0 && typeof params[0] === "string") { + refStore.delete(params[0]); + } else if (op === "addRef" && outputref) { + refStore.set(outputref, resolvedParams[0]); + } else if (typeof ctx[op as keyof CanvasRenderingContext2D] === "function") { + (ctx[op as keyof CanvasRenderingContext2D] as Function).apply(ctx, resolvedParams); + } else if (op in ctx) { + (ctx as any)[op] = resolvedParams[0]; + } else { + console.error(`Unsupported canvas operation: ${op}`); + } +} diff --git a/frontend/app/view/vdom/vdom.tsx b/frontend/app/view/vdom/vdom.tsx index c4098dfcd4..48d2b3032d 100644 --- a/frontend/app/view/vdom/vdom.tsx +++ b/frontend/app/view/vdom/vdom.tsx @@ -72,6 +72,7 @@ const AllowedSimpleTags: { [tagName: string]: boolean } = { br: true, pre: true, code: true, + canvas: true, }; const AllowedSvgTags = { @@ -452,11 +453,15 @@ const testVDom: VDomElem = { }; function VDomRoot({ model }: { model: VDomModel }) { + let version = jotai.useAtomValue(model.globalVersion); let rootNode = jotai.useAtomValue(model.vdomRoot); + React.useEffect(() => { + model.renderDone(version); + }, [version]); if (model.viewRef.current == null || rootNode == null) { return null; } - dlog("render", rootNode); + dlog("render", version, rootNode); let rtn = convertElemToTag(rootNode, model); return
{rtn}
; } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index d59b5c0282..2954c8f8d1 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -741,6 +741,7 @@ declare global { refid: string; op: string; params?: any[]; + outputref?: string; }; // vdom.VDomRefPosition From a6b4e962bef564125eec04c84a5e93a26cbd32b8 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 18:14:40 -0800 Subject: [PATCH 3/6] implement some canvas ops to test in html --- cmd/wsh/cmd/wshcmd-html.go | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cmd/wsh/cmd/wshcmd-html.go b/cmd/wsh/cmd/wshcmd-html.go index b17119f219..b1c97277d7 100644 --- a/cmd/wsh/cmd/wshcmd-html.go +++ b/cmd/wsh/cmd/wshcmd-html.go @@ -7,6 +7,8 @@ import ( "context" _ "embed" "log" + "math/rand" + "time" "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/vdom" @@ -112,9 +114,51 @@ var BgList = vdomclient.DefineComponent[BgListProps](HtmlVDomClient, "BgList", }, ) +type CanvasUpdaterProps struct { + CanvasRef *vdom.VDomRef `json:"canvasRef"` +} + +var CanvasUpdater = vdomclient.DefineComponent[CanvasUpdaterProps](HtmlVDomClient, "CanvasUpdater", + func(ctx context.Context, props CanvasUpdaterProps) any { + tickNum, _, setTickNum := vdom.UseStateWithFn(ctx, 0) + canvasRef := props.CanvasRef + vdom.UseEffect(ctx, func() func() { + if !canvasRef.HasCurrent { + return nil + } + x := rand.Intn(200) // Random x position between 0 and 200 + y := rand.Intn(200) // Random y position between 0 and 200 + width := rand.Intn(300 - x) // Random width that fits within canvas width + height := rand.Intn(300 - y) // Random height that fits within canvas height + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{"#FF5733"}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "clearRect", + Params: []any{0, 0, 300, 300}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillRect", + Params: []any{x, y, width, height}, + }) + go func() { + time.Sleep(1 * time.Second) + setTickNum(func(prev int) int { + return prev + 1 + }) + HtmlVDomClient.SendAsyncInitiation() + }() + return nil + }, []any{tickNum}) + return nil + }, +) + var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", func(ctx context.Context, _ struct{}) any { inputText, setInputText := vdom.UseState(ctx, "start") + canvasRef := vdom.UseVDomRef(ctx) bgItems := []BgItem{ {Bg: "", Label: "default"}, @@ -126,6 +170,7 @@ var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", return vdom.E("div", vdom.Class("root"), Style(struct{}{}), + CanvasUpdater(CanvasUpdaterProps{CanvasRef: canvasRef}), vdom.E("h1", nil, "Set Background"), vdom.E("div", nil, vdom.E("wave:markdown", @@ -155,6 +200,9 @@ var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", ), vdom.E("div", nil, "text ", inputText), ), + vdom.E("canvas", vdom.P("ref", canvasRef), + vdom.P("width", "300"), vdom.P("height", "300"), + vdom.PStyle("width", 300), vdom.PStyle("height", 300)), ) }, ) From 79808add253dfa1dc61a6cec2ee2b94ed3edf594 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 19:32:40 -0800 Subject: [PATCH 4/6] fix null ptr --- frontend/app/view/vdom/vdom-utils.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/app/view/vdom/vdom-utils.tsx b/frontend/app/view/vdom/vdom-utils.tsx index af48a79a3a..ce6052def2 100644 --- a/frontend/app/view/vdom/vdom-utils.tsx +++ b/frontend/app/view/vdom/vdom-utils.tsx @@ -205,8 +205,13 @@ export function applyCanvasOp(canvas: HTMLCanvasElement, canvasOp: VDomRefOperat return; } - const { op, params, outputref } = canvasOp; - + let { op, params, outputref } = canvasOp; + if (params == null) { + params = []; + } + if (op == null || op == "") { + return; + } // Resolve any reference parameters in params const resolvedParams: any[] = []; params.forEach((param) => { From a91a44986a3920a1c0aa92f4cc812ff447cce458 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 19:32:59 -0800 Subject: [PATCH 5/6] userenderts --- pkg/vdom/vdom.go | 8 ++++++++ pkg/vdom/vdom_root.go | 1 + pkg/vdom/vdomclient/vdomserverimpl.go | 2 ++ 3 files changed, 11 insertions(+) diff --git a/pkg/vdom/vdom.go b/pkg/vdom/vdom.go index 88d6be5496..4266ddf602 100644 --- a/pkg/vdom/vdom.go +++ b/pkg/vdom/vdom.go @@ -346,6 +346,14 @@ func UseId(ctx context.Context) string { return vc.Comp.WaveId } +func UseRenderTs(ctx context.Context) int64 { + vc := getRenderContext(ctx) + if vc == nil { + panic("UseRenderTs must be called within a component (no context)") + } + return vc.Root.RenderTs +} + func QueueRefOp(ctx context.Context, ref *VDomRef, op VDomRefOperation) { if ref == nil || !ref.HasCurrent { return diff --git a/pkg/vdom/vdom_root.go b/pkg/vdom/vdom_root.go index 5538aaa80a..9bcbf7844b 100644 --- a/pkg/vdom/vdom_root.go +++ b/pkg/vdom/vdom_root.go @@ -39,6 +39,7 @@ type Atom struct { type RootElem struct { OuterCtx context.Context Root *ComponentImpl + RenderTs int64 CFuncs map[string]any CompMap map[string]*ComponentImpl // component waveid -> component EffectWorkQueue []*EffectWorkElem diff --git a/pkg/vdom/vdomclient/vdomserverimpl.go b/pkg/vdom/vdomclient/vdomserverimpl.go index 70a803cc4c..e14c2f4767 100644 --- a/pkg/vdom/vdomclient/vdomserverimpl.go +++ b/pkg/vdom/vdomclient/vdomserverimpl.go @@ -46,6 +46,8 @@ func (impl *VDomServerImpl) VDomRenderCommand(ctx context.Context, feUpdate vdom return respChan } + impl.Client.Root.RenderTs = feUpdate.Ts + // set atoms for _, ss := range feUpdate.StateSync { impl.Client.Root.SetAtomVal(ss.Atom, ss.Value, false) From 0b764ef594847a4c8537b0abc634a60ae0591ff4 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 5 Nov 2024 23:04:11 -0800 Subject: [PATCH 6/6] prop to remove rehype (memory leak). also remove canvas from 'html' demo, move to its own app --- cmd/wsh/cmd/wshcmd-html.go | 50 +---------------------- frontend/app/element/markdown.tsx | 67 +++++++++++++------------------ frontend/app/view/vdom/vdom.tsx | 8 +++- 3 files changed, 36 insertions(+), 89 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-html.go b/cmd/wsh/cmd/wshcmd-html.go index b1c97277d7..c7af594934 100644 --- a/cmd/wsh/cmd/wshcmd-html.go +++ b/cmd/wsh/cmd/wshcmd-html.go @@ -7,8 +7,6 @@ import ( "context" _ "embed" "log" - "math/rand" - "time" "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/vdom" @@ -114,51 +112,9 @@ var BgList = vdomclient.DefineComponent[BgListProps](HtmlVDomClient, "BgList", }, ) -type CanvasUpdaterProps struct { - CanvasRef *vdom.VDomRef `json:"canvasRef"` -} - -var CanvasUpdater = vdomclient.DefineComponent[CanvasUpdaterProps](HtmlVDomClient, "CanvasUpdater", - func(ctx context.Context, props CanvasUpdaterProps) any { - tickNum, _, setTickNum := vdom.UseStateWithFn(ctx, 0) - canvasRef := props.CanvasRef - vdom.UseEffect(ctx, func() func() { - if !canvasRef.HasCurrent { - return nil - } - x := rand.Intn(200) // Random x position between 0 and 200 - y := rand.Intn(200) // Random y position between 0 and 200 - width := rand.Intn(300 - x) // Random width that fits within canvas width - height := rand.Intn(300 - y) // Random height that fits within canvas height - vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ - Op: "fillStyle", - Params: []any{"#FF5733"}, - }) - vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ - Op: "clearRect", - Params: []any{0, 0, 300, 300}, - }) - vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ - Op: "fillRect", - Params: []any{x, y, width, height}, - }) - go func() { - time.Sleep(1 * time.Second) - setTickNum(func(prev int) int { - return prev + 1 - }) - HtmlVDomClient.SendAsyncInitiation() - }() - return nil - }, []any{tickNum}) - return nil - }, -) - var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", func(ctx context.Context, _ struct{}) any { inputText, setInputText := vdom.UseState(ctx, "start") - canvasRef := vdom.UseVDomRef(ctx) bgItems := []BgItem{ {Bg: "", Label: "default"}, @@ -170,11 +126,12 @@ var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", return vdom.E("div", vdom.Class("root"), Style(struct{}{}), - CanvasUpdater(CanvasUpdaterProps{CanvasRef: canvasRef}), vdom.E("h1", nil, "Set Background"), vdom.E("div", nil, vdom.E("wave:markdown", vdom.P("text", "*quick vdom application to set background colors*"), + vdom.P("scrollable", false), + vdom.P("rehype", false), ), ), vdom.E("div", nil, @@ -200,9 +157,6 @@ var App = vdomclient.DefineComponent[struct{}](HtmlVDomClient, "App", ), vdom.E("div", nil, "text ", inputText), ), - vdom.E("canvas", vdom.P("ref", canvasRef), - vdom.P("width", "300"), vdom.P("height", "300"), - vdom.PStyle("width", 300), vdom.PStyle("height", 300)), ) }, ) diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index ccbdf10049..fe2386bd25 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -180,6 +180,7 @@ type MarkdownProps = { onClickExecute?: (cmd: string) => void; resolveOpts?: MarkdownResolveOpts; scrollable?: boolean; + rehype?: boolean; }; const Markdown = ({ @@ -190,6 +191,7 @@ const Markdown = ({ className, resolveOpts, scrollable = true, + rehype = true, onClickExecute, }: MarkdownProps) => { const textAtomValue = useAtomValueSafe(textAtom); @@ -250,6 +252,29 @@ const Markdown = ({ }, [showToc, tocRef]); text = textAtomValue ?? text; + let rehypePlugins = null; + if (rehype) { + rehypePlugins = [ + rehypeRaw, + rehypeHighlight, + () => + rehypeSanitize({ + ...defaultSchema, + attributes: { + ...defaultSchema.attributes, + span: [ + ...(defaultSchema.attributes?.span || []), + // Allow all class names starting with `hljs-`. + ["className", /^hljs-./], + // Alternatively, to allow only certain class names: + // ['className', 'hljs-number', 'hljs-title', 'hljs-variable'] + ], + }, + tagNames: [...(defaultSchema.tagNames || []), "span"], + }), + () => rehypeSlug({ prefix: idPrefix }), + ]; + } const ScrollableMarkdown = () => { return ( @@ -260,26 +285,7 @@ const Markdown = ({ > - rehypeSanitize({ - ...defaultSchema, - attributes: { - ...defaultSchema.attributes, - span: [ - ...(defaultSchema.attributes?.span || []), - // Allow all class names starting with `hljs-`. - ["className", /^hljs-./], - // Alternatively, to allow only certain class names: - // ['className', 'hljs-number', 'hljs-title', 'hljs-variable'] - ], - }, - tagNames: [...(defaultSchema.tagNames || []), "span"], - }), - () => rehypeSlug({ prefix: idPrefix }), - ]} + rehypePlugins={rehypePlugins} components={markdownComponents} > {text} @@ -293,26 +299,7 @@ const Markdown = ({
- rehypeSanitize({ - ...defaultSchema, - attributes: { - ...defaultSchema.attributes, - span: [ - ...(defaultSchema.attributes?.span || []), - // Allow all class names starting with `hljs-`. - ["className", /^hljs-./], - // Alternatively, to allow only certain class names: - // ['className', 'hljs-number', 'hljs-title', 'hljs-variable'] - ], - }, - tagNames: [...(defaultSchema.tagNames || []), "span"], - }), - () => rehypeSlug({ prefix: idPrefix }), - ]} + rehypePlugins={rehypePlugins} components={markdownComponents} > {text} diff --git a/frontend/app/view/vdom/vdom.tsx b/frontend/app/view/vdom/vdom.tsx index 48d2b3032d..19c3fc4b34 100644 --- a/frontend/app/view/vdom/vdom.tsx +++ b/frontend/app/view/vdom/vdom.tsx @@ -351,7 +351,13 @@ function useVDom(model: VDomModel, elem: VDomElem): GenericPropsType { function WaveMarkdown({ elem, model }: { elem: VDomElem; model: VDomModel }) { const props = useVDom(model, elem); return ( - + ); }