@stll/folio-core 0.44.0 → 0.45.0

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.
Files changed (90) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +1 -0
  3. package/dist/compare/content-alignment.js +78 -53
  4. package/dist/compare/inline-atoms.js +34 -20
  5. package/dist/content-controls/mutateContentControls.js +4 -2
  6. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  7. package/dist/document-operations.js +14 -3
  8. package/dist/docx/borderParser.js +5 -5
  9. package/dist/docx/commentParser.js +47 -36
  10. package/dist/docx/commentThreadKey.d.ts +18 -0
  11. package/dist/docx/commentThreadKey.js +22 -0
  12. package/dist/docx/diagramPreview.js +87 -27
  13. package/dist/docx/groupDrawingParser.js +3 -3
  14. package/dist/docx/hyperlinkParser.js +2 -2
  15. package/dist/docx/imageParser.d.ts +9 -1
  16. package/dist/docx/imageParser.js +58 -12
  17. package/dist/docx/imageRawXml.d.ts +14 -1
  18. package/dist/docx/imageRawXml.js +30 -6
  19. package/dist/docx/mathToMathml.js +12 -14
  20. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  21. package/dist/docx/nonVisualDrawingProps.js +46 -0
  22. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  23. package/dist/docx/parser.js +2 -2
  24. package/dist/docx/previewBudget.d.ts +64 -0
  25. package/dist/docx/previewBudget.js +88 -0
  26. package/dist/docx/revisionIdNormalization.js +17 -5
  27. package/dist/docx/rezip.js +17 -18
  28. package/dist/docx/sdtPropertiesPatch.js +24 -18
  29. package/dist/docx/sectionReferenceHistory.js +2 -2
  30. package/dist/docx/selectiveSave.js +6 -6
  31. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  32. package/dist/docx/serializer/borderSerializer.d.ts +1 -1
  33. package/dist/docx/serializer/borderSerializer.js +13 -12
  34. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  35. package/dist/docx/serializer/commentSerializer.js +82 -85
  36. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  37. package/dist/docx/serializer/headerFooterSerializer.js +5 -5
  38. package/dist/docx/serializer/markupRangeAttributes.js +2 -2
  39. package/dist/docx/serializer/numberingSerializer.js +7 -6
  40. package/dist/docx/serializer/paragraphSerializer.js +19 -18
  41. package/dist/docx/serializer/partNamespaces.js +2 -2
  42. package/dist/docx/serializer/runSerializer.js +48 -28
  43. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  44. package/dist/docx/serializer/settingsSerializer.js +4 -3
  45. package/dist/docx/serializer/stylesSerializer.js +6 -6
  46. package/dist/docx/serializer/tableSerializer.js +10 -9
  47. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  48. package/dist/docx/serializer/themeSerializer.js +6 -6
  49. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  50. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  51. package/dist/docx/serializer/xmlUtils.js +1 -13
  52. package/dist/docx/server/boundedArchive.d.ts +12 -0
  53. package/dist/docx/server/boundedArchive.js +20 -1
  54. package/dist/docx/server/validateDocxConformance.js +22 -1
  55. package/dist/docx/shapeParser.js +7 -5
  56. package/dist/docx/textBoxParser.js +7 -2
  57. package/dist/docx/unzip.d.ts +23 -0
  58. package/dist/docx/unzip.js +32 -22
  59. package/dist/docx/verbatimCapture.js +1 -1
  60. package/dist/docx/vmlImageParser.js +3 -2
  61. package/dist/docx/vmlPreview.d.ts +1 -3
  62. package/dist/docx/vmlPreview.js +2 -30
  63. package/dist/docx/xmlParser.d.ts +16 -1
  64. package/dist/docx/xmlParser.js +56 -26
  65. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  66. package/dist/docx/xmlResourceLimits.js +105 -24
  67. package/dist/internal/paragraphFormattingSerialization.js +3 -2
  68. package/dist/layout-painter/renderImage.js +4 -3
  69. package/dist/layout-painter/renderParagraph.js +4 -3
  70. package/dist/managers/autoSaveCodec.js +2 -8
  71. package/dist/markdown/images.js +1 -4
  72. package/dist/prosemirror/attrs/index.js +69 -0
  73. package/dist/prosemirror/commands/image.js +1 -0
  74. package/dist/prosemirror/conversion/fromProseDoc.js +69 -29
  75. package/dist/prosemirror/conversion/toProseDoc.js +83 -19
  76. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  77. package/dist/prosemirror/extensions/nodes/ImageExtension.js +4 -0
  78. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +7 -2
  79. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  80. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
  81. package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
  82. package/dist/prosemirror/schema/nodes.d.ts +50 -1
  83. package/dist/utils/base64.d.ts +36 -0
  84. package/dist/utils/base64.js +40 -0
  85. package/dist/utils/clipboard.js +2 -1
  86. package/dist/utils/units.d.ts +10 -1
  87. package/dist/utils/units.js +12 -1
  88. package/dist/utils/urlSecurity.d.ts +8 -2
  89. package/dist/utils/urlSecurity.js +21 -3
  90. package/package.json +2 -2
@@ -264,6 +264,25 @@ type ImagePositionAttrs = {
264
264
  align?: NonNullable<document_d_exports.ImagePosition["vertical"]["alignment"]>;
265
265
  };
266
266
  };
267
+ /**
268
+ * The EMUs a drawing's pixel attributes were projected from, keyed by the
269
+ * pixel attribute each one became.
270
+ *
271
+ * EMU → px → EMU does not land back on the same number: a size rounds to whole
272
+ * pixels, a stroke or an inset to two. Without the authored value beside the
273
+ * projected one, opening a document and saving it again moved every drawing
274
+ * off the number its author wrote. `fromProseDoc` writes the authored EMU back
275
+ * while the pixel attribute still projects from it, and converts the pixel
276
+ * attribute once an editor command has moved it.
277
+ *
278
+ * A key carries `undefined` when the source authored no such value, so an
279
+ * absent value stays absent rather than acquiring one the document never had.
280
+ */
281
+ type AuthoredEmuAttrs<Key extends string> = { readonly [K in Key]?: number | undefined; };
282
+ /** The pixel attributes a drawing's `wp:wrap*` insets are projected into. */
283
+ type WrapDistanceAttr = "distTop" | "distBottom" | "distLeft" | "distRight";
284
+ /** The pixel attributes a text box's internal margins are projected into. */
285
+ type TextBoxMarginAttr = "marginTop" | "marginBottom" | "marginLeft" | "marginRight";
267
286
  /**
268
287
  * Image node attributes
269
288
  */
@@ -320,6 +339,16 @@ type ImageAttrs = {
320
339
  position?: ImagePositionAttrs;
321
340
  /** Use the containing table cell as the anchor's positioning scope (the OOXML default). */
322
341
  layoutInCell?: boolean;
342
+ /**
343
+ * The image carries no information, so assistive technology skips it
344
+ * (`wp:docPr`'s decorative extension). Not {@link hidden}: a decorative
345
+ * image is still displayed.
346
+ */
347
+ decorative?: boolean;
348
+ /** `wp:docPr @hidden`: the drawing is not displayed. */
349
+ hidden?: boolean;
350
+ /** `wp:docPr` extensions folio does not model, carried through verbatim. */
351
+ docPrExtensions?: string[];
323
352
  /**
324
353
  * Authored `a:graphicFrameLocks`. Carried through the editor so a resize,
325
354
  * which forces the serializer to regenerate DrawingML, cannot silently
@@ -328,6 +357,8 @@ type ImageAttrs = {
328
357
  frameLocks?: document_d_exports.ImageFrameLocks;
329
358
  /** Border width in pixels */
330
359
  borderWidth?: number;
360
+ /** The EMUs behind `width`, `height`, `borderWidth` and the wrap insets. */
361
+ _docxAuthoredEmu?: AuthoredEmuAttrs<"width" | "height" | "borderWidth" | WrapDistanceAttr>;
331
362
  /** Border color as CSS color string */
332
363
  borderColor?: string;
333
364
  /** Border style (CSS border-style value) */
@@ -468,6 +499,12 @@ type ShapeAttrs = {
468
499
  geometryAdjustments?: string;
469
500
  /** Unique identifier */
470
501
  shapeId?: string;
502
+ /** Authored non-visual drawing name (`wp:docPr@name` / `wps:cNvPr@name`) */
503
+ shapeName?: string;
504
+ /** Alt text for accessibility (`wp:docPr@descr`) */
505
+ alt?: string;
506
+ /** Authored non-visual drawing title (`wp:docPr@title`) */
507
+ title?: string;
471
508
  /** Width in pixels */
472
509
  width?: number;
473
510
  /** Height in pixels */
@@ -494,6 +531,8 @@ type ShapeAttrs = {
494
531
  outlineStyle?: OutlineStyleAttr;
495
532
  /** Line cap */
496
533
  outlineCap?: NonNullable<document_d_exports.ShapeOutline["cap"]>;
534
+ /** Line join */
535
+ outlineJoin?: NonNullable<document_d_exports.ShapeOutline["join"]>;
497
536
  /** Head arrow/end marker */
498
537
  outlineHeadEnd?: NonNullable<document_d_exports.ShapeOutline["headEnd"]>;
499
538
  /** Tail arrow/end marker */
@@ -538,6 +577,8 @@ type ShapeAttrs = {
538
577
  * census).
539
578
  */
540
579
  _docxRunFormatting?: document_d_exports.TextFormatting;
580
+ /** The EMUs behind `width`, `height`, `outlineWidth` and the wrap insets. */
581
+ _docxAuthoredEmu?: AuthoredEmuAttrs<"width" | "height" | "outlineWidth" | WrapDistanceAttr>;
541
582
  };
542
583
  /**
543
584
  * Text box node attributes
@@ -551,6 +592,8 @@ type TextBoxAttrs = {
551
592
  width?: number;
552
593
  /** Height in pixels */
553
594
  height?: number;
595
+ /** The EMUs behind the size, `outlineWidth`, the wrap insets and the margins. */
596
+ _docxAuthoredEmu?: AuthoredEmuAttrs<"width" | "height" | "outlineWidth" | WrapDistanceAttr | TextBoxMarginAttr>;
554
597
  /** Text fitting behavior */
555
598
  autoFit?: document_d_exports.ShapeTextBody["autoFit"];
556
599
  /** Authored DrawingML WordArt metadata. */
@@ -559,6 +602,12 @@ type TextBoxAttrs = {
559
602
  textWrap?: document_d_exports.ShapeTextBody["textWrap"];
560
603
  /** Unique identifier */
561
604
  textBoxId?: string;
605
+ /** Authored non-visual drawing name (`wp:docPr@name` / `wps:cNvPr@name`) */
606
+ textBoxName?: string;
607
+ /** Alt text for accessibility (`wp:docPr@descr`) */
608
+ alt?: string;
609
+ /** Authored non-visual drawing title (`wp:docPr@title`) */
610
+ title?: string;
562
611
  /** Fill color as CSS color */
563
612
  fillColor?: string;
564
613
  /** Outline width in pixels */
@@ -836,4 +885,4 @@ type TableCellAttrs = {
836
885
  _docxVMergeContinuationCells?: unknown;
837
886
  };
838
887
  //#endregion
839
- export { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs, TextBoxTextBodyContentState };
888
+ export { AuthoredEmuAttrs, BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TEXT_BOX_TEXT_BODY_CONTENT_STATE_TYPES, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs, TextBoxTextBodyContentState };
@@ -0,0 +1,36 @@
1
+ //#region src/utils/base64.d.ts
2
+ /**
3
+ * The package's one bytes-to-base64 encoder.
4
+ *
5
+ * Every call site that spelled this itself got a different answer to the same
6
+ * two questions, and one of them got the first question wrong:
7
+ *
8
+ * - `btoa(new TextDecoder("latin1").decode(bytes))` is not a binary string.
9
+ * The Encoding standard maps the `latin1` label to windows-1252, so bytes
10
+ * 0x80-0x9F decode to characters outside Latin-1 and `btoa` throws
11
+ * `InvalidCharacterError` on them. Ordinary image bytes contain those values,
12
+ * so the browser path of that spelling fails on real input.
13
+ * - `String.fromCharCode(...chunk)` is correct but spreads a chunk's worth of
14
+ * arguments per call, and the intermediate binary string is as large as the
15
+ * input.
16
+ *
17
+ * Neither question has to be asked per call site, so it is asked here once.
18
+ * The runtime's own encoder answers it when the runtime has one, and the
19
+ * portable encoder below answers it when not; both produce canonical base64,
20
+ * so the choice is invisible in the output.
21
+ */
22
+ /**
23
+ * Encode into exactly one intermediate buffer: ASCII codes are written into a
24
+ * single array and decoded once, so a megabyte-scale input leaves the output
25
+ * string and nothing else.
26
+ *
27
+ * Exported because the runtimes the tests run on all carry a native encoder,
28
+ * and the fallback is the branch the browsers that lack one take.
29
+ */
30
+ declare const encodeBase64Portable: (bytes: Uint8Array) => string;
31
+ /** Encode bytes as canonical, padded, standard-alphabet base64. */
32
+ declare const bytesToBase64: (bytes: Uint8Array) => string;
33
+ /** Encode bytes as a `data:` URL with the given media type. */
34
+ declare const bytesToDataUrl: (bytes: Uint8Array, mimeType: string) => string;
35
+ //#endregion
36
+ export { bytesToBase64, bytesToDataUrl, encodeBase64Portable };
@@ -0,0 +1,40 @@
1
+ //#region src/utils/base64.ts
2
+ const BASE64_CODES = Uint8Array.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", (character) => character.charCodeAt(0));
3
+ const BASE64_PAD = 61;
4
+ /**
5
+ * Encode into exactly one intermediate buffer: ASCII codes are written into a
6
+ * single array and decoded once, so a megabyte-scale input leaves the output
7
+ * string and nothing else.
8
+ *
9
+ * Exported because the runtimes the tests run on all carry a native encoder,
10
+ * and the fallback is the branch the browsers that lack one take.
11
+ */
12
+ const encodeBase64Portable = (bytes) => {
13
+ const groups = Math.ceil(bytes.length / 3);
14
+ const encoded = new Uint8Array(groups * 4);
15
+ let read = 0;
16
+ let write = 0;
17
+ for (; read + 2 < bytes.length; read += 3) {
18
+ const triple = bytes[read] << 16 | bytes[read + 1] << 8 | bytes[read + 2];
19
+ encoded[write] = BASE64_CODES[triple >> 18 & 63];
20
+ encoded[write + 1] = BASE64_CODES[triple >> 12 & 63];
21
+ encoded[write + 2] = BASE64_CODES[triple >> 6 & 63];
22
+ encoded[write + 3] = BASE64_CODES[triple & 63];
23
+ write += 4;
24
+ }
25
+ const remaining = bytes.length - read;
26
+ if (remaining > 0) {
27
+ const tail = bytes[read] << 16 | (remaining === 2 ? bytes[read + 1] << 8 : 0);
28
+ encoded[write] = BASE64_CODES[tail >> 18 & 63];
29
+ encoded[write + 1] = BASE64_CODES[tail >> 12 & 63];
30
+ encoded[write + 2] = remaining === 2 ? BASE64_CODES[tail >> 6 & 63] : BASE64_PAD;
31
+ encoded[write + 3] = BASE64_PAD;
32
+ }
33
+ return new TextDecoder().decode(encoded);
34
+ };
35
+ /** Encode bytes as canonical, padded, standard-alphabet base64. */
36
+ const bytesToBase64 = ("toBase64" in Uint8Array.prototype ? (bytes) => bytes.toBase64() : void 0) ?? encodeBase64Portable;
37
+ /** Encode bytes as a `data:` URL with the given media type. */
38
+ const bytesToDataUrl = (bytes, mimeType) => `data:${mimeType};base64,${bytesToBase64(bytes)}`;
39
+ //#endregion
40
+ export { bytesToBase64, bytesToDataUrl, encodeBase64Portable };
@@ -1,5 +1,6 @@
1
1
  import { htmlCommentEnd } from "./htmlComments.js";
2
2
  import { stripXmlDeclarations } from "./stripXmlDeclarations.js";
3
+ import { sanitizeXmlCharacters } from "@stll/docx-core";
3
4
  import createDOMPurify from "dompurify";
4
5
  //#region src/utils/clipboard.ts
5
6
  /**
@@ -567,7 +568,7 @@ function createTextRun(text, formatting) {
567
568
  ...formatting !== void 0 ? { formatting } : {},
568
569
  content: [{
569
570
  type: "text",
570
- text
571
+ text: sanitizeXmlCharacters(text)
571
572
  }]
572
573
  };
573
574
  }
@@ -48,6 +48,15 @@ declare function emuToPixels(emu: number | undefined | null): number;
48
48
  * floating-point drift (e.g. 52 px → 495299.99999999994) out of the document.
49
49
  */
50
50
  declare function pixelsToEmu(px: number): number;
51
+ /**
52
+ * Convert EMUs to pixels for a stroke width.
53
+ *
54
+ * Strokes are sub-pixel, so unlike a box size they keep two decimals rather
55
+ * than rounding to whole pixels. The projection is still lossy: a consumer that
56
+ * has to write the document's own number back carries the authored EMU beside
57
+ * the pixels rather than converting these back.
58
+ */
59
+ declare function emuToStrokePixels(emu: number): number;
51
60
  /**
52
61
  * Convert EMUs to twips. Twips are integer-typed in OOXML; round here.
53
62
  */
@@ -96,4 +105,4 @@ declare function clamp(value: number, min: number, max: number): number;
96
105
  */
97
106
  declare function formatPx(px: number): string;
98
107
  //#endregion
99
- export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
108
+ export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToStrokePixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
@@ -68,6 +68,17 @@ function pixelsToEmu(px) {
68
68
  return Math.round(px / 96 * EMUS_PER_INCH);
69
69
  }
70
70
  /**
71
+ * Convert EMUs to pixels for a stroke width.
72
+ *
73
+ * Strokes are sub-pixel, so unlike a box size they keep two decimals rather
74
+ * than rounding to whole pixels. The projection is still lossy: a consumer that
75
+ * has to write the document's own number back carries the authored EMU beside
76
+ * the pixels rather than converting these back.
77
+ */
78
+ function emuToStrokePixels(emu) {
79
+ return roundPixels(emu / EMUS_PER_INCH * 96);
80
+ }
81
+ /**
71
82
  * Convert EMUs to twips. Twips are integer-typed in OOXML; round here.
72
83
  */
73
84
  function emuToTwips(emu) {
@@ -136,4 +147,4 @@ function formatPx(px) {
136
147
  return `${roundPixels(px)}px`;
137
148
  }
138
149
  //#endregion
139
- export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
150
+ export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToStrokePixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
@@ -2,6 +2,12 @@ import { sanitizeExternalUrl } from "@stll/docx-core";
2
2
  //#region src/utils/urlSecurity.d.ts
3
3
  declare function normalizeUserUrl(rawUrl: string): string;
4
4
  declare function isAllowedUserUrl(rawUrl: string): boolean;
5
- declare function sanitizeLinkTarget(target: string | undefined): string;
5
+ type AnchorTargetAttrs = {
6
+ target: string;
7
+ rel: string;
8
+ };
9
+ declare function anchorTargetAttrs(authoredFrame: string | undefined): AnchorTargetAttrs;
10
+ /** `window.open` features carrying the same guarantees as {@link ANCHOR_REL}. */
11
+ declare function windowFeaturesFor({ rel }: AnchorTargetAttrs): string;
6
12
  //#endregion
7
- export { isAllowedUserUrl, normalizeUserUrl, sanitizeExternalUrl, sanitizeLinkTarget };
13
+ export { AnchorTargetAttrs, anchorTargetAttrs, isAllowedUserUrl, normalizeUserUrl, sanitizeExternalUrl, windowFeaturesFor };
@@ -23,8 +23,26 @@ function normalizeUserUrl(rawUrl) {
23
23
  function isAllowedUserUrl(rawUrl) {
24
24
  return normalizeUserUrl(rawUrl) !== "";
25
25
  }
26
- function sanitizeLinkTarget(target) {
27
- return target && ALLOWED_TARGETS.has(target) ? target : "_blank";
26
+ /**
27
+ * The `target` and `rel` one authored frame may produce on a DOM anchor.
28
+ *
29
+ * A `w:tgtFrame` is a frame name, and a named frame addresses another browsing
30
+ * context: honouring an arbitrary one would let a document reach a frame it did
31
+ * not create. Every anchor and every navigation folio produces goes through
32
+ * here, so the clamp and the `rel` that must accompany `_blank` are decided
33
+ * once. The authored name itself stays in the model and is written back on
34
+ * save; clamping it at parse rewrote the document.
35
+ */
36
+ const ANCHOR_REL = "noopener noreferrer";
37
+ function anchorTargetAttrs(authoredFrame) {
38
+ return {
39
+ target: authoredFrame && ALLOWED_TARGETS.has(authoredFrame) ? authoredFrame : "_blank",
40
+ rel: ANCHOR_REL
41
+ };
42
+ }
43
+ /** `window.open` features carrying the same guarantees as {@link ANCHOR_REL}. */
44
+ function windowFeaturesFor({ rel }) {
45
+ return rel.replaceAll(" ", ",");
28
46
  }
29
47
  function findFirstPathSeparatorIndex(value) {
30
48
  return [
@@ -44,4 +62,4 @@ function startsWithPortSuffix(value) {
44
62
  return index > 0 && (index === value.length || "/?#".includes(value[index]));
45
63
  }
46
64
  //#endregion
47
- export { isAllowedUserUrl, normalizeUserUrl, sanitizeExternalUrl, sanitizeLinkTarget };
65
+ export { anchorTargetAttrs, isAllowedUserUrl, normalizeUserUrl, sanitizeExternalUrl, windowFeaturesFor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -121,7 +121,7 @@
121
121
  "perf": "bun scripts/profile-editor.ts"
122
122
  },
123
123
  "dependencies": {
124
- "@stll/docx-core": "^0.22.0",
124
+ "@stll/docx-core": "^0.23.0",
125
125
  "@stll/docx-utils": "^0.1.0",
126
126
  "@stll/template-conditions": ">=0.4.0 <1.0.0",
127
127
  "better-result": "3.0.1",