@heyramzi/ui 2.11.1 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/helpers.js +38 -38
- package/dist/agents/page-state.js +49 -50
- package/dist/agents/vendor/color.js +146 -127
- package/dist/agents/vendor/draw.js +617 -508
- package/dist/agents/vendor/engine.js +698 -599
- package/dist/agents/vendor/mesh.js +653 -683
- package/dist/agents/vendor/plastic.js +3913 -3112
- package/dist/agents/vendor/presets.js +163 -164
- package/dist/agents/vendor/shapes.js +21 -20
- package/dist/agents/vendor/ticker.js +41 -42
- package/dist/agents/vendor/types.js +1 -0
- package/dist/agents/vendor/wear.js +1421 -1448
- package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
- package/dist/ai/confirmation/index.js +3 -1
- package/dist/ai/index.js +33 -0
- package/dist/ai/prompt-input/index.js +3 -1
- package/dist/ai/reasoning/index.js +3 -1
- package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
- package/dist/ai/suggestion/index.js +3 -1
- package/dist/ai/typing-dots/index.js +3 -1
- package/dist/animations/text-swap.js +24 -24
- package/dist/brand/index.js +54 -48
- package/dist/canvas/card/resize.js +5 -3
- package/dist/canvas/edges/edge-path.js +255 -242
- package/dist/canvas/editor/context-menu.js +39 -36
- package/dist/canvas/editor/dispatch.svelte.js +154 -151
- package/dist/canvas/editor/editor-context.js +7 -2
- package/dist/canvas/editor/editor-mode.js +1 -0
- package/dist/canvas/editor/frame.js +73 -76
- package/dist/canvas/editor/hand-context.js +5 -2
- package/dist/canvas/editor/history-core.js +163 -191
- package/dist/canvas/editor/keymap.js +95 -169
- package/dist/canvas/editor/layout-actions.js +143 -144
- package/dist/canvas/editor/lock.js +52 -51
- package/dist/canvas/editor/rebase.js +217 -240
- package/dist/canvas/editor/style.js +132 -137
- package/dist/canvas/editor/text-field-context.js +7 -2
- package/dist/canvas/editor/z-order.js +65 -70
- package/dist/canvas/headless/alignment.js +22 -16
- package/dist/canvas/headless/break-apart-find.js +286 -279
- package/dist/canvas/headless/break-apart-pieces.js +62 -58
- package/dist/canvas/headless/break-apart-plan.js +299 -325
- package/dist/canvas/headless/break-apart-style.js +75 -70
- package/dist/canvas/headless/break-apart.js +188 -181
- package/dist/canvas/headless/canvas-cleanup.js +78 -58
- package/dist/canvas/headless/edge-anchors.js +115 -116
- package/dist/canvas/headless/edge-memo.js +24 -22
- package/dist/canvas/headless/edge-serialization.js +17 -27
- package/dist/canvas/headless/figure-geometry.js +232 -264
- package/dist/canvas/headless/fit-viewport.js +84 -67
- package/dist/canvas/headless/grid.js +88 -86
- package/dist/canvas/headless/icon-node.js +44 -36
- package/dist/canvas/headless/index.js +1 -0
- package/dist/canvas/headless/linkify.js +17 -19
- package/dist/canvas/headless/pen-marks.js +101 -95
- package/dist/canvas/headless/sketch.js +231 -209
- package/dist/canvas/headless/snap-guides.js +99 -83
- package/dist/canvas/headless/svg-figure.js +133 -117
- package/dist/canvas/headless/table-layout.js +146 -107
- package/dist/canvas/index.js +2 -0
- package/dist/canvas/lod/lod-mode.svelte.js +20 -18
- package/dist/canvas/lod/render-all.svelte.js +36 -39
- package/dist/canvas/ops/arrange.js +78 -71
- package/dist/canvas/ops/ops.js +260 -255
- package/dist/canvas/pen/colors.js +37 -35
- package/dist/canvas/pen/eraser.js +28 -27
- package/dist/canvas/pen/geometry.js +120 -130
- package/dist/canvas/pen/index.js +1 -0
- package/dist/canvas/pen/ops.js +11 -11
- package/dist/canvas/pen/pen-session.js +150 -159
- package/dist/canvas/pen/plugin.js +107 -104
- package/dist/canvas/pen/shapes.js +73 -68
- package/dist/canvas/pen/stroke-path.js +32 -30
- package/dist/canvas/pen/types.js +21 -21
- package/dist/canvas/pen/ui.js +10 -8
- package/dist/canvas/plugin.js +46 -46
- package/dist/canvas/screen/glyphs.js +12 -11
- package/dist/canvas/screen/labels.js +22 -22
- package/dist/canvas/screen/parts.js +98 -111
- package/dist/canvas/screen/plugin.js +47 -56
- package/dist/canvas/screen/types.js +30 -35
- package/dist/canvas/screen/ui.js +7 -7
- package/dist/canvas/test-utils.js +14 -14
- package/dist/canvas/types.js +1 -0
- package/dist/canvas/ui/forward-wheel.js +24 -25
- package/dist/canvas/ui/icons.js +64 -131
- package/dist/canvas/ui/place-panel.js +58 -49
- package/dist/canvas/ui/shortcut-tip.js +14 -12
- package/dist/canvas/ui/tools.js +300 -385
- package/dist/charts/dashboard/chart.js +4 -0
- package/dist/charts/dashboard/data-table.js +11 -0
- package/dist/charts/dashboard/windows.js +8 -3
- package/dist/charts/edge-fade.js +10 -5
- package/dist/charts/hover-band.js +16 -11
- package/dist/charts/index.js +0 -1
- package/dist/charts/motion.js +11 -7
- package/dist/charts/primitives/chart-utils.js +12 -12
- package/dist/components/copy/clipboard.js +32 -32
- package/dist/components/copy/copied.svelte.js +31 -23
- package/dist/components/data-table/data-table.js +39 -39
- package/dist/components/docs/index.js +4 -0
- package/dist/components/docs/scroll-spy.svelte.js +73 -68
- package/dist/components/list-page/filter-section.js +4 -1
- package/dist/components/list-page/index.js +2 -0
- package/dist/components/list-page/list-table.js +7 -2
- package/dist/components/list-page/queue-rail.js +14 -0
- package/dist/components/list-page/section-host.js +4 -2
- package/dist/components/list-page/step.js +4 -3
- package/dist/components/list-page/types.js +1 -0
- package/dist/components/picker/trigger.js +48 -30
- package/dist/components/picker/types.js +1 -0
- package/dist/components/segmented/collapse.js +38 -27
- package/dist/components/segmented/index.js +8 -0
- package/dist/components/segmented/segmented.js +67 -42
- package/dist/components/star-rating-input/index.js +3 -1
- package/dist/components/table-view/types.js +1 -0
- package/dist/components/tags-input/tags.js +81 -69
- package/dist/components/theme/index.js +4 -0
- package/dist/components/toolbar/index.js +4 -0
- package/dist/components/toolbar/toolbar-trigger.js +14 -1
- package/dist/editor/anchors.js +17 -13
- package/dist/editor/block-id.js +52 -51
- package/dist/editor/block-ops.js +108 -80
- package/dist/editor/doc-blocks.js +155 -166
- package/dist/editor/extensions.js +139 -180
- package/dist/editor/index.js +4 -0
- package/dist/editor/markdown-test-helpers.js +4 -2
- package/dist/editor/markdown.js +420 -408
- package/dist/editor/serializer.js +204 -200
- package/dist/editor/slash-items.js +89 -101
- package/dist/editor/youtube-url.js +25 -20
- package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
- package/dist/effects/animated-beam/types.js +1 -0
- package/dist/effects/border-beam/css.js +92 -78
- package/dist/effects/border-beam/presets.js +1705 -1720
- package/dist/effects/border-beam/register-properties.js +43 -33
- package/dist/effects/border-beam/types.js +1 -0
- package/dist/effects/image-comparison/compare.js +3 -2
- package/dist/effects/image-reveal/types.js +1 -0
- package/dist/effects/magnetic/magnetic.js +58 -52
- package/dist/effects/reveal/observe.js +50 -40
- package/dist/effects/reveal/types.js +1 -0
- package/dist/effects/shader-gradient/color.js +36 -29
- package/dist/effects/shader-gradient/renderer.js +384 -295
- package/dist/effects/shader-gradient/shaders.js +31 -0
- package/dist/effects/shader-gradient/types.js +1 -0
- package/dist/icons/apple-symbol-exports.js +99 -124
- package/dist/icons/sf-symbols.js +357 -461
- package/dist/icons/trades.js +43 -28
- package/dist/inbox/index.js +1 -0
- package/dist/inbox/logic.js +126 -122
- package/dist/inbox/types.js +43 -43
- package/dist/lib/arrive.js +19 -12
- package/dist/lib/icon-types.js +1 -0
- package/dist/lib/initials.js +16 -10
- package/dist/lib/layout.js +5 -0
- package/dist/lib/panel.js +13 -0
- package/dist/lib/result.js +22 -22
- package/dist/lib/sliding-pill.js +59 -44
- package/dist/lib/theme-transition.js +43 -43
- package/dist/lib/tone.js +31 -24
- package/dist/lib/utils.js +3 -1
- package/dist/onboarding/define-tours.js +24 -23
- package/dist/onboarding/onboarding.svelte.js +16 -12
- package/dist/onboarding/pick.js +10 -6
- package/dist/onboarding/stores.js +40 -41
- package/dist/onboarding/wait.js +24 -23
- package/dist/panel/side-panel-types.js +1 -0
- package/dist/record/demo-data.js +459 -486
- package/dist/record/fields.js +129 -131
- package/dist/record/format.js +70 -82
- package/dist/record/gutter.js +1 -0
- package/dist/record/kinds.js +95 -105
- package/dist/record/logic.js +238 -219
- package/dist/record/types.js +49 -48
- package/dist/shadcn/accordion/index.js +3 -1
- package/dist/shadcn/alert/index.js +3 -1
- package/dist/shadcn/alert-dialog/index.js +3 -1
- package/dist/shadcn/attachment/index.js +3 -1
- package/dist/shadcn/avatar/index.js +3 -1
- package/dist/shadcn/breadcrumb/index.js +3 -1
- package/dist/shadcn/bubble/index.js +3 -1
- package/dist/shadcn/button/index.js +3 -1
- package/dist/shadcn/calendar/index.js +3 -1
- package/dist/shadcn/card/index.js +3 -1
- package/dist/shadcn/checkbox/index.js +3 -1
- package/dist/shadcn/code-block/index.js +3 -1
- package/dist/shadcn/collapsible/index.js +3 -1
- package/dist/shadcn/command/index.js +4 -1
- package/dist/shadcn/context-menu/index.js +3 -1
- package/dist/shadcn/dialog/index.js +3 -1
- package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
- package/dist/shadcn/empty/index.js +3 -1
- package/dist/shadcn/field/index.js +3 -1
- package/dist/shadcn/hooks/highlighter.js +77 -75
- package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
- package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
- package/dist/shadcn/input/index.js +3 -1
- package/dist/shadcn/kbd/index.js +3 -1
- package/dist/shadcn/label/index.js +3 -1
- package/dist/shadcn/marker/index.js +3 -1
- package/dist/shadcn/message/index.js +3 -1
- package/dist/shadcn/message-scroller/index.js +3 -1
- package/dist/shadcn/popover/index.js +3 -1
- package/dist/shadcn/progress/index.js +3 -1
- package/dist/shadcn/progress/tone.js +8 -7
- package/dist/shadcn/radio-group/index.js +3 -1
- package/dist/shadcn/scroll-area/index.js +3 -1
- package/dist/shadcn/select/index.js +3 -1
- package/dist/shadcn/separator/index.js +3 -1
- package/dist/shadcn/sheet/index.js +3 -1
- package/dist/shadcn/sidebar/context.svelte.js +42 -28
- package/dist/shadcn/sidebar/index.js +3 -1
- package/dist/shadcn/skeleton/index.js +3 -1
- package/dist/shadcn/slider/index.js +3 -1
- package/dist/shadcn/switch/index.js +3 -1
- package/dist/shadcn/table/index.js +3 -1
- package/dist/shadcn/tabs/index.js +3 -1
- package/dist/shadcn/textarea/index.js +3 -1
- package/dist/shadcn/toggle/index.js +3 -1
- package/dist/shadcn/toggle-group/index.js +3 -1
- package/dist/shadcn/tooltip/context.js +7 -2
- package/dist/shadcn/tooltip/index.js +3 -1
- package/dist/shadcn/trace/index.js +3 -1
- package/dist/slides/bridges.js +65 -59
- package/dist/slides/deck-controller.svelte.js +210 -207
- package/dist/slides/export/capture.js +66 -71
- package/dist/slides/export/ffmpeg.js +76 -72
- package/dist/slides/export/index.js +3 -0
- package/dist/slides/export/launch.js +9 -6
- package/dist/slides/export/pdf.js +60 -54
- package/dist/slides/export/types.js +1 -0
- package/dist/slides/labels.js +24 -24
- package/dist/slides/layout.js +39 -41
- package/dist/slides/model.js +9 -7
- package/dist/slides/navigation.js +78 -73
- package/dist/slides/types.js +2 -2
- package/dist/status/define-statuses.js +18 -14
- package/dist/status/status-types.js +1 -0
- package/dist/test-setup.js +24 -20
- package/dist/thread/logic.js +73 -77
- package/dist/thread/types.js +21 -21
- package/dist/work/board-order.js +94 -90
- package/dist/work/dates.js +16 -9
- package/dist/work/detail/detail-types.js +18 -18
- package/dist/work/docs/edited.js +32 -29
- package/dist/work/docs/index.js +4 -0
- package/dist/work/docs/labels.js +34 -34
- package/dist/work/docs/menu.js +113 -106
- package/dist/work/docs/outline.js +39 -29
- package/dist/work/gantt/logic.js +211 -235
- package/dist/work/inbox/inbox-logic.js +71 -75
- package/dist/work/inbox/inbox-statuses.js +75 -72
- package/dist/work/inbox/inbox-types.js +1 -0
- package/dist/work/kanban-drag.js +27 -22
- package/dist/work/kanban-types.js +1 -0
- package/dist/work/presets/sales.js +36 -32
- package/dist/work/reorder.js +9 -8
- package/dist/work/task-types.js +1 -0
- package/dist/work/time/duration.js +51 -46
- package/dist/work/time/time-types.js +23 -23
- package/dist/work/use-kanban-dnd.svelte.js +126 -125
- package/package.json +3 -1
- package/src/components/list-page/master-detail.svelte +3 -1
- package/src/components/list-page/rail-row.svelte +4 -4
- package/src/editor/markdown.ts +30 -35
|
@@ -1,268 +1,272 @@
|
|
|
1
|
+
// WHY our own slugify: the website's uses NFKD, which strips accents from French
|
|
2
|
+
// headings. The vibe-kit utils one doesn't, so accented anchors would differ.
|
|
1
3
|
function slugify(input, maxLength = 60) {
|
|
2
|
-
|
|
4
|
+
return input.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, maxLength).replace(/-$/g, "");
|
|
3
5
|
}
|
|
4
6
|
function newContext() {
|
|
5
|
-
|
|
6
|
-
headingSlugs: new Map()
|
|
7
|
-
};
|
|
7
|
+
return { headingSlugs: new Map() };
|
|
8
8
|
}
|
|
9
9
|
function uniqueHeadingId(text, ctx) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const base = slugify(text) || "section";
|
|
11
|
+
const seen = ctx.headingSlugs.get(base) ?? 0;
|
|
12
|
+
ctx.headingSlugs.set(base, seen + 1);
|
|
13
|
+
return seen === 0 ? base : `${base}-${seen + 1}`;
|
|
14
14
|
}
|
|
15
15
|
export function renderTiptapContent(content) {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (!content || typeof content !== "object") return "";
|
|
17
|
+
return renderNode(content, newContext());
|
|
18
18
|
}
|
|
19
19
|
export function extractHeadings(content, options = {}) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const headings = [];
|
|
27
|
-
walkHeadings(content, ctx, levels, headings);
|
|
28
|
-
return headings;
|
|
20
|
+
if (!content || typeof content !== "object") return [];
|
|
21
|
+
const levels = options.levels ?? [2, 3];
|
|
22
|
+
const ctx = newContext();
|
|
23
|
+
const headings = [];
|
|
24
|
+
walkHeadings(content, ctx, levels, headings);
|
|
25
|
+
return headings;
|
|
29
26
|
}
|
|
30
27
|
function walkHeadings(node, ctx, levels, out) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
28
|
+
if (node.type === "heading") {
|
|
29
|
+
const level = clampHeadingLevel(node.attrs?.level);
|
|
30
|
+
const text = extractText(node.content).trim();
|
|
31
|
+
const id = uniqueHeadingId(text, ctx);
|
|
32
|
+
if (text && levels.includes(level)) out.push({
|
|
33
|
+
id,
|
|
34
|
+
level,
|
|
35
|
+
text
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (node.content?.length) {
|
|
40
|
+
for (const child of node.content) walkHeadings(child, ctx, levels, out);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// WHY: a consumer like the website's VideoObject schema gets one entry per
|
|
44
|
+
// `youtube` node without redoing the walk or the videoId check.
|
|
46
45
|
export function extractYoutubeVideos(content) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
if (!content || typeof content !== "object") return [];
|
|
47
|
+
const videos = [];
|
|
48
|
+
walkYoutubeVideos(content, videos);
|
|
49
|
+
return videos;
|
|
51
50
|
}
|
|
52
51
|
function walkYoutubeVideos(node, out) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
if (node.type === "youtube") {
|
|
53
|
+
const video = youtubeVideoOf(node);
|
|
54
|
+
if (video) out.push(video);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const child of node.content ?? []) walkYoutubeVideos(child, out);
|
|
59
58
|
}
|
|
60
59
|
function youtubeVideoOf(node) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
60
|
+
const videoId = strAttr(node.attrs, "videoId") ?? "";
|
|
61
|
+
if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return null;
|
|
62
|
+
return {
|
|
63
|
+
videoId,
|
|
64
|
+
title: strAttr(node.attrs, "title") ?? "",
|
|
65
|
+
start: numAttr(node.attrs, "start") ?? 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// A typed read of one attr: the value when it has the asked type, else undefined.
|
|
69
69
|
function strAttr(attrs, key) {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const value = attrs?.[key];
|
|
71
|
+
return typeof value === "string" ? value : undefined;
|
|
72
72
|
}
|
|
73
73
|
function numAttr(attrs, key) {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const value = attrs?.[key];
|
|
75
|
+
return typeof value === "number" ? value : undefined;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
// Wraps a node's rendered children in one tag.
|
|
78
|
+
const wrapIn = (tag) => (node, ctx) => `<${tag}>${renderChildren(node.content, ctx) || ""}</${tag}>`;
|
|
78
79
|
function renderHeading(node, ctx) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
const level = clampHeadingLevel(node.attrs?.level);
|
|
81
|
+
const text = extractText(node.content).trim();
|
|
82
|
+
const inner = renderChildren(node.content, ctx) || "";
|
|
83
|
+
if (!text) return `<h${level}>${inner}</h${level}>`;
|
|
84
|
+
const id = uniqueHeadingId(text, ctx);
|
|
85
|
+
return `<h${level} id="${escapeAttr(id)}">${inner}</h${level}>`;
|
|
85
86
|
}
|
|
86
87
|
function renderOrderedList(node, ctx) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
91
|
-
return `<ol${attrs}>${renderChildren(node.content, ctx) || ""}</ol>`;
|
|
88
|
+
const start = numAttr(node.attrs, "start");
|
|
89
|
+
const attrs = renderAttributes({ start: start === undefined ? undefined : String(start) });
|
|
90
|
+
return `<ol${attrs}>${renderChildren(node.content, ctx) || ""}</ol>`;
|
|
92
91
|
}
|
|
93
92
|
function renderCodeBlock(node) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const lang = strAttr(node.attrs, "language");
|
|
94
|
+
const language = lang ? ` class="language-${escapeAttr(lang)}"` : "";
|
|
95
|
+
return `<pre><code${language}>${escapeHtml(extractText(node.content))}</code></pre>`;
|
|
97
96
|
}
|
|
98
97
|
function numString(attrs, key) {
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const value = numAttr(attrs, key);
|
|
99
|
+
return value === undefined ? undefined : String(value);
|
|
101
100
|
}
|
|
102
101
|
function renderImage(node) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
const src = strAttr(node.attrs, "src");
|
|
103
|
+
if (!src) return "";
|
|
104
|
+
const attrs = renderAttributes({
|
|
105
|
+
alt: strAttr(node.attrs, "alt") ?? "",
|
|
106
|
+
title: strAttr(node.attrs, "title"),
|
|
107
|
+
width: numString(node.attrs, "width"),
|
|
108
|
+
height: numString(node.attrs, "height"),
|
|
109
|
+
"data-align": strAttr(node.attrs, "align")
|
|
110
|
+
});
|
|
111
|
+
return `<img src="${escapeAttr(src)}"${attrs} />`;
|
|
113
112
|
}
|
|
114
113
|
function renderTable(node, ctx) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
const winnerColIndex = findWinnerColumnIndex(node);
|
|
115
|
+
const rows = node.content ?? [];
|
|
116
|
+
const body = rows.map((row, rowIdx) => renderTableRow(row, ctx, {
|
|
117
|
+
winnerColIndex,
|
|
118
|
+
isFirstRow: rowIdx === 0
|
|
119
|
+
})).join("");
|
|
120
|
+
const tableClass = winnerColIndex !== -1 ? " class=\"comparison-table\"" : "";
|
|
121
|
+
return `<div class="tiptap-table-wrap"><table${tableClass}>${body}</table></div>`;
|
|
123
122
|
}
|
|
124
123
|
const RENDERERS = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
124
|
+
doc: (node, ctx) => renderChildren(node.content, ctx),
|
|
125
|
+
paragraph: wrapIn("p"),
|
|
126
|
+
heading: renderHeading,
|
|
127
|
+
blockquote: wrapIn("blockquote"),
|
|
128
|
+
bulletList: wrapIn("ul"),
|
|
129
|
+
orderedList: renderOrderedList,
|
|
130
|
+
listItem: wrapIn("li"),
|
|
131
|
+
codeBlock: renderCodeBlock,
|
|
132
|
+
horizontalRule: () => "<hr />",
|
|
133
|
+
hardBreak: () => "<br />",
|
|
134
|
+
image: renderImage,
|
|
135
|
+
cmsBlock: () => "",
|
|
136
|
+
youtube: renderYoutube,
|
|
137
|
+
table: renderTable,
|
|
138
|
+
tableRow: (node, ctx) => renderTableRow(node, ctx, {
|
|
139
|
+
winnerColIndex: -1,
|
|
140
|
+
isFirstRow: false
|
|
141
|
+
}),
|
|
142
|
+
tableCell: (node, ctx) => renderTableCell(node, ctx, false),
|
|
143
|
+
tableHeader: (node, ctx) => renderTableCell(node, ctx, false),
|
|
144
|
+
text: renderText
|
|
146
145
|
};
|
|
146
|
+
// Own keys only, so a node typed "toString" never reaches Object.prototype.
|
|
147
147
|
function pick(table, key) {
|
|
148
|
-
|
|
148
|
+
return key !== undefined && Object.hasOwn(table, key) ? table[key] : undefined;
|
|
149
149
|
}
|
|
150
150
|
function renderNode(node, ctx) {
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
const render = pick(RENDERERS, node.type);
|
|
152
|
+
return render ? render(node, ctx) : renderChildren(node.content, ctx);
|
|
153
153
|
}
|
|
154
154
|
function renderChildren(content, ctx) {
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
if (!content?.length) return "";
|
|
156
|
+
return content.map((child) => renderNode(child, ctx)).join("");
|
|
157
157
|
}
|
|
158
|
+
// WHY a facade, never a bare <iframe> on first paint: it's roughly 700KB and wrecks
|
|
159
|
+
// LCP. renderer.svelte's delegated click listener swaps in the iframe.
|
|
158
160
|
const YOUTUBE_VIDEO_ID_RE = /^[A-Za-z0-9_-]{11}$/;
|
|
159
161
|
function renderYoutube(node) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
const videoId = typeof node.attrs?.videoId === "string" ? node.attrs.videoId : "";
|
|
163
|
+
if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return "";
|
|
164
|
+
const title = typeof node.attrs?.title === "string" ? node.attrs.title : "";
|
|
165
|
+
const start = typeof node.attrs?.start === "number" ? node.attrs.start : 0;
|
|
166
|
+
const safeVideoId = escapeAttr(videoId);
|
|
167
|
+
const safeTitle = escapeAttr(title);
|
|
168
|
+
return `<div class="tiptap-youtube" data-video-id="${safeVideoId}" data-start="${start}">` + `<button type="button" class="tiptap-youtube-trigger" aria-label="Play video: ${safeTitle}">` + `<img class="tiptap-youtube-poster" src="https://i.ytimg.com/vi/${safeVideoId}/hqdefault.jpg" alt="" loading="lazy" width="480" height="360" />` + `<span class="tiptap-youtube-play" aria-hidden="true"></span>` + `<span class="tiptap-youtube-title">${escapeHtml(title)}</span>` + `</button></div>`;
|
|
167
169
|
}
|
|
168
170
|
function renderTableRow(row, ctx, { winnerColIndex, isFirstRow }) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
if (row.type !== "tableRow" || !row.content?.length) {
|
|
172
|
+
return `<tr>${renderChildren(row.content, ctx)}</tr>`;
|
|
173
|
+
}
|
|
174
|
+
const cellsHtml = row.content.map((cell, cellIdx) => {
|
|
175
|
+
const isWinnerCell = !isFirstRow && winnerColIndex !== -1 && cellIdx === winnerColIndex;
|
|
176
|
+
return renderTableCell(cell, ctx, isWinnerCell);
|
|
177
|
+
}).join("");
|
|
178
|
+
return `<tr>${cellsHtml}</tr>`;
|
|
177
179
|
}
|
|
178
180
|
function renderTableCell(cell, ctx, isWinnerCell) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
181
|
+
const tag = cell.type === "tableHeader" ? "th" : "td";
|
|
182
|
+
const colspan = typeof cell.attrs?.colspan === "number" && cell.attrs.colspan !== 1 ? String(cell.attrs.colspan) : undefined;
|
|
183
|
+
const rowspan = typeof cell.attrs?.rowspan === "number" && cell.attrs.rowspan !== 1 ? String(cell.attrs.rowspan) : undefined;
|
|
184
|
+
const inner = renderChildren(cell.content, ctx);
|
|
185
|
+
if (!isWinnerCell) {
|
|
186
|
+
const attrs = renderAttributes({
|
|
187
|
+
colspan,
|
|
188
|
+
rowspan
|
|
189
|
+
});
|
|
190
|
+
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
191
|
+
}
|
|
192
|
+
const winnerText = extractText(cell.content).trim();
|
|
193
|
+
const winner = normalizeWinner(winnerText);
|
|
194
|
+
const attrs = renderAttributes({
|
|
195
|
+
class: "winner-cell",
|
|
196
|
+
"data-winner": winner,
|
|
197
|
+
colspan,
|
|
198
|
+
rowspan
|
|
199
|
+
});
|
|
200
|
+
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
199
201
|
}
|
|
200
202
|
const WINNER_HEADERS = [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
"winner",
|
|
204
|
+
"gagnant",
|
|
205
|
+
"vainqueur"
|
|
204
206
|
];
|
|
205
207
|
function findWinnerColumnIndex(table) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
208
|
+
const firstRow = table.content?.[0];
|
|
209
|
+
if (!firstRow?.content) return -1;
|
|
210
|
+
for (let i = 0; i < firstRow.content.length; i++) {
|
|
211
|
+
const headerText = extractText(firstRow.content[i]?.content).trim().toLowerCase();
|
|
212
|
+
if (WINNER_HEADERS.includes(headerText)) return i;
|
|
213
|
+
}
|
|
214
|
+
return -1;
|
|
215
|
+
}
|
|
216
|
+
// WHY the first word: "Acme wins" and "Acme" are one winner. The host styles a
|
|
217
|
+
// product with `[data-winner="acme"]`, so the serializer names no product.
|
|
214
218
|
function normalizeWinner(text) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
const lower = text.toLowerCase().trim();
|
|
220
|
+
if (!lower) return "other";
|
|
221
|
+
if (lower.startsWith("tie") || lower.startsWith("égal") || lower.startsWith("egal")) {
|
|
222
|
+
return "tie";
|
|
223
|
+
}
|
|
224
|
+
return lower.match(/^[\p{L}\p{N}]+/u)?.[0] ?? "other";
|
|
221
225
|
}
|
|
222
226
|
function renderText(node) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
const text = escapeHtml(node.text ?? "");
|
|
228
|
+
if (!node.marks?.length) return text;
|
|
229
|
+
return node.marks.reduce((html, mark) => wrapMark(html, mark), text);
|
|
226
230
|
}
|
|
227
231
|
const MARK_TAGS = {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
bold: "strong",
|
|
233
|
+
italic: "em",
|
|
234
|
+
strike: "s",
|
|
235
|
+
underline: "u",
|
|
236
|
+
code: "code"
|
|
233
237
|
};
|
|
234
238
|
function wrapMark(content, mark) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
if (mark.type === "link") {
|
|
240
|
+
const attrs = renderAttributes({
|
|
241
|
+
href: strAttr(mark.attrs, "href") ?? "",
|
|
242
|
+
target: strAttr(mark.attrs, "target"),
|
|
243
|
+
rel: strAttr(mark.attrs, "rel")
|
|
244
|
+
});
|
|
245
|
+
return `<a${attrs}>${content}</a>`;
|
|
246
|
+
}
|
|
247
|
+
const tag = pick(MARK_TAGS, mark.type);
|
|
248
|
+
return tag ? `<${tag}>${content}</${tag}>` : content;
|
|
245
249
|
}
|
|
246
250
|
function renderAttributes(attrs) {
|
|
247
|
-
|
|
248
|
-
|
|
251
|
+
const parts = Object.entries(attrs).filter(([, value]) => value !== undefined && value !== "").map(([key, value]) => ` ${key}="${escapeAttr(value ?? "")}"`);
|
|
252
|
+
return parts.join("");
|
|
249
253
|
}
|
|
250
254
|
function extractText(content) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
if (!content?.length) return "";
|
|
256
|
+
return content.map((node) => {
|
|
257
|
+
if (node.type === "text") return node.text ?? "";
|
|
258
|
+
return extractText(node.content);
|
|
259
|
+
}).join("");
|
|
256
260
|
}
|
|
257
261
|
function clampHeadingLevel(level) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
if (level === 1 || level === 2 || level === 3 || level === 4 || level === 5 || level === 6) {
|
|
263
|
+
return level;
|
|
264
|
+
}
|
|
265
|
+
return 2;
|
|
262
266
|
}
|
|
263
267
|
function escapeHtml(value) {
|
|
264
|
-
|
|
268
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
265
269
|
}
|
|
266
270
|
function escapeAttr(value) {
|
|
267
|
-
|
|
271
|
+
return escapeHtml(value).replaceAll("`", "`");
|
|
268
272
|
}
|