@heyramzi/ui 2.11.0 → 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/components/tags-input/tags-input.svelte +2 -0
- package/src/editor/markdown.ts +30 -35
|
@@ -1,100 +1,116 @@
|
|
|
1
|
+
/** Default pull, in flow units. Callers divide by zoom to hold it on screen. */
|
|
1
2
|
export const SNAP_THRESHOLD = 6;
|
|
2
3
|
const SIZE_KEY = {
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
x: "width",
|
|
5
|
+
y: "height"
|
|
5
6
|
};
|
|
6
7
|
const CROSS = {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
x: "y",
|
|
9
|
+
y: "x"
|
|
9
10
|
};
|
|
11
|
+
/** The three lines a rect offers on one axis: its two edges and its centre. */
|
|
10
12
|
function linesOf(rect, axis) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const start = rect[axis];
|
|
14
|
+
const size = rect[SIZE_KEY[axis]];
|
|
15
|
+
return [
|
|
16
|
+
start,
|
|
17
|
+
start + size / 2,
|
|
18
|
+
start + size
|
|
19
|
+
];
|
|
18
20
|
}
|
|
19
21
|
function snapAxis(dragged, others, axis, threshold) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const mine = linesOf(dragged, axis);
|
|
23
|
+
// Flat rather than nested: every pairing of one of our lines with one of
|
|
24
|
+
// theirs, then the nearest pairing inside the threshold.
|
|
25
|
+
const pairings = others.flatMap((other) => linesOf(other, axis).flatMap((theirs) => mine.map((own) => ({
|
|
26
|
+
delta: theirs - own,
|
|
27
|
+
position: theirs
|
|
28
|
+
}))));
|
|
29
|
+
const inRange = pairings.filter((pair) => Math.abs(pair.delta) <= threshold);
|
|
30
|
+
if (inRange.length === 0) return {
|
|
31
|
+
delta: 0,
|
|
32
|
+
position: null
|
|
33
|
+
};
|
|
34
|
+
return inRange.reduce((a, b) => Math.abs(b.delta) < Math.abs(a.delta) ? b : a);
|
|
31
35
|
}
|
|
36
|
+
/** Every other rect that sits on `position`, so the guide spans all of them. */
|
|
32
37
|
function guideFor(snapped, others, axis, position) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const cross = CROSS[axis];
|
|
39
|
+
const size = SIZE_KEY[cross];
|
|
40
|
+
const onLine = others.filter((other) => linesOf(other, axis).some((line) => Math.abs(line - position) < .5));
|
|
41
|
+
return {
|
|
42
|
+
axis,
|
|
43
|
+
position,
|
|
44
|
+
start: Math.min(snapped[cross], ...onLine.map((o) => o[cross])),
|
|
45
|
+
end: Math.max(snapped[cross] + snapped[size], ...onLine.map((o) => o[cross] + o[size]))
|
|
46
|
+
};
|
|
42
47
|
}
|
|
48
|
+
/** Pull `dragged` onto the nearest line of `others`, one axis at a time. */
|
|
43
49
|
export function computeSnap(dragged, others, threshold = SNAP_THRESHOLD) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
if (others.length === 0) return {
|
|
51
|
+
x: dragged.x,
|
|
52
|
+
y: dragged.y,
|
|
53
|
+
guides: []
|
|
54
|
+
};
|
|
55
|
+
const horizontal = snapAxis(dragged, others, "x", threshold);
|
|
56
|
+
const vertical = snapAxis(dragged, others, "y", threshold);
|
|
57
|
+
const snapped = {
|
|
58
|
+
...dragged,
|
|
59
|
+
x: dragged.x + horizontal.delta,
|
|
60
|
+
y: dragged.y + vertical.delta
|
|
61
|
+
};
|
|
62
|
+
const guides = [];
|
|
63
|
+
if (horizontal.position !== null) {
|
|
64
|
+
guides.push(guideFor(snapped, others, "x", horizontal.position));
|
|
65
|
+
}
|
|
66
|
+
if (vertical.position !== null) {
|
|
67
|
+
guides.push(guideFor(snapped, others, "y", vertical.position));
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
x: snapped.x,
|
|
71
|
+
y: snapped.y,
|
|
72
|
+
guides
|
|
73
|
+
};
|
|
68
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The editor's seam. Pull a held free node onto the others and report the
|
|
77
|
+
* lines. A node in a container is not a candidate.
|
|
78
|
+
*/
|
|
79
|
+
// The correction goes back into the returned nodes, which is what makes it
|
|
80
|
+
// magnetism and not a hint. xyflow recomputes from the drag origin, so it
|
|
81
|
+
// never stacks.
|
|
69
82
|
export function snapDraggedNode(nodes, held, zoom) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const rect = freeNodeRect(held);
|
|
84
|
+
if (!held || !rect) return {
|
|
85
|
+
nodes,
|
|
86
|
+
guides: []
|
|
87
|
+
};
|
|
88
|
+
const others = nodes.filter((n) => n.id !== held.id).map(freeNodeRect).filter((r) => r !== null);
|
|
89
|
+
// The threshold is in flow units, so the pull is the same few pixels on
|
|
90
|
+
// screen whether the board is zoomed to a wall or to one card.
|
|
91
|
+
const snap = computeSnap(rect, others, SNAP_THRESHOLD / (zoom || 1));
|
|
92
|
+
const moved = snap.x !== held.position.x || snap.y !== held.position.y;
|
|
93
|
+
const next = moved ? nodes.map((n) => n.id === held.id ? {
|
|
94
|
+
...n,
|
|
95
|
+
position: {
|
|
96
|
+
x: snap.x,
|
|
97
|
+
y: snap.y
|
|
98
|
+
}
|
|
99
|
+
} : n) : nodes;
|
|
100
|
+
return {
|
|
101
|
+
nodes: next,
|
|
102
|
+
guides: snap.guides
|
|
103
|
+
};
|
|
89
104
|
}
|
|
105
|
+
/** A candidate rect, or null for a node a container positions or hides. */
|
|
90
106
|
function freeNodeRect(node) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
if (!node || node.parentId || node.hidden) return null;
|
|
108
|
+
const { width, height } = node.measured ?? {};
|
|
109
|
+
if (!width || !height) return null;
|
|
110
|
+
return {
|
|
111
|
+
x: node.position.x,
|
|
112
|
+
y: node.position.y,
|
|
113
|
+
width,
|
|
114
|
+
height
|
|
115
|
+
};
|
|
100
116
|
}
|
|
@@ -1,137 +1,153 @@
|
|
|
1
|
+
// A small drawing on a board. Markup arrives over the API, so it's rebuilt tag by
|
|
2
|
+
// tag from an allowlist. Authoring: see the package README.
|
|
3
|
+
/** Shapes, text and the local defs they reference. No `a`, `use` or `image`:
|
|
4
|
+
* each one can point somewhere off the page. */
|
|
1
5
|
const ALLOWED_TAGS = new Set([
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
"svg",
|
|
7
|
+
"g",
|
|
8
|
+
"defs",
|
|
9
|
+
"title",
|
|
10
|
+
"desc",
|
|
11
|
+
"linearGradient",
|
|
12
|
+
"radialGradient",
|
|
13
|
+
"stop",
|
|
14
|
+
"marker",
|
|
15
|
+
"clipPath",
|
|
16
|
+
"path",
|
|
17
|
+
"rect",
|
|
18
|
+
"circle",
|
|
19
|
+
"ellipse",
|
|
20
|
+
"line",
|
|
21
|
+
"polyline",
|
|
22
|
+
"polygon",
|
|
23
|
+
"text",
|
|
24
|
+
"tspan"
|
|
21
25
|
]);
|
|
26
|
+
/** Geometry and paint only. An allowlist and not a denylist, so `onload` and
|
|
27
|
+
* `href` are out by being absent rather than by being remembered. */
|
|
22
28
|
const ALLOWED_ATTRS = new Set([
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
29
|
+
"viewBox",
|
|
30
|
+
"width",
|
|
31
|
+
"height",
|
|
32
|
+
"x",
|
|
33
|
+
"y",
|
|
34
|
+
"x1",
|
|
35
|
+
"x2",
|
|
36
|
+
"y1",
|
|
37
|
+
"y2",
|
|
38
|
+
"cx",
|
|
39
|
+
"cy",
|
|
40
|
+
"r",
|
|
41
|
+
"rx",
|
|
42
|
+
"ry",
|
|
43
|
+
"d",
|
|
44
|
+
"points",
|
|
45
|
+
"dx",
|
|
46
|
+
"dy",
|
|
47
|
+
"transform",
|
|
48
|
+
"fill",
|
|
49
|
+
"fill-opacity",
|
|
50
|
+
"fill-rule",
|
|
51
|
+
"stroke",
|
|
52
|
+
"stroke-width",
|
|
53
|
+
"stroke-dasharray",
|
|
54
|
+
"stroke-dashoffset",
|
|
55
|
+
"stroke-linecap",
|
|
56
|
+
"stroke-linejoin",
|
|
57
|
+
"stroke-opacity",
|
|
58
|
+
"opacity",
|
|
59
|
+
"font-size",
|
|
60
|
+
"font-weight",
|
|
61
|
+
"font-style",
|
|
62
|
+
"letter-spacing",
|
|
63
|
+
"text-anchor",
|
|
64
|
+
"dominant-baseline",
|
|
65
|
+
"offset",
|
|
66
|
+
"stop-color",
|
|
67
|
+
"stop-opacity",
|
|
68
|
+
"gradientUnits",
|
|
69
|
+
"gradientTransform",
|
|
70
|
+
"clip-path",
|
|
71
|
+
"clipPathUnits",
|
|
72
|
+
"marker-end",
|
|
73
|
+
"marker-start",
|
|
74
|
+
"markerWidth",
|
|
75
|
+
"markerHeight",
|
|
76
|
+
"refX",
|
|
77
|
+
"refY",
|
|
78
|
+
"orient",
|
|
79
|
+
"id",
|
|
80
|
+
"class",
|
|
81
|
+
"preserveAspectRatio"
|
|
76
82
|
]);
|
|
77
|
-
|
|
83
|
+
/** A figure is a diagram, not a pasted illustration: past this it is an asset
|
|
84
|
+
* with a URL, and the board holds an image node instead. */
|
|
85
|
+
export const MAX_FIGURE_CHARS = 16e3;
|
|
78
86
|
const TAG = /<\/?([a-zA-Z][\w:-]*)((?:"[^"]*"|'[^']*'|[^>])*?)\/?>/g;
|
|
79
87
|
const ATTR = /([a-zA-Z][\w:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
88
|
+
/** A reference out of the document. `url(#local)` stays; everything else goes. */
|
|
80
89
|
const FOREIGN_URL = /url\(\s*['"]?(?!#)/i;
|
|
81
90
|
function cleanAttrs(raw) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
const kept = [];
|
|
92
|
+
for (const match of raw.matchAll(ATTR)) {
|
|
93
|
+
const name = match[1];
|
|
94
|
+
const value = match[3] ?? match[4] ?? "";
|
|
95
|
+
if (!ALLOWED_ATTRS.has(name)) continue;
|
|
96
|
+
if (FOREIGN_URL.test(value)) continue;
|
|
97
|
+
kept.push(`${name}="${value.replace(/"/g, """)}"`);
|
|
98
|
+
}
|
|
99
|
+
return kept.length > 0 ? ` ${kept.join(" ")}` : "";
|
|
91
100
|
}
|
|
101
|
+
/** The figure as it's safe to draw, or null without an `<svg>`. Script, style and foreignObject go with contents; unknown tags drop, keeping text. */
|
|
92
102
|
export function sanitizeFigureSvg(raw) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
if (typeof raw !== "string" || raw.length > MAX_FIGURE_CHARS) return null;
|
|
104
|
+
const stripped = raw.replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<\?[\s\S]*?\?>/g, "").replace(/<!DOCTYPE[^>]*>/gi, "").replace(/<(script|style|foreignObject|iframe)\b[\s\S]*?<\/\1\s*>/gi, "");
|
|
105
|
+
if (!/<svg\b/i.test(stripped)) return null;
|
|
106
|
+
let out = "";
|
|
107
|
+
let cursor = 0;
|
|
108
|
+
for (const match of stripped.matchAll(TAG)) {
|
|
109
|
+
const [tag, name, attrs] = match;
|
|
110
|
+
const start = match.index ?? 0;
|
|
111
|
+
out += stripped.slice(cursor, start);
|
|
112
|
+
cursor = start + tag.length;
|
|
113
|
+
if (!ALLOWED_TAGS.has(name)) continue;
|
|
114
|
+
if (tag.startsWith("</")) {
|
|
115
|
+
out += `</${name}>`;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
out += `<${name}${cleanAttrs(attrs)}${tag.endsWith("/>") ? " />" : ">"}`;
|
|
119
|
+
}
|
|
120
|
+
out += stripped.slice(cursor);
|
|
121
|
+
return out.trim();
|
|
112
122
|
}
|
|
113
123
|
function rootAttr(svg, name) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
const root = svg.match(/<svg\b([^>]*)>/i);
|
|
125
|
+
if (!root) return undefined;
|
|
126
|
+
const found = root[1].match(new RegExp(`${name}\\s*=\\s*"([^"]*)"`, "i"));
|
|
127
|
+
return found?.[1];
|
|
118
128
|
}
|
|
129
|
+
/** Height over width, from the viewBox the author drew in. */
|
|
119
130
|
export function figureRatio(svg) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const box = rootAttr(svg, "viewBox");
|
|
132
|
+
if (box) {
|
|
133
|
+
const [, , w, h] = box.trim().split(/[\s,]+/).map(Number);
|
|
134
|
+
if (w > 0 && h > 0) return h / w;
|
|
135
|
+
}
|
|
136
|
+
const w = Number.parseFloat(rootAttr(svg, "width") ?? "");
|
|
137
|
+
const h = Number.parseFloat(rootAttr(svg, "height") ?? "");
|
|
138
|
+
return w > 0 && h > 0 ? h / w : null;
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
/** The box the drawing needs. The fallback is a wide banner, the shape a
|
|
141
|
+
* board figure takes when the author gave no viewBox. */
|
|
142
|
+
export function figureBoxHeight(svg, width, fallback = .4) {
|
|
143
|
+
return Math.round(width * (figureRatio(svg) ?? fallback));
|
|
131
144
|
}
|
|
145
|
+
/** The heading over a figure and the line under it, with their margins. These
|
|
146
|
+
* mirror sketch-figure.svelte and move when its type sizes do. */
|
|
132
147
|
const LABEL_H = 27;
|
|
133
148
|
const CAPTION_H = 24;
|
|
149
|
+
/** The whole node: the drawing, plus whatever text sits around it. */
|
|
134
150
|
export function figureNodeHeight(figure, width) {
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
const art = figure.height ?? figureBoxHeight(figure.svg ?? "", width);
|
|
152
|
+
return art + (figure.label ? LABEL_H : 0) + (figure.caption ? CAPTION_H : 0);
|
|
137
153
|
}
|