@formicoidea/labre-core 0.50.0 → 0.52.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 (210) hide show
  1. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
  2. package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
  3. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
  4. package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
  5. package/dist/_pkgs/affine-block-attachment/index.js +1 -0
  6. package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
  7. package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
  8. package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
  9. package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
  10. package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
  11. package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
  12. package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
  13. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
  14. package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
  15. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
  16. package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
  17. package/dist/_pkgs/affine-block-frame/index.js +1 -0
  18. package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
  19. package/dist/_pkgs/affine-block-frame/translations.js +16 -1
  20. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  21. package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
  22. package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
  23. package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
  24. package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
  25. package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
  26. package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
  27. package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
  28. package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
  29. package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
  30. package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
  31. package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
  32. package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
  33. package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
  34. package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
  35. package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
  36. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
  37. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
  38. package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
  39. package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
  40. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
  41. package/dist/_pkgs/affine-block-image/effects.js +2 -0
  42. package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
  43. package/dist/_pkgs/affine-block-image/image-block.js +2 -0
  44. package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
  45. package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
  46. package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
  47. package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
  48. package/dist/_pkgs/affine-block-image/index.js +1 -0
  49. package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
  50. package/dist/_pkgs/affine-block-image/translations.js +43 -0
  51. package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
  52. package/dist/_pkgs/affine-block-image/utils.js +10 -1
  53. package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
  54. package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
  55. package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
  56. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  57. package/dist/_pkgs/affine-block-root/index.js +1 -0
  58. package/dist/_pkgs/affine-block-root/view.js +8 -0
  59. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
  60. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
  61. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
  62. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
  63. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
  64. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
  65. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
  66. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
  67. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
  68. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
  69. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
  70. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
  71. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
  72. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
  73. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
  74. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
  75. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
  76. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
  77. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
  78. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
  79. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
  80. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
  81. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
  82. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
  83. package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
  84. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
  85. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
  86. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
  87. package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
  88. package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
  89. package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
  90. package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
  91. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
  92. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
  93. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
  94. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
  95. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
  96. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
  97. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
  98. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
  99. package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
  100. package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
  101. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
  102. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
  103. package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
  104. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  105. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  106. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
  107. package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
  108. package/dist/_pkgs/affine-block-surface/view.js +22 -2
  109. package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
  110. package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
  111. package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
  112. package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
  113. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
  114. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
  115. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
  116. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
  117. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
  118. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
  119. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
  120. package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
  121. package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
  122. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
  123. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
  124. package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
  125. package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
  126. package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
  127. package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
  128. package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
  129. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
  130. package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
  131. package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
  132. package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
  133. package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
  134. package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
  135. package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
  136. package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
  137. package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
  138. package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
  139. package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
  140. package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
  141. package/dist/_pkgs/affine-model/consts/shape.js +5 -0
  142. package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
  143. package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
  144. package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
  145. package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
  146. package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
  147. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
  148. package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
  149. package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
  150. package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
  151. package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
  152. package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
  153. package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
  154. package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
  155. package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
  156. package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
  157. package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
  158. package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
  159. package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
  160. package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
  161. package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
  162. package/dist/_pkgs/affine-model/headless.d.ts +19 -0
  163. package/dist/_pkgs/affine-model/headless.js +19 -0
  164. package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
  165. package/dist/_pkgs/affine-shared/headless.js +10 -0
  166. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
  167. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
  168. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
  169. package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
  170. package/dist/_pkgs/affine-shared/services/index.d.ts +3 -0
  171. package/dist/_pkgs/affine-shared/services/index.js +3 -0
  172. package/dist/_pkgs/affine-shared/services/thread-marks-service.d.ts +69 -0
  173. package/dist/_pkgs/affine-shared/services/thread-marks-service.js +20 -0
  174. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
  175. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
  176. package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
  177. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
  178. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
  179. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
  180. package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
  181. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
  182. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
  183. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
  184. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
  185. package/dist/_pkgs/affine-widget-thread-marks/effects.d.ts +1 -0
  186. package/dist/_pkgs/affine-widget-thread-marks/effects.js +4 -0
  187. package/dist/_pkgs/affine-widget-thread-marks/excerpt.d.ts +19 -0
  188. package/dist/_pkgs/affine-widget-thread-marks/excerpt.js +68 -0
  189. package/dist/_pkgs/affine-widget-thread-marks/index.d.ts +5 -0
  190. package/dist/_pkgs/affine-widget-thread-marks/index.js +8 -0
  191. package/dist/_pkgs/affine-widget-thread-marks/toolbar.d.ts +49 -0
  192. package/dist/_pkgs/affine-widget-thread-marks/toolbar.js +108 -0
  193. package/dist/_pkgs/affine-widget-thread-marks/translations.d.ts +9 -0
  194. package/dist/_pkgs/affine-widget-thread-marks/translations.js +11 -0
  195. package/dist/_pkgs/affine-widget-thread-marks/view.d.ts +12 -0
  196. package/dist/_pkgs/affine-widget-thread-marks/view.js +25 -0
  197. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +51 -0
  198. package/dist/_pkgs/affine-widget-thread-marks/widget.js +214 -0
  199. package/dist/commands.js +5 -2
  200. package/dist/extensions/view.js +2 -0
  201. package/dist/host-panels.d.ts +24 -1
  202. package/dist/host-panels.js +61 -10
  203. package/dist/interchange.d.ts +1 -0
  204. package/dist/interchange.js +1 -0
  205. package/dist/model/headless.d.ts +1 -0
  206. package/dist/model/headless.js +1 -0
  207. package/dist/shared/headless.d.ts +1 -0
  208. package/dist/shared/headless.js +1 -0
  209. package/dist/translations.js +10 -1
  210. package/package.json +13 -1
@@ -0,0 +1,570 @@
1
+ /**
2
+ * A tolerant XML READER — the inverse of UML's `xml.ts` writer, and the one
3
+ * thing an importer cannot do without. Read by the XMI and draw.io importers,
4
+ * by BPMN and by the SVG sketch (through `xml-element.ts`), all four servable
5
+ * from the editor-free `./interchange` entry.
6
+ *
7
+ * ## Why not `DOMParser`
8
+ *
9
+ * BPMN and the SVG sketch used to call it, and the P0 import spike measured
10
+ * the cost on a server: a DOM to install (jsdom, 6 MB, behind `vm`), and a
11
+ * BPMN read that grew quadratically on it. An interchange parser is a PURE function of a string (`docs/adr/0012` P3) — the
12
+ * same function an editor command, a unit test and labre-mcp all call — and
13
+ * `DOMParser` is a DOM global that exists in exactly one of those three. A
14
+ * reader that needs `happy-dom` to be loaded is a reader with an environment in
15
+ * its signature.
16
+ *
17
+ * So: two hundred lines of scanner, and no global in sight.
18
+ *
19
+ * ## Tolerant, and what that costs
20
+ *
21
+ * **It never throws.** A file that stops mid-element, a close tag that names
22
+ * the wrong element, an attribute with no quotes, an entity nobody declared —
23
+ * every one of them produces a tree with a NOTE beside it rather than an
24
+ * exception. That is not sloppiness, it is the job: the files this reader is
25
+ * handed were written by Papyrus, StarUML, Enterprise Architect and a dozen
26
+ * scripts, and a user who exported a model from one of them is entitled to get
27
+ * back everything it said even when part of it is malformed. The alternative —
28
+ * one exception for one stray `&` — imports nothing at all.
29
+ *
30
+ * What it does NOT do is pretend to be a conformant parser. There is no DTD, no
31
+ * entity declaration, no namespace RESOLUTION (prefixes are kept exactly as the
32
+ * file spelled them — see {@link XmlNode.prefix}), no attribute-value
33
+ * normalization beyond the character references XML 1.0 §3.3.3 makes
34
+ * mandatory. Every one of those is a feature an XMI file in the wild does not
35
+ * use, and each would be a piece of surface with nothing behind it.
36
+ *
37
+ * ## Verbatim means the source's own bytes
38
+ *
39
+ * Every node records the byte RANGE it was read from ({@link XmlNode.range}),
40
+ * and {@link xmlFragmentOf} hands back that slice of the original string. So a
41
+ * quarantined fragment (ADR 0012 D5) is the file's own text — its comments, its
42
+ * whitespace, its attribute order, its prefixes — and not this reader's opinion
43
+ * of it. {@link serializeXmlNode} re-serializes from the tree instead, and is
44
+ * the fallback for a node assembled rather than read.
45
+ *
46
+ * ## Bounded, because a server reads it too
47
+ *
48
+ * The one exception to "never throws": a document past {@link XML_MAX_NODES}
49
+ * nodes or {@link XML_MAX_CHARACTERS} characters is refused with an
50
+ * {@link InterchangeImportError} carrying a static key, at the node that
51
+ * crosses the line and not after the tree is built. Each node costs about two
52
+ * hundred bytes of heap, and a 1.5 KiB `.drawio` inflates to a million of
53
+ * them (the 2026-10-14 security review: 3.2 s and 223 MiB before the cap). The
54
+ * scan is linear in the source otherwise: an orphan close tag is answered from
55
+ * a count of open names, not by searching the stack.
56
+ *
57
+ * Pure: a string in, a tree out. No `std`, no DOM, no clock.
58
+ */
59
+ import { InterchangeImportError } from './interchange.js';
60
+ /**
61
+ * Safe for a function with 256 MiB and two seconds: the largest file of the
62
+ * corpus holds a few thousand nodes, a reading at the cap about 40 MiB.
63
+ */
64
+ export const XML_MAX_NODES = 200_000;
65
+ /** Notes written in full; the rest are counted in one last note. */
66
+ export const XML_MAX_NOTES = 100;
67
+ /** A name as a note quotes it: its first 64 characters. */
68
+ function shown(name) {
69
+ return name.length > 64 ? `${name.slice(0, 64)}…` : name;
70
+ }
71
+ /** 2 MiB of decoded characters, well past any drawing the canvas can show. */
72
+ export const XML_MAX_CHARACTERS = 2 * 1024 * 1024;
73
+ /** The reader's one refusal; static, so a server can hand the key on. */
74
+ export const XML_READ_TOO_LARGE = [
75
+ 'com.labre.interchange.xml.too-large',
76
+ 'This file holds more than an import accepts, so it was not read.',
77
+ ];
78
+ /* ── Entities ─────────────────────────────────────────────────────────── */
79
+ /** The five XML 1.0 §4.6 predefined entities, and nothing else. */
80
+ const NAMED_ENTITIES = {
81
+ amp: '&',
82
+ lt: '<',
83
+ gt: '>',
84
+ quot: '"',
85
+ apos: "'",
86
+ };
87
+ /**
88
+ * Character data as its characters — the five named entities and any numeric
89
+ * reference.
90
+ *
91
+ * An entity this reader does not know (`&nbsp;`, a DTD-declared one) is left
92
+ * EXACTLY as it was written, unresolved and unannounced. Two reasons, and the
93
+ * second is the one that matters: resolving it would need the DTD the file
94
+ * does not ship, and dropping it would lose a character; leaving the reference
95
+ * standing means a later re-serialization writes back what the file said, which
96
+ * is the promise `xmlFragmentOf` makes and this keeps true for text as well.
97
+ */
98
+ export function decodeXmlText(value) {
99
+ if (!value.includes('&'))
100
+ return value;
101
+ return value.replaceAll(/&(#x[0-9a-fA-F]+|#\d+|[A-Za-z][\w.-]*);/g, (match, reference) => {
102
+ if (reference.startsWith('#')) {
103
+ const hex = reference[1] === 'x' || reference[1] === 'X';
104
+ const code = Number.parseInt(hex ? reference.slice(2) : reference.slice(1), hex ? 16 : 10);
105
+ // Out of Unicode's range is a reference no character answers, and
106
+ // `String.fromCodePoint` THROWS on one — which is the single place this
107
+ // reader could have raised. It hands back the reference instead.
108
+ return Number.isInteger(code) && code >= 0 && code <= 0x10ffff
109
+ ? String.fromCodePoint(code)
110
+ : match;
111
+ }
112
+ return NAMED_ENTITIES[reference] ?? match;
113
+ });
114
+ }
115
+ /* ── The scanner ──────────────────────────────────────────────────────── */
116
+ /** A name character, generously: XML's NameChar minus the exotic ranges. */
117
+ const NAME_CHAR = /[\w.:_\-À-￿]/;
118
+ /** The prefix and the local half of a qualified name. */
119
+ function splitName(name) {
120
+ const colon = name.indexOf(':');
121
+ return colon < 0
122
+ ? { prefix: '', local: name }
123
+ : { prefix: name.slice(0, colon), local: name.slice(colon + 1) };
124
+ }
125
+ /** A node with no children and no text, ready to be filled. */
126
+ function nodeOf(name, start) {
127
+ const { prefix, local } = splitName(name);
128
+ return {
129
+ name,
130
+ prefix,
131
+ local,
132
+ // No prototype: an attribute may be called `__proto__` or `constructor`.
133
+ attrs: Object.create(null),
134
+ children: [],
135
+ range: [start, start],
136
+ content: [],
137
+ };
138
+ }
139
+ /**
140
+ * Read a document. Throws only past its size caps; see the header.
141
+ *
142
+ * The scan is a single left-to-right pass with an explicit stack, which is what
143
+ * makes the tolerance cheap: every recovery is "say what is wrong, and carry on
144
+ * from the next `<`", and there is no recursion to unwind. In conformant mode
145
+ * the first note STOPS the read: its callers refuse on any note, and a file
146
+ * built to raise twenty thousand of them must not get to.
147
+ */
148
+ export function readXml(source, options = {}) {
149
+ const conformant = options.conformant === true;
150
+ const maxNodes = options.maxNodes ?? XML_MAX_NODES;
151
+ const maxCharacters = options.maxCharacters ?? XML_MAX_CHARACTERS;
152
+ const notes = [];
153
+ const roots = [];
154
+ const stack = [];
155
+ /** How many of each name are on `stack`: an orphan close costs O(1). */
156
+ const openNames = new Map();
157
+ /** Character data accumulated for the element currently open. */
158
+ const pending = [];
159
+ let cursor = 0;
160
+ let nodes = 0;
161
+ let spent = 0;
162
+ let stopped = false;
163
+ const spend = (length) => {
164
+ nodes += 1;
165
+ spent += length;
166
+ if (nodes > maxNodes || spent > maxCharacters) {
167
+ throw new InterchangeImportError(XML_READ_TOO_LARGE[1], {
168
+ messageKey: XML_READ_TOO_LARGE[0],
169
+ });
170
+ }
171
+ };
172
+ // A note quotes a name the file chose, and 140 KiB of faults used to make
173
+ // 20 000 notes and two billion characters, past what `JSON.stringify` takes
174
+ // (counter-review of 49aa247ce). So: a name is quoted to its first 64
175
+ // characters, and past XML_MAX_NOTES the rest are counted, not written.
176
+ let unlisted = 0;
177
+ const note = (line) => {
178
+ if (stopped)
179
+ return;
180
+ if (notes.length >= XML_MAX_NOTES) {
181
+ unlisted += 1;
182
+ return;
183
+ }
184
+ spend(0);
185
+ notes.push(line);
186
+ stopped = conformant;
187
+ };
188
+ const enter = (node) => {
189
+ stack.push(node);
190
+ openNames.set(node.name, (openNames.get(node.name) ?? 0) + 1);
191
+ };
192
+ const leave = () => {
193
+ const node = stack.pop();
194
+ openNames.set(node.name, (openNames.get(node.name) ?? 1) - 1);
195
+ return node;
196
+ };
197
+ /** Character data, kept for {@link XmlNode.text} and in order in `content`. */
198
+ const characters = (value) => {
199
+ if (value.length === 0)
200
+ return;
201
+ spend(value.length);
202
+ pending.push(value);
203
+ stack[stack.length - 1]?.content?.push({ kind: 'text', value });
204
+ };
205
+ /** Attach the text read since the last tag to whatever is open. */
206
+ const flushText = () => {
207
+ if (pending.length === 0)
208
+ return;
209
+ const text = pending.join('');
210
+ pending.length = 0;
211
+ const open = stack[stack.length - 1];
212
+ if (!open)
213
+ return;
214
+ // Whitespace between two indented children is layout, not content, and an
215
+ // element that held only that keeps no `text` at all — which is what lets a
216
+ // caller ask `if (node.text)` and mean "the file wrote something here".
217
+ if (!/\S/.test(text) && open.children.length > 0)
218
+ return;
219
+ if (!/\S/.test(text) && open.text === undefined)
220
+ return;
221
+ open.text = (open.text ?? '') + text;
222
+ };
223
+ const close = (node, end) => {
224
+ node.range[1] = end;
225
+ const parent = stack[stack.length - 1];
226
+ if (parent) {
227
+ parent.children.push(node);
228
+ parent.content?.push(node);
229
+ }
230
+ else {
231
+ if (conformant && roots.length === 1) {
232
+ note('This file has more than one top-level element.');
233
+ }
234
+ roots.push(node);
235
+ }
236
+ };
237
+ while (cursor < source.length && !stopped) {
238
+ const open = source.indexOf('<', cursor);
239
+ if (open < 0) {
240
+ characters(decodeXmlText(source.slice(cursor)));
241
+ break;
242
+ }
243
+ if (open > cursor)
244
+ characters(decodeXmlText(source.slice(cursor, open)));
245
+ /* ── The four things that are not an element ─────────────────────── */
246
+ if (source.startsWith('<!--', open)) {
247
+ const end = source.indexOf('-->', open + 4);
248
+ if (end < 0) {
249
+ note('A comment in this file is never closed; the rest was read.');
250
+ break;
251
+ }
252
+ const value = source.slice(open + 4, end);
253
+ spend(value.length);
254
+ stack[stack.length - 1]?.content?.push({ kind: 'comment', value });
255
+ cursor = end + 3;
256
+ continue;
257
+ }
258
+ if (source.startsWith('<![CDATA[', open)) {
259
+ const end = source.indexOf(']]>', open + 9);
260
+ if (end < 0) {
261
+ note('A CDATA section in this file is never closed.');
262
+ characters(source.slice(open + 9));
263
+ break;
264
+ }
265
+ // CDATA is character data with the escaping turned OFF, so it is pushed
266
+ // raw: decoding it would resolve a `&amp;` the author wrote literally.
267
+ characters(source.slice(open + 9, end));
268
+ cursor = end + 3;
269
+ continue;
270
+ }
271
+ if (source.startsWith('<?', open)) {
272
+ const end = source.indexOf('?>', open + 2);
273
+ if (end < 0) {
274
+ note('A processing instruction in this file is never closed.');
275
+ break;
276
+ }
277
+ const [, target = '', data = ''] = /^(\S*)\s*([\s\S]*)$/.exec(source.slice(open + 2, end)) ?? [];
278
+ spend(target.length + data.length);
279
+ stack[stack.length - 1]?.content?.push({ kind: 'pi', target, data });
280
+ cursor = end + 2;
281
+ continue;
282
+ }
283
+ if (source.startsWith('<!', open)) {
284
+ // A DOCTYPE, with or without an internal subset. The subset is skipped
285
+ // whole rather than parsed: this reader declares no entities, so what is
286
+ // in there could only change how a `&name;` it cannot resolve is read.
287
+ let scan = open + 2;
288
+ let depth = 0;
289
+ while (scan < source.length) {
290
+ const char = source[scan];
291
+ if (char === '[')
292
+ depth += 1;
293
+ else if (char === ']')
294
+ depth -= 1;
295
+ else if (char === '>' && depth <= 0)
296
+ break;
297
+ scan += 1;
298
+ }
299
+ cursor = Math.min(scan + 1, source.length);
300
+ continue;
301
+ }
302
+ /* ── A close tag ─────────────────────────────────────────────────── */
303
+ if (source.startsWith('</', open)) {
304
+ const end = source.indexOf('>', open);
305
+ if (end < 0) {
306
+ note('A close tag in this file is never finished.');
307
+ break;
308
+ }
309
+ flushText();
310
+ const name = source.slice(open + 2, end).trim();
311
+ // Searched only when the name IS open, and then everything above the
312
+ // match is popped: linear overall, where `<a>`×N `</b>`×N was N².
313
+ const depth = openNames.get(name)
314
+ ? stack.findLastIndex(node => node.name === name)
315
+ : -1;
316
+ if (depth < 0) {
317
+ note(`</${shown(name)}> closes an element this file never opened; it was ignored.`);
318
+ }
319
+ else {
320
+ // Everything still open INSIDE the element being closed is closed with
321
+ // it, in order. `<a><b></a>` is a file that lost a tag, not a file with
322
+ // no `<b>` in it, and the tree keeps the `<b>`.
323
+ for (let index = stack.length - 1; index > depth; index -= 1) {
324
+ const orphan = leave();
325
+ note(`<${shown(orphan.name)}> is closed by </${shown(name)}> rather than by a tag of ` +
326
+ `its own; it was read as far as it goes.`);
327
+ close(orphan, open);
328
+ }
329
+ close(leave(), end + 1);
330
+ }
331
+ cursor = end + 1;
332
+ continue;
333
+ }
334
+ /* ── An open tag ─────────────────────────────────────────────────── */
335
+ let scan = open + 1;
336
+ while (scan < source.length && NAME_CHAR.test(source[scan]))
337
+ scan += 1;
338
+ const name = source.slice(open + 1, scan);
339
+ if (name.length === 0) {
340
+ // A bare `<` in character data — illegal, and every second hand-written
341
+ // file has one. It is kept as the character it is.
342
+ note('A "<" in this file starts no tag; it was read as a plain character.');
343
+ characters('<');
344
+ cursor = open + 1;
345
+ continue;
346
+ }
347
+ flushText();
348
+ spend(0);
349
+ const node = nodeOf(name, open);
350
+ /* ── Its attributes ──────────────────────────────────────────────── */
351
+ let selfClosing = false;
352
+ let tagEnd = source.length;
353
+ while (scan < source.length && !stopped) {
354
+ while (scan < source.length && /\s/.test(source[scan]))
355
+ scan += 1;
356
+ if (scan >= source.length)
357
+ break;
358
+ if (source.startsWith('/>', scan)) {
359
+ selfClosing = true;
360
+ tagEnd = scan + 2;
361
+ break;
362
+ }
363
+ if (source[scan] === '>') {
364
+ tagEnd = scan + 1;
365
+ break;
366
+ }
367
+ const attrStart = scan;
368
+ while (scan < source.length && NAME_CHAR.test(source[scan]))
369
+ scan += 1;
370
+ const attrName = source.slice(attrStart, scan);
371
+ if (attrName.length === 0) {
372
+ // Something that is neither a name, a `>` nor whitespace. Skipped one
373
+ // character at a time so the scan cannot stall on it.
374
+ scan += 1;
375
+ continue;
376
+ }
377
+ while (scan < source.length && /\s/.test(source[scan]))
378
+ scan += 1;
379
+ if (source[scan] !== '=') {
380
+ // A valueless attribute — HTML's habit, not XML's. Recorded as the
381
+ // empty string, which is what every tolerant reader does with one.
382
+ note(`The attribute "${shown(attrName)}" on <${shown(name)}> has no value; it was read ` +
383
+ `as empty.`);
384
+ spend(0);
385
+ node.attrs[attrName] = '';
386
+ continue;
387
+ }
388
+ scan += 1;
389
+ while (scan < source.length && /\s/.test(source[scan]))
390
+ scan += 1;
391
+ const quote = source[scan];
392
+ let value = '';
393
+ if (quote === '"' || quote === "'") {
394
+ const end = source.indexOf(quote, scan + 1);
395
+ if (end < 0) {
396
+ note(`The value of "${shown(attrName)}" on <${shown(name)}> is never closed; the ` +
397
+ `rest of the file was read as its value.`);
398
+ value = source.slice(scan + 1);
399
+ scan = source.length;
400
+ }
401
+ else {
402
+ value = source.slice(scan + 1, end);
403
+ scan = end + 1;
404
+ }
405
+ }
406
+ else {
407
+ // Unquoted. Read to the next whitespace or tag end, which is the only
408
+ // reading available and the one every tolerant parser agrees on.
409
+ const start = scan;
410
+ while (scan < source.length && !/[\s>/]/.test(source[scan]))
411
+ scan += 1;
412
+ value = source.slice(start, scan);
413
+ note(`The value of "${shown(attrName)}" on <${shown(name)}> is not quoted; it was read ` +
414
+ `up to the next space.`);
415
+ }
416
+ // §3.3.3: literal whitespace normalizes BEFORE references decode, so a
417
+ // `&#10;` the writer escaped on purpose stays a newline.
418
+ const decoded = decodeXmlText(conformant ? value.replaceAll(/\r\n|[\t\n\r]/g, ' ') : value);
419
+ spend(decoded.length);
420
+ node.attrs[attrName] = decoded;
421
+ }
422
+ if (tagEnd === source.length && !selfClosing && source[scan] !== '>') {
423
+ note(`<${shown(name)}> is never finished; the file stops inside its tag.`);
424
+ }
425
+ if (selfClosing) {
426
+ close(node, tagEnd);
427
+ }
428
+ else {
429
+ enter(node);
430
+ }
431
+ cursor = tagEnd;
432
+ }
433
+ flushText();
434
+ // Whatever the file left open. Closed at the end of what there was, so the
435
+ // tree holds every element the file named even when it stops mid-document.
436
+ while (stack.length > 0) {
437
+ const orphan = leave();
438
+ note(`<${shown(orphan.name)}> is never closed; it was read to the end.`);
439
+ close(orphan, source.length);
440
+ }
441
+ if (unlisted > 0) {
442
+ notes.push(`+${unlisted} more problems of the kinds above were not listed.`);
443
+ }
444
+ return { root: roots[0], roots, notes };
445
+ }
446
+ /* ── Getting back out ─────────────────────────────────────────────────── */
447
+ /**
448
+ * The element's own bytes, from the string it was read from.
449
+ *
450
+ * This is what a quarantined fragment is (ADR 0012 D5): the file's text, not a
451
+ * re-rendering of this reader's tree. Comments, attribute order, prefixes and
452
+ * whitespace all survive, because none of them was ever taken apart.
453
+ */
454
+ export function xmlFragmentOf(source, node) {
455
+ return source.slice(node.range[0], node.range[1]);
456
+ }
457
+ /** The three characters that would otherwise start markup. */
458
+ function escapeText(value) {
459
+ return value
460
+ .replaceAll('&', '&amp;')
461
+ .replaceAll('<', '&lt;')
462
+ .replaceAll('>', '&gt;');
463
+ }
464
+ /** An attribute value — see `xml.ts`, whose escaping this mirrors exactly. */
465
+ function escapeAttr(value) {
466
+ return escapeText(value)
467
+ .replaceAll('"', '&quot;')
468
+ .replaceAll("'", '&apos;')
469
+ .replaceAll('\n', '&#10;')
470
+ .replaceAll('\r', '&#13;')
471
+ .replaceAll('\t', '&#9;');
472
+ }
473
+ /**
474
+ * A node back as XML, from the TREE rather than from the source.
475
+ *
476
+ * The fallback for a fragment that has no range to slice — one assembled by a
477
+ * caller, or read out of a document this function's caller no longer holds. It
478
+ * is faithful to what the tree records and therefore drops what the tree does
479
+ * not: comments, processing instructions, and the file's own indentation.
480
+ * Prefer {@link xmlFragmentOf} whenever the source string is still in hand.
481
+ */
482
+ export function serializeXmlNode(node) {
483
+ // A stack of nodes still to open and close tags still to write: a tree as
484
+ // deep as its author chose costs a loop, not the call stack.
485
+ const out = [];
486
+ const stack = [node];
487
+ while (stack.length > 0) {
488
+ const next = stack.pop();
489
+ if (typeof next === 'string') {
490
+ out.push(next);
491
+ continue;
492
+ }
493
+ const attrs = Object.entries(next.attrs)
494
+ .map(([key, value]) => ` ${key}="${escapeAttr(value)}"`)
495
+ .join('');
496
+ const text = next.text === undefined ? '' : escapeText(next.text);
497
+ if (text.length === 0 && next.children.length === 0) {
498
+ out.push(`<${next.name}${attrs}/>`);
499
+ continue;
500
+ }
501
+ out.push(`<${next.name}${attrs}>`, text);
502
+ stack.push(`</${next.name}>`);
503
+ for (let index = next.children.length - 1; index >= 0; index -= 1) {
504
+ stack.push(next.children[index]);
505
+ }
506
+ }
507
+ return out.join('');
508
+ }
509
+ /* ── Reading a tree ───────────────────────────────────────────────────── */
510
+ /** Children with this local name, whatever prefix the file gave them. */
511
+ export function xmlChildren(node, local) {
512
+ return node.children.filter(child => child.local === local);
513
+ }
514
+ /** The first child with this local name, if any. */
515
+ export function xmlChild(node, local) {
516
+ return node.children.find(child => child.local === local);
517
+ }
518
+ /**
519
+ * An attribute by LOCAL name — `xmi:id`, `xmi:ID` and a bare `id` all answer.
520
+ *
521
+ * Prefix-insensitive on purpose, and it is the difference between reading three
522
+ * tools and reading one: the XMI prefix is `xmi` by convention and by nothing
523
+ * else, Enterprise Architect has shipped files under `XMI`, and an
524
+ * attribute-defaulted file drops it altogether. The QUALIFIED name is still in
525
+ * `attrs` for anything that needs it, and {@link xmlFragmentOf} still writes
526
+ * back what the file said.
527
+ *
528
+ * The first match in document order wins, which only matters for a file that
529
+ * spells the same attribute twice under two prefixes.
530
+ */
531
+ export function xmlAttr(node, local) {
532
+ const direct = node.attrs[local];
533
+ if (direct !== undefined)
534
+ return direct;
535
+ for (const [key, value] of Object.entries(node.attrs)) {
536
+ if (splitName(key).local === local)
537
+ return value;
538
+ }
539
+ return undefined;
540
+ }
541
+ /** Every `xmlns` / `xmlns:*` declaration on this element, by prefix (`''`). */
542
+ export function xmlNamespaces(node) {
543
+ const declarations = {};
544
+ for (const [key, value] of Object.entries(node.attrs)) {
545
+ if (key === 'xmlns')
546
+ declarations[''] = value;
547
+ else if (key.startsWith('xmlns:'))
548
+ declarations[key.slice(6)] = value;
549
+ }
550
+ return declarations;
551
+ }
552
+ /**
553
+ * Every element under `node`, at any depth, in document order.
554
+ *
555
+ * An explicit stack rather than recursion: the depth of a file is whatever its
556
+ * author chose, and a hostile one nested a hundred thousand deep must cost a
557
+ * loop, not the call stack.
558
+ */
559
+ export function xmlDescendants(node) {
560
+ const found = [];
561
+ const stack = [...node.children].reverse();
562
+ while (stack.length > 0) {
563
+ const next = stack.pop();
564
+ found.push(next);
565
+ for (let index = next.children.length - 1; index >= 0; index -= 1) {
566
+ stack.push(next.children[index]);
567
+ }
568
+ }
569
+ return found;
570
+ }
@@ -1,4 +1,4 @@
1
- import { NOTATION_NEUTRALS } from '../../affine-shared/consts/index.js';
1
+ import { NOTATION_NEUTRALS } from '../../affine-shared/headless.js';
2
2
  /**
3
3
  * The default card: a plain rectangle in the shared notation scale's card
4
4
  * fill, no border, no radius.
@@ -4,6 +4,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
4
4
  export { CanvasElementType } from './element-model/index.js';
5
5
  export { CanvasRenderer } from './renderer/canvas-renderer.js';
6
6
  export { DomRenderer } from './renderer/dom-renderer.js';
7
+ export { isPainted } from './renderer/paint-predicate.js';
7
8
  export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
8
9
  export type { ElementRenderer } from './renderer/elements/index.js';
9
10
  export * from './renderer/elements/type.js';
@@ -6,6 +6,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
6
6
  export { CanvasElementType } from './element-model/index.js';
7
7
  export { CanvasRenderer } from './renderer/canvas-renderer.js';
8
8
  export { DomRenderer } from './renderer/dom-renderer.js';
9
+ export { isPainted } from './renderer/paint-predicate.js';
9
10
  export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
10
11
  export * from './renderer/elements/type.js';
11
12
  export { Overlay, OverlayIdentifier } from './renderer/overlay.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@labre/affine-block-surface/interchange` — the interchange platform with no
3
+ * editor behind it (ADR 0012, P3 as amended 2026-10-14).
4
+ *
5
+ * The registry's types and helpers, `InterchangeImportError`, the generic SVG
6
+ * reader, the framework-background geometry and the constants a framework's
7
+ * reader shares with the editor. What
8
+ * is NOT here is the point: the graph of this entry imports no Lit, no
9
+ * `@atlaskit/pragmatic-drag-and-drop`, no `@labre/std` runtime and no block —
10
+ * so a server (the labre import function, labre-mcp) loads the readers in a
11
+ * few hundred kilobytes instead of the whole editor.
12
+ *
13
+ * Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`, which
14
+ * bundles this entry and every framework's `./interchange` and fails on any
15
+ * forbidden module in the graph. A new export here is a new edge in that
16
+ * graph: import it from a file that is itself editor-free, never from the
17
+ * package index.
18
+ */
19
+ export * from './extensions/export-svg/marker-version.js';
20
+ export * from './extensions/interchange.js';
21
+ export * from './extensions/legend-role.js';
22
+ export * from './extensions/svg-sanitize.js';
23
+ export * from './extensions/svg-sketch.js';
24
+ export * from './extensions/svg-sketch-translations.js';
25
+ export * from './extensions/xml-element.js';
26
+ export * from './extensions/xml-reader.js';
27
+ export * from './framework-background/def.js';
28
+ export * from './framework-background/facts.js';
29
+ export * from './framework-background/labels.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@labre/affine-block-surface/interchange` — the interchange platform with no
3
+ * editor behind it (ADR 0012, P3 as amended 2026-10-14).
4
+ *
5
+ * The registry's types and helpers, `InterchangeImportError`, the generic SVG
6
+ * reader, the framework-background geometry and the constants a framework's
7
+ * reader shares with the editor. What
8
+ * is NOT here is the point: the graph of this entry imports no Lit, no
9
+ * `@atlaskit/pragmatic-drag-and-drop`, no `@labre/std` runtime and no block —
10
+ * so a server (the labre import function, labre-mcp) loads the readers in a
11
+ * few hundred kilobytes instead of the whole editor.
12
+ *
13
+ * Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`, which
14
+ * bundles this entry and every framework's `./interchange` and fails on any
15
+ * forbidden module in the graph. A new export here is a new edge in that
16
+ * graph: import it from a file that is itself editor-free, never from the
17
+ * package index.
18
+ */
19
+ export * from './extensions/export-svg/marker-version.js';
20
+ export * from './extensions/interchange.js';
21
+ export * from './extensions/legend-role.js';
22
+ export * from './extensions/svg-sanitize.js';
23
+ export * from './extensions/svg-sketch.js';
24
+ export * from './extensions/svg-sketch-translations.js';
25
+ export * from './extensions/xml-element.js';
26
+ export * from './extensions/xml-reader.js';
27
+ // The board geometry an exporter and a reader both compute with: a
28
+ // background's declaration, its plot and its instance zones. Pure data and
29
+ // arithmetic, editor-free like the rest.
30
+ export * from './framework-background/def.js';
31
+ export * from './framework-background/facts.js';
32
+ export * from './framework-background/labels.js';