@bendyline/squisq 2.3.1 → 2.3.3

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/README.md CHANGED
@@ -18,7 +18,7 @@ npm install @bendyline/squisq
18
18
  | Module | Description |
19
19
  | ------------ | -------------------------------------------------------------------------------- |
20
20
  | **schemas** | Type definitions — `Doc`, `BlockTemplate`, `Viewport`, `Theme`, `LayoutStrategy` |
21
- | **doc** | Template registry, 25 block templates, animation/theme utilities |
21
+ | **doc** | Template registry, 26 block templates, animation/theme utilities |
22
22
  | **markdown** | Markdown parsing, stringifying, AST types (`MarkdownDocument`), tree utilities |
23
23
  | **spatial** | Haversine distance, Geohash encode/decode |
24
24
  | **storage** | `StorageAdapter` interface, Memory + LocalStorage + LocalForage adapters |
@@ -271,6 +271,7 @@ declare function getSafeTextBounds(orientation: ViewportOrientation): {
271
271
  * Template Types:
272
272
  * - title: Doc intro with title + subtitle
273
273
  * - sectionHeader: Colored section divider
274
+ * - content: Heading with the complete Markdown body
274
275
  * - statHighlight: Big number/stat with description
275
276
  * - quote: Large centered quote
276
277
  * - factCard: Key fact with explanation
@@ -394,6 +395,18 @@ interface SectionHeaderInput extends BaseTemplateBlock {
394
395
  /** Ambient motion for background image */
395
396
  ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
396
397
  }
398
+ /**
399
+ * Content block - loss-averse heading and body layout.
400
+ *
401
+ * This is the safe default for agent-authored presentation drafts: it renders
402
+ * the complete plain-text projection of the block body and lets preview
403
+ * diagnostics report overflow before a later visual-optimization pass.
404
+ */
405
+ interface ContentBlockInput extends BaseTemplateBlock {
406
+ template: 'content';
407
+ /** Heading shown above the body. */
408
+ title: string;
409
+ }
397
410
  /**
398
411
  * Stat highlight - big number with description and optional detail.
399
412
  */
@@ -929,7 +942,7 @@ interface DataTableInput extends BaseTemplateBlock {
929
942
  /**
930
943
  * Union of all template block types.
931
944
  */
932
- type TemplateBlock = TitleBlockInput | SectionHeaderInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput | DiagramBlockInput | TreeBlockInput | TimelineBlockInput | DrawingBlockInput | RawLayersInput;
945
+ type TemplateBlock = TitleBlockInput | SectionHeaderInput | ContentBlockInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput | DiagramBlockInput | TreeBlockInput | TimelineBlockInput | DrawingBlockInput | RawLayersInput;
933
946
  /**
934
947
  * A block can be either a raw Block or a TemplateBlock.
935
948
  */
@@ -2544,4 +2557,4 @@ declare function getBlockAtTime(blocks: Block[], time: number): Block | null;
2544
2557
  */
2545
2558
  declare function getCaptionAtTime(captions: CaptionTrack | undefined, time: number): CaptionPhrase | null;
2546
2559
 
2547
- export { type MapBlockInput as $, type AccentImage as A, type Block as B, type CaptionPhrase as C, DARK_SURFACE as D, type DiagramTemplateNode as E, type Doc as F, type DocBlock as G, type DocDiagnostic as H, type DrawingBlockInput as I, FRONTMATTER_CUSTOM_TEMPLATES_KEY as J, FRONTMATTER_CUSTOM_THEMES_KEY as K, type FactCardInput as L, type FontFamily as M, type FontFamilyKind as N, type FullBleedQuoteInput as O, type GradientBackgroundConfig as P, type ImageBackgroundConfig as Q, type ImageLayer as R, type ImageTreatment as S, type ImageWithCaptionInput as T, LIGHT_SURFACE as U, type Layer as V, type LayerRepeat as W, type LayoutHints as X, type LeftFeatureInput as Y, type LinearGradient as Z, type ListBlockInput as _, type AccentPosition as a, type TextLayer as a$, type MapLayer as a0, type MapMarker as a1, type MapTileStyle as a2, type MarkerStyle as a3, type MediaClip as a4, type MermaidLayer as a5, PAGE_ACCENT_STRATEGIES as a6, PAGE_BACKGROUNDS as a7, PAGE_BACKGROUND_RHYTHMS as a8, PAGE_DESIGN_FAMILIES as a9, type PersistentLayerTemplate as aA, type PersistentLayerTemplateConfig as aB, type PersistentLayerTemplateType as aC, type PhotoGridInput as aD, type Position as aE, type ProgressIndicatorConfig as aF, type PullQuoteInput as aG, type QuoteBlockInput as aH, type RawLayersInput as aI, type RenderStyle as aJ, type RightFeatureInput as aK, type ScheduledClip as aL, type SectionHeaderInput as aM, type ShapeFilter as aN, type ShapeLayer as aO, type ShapePattern as aP, type SolidBackgroundConfig as aQ, type StartBlockConfig as aR, type StatHighlightInput as aS, type SurfaceScheme as aT, THEME_SCHEMA_VERSION as aU, type TableLayer as aV, type TableLayerStyle as aW, type TemplateBlock as aX, type TemplateContext as aY, type TemplateFunction as aZ, type TemplateRegistry as a_, PAGE_DIVIDERS as aa, PAGE_EMPHASES as ab, PAGE_EYEBROWS as ac, PAGE_HEADING_CASES as ad, PAGE_HEADING_SCALES as ae, PAGE_HEADING_UNDERLINES as af, PAGE_HERO_STYLES as ag, PAGE_IMAGE_FRAMINGS as ah, PAGE_NUMERAL_STYLES as ai, PAGE_PATTERNS as aj, PAGE_QUOTE_MARKS as ak, PAGE_SECTION_KINDS as al, PAGE_SECTION_SPACINGS as am, PAGE_SHADOWS as an, type PageAccentRotation as ao, type PageBackground as ap, type PageDesignFamily as aq, type PageEmphasis as ar, type PageHeadingTreatment as as, type PageSectionKind as at, type PageSectionOverride as au, type PageTokens as av, type PathLayer as aw, type PatternBackgroundConfig as ax, type PersistentLayer as ay, type PersistentLayerConfig as az, type AmbientGradientConfig as b, type TextStyle as b0, type Theme as b1, type ThemeColorPalette as b2, type ThemeColorScheme as b3, type ThemePageStyle as b4, type ThemeRegistry as b5, type ThemeSchemaVersion as b6, type ThemeSeedColors as b7, type ThemeStyle as b8, type ThemeTypography as b9, getAspectRatioString as bA, getBlockAtTime as bB, getCaptionAtTime as bC, getDocPlaybackDuration as bD, getLayoutHints as bE, getSafeTextBounds as bF, getSegmentAtTime as bG, getTwoColumnPositions as bH, getViewport as bI, getViewportOrientation as bJ, isPersistentLayerTemplate as bK, isTemplateBlock as bL, scaledFontSize$1 as bM, resolveMediaSchedule as bN, scaledFontSize as bO, type TimelineBlockInput as ba, type TimelineTemplateEvent as bb, type TimelineTemplateLink as bc, type TimelineTemplateTrack as bd, type TitleBlockInput as be, type TitleCaptionConfig as bf, type TreeBlockInput as bg, type TreeLayer as bh, type TreeLayerItem as bi, type TreeLayerStyle as bj, type TreeTemplateItem as bk, type TwoColumnInput as bl, VIEWPORT_PRESETS as bm, type VideoLayer as bn, type VideoPullQuoteInput as bo, type VideoWithCaptionInput as bp, type ViewportConfig as bq, type ViewportOrientation as br, type ViewportPreset as bs, type VignetteConfig as bt, applySurface as bu, calculateDuration as bv, calculateFontScale as bw, createTemplateContext as bx, createTheme as by, createThemeRegistry as bz, type Animation as c, type AnimationType as d, type AudioBookmark as e, type AudioSegment as f, type AudioTimingData as g, type AudioTrack as h, type BlockConnection as i, type BorderStyle as j, type CaptionTrack as k, type CaptionWord as l, type ColorScheme as m, type ComparisonBarInput as n, type CornerBrandingConfig as o, type CustomTemplateDefinition as p, type CustomTemplateLayer as q, DEFAULT_DOC_FONT as r, DEFAULT_TITLE_FONT as s, type DataTableInput as t, type DateEventInput as u, type DeepPartial as v, type DefinitionCardInput as w, type DiagramBlockInput as x, type DiagramEdgeAnchor as y, type DiagramTemplateEdge as z };
2560
+ export { type ListBlockInput as $, type AccentImage as A, type Block as B, type CaptionPhrase as C, DARK_SURFACE as D, type DiagramTemplateEdge as E, type DiagramTemplateNode as F, type Doc as G, type DocBlock as H, type DocDiagnostic as I, type DrawingBlockInput as J, FRONTMATTER_CUSTOM_TEMPLATES_KEY as K, FRONTMATTER_CUSTOM_THEMES_KEY as L, type FactCardInput as M, type FontFamily as N, type FontFamilyKind as O, type FullBleedQuoteInput as P, type GradientBackgroundConfig as Q, type ImageBackgroundConfig as R, type ImageLayer as S, type ImageTreatment as T, type ImageWithCaptionInput as U, LIGHT_SURFACE as V, type Layer as W, type LayerRepeat as X, type LayoutHints as Y, type LeftFeatureInput as Z, type LinearGradient as _, type AccentPosition as a, type TemplateRegistry as a$, type MapBlockInput as a0, type MapLayer as a1, type MapMarker as a2, type MapTileStyle as a3, type MarkerStyle as a4, type MediaClip as a5, type MermaidLayer as a6, PAGE_ACCENT_STRATEGIES as a7, PAGE_BACKGROUNDS as a8, PAGE_BACKGROUND_RHYTHMS as a9, type PersistentLayerConfig as aA, type PersistentLayerTemplate as aB, type PersistentLayerTemplateConfig as aC, type PersistentLayerTemplateType as aD, type PhotoGridInput as aE, type Position as aF, type ProgressIndicatorConfig as aG, type PullQuoteInput as aH, type QuoteBlockInput as aI, type RawLayersInput as aJ, type RenderStyle as aK, type RightFeatureInput as aL, type ScheduledClip as aM, type SectionHeaderInput as aN, type ShapeFilter as aO, type ShapeLayer as aP, type ShapePattern as aQ, type SolidBackgroundConfig as aR, type StartBlockConfig as aS, type StatHighlightInput as aT, type SurfaceScheme as aU, THEME_SCHEMA_VERSION as aV, type TableLayer as aW, type TableLayerStyle as aX, type TemplateBlock as aY, type TemplateContext as aZ, type TemplateFunction as a_, PAGE_DESIGN_FAMILIES as aa, PAGE_DIVIDERS as ab, PAGE_EMPHASES as ac, PAGE_EYEBROWS as ad, PAGE_HEADING_CASES as ae, PAGE_HEADING_SCALES as af, PAGE_HEADING_UNDERLINES as ag, PAGE_HERO_STYLES as ah, PAGE_IMAGE_FRAMINGS as ai, PAGE_NUMERAL_STYLES as aj, PAGE_PATTERNS as ak, PAGE_QUOTE_MARKS as al, PAGE_SECTION_KINDS as am, PAGE_SECTION_SPACINGS as an, PAGE_SHADOWS as ao, type PageAccentRotation as ap, type PageBackground as aq, type PageDesignFamily as ar, type PageEmphasis as as, type PageHeadingTreatment as at, type PageSectionKind as au, type PageSectionOverride as av, type PageTokens as aw, type PathLayer as ax, type PatternBackgroundConfig as ay, type PersistentLayer as az, type AmbientGradientConfig as b, type TextLayer as b0, type TextStyle as b1, type Theme as b2, type ThemeColorPalette as b3, type ThemeColorScheme as b4, type ThemePageStyle as b5, type ThemeRegistry as b6, type ThemeSchemaVersion as b7, type ThemeSeedColors as b8, type ThemeStyle as b9, createThemeRegistry as bA, getAspectRatioString as bB, getBlockAtTime as bC, getCaptionAtTime as bD, getDocPlaybackDuration as bE, getLayoutHints as bF, getSafeTextBounds as bG, getSegmentAtTime as bH, getTwoColumnPositions as bI, getViewport as bJ, getViewportOrientation as bK, isPersistentLayerTemplate as bL, isTemplateBlock as bM, scaledFontSize$1 as bN, resolveMediaSchedule as bO, scaledFontSize as bP, type ThemeTypography as ba, type TimelineBlockInput as bb, type TimelineTemplateEvent as bc, type TimelineTemplateLink as bd, type TimelineTemplateTrack as be, type TitleBlockInput as bf, type TitleCaptionConfig as bg, type TreeBlockInput as bh, type TreeLayer as bi, type TreeLayerItem as bj, type TreeLayerStyle as bk, type TreeTemplateItem as bl, type TwoColumnInput as bm, VIEWPORT_PRESETS as bn, type VideoLayer as bo, type VideoPullQuoteInput as bp, type VideoWithCaptionInput as bq, type ViewportConfig as br, type ViewportOrientation as bs, type ViewportPreset as bt, type VignetteConfig as bu, applySurface as bv, calculateDuration as bw, calculateFontScale as bx, createTemplateContext as by, createTheme as bz, type Animation as c, type AnimationType as d, type AudioBookmark as e, type AudioSegment as f, type AudioTimingData as g, type AudioTrack as h, type BlockConnection as i, type BorderStyle as j, type CaptionTrack as k, type CaptionWord as l, type ColorScheme as m, type ComparisonBarInput as n, type ContentBlockInput as o, type CornerBrandingConfig as p, type CustomTemplateDefinition as q, type CustomTemplateLayer as r, DEFAULT_DOC_FONT as s, DEFAULT_TITLE_FONT as t, type DataTableInput as u, type DateEventInput as v, type DeepPartial as w, type DefinitionCardInput as x, type DiagramBlockInput as y, type DiagramEdgeAnchor as z };
@@ -1,4 +1,4 @@
1
- import { R as ImageLayer, a$ as TextLayer, aO as ShapeLayer, aw as PathLayer } from './Doc-COe-rPQB.js';
1
+ import { S as ImageLayer, b0 as TextLayer, aP as ShapeLayer, ax as PathLayer } from './Doc-BrgZC7SE.js';
2
2
 
3
3
  /**
4
4
  * ImageEditDoc — JSON document describing a layered, editable image.
@@ -5,6 +5,249 @@ import {
5
5
  normalizeTransitionType
6
6
  } from "./chunk-4VOD55SX.js";
7
7
 
8
+ // src/markdown/sanitize.ts
9
+ var SAFE_LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
10
+ var SAFE_MEDIA_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "blob"]);
11
+ var NEVER_ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["javascript", "vbscript", "data"]);
12
+ var SAFE_DATA_MEDIA_RE = /^data:(?:image\/(?!svg\+xml)[a-z0-9.+-]+|audio\/[a-z0-9.+-]+|video\/[a-z0-9.+-]+);/i;
13
+ var SAFE_TAGS = /* @__PURE__ */ new Set([
14
+ "a",
15
+ "abbr",
16
+ "b",
17
+ "blockquote",
18
+ "br",
19
+ "caption",
20
+ "cite",
21
+ "code",
22
+ "col",
23
+ "colgroup",
24
+ "data",
25
+ "dd",
26
+ "del",
27
+ "details",
28
+ "dfn",
29
+ "div",
30
+ "dl",
31
+ "dt",
32
+ "em",
33
+ "figcaption",
34
+ "figure",
35
+ "h1",
36
+ "h2",
37
+ "h3",
38
+ "h4",
39
+ "h5",
40
+ "h6",
41
+ "hr",
42
+ "i",
43
+ "img",
44
+ "kbd",
45
+ "li",
46
+ "mark",
47
+ "ol",
48
+ "p",
49
+ "pre",
50
+ "q",
51
+ "s",
52
+ "samp",
53
+ "small",
54
+ "source",
55
+ "span",
56
+ "strong",
57
+ "sub",
58
+ "summary",
59
+ "sup",
60
+ "table",
61
+ "tbody",
62
+ "td",
63
+ "tfoot",
64
+ "th",
65
+ "thead",
66
+ "time",
67
+ "tr",
68
+ "track",
69
+ "u",
70
+ "ul",
71
+ "var",
72
+ "video",
73
+ "audio"
74
+ ]);
75
+ var DROP_WITH_CONTENT_TAGS = /* @__PURE__ */ new Set([
76
+ "base",
77
+ "button",
78
+ "embed",
79
+ "form",
80
+ "iframe",
81
+ "input",
82
+ "link",
83
+ "meta",
84
+ "object",
85
+ "option",
86
+ "script",
87
+ "select",
88
+ "style",
89
+ "svg",
90
+ "template",
91
+ "textarea"
92
+ ]);
93
+ var GLOBAL_ATTRS = /* @__PURE__ */ new Set(["class", "id", "title", "role"]);
94
+ var TAG_ATTRS = {
95
+ a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
96
+ audio: /* @__PURE__ */ new Set(["src", "controls", "preload", "muted", "loop"]),
97
+ col: /* @__PURE__ */ new Set(["span", "width"]),
98
+ colgroup: /* @__PURE__ */ new Set(["span"]),
99
+ data: /* @__PURE__ */ new Set(["value"]),
100
+ img: /* @__PURE__ */ new Set(["src", "alt", "width", "height", "loading", "decoding"]),
101
+ li: /* @__PURE__ */ new Set(["value"]),
102
+ ol: /* @__PURE__ */ new Set(["start", "type"]),
103
+ q: /* @__PURE__ */ new Set(["cite"]),
104
+ source: /* @__PURE__ */ new Set(["src", "type", "media"]),
105
+ td: /* @__PURE__ */ new Set(["colspan", "rowspan", "align"]),
106
+ th: /* @__PURE__ */ new Set(["colspan", "rowspan", "align", "scope"]),
107
+ time: /* @__PURE__ */ new Set(["datetime"]),
108
+ track: /* @__PURE__ */ new Set(["src", "kind", "label", "srclang", "default"]),
109
+ video: /* @__PURE__ */ new Set([
110
+ "src",
111
+ "poster",
112
+ "width",
113
+ "height",
114
+ "controls",
115
+ "preload",
116
+ "muted",
117
+ "loop",
118
+ "playsinline"
119
+ ])
120
+ };
121
+ var SAFE_ATTR_NAME_RE = /^[a-z][a-z0-9:_.-]*$/;
122
+ var SAFE_OL_TYPES = /* @__PURE__ */ new Set(["1", "a", "A", "i", "I"]);
123
+ var SAFE_PRELOAD_VALUES = /* @__PURE__ */ new Set(["none", "metadata", "auto"]);
124
+ var SAFE_TRACK_KINDS = /* @__PURE__ */ new Set(["subtitles", "captions", "descriptions", "chapters", "metadata"]);
125
+ function sanitizeUrl(url, kind = "link", options) {
126
+ if (typeof url !== "string") return null;
127
+ const trimmed = url.trim();
128
+ if (!trimmed) return null;
129
+ const compact = stripUrlSchemeNoise(trimmed);
130
+ const colon = compact.indexOf(":");
131
+ const firstPathChar = firstIndexOfAny(compact, ["/", "?", "#"]);
132
+ const hasScheme = colon >= 0 && (firstPathChar < 0 || colon < firstPathChar);
133
+ if (!hasScheme) return trimmed;
134
+ const scheme = compact.slice(0, colon).toLowerCase();
135
+ if (kind === "media") {
136
+ if (scheme === "data") return SAFE_DATA_MEDIA_RE.test(compact) ? trimmed : null;
137
+ return SAFE_MEDIA_SCHEMES.has(scheme) ? trimmed : null;
138
+ }
139
+ if (SAFE_LINK_SCHEMES.has(scheme)) return trimmed;
140
+ if (options?.extraLinkSchemes?.includes(scheme) && !NEVER_ALLOWED_SCHEMES.has(scheme)) {
141
+ return trimmed;
142
+ }
143
+ return null;
144
+ }
145
+ function sanitizeHtmlNodes(nodes) {
146
+ const out = [];
147
+ for (const node of nodes) {
148
+ out.push(...sanitizeHtmlNode(node));
149
+ }
150
+ return out;
151
+ }
152
+ function sanitizeHtmlNode(node) {
153
+ switch (node.type) {
154
+ case "htmlText":
155
+ return [node];
156
+ case "htmlComment":
157
+ return [];
158
+ case "htmlElement":
159
+ return sanitizeHtmlElement(node);
160
+ }
161
+ }
162
+ function sanitizeHtmlElement(node) {
163
+ const tag = node.tagName.toLowerCase();
164
+ if (DROP_WITH_CONTENT_TAGS.has(tag)) return [];
165
+ const children = sanitizeHtmlNodes(node.children);
166
+ if (!SAFE_TAGS.has(tag)) return children;
167
+ return [
168
+ {
169
+ type: "htmlElement",
170
+ tagName: tag,
171
+ attributes: sanitizeAttrs(tag, node.attributes),
172
+ children,
173
+ selfClosing: node.selfClosing
174
+ }
175
+ ];
176
+ }
177
+ function sanitizeAttrs(tag, attrs) {
178
+ const out = {};
179
+ for (const [rawName, rawValue] of Object.entries(attrs)) {
180
+ const name = rawName.toLowerCase();
181
+ if (!isAllowedAttr(tag, name)) continue;
182
+ const value = String(rawValue);
183
+ const safeValue = sanitizeAttrValue(tag, name, value);
184
+ if (safeValue === null) continue;
185
+ out[name] = safeValue;
186
+ }
187
+ if (tag === "a" && out.target === "_blank") {
188
+ out.rel = ensureRelTokens(out.rel, ["noopener", "noreferrer"]);
189
+ }
190
+ return out;
191
+ }
192
+ function isAllowedAttr(tag, name) {
193
+ if (!SAFE_ATTR_NAME_RE.test(name)) return false;
194
+ if (name.startsWith("on")) return false;
195
+ if (name.startsWith("aria-") || name.startsWith("data-")) return true;
196
+ if (GLOBAL_ATTRS.has(name)) return true;
197
+ return TAG_ATTRS[tag]?.has(name) ?? false;
198
+ }
199
+ function sanitizeAttrValue(tag, name, value) {
200
+ if (name === "href") return sanitizeUrl(value, "link");
201
+ if (name === "src" || name === "poster") return sanitizeUrl(value, "media");
202
+ if ((name === "width" || name === "height" || name === "span" || name === "colspan" || name === "rowspan" || name === "value") && !isNonNegativeInteger(value)) {
203
+ return null;
204
+ }
205
+ if (name === "start" && !isInteger(value)) return null;
206
+ if (name === "type" && tag === "ol" && !SAFE_OL_TYPES.has(value)) return null;
207
+ if (name === "preload" && !SAFE_PRELOAD_VALUES.has(value)) return null;
208
+ if (name === "kind" && tag === "track" && !SAFE_TRACK_KINDS.has(value)) return null;
209
+ if (name === "align" && value !== "left" && value !== "right" && value !== "center") return null;
210
+ if (name === "target")
211
+ return value === "_blank" || value === "_self" || value === "_parent" || value === "_top" ? value : null;
212
+ if (name === "rel") return sanitizeRel(value);
213
+ return value;
214
+ }
215
+ function sanitizeRel(value) {
216
+ return relTokens(value).join(" ");
217
+ }
218
+ function ensureRelTokens(value, required) {
219
+ const tokens = new Set(value ? relTokens(value) : []);
220
+ for (const token of required) tokens.add(token);
221
+ return Array.from(tokens).join(" ");
222
+ }
223
+ function relTokens(value) {
224
+ const tokens = value.split(/\s+/).map((token) => token.trim().toLowerCase()).filter(Boolean).filter((token) => /^[a-z0-9_-]+$/.test(token));
225
+ return Array.from(new Set(tokens));
226
+ }
227
+ function isInteger(value) {
228
+ return /^-?\d+$/.test(value.trim());
229
+ }
230
+ function isNonNegativeInteger(value) {
231
+ return /^\d+$/.test(value.trim());
232
+ }
233
+ function firstIndexOfAny(value, needles) {
234
+ let result = -1;
235
+ for (const needle of needles) {
236
+ const index = value.indexOf(needle);
237
+ if (index >= 0 && (result < 0 || index < result)) result = index;
238
+ }
239
+ return result;
240
+ }
241
+ function stripUrlSchemeNoise(value) {
242
+ let out = "";
243
+ for (const char of value) {
244
+ const code = char.charCodeAt(0);
245
+ if (code <= 32 || code === 127 || char.trim() === "") continue;
246
+ out += char;
247
+ }
248
+ return out;
249
+ }
250
+
8
251
  // src/markdown/annotationCoercion.ts
9
252
  var KNOWN_BLOCK_META_KEYS = {
10
253
  x: "number",
@@ -275,6 +518,8 @@ function serializeAnnotation(template, params) {
275
518
  }
276
519
 
277
520
  export {
521
+ sanitizeUrl,
522
+ sanitizeHtmlNodes,
278
523
  KNOWN_BLOCK_META_KEYS,
279
524
  BLOCK_META_KEY_DESCRIPTORS,
280
525
  coerceAnnotationValues,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  expectedSyllablesAt,
3
3
  wordPosAtExpectedSyllables
4
- } from "./chunk-E3RXZUNP.js";
4
+ } from "./chunk-GQNH7PLN.js";
5
5
 
6
6
  // src/narration/types.ts
7
7
  var DEFAULT_FEATURE_CONFIG = Object.freeze({
@@ -5,7 +5,7 @@ import {
5
5
  quoteAttrValue,
6
6
  splitKeyValueToken,
7
7
  tokenizeAttrTokens
8
- } from "./chunk-ZQKZSJAX.js";
8
+ } from "./chunk-2VCNTDNZ.js";
9
9
  import {
10
10
  resolveIcon
11
11
  } from "./chunk-7N4G32LG.js";