@restless-dreams/orbit 0.0.0-stage → 0.2.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -2
  3. package/dist/bin/orbit.js +11666 -0
  4. package/dist/bin/orbit.js.map +60 -0
  5. package/dist/chunk-fkdfgtx9.js +17295 -0
  6. package/dist/chunk-fkdfgtx9.js.map +75 -0
  7. package/dist/chunk-y748hzcr.js +82 -0
  8. package/dist/chunk-y748hzcr.js.map +10 -0
  9. package/dist/chunk-zze4tc9n.js +408 -0
  10. package/dist/chunk-zze4tc9n.js.map +10 -0
  11. package/dist/core/auto-render.d.ts +85 -0
  12. package/dist/core/branch-colors.d.ts +57 -0
  13. package/dist/core/browser.d.ts +23 -0
  14. package/dist/core/create.d.ts +23 -0
  15. package/dist/core/diagnostics.d.ts +40 -0
  16. package/dist/core/dispatch.d.ts +23 -0
  17. package/dist/core/document-options.d.ts +18 -0
  18. package/dist/core/document-source.d.ts +128 -0
  19. package/dist/core/editing.d.ts +66 -0
  20. package/dist/core/engine/animation-clock.d.ts +40 -0
  21. package/dist/core/engine/camera.d.ts +70 -0
  22. package/dist/core/engine/colors.d.ts +51 -0
  23. package/dist/core/engine/display.d.ts +125 -0
  24. package/dist/core/engine/export.d.ts +32 -0
  25. package/dist/core/engine/glide.d.ts +34 -0
  26. package/dist/core/engine/labels.d.ts +106 -0
  27. package/dist/core/engine/node-drag.d.ts +49 -0
  28. package/dist/core/engine/scene.d.ts +160 -0
  29. package/dist/core/engine/spin.d.ts +62 -0
  30. package/dist/core/engine/surface-pattern.d.ts +20 -0
  31. package/dist/core/engine/text-measure.d.ts +116 -0
  32. package/dist/core/engine/views.d.ts +361 -0
  33. package/dist/core/extensions.d.ts +155 -0
  34. package/dist/core/format-upgrades.d.ts +33 -0
  35. package/dist/core/index.d.ts +46 -0
  36. package/dist/core/input-controller.d.ts +116 -0
  37. package/dist/core/languages.d.ts +201 -0
  38. package/dist/core/layout.d.ts +61 -0
  39. package/dist/core/map-tools/definitions.d.ts +71 -0
  40. package/dist/core/map-tools/input.d.ts +62 -0
  41. package/dist/core/map-tools/reading.d.ts +40 -0
  42. package/dist/core/map-tools/tools.d.ts +51 -0
  43. package/dist/core/markdown-writing.d.ts +87 -0
  44. package/dist/core/material.d.ts +25 -0
  45. package/dist/core/media.d.ts +42 -0
  46. package/dist/core/node-attributes.d.ts +31 -0
  47. package/dist/core/node-links.d.ts +25 -0
  48. package/dist/core/other-apps/freemind.d.ts +8 -0
  49. package/dist/core/other-apps/icons.d.ts +16 -0
  50. package/dist/core/other-apps/ideas.d.ts +76 -0
  51. package/dist/core/other-apps/import.d.ts +28 -0
  52. package/dist/core/other-apps/inflate.d.ts +22 -0
  53. package/dist/core/other-apps/markup.d.ts +51 -0
  54. package/dist/core/other-apps/notes.d.ts +80 -0
  55. package/dist/core/other-apps/opml.d.ts +26 -0
  56. package/dist/core/other-apps/reading.d.ts +36 -0
  57. package/dist/core/other-apps/xmind.d.ts +14 -0
  58. package/dist/core/other-apps/zip.d.ts +37 -0
  59. package/dist/core/parser-markdown.d.ts +42 -0
  60. package/dist/core/parser-yaml.d.ts +8 -0
  61. package/dist/core/print-layout.d.ts +70 -0
  62. package/dist/core/render-to-svg.d.ts +57 -0
  63. package/dist/core/renderer/content-painter.d.ts +30 -0
  64. package/dist/core/renderer/dom.d.ts +15 -0
  65. package/dist/core/renderer/families.d.ts +35 -0
  66. package/dist/core/renderer/label-painter.d.ts +93 -0
  67. package/dist/core/renderer/node-painter.d.ts +113 -0
  68. package/dist/core/renderer/numbers.d.ts +2 -0
  69. package/dist/core/renderer/options.d.ts +12 -0
  70. package/dist/core/renderer/rings-and-links.d.ts +23 -0
  71. package/dist/core/renderer/scene-helpers.d.ts +33 -0
  72. package/dist/core/renderer/trees.d.ts +22 -0
  73. package/dist/core/serializer-markdown.d.ts +18 -0
  74. package/dist/core/serializer-yaml.d.ts +2 -0
  75. package/dist/core/source-patch.d.ts +24 -0
  76. package/dist/core/source.d.ts +27 -0
  77. package/dist/core/styles.d.ts +6 -0
  78. package/dist/core/suggest.d.ts +8 -0
  79. package/dist/core/svg-renderer.d.ts +626 -0
  80. package/dist/core/text-layout.d.ts +54 -0
  81. package/dist/core/themes.d.ts +62 -0
  82. package/dist/core/types.d.ts +471 -0
  83. package/dist/core/ui/actions.d.ts +75 -0
  84. package/dist/core/ui/controls.d.ts +61 -0
  85. package/dist/core/ui/detail-sheet.d.ts +88 -0
  86. package/dist/core/ui/frames.d.ts +11 -0
  87. package/dist/core/ui/icons.d.ts +14 -0
  88. package/dist/core/ui/inline-editor.d.ts +79 -0
  89. package/dist/core/ui/media-players.d.ts +65 -0
  90. package/dist/core/ui/media-viewer.d.ts +169 -0
  91. package/dist/core/ui/media-zoom.d.ts +51 -0
  92. package/dist/core/ui/note-markdown.d.ts +19 -0
  93. package/dist/core/ui/outline.d.ts +44 -0
  94. package/dist/core/url-guard.d.ts +40 -0
  95. package/dist/core/utils.d.ts +23 -0
  96. package/dist/core/web-page/page.d.ts +59 -0
  97. package/dist/core/web-page/runtime.d.ts +13 -0
  98. package/dist/core/web-page/start.d.ts +1 -0
  99. package/dist/core/yaml-mini.d.ts +54 -0
  100. package/dist/index.d.ts +1 -0
  101. package/dist/index.js +4357 -0
  102. package/dist/index.js.map +29 -0
  103. package/dist/marked/index.d.ts +31 -0
  104. package/dist/marked/index.js +110 -0
  105. package/dist/marked/index.js.map +10 -0
  106. package/dist/react/index.d.ts +55 -0
  107. package/dist/react/index.js +352 -0
  108. package/dist/react/index.js.map +1 -0
  109. package/dist/web-page.d.ts +2 -0
  110. package/dist/web-page.js +130 -0
  111. package/llms-full.txt +2785 -0
  112. package/llms.txt +36 -0
  113. package/package.json +118 -4
  114. package/skills/orbit/SKILL.md +140 -0
  115. package/skills/orbit/references/editing.md +195 -0
  116. package/skills/orbit/references/extending.md +247 -0
  117. package/skills/orbit/references/format.md +331 -0
  118. package/skills/orbit/references/guide.md +546 -0
  119. package/skills/orbit/references/import-export.md +115 -0
  120. package/skills/orbit/references/languages.md +160 -0
  121. package/skills/orbit/references/map-tools.md +188 -0
  122. package/skills/orbit/references/media.md +160 -0
  123. package/skills/orbit/references/recipes.md +236 -0
  124. package/skills/orbit/references/theming.md +199 -0
package/dist/index.js ADDED
@@ -0,0 +1,4357 @@
1
+ import {
2
+ renderToSVGString2
3
+ } from "./chunk-y748hzcr.js";
4
+ import {
5
+ renderOrbitElement2,
6
+ renderOrbitMarkdownBlocks2,
7
+ defineOrbitMapElement2
8
+ } from "./chunk-zze4tc9n.js";
9
+ import {
10
+ assignKey,
11
+ emitYaml,
12
+ nodeSlug,
13
+ setParent,
14
+ ensureUniqueNodeIds,
15
+ nodesWithExplicitIds,
16
+ findNodeById2,
17
+ getNodePath2,
18
+ guardUrl,
19
+ resolveUrl,
20
+ videoPageUrl,
21
+ youtubeThumbnailUrl,
22
+ hasMediaSource,
23
+ DIAGNOSTIC_CODES2,
24
+ OrbitParseError2,
25
+ formatDiagnostic2,
26
+ reportParseDiagnostics,
27
+ branchPalette,
28
+ suggest,
29
+ mediaExtras,
30
+ THEME_TOKENS2,
31
+ ORBIT_THEME_PRESETS2,
32
+ isOrbitThemePreset2,
33
+ themeName,
34
+ registerTheme2,
35
+ isThemeName2,
36
+ themeNames2,
37
+ expandTheme,
38
+ mergeThemes,
39
+ themeTokens2,
40
+ resolveThemeTokens,
41
+ resolvePalette,
42
+ documentToRendererOptions2,
43
+ isNodeLink,
44
+ findLinkedNode,
45
+ findIndexedLinkedNode,
46
+ indexNodes,
47
+ nodeLinksOf,
48
+ SUPPORTED_SCHEMA_VERSION2,
49
+ leadingEmoji,
50
+ parseOrbitMarkdown2,
51
+ parseOrbitYAML2,
52
+ parseOrbit2,
53
+ serializeOrbitMarkdown2,
54
+ newNodeFrom,
55
+ addToTree,
56
+ updateInTree,
57
+ removeFromTree,
58
+ moveInTree,
59
+ ensureStyles,
60
+ assignNodeColors,
61
+ registerLanguage2,
62
+ languageTags2,
63
+ OrbitWords,
64
+ wordsFor,
65
+ parseColor,
66
+ toHex,
67
+ toOklch,
68
+ toneColor,
69
+ resolveColor,
70
+ renderNoteMarkdown2,
71
+ statusTone,
72
+ registerNodeType2,
73
+ registerAction2,
74
+ registerStatus2,
75
+ MapExtensions,
76
+ upgradeNodeTypes2,
77
+ guarded,
78
+ OrbitSVGRenderer2
79
+ } from "./chunk-fkdfgtx9.js";
80
+ // src/core/serializer-yaml.ts
81
+ var serializeOrbitYAML = (doc) => {
82
+ const out = {};
83
+ const opts = doc.options;
84
+ if (opts.title) {
85
+ out.title = opts.title;
86
+ }
87
+ if (opts.theme) {
88
+ out.theme = opts.theme;
89
+ }
90
+ if (opts.viewMode) {
91
+ out.view = opts.viewMode;
92
+ }
93
+ if (opts.showRings !== undefined) {
94
+ out.showRings = opts.showRings;
95
+ }
96
+ if (opts.interactive !== undefined) {
97
+ out.interactive = opts.interactive;
98
+ }
99
+ if (opts.orbitAnimation !== undefined) {
100
+ out.animation = opts.orbitAnimation;
101
+ }
102
+ if (opts.orbitSpeed !== undefined) {
103
+ out.speed = opts.orbitSpeed;
104
+ }
105
+ if (opts.palette) {
106
+ out.palette = opts.palette;
107
+ }
108
+ if (opts.height !== undefined) {
109
+ out.height = opts.height;
110
+ }
111
+ if (opts.maxDepth !== undefined) {
112
+ out.maxDepth = opts.maxDepth;
113
+ }
114
+ if (opts.className) {
115
+ out.className = opts.className;
116
+ }
117
+ if (opts.baseUrl) {
118
+ out.baseUrl = opts.baseUrl;
119
+ }
120
+ if (opts.schemaVersion !== undefined) {
121
+ out.schemaVersion = opts.schemaVersion;
122
+ }
123
+ for (const [key, value] of Object.entries(doc.extra ?? {})) {
124
+ if (!Object.prototype.hasOwnProperty.call(out, key) && key !== "nodes") {
125
+ assignKey(out, key, value);
126
+ }
127
+ }
128
+ out.nodes = [serializeNode(doc.root, nodesWithExplicitIds(doc.root))];
129
+ return emitYaml(out);
130
+ };
131
+ var serializeNode = (n, explicitIds) => {
132
+ const obj = { text: n.text };
133
+ if (explicitIds.has(n)) {
134
+ obj.id = n.id;
135
+ }
136
+ if (n.icon) {
137
+ obj.icon = n.icon;
138
+ }
139
+ if (n.color) {
140
+ obj.color = n.color;
141
+ }
142
+ if (n.paletteColor !== undefined) {
143
+ obj.paletteColor = n.paletteColor;
144
+ }
145
+ if (n.type) {
146
+ obj.type = n.typeVersion === undefined ? n.type : `${n.type}@${n.typeVersion}`;
147
+ }
148
+ if (n.link) {
149
+ obj.link = n.link;
150
+ }
151
+ if (n.tags) {
152
+ obj.tags = n.tags;
153
+ }
154
+ if (n.status) {
155
+ obj.status = n.status;
156
+ }
157
+ if (n.priority !== undefined) {
158
+ obj.priority = n.priority;
159
+ }
160
+ if (n.collapsed !== undefined) {
161
+ obj.collapsed = n.collapsed;
162
+ }
163
+ if (n.image) {
164
+ obj.image = { ...mediaExtras(n.image), ...n.image };
165
+ }
166
+ if (n.video) {
167
+ obj.video = { ...mediaExtras(n.video), ...n.video };
168
+ }
169
+ if (n.embed) {
170
+ obj.embed = { ...mediaExtras(n.embed), ...n.embed };
171
+ }
172
+ if (n.note) {
173
+ obj.note = n.note;
174
+ if (n.noteFormat && n.noteFormat !== "markdown") {
175
+ obj.noteFormat = n.noteFormat;
176
+ }
177
+ }
178
+ if (n.attrs) {
179
+ for (const [k, v] of Object.entries(n.attrs)) {
180
+ if (!RESERVED_NODE_KEYS.has(k) && !Object.prototype.hasOwnProperty.call(obj, k)) {
181
+ assignKey(obj, k, v);
182
+ }
183
+ }
184
+ }
185
+ if (n.children.length) {
186
+ obj.children = n.children.map((child) => serializeNode(child, explicitIds));
187
+ }
188
+ return obj;
189
+ };
190
+ var RESERVED_NODE_KEYS = new Set(["text", "children"]);
191
+ // src/core/create.ts
192
+ var createOrbitMap = (target, source, options = {}) => {
193
+ const { strict, ...rendererOptions } = options;
194
+ const renderer = new OrbitSVGRenderer2(target, rendererOptions);
195
+ if (typeof source === "string") {
196
+ const doc = parseOrbit2(source, { strict, untitled: wordsFor(rendererOptions.language, target, rendererOptions.strings).text("untitledMap") });
197
+ reportParseDiagnostics(doc);
198
+ renderer.render(doc);
199
+ } else {
200
+ renderer.render(source);
201
+ }
202
+ return renderer;
203
+ };
204
+ // src/core/print-layout.ts
205
+ var PRINT_THEMES = {
206
+ auto: "paper",
207
+ night: "paper",
208
+ "glass-night": "glass",
209
+ cosmos: "paper",
210
+ blueprint: "ink",
211
+ "flat-night": "flat",
212
+ "material-night": "material",
213
+ "liquid-night": "liquid"
214
+ };
215
+ var PALETTE_TOKENS = ["color-root", "color-1", "color-2", "color-3", "color-4", "color-5", "color-6", "color-7"];
216
+ var PICTURE_SIZE = 720;
217
+ var printTheme = (theme) => {
218
+ const expanded = expandTheme(theme);
219
+ const preset = themeName(expanded);
220
+ const light = PRINT_THEMES[preset] ?? (preset === "auto" ? "paper" : preset);
221
+ const own = typeof expanded === "object" ? expanded.tokens ?? {} : {};
222
+ const kept = light === preset ? own : Object.fromEntries(Object.entries(own).filter(([token]) => PALETTE_TOKENS.includes(token)));
223
+ return { preset: light, tokens: { ...kept, surface: "transparent", "surface-pattern": "none" } };
224
+ };
225
+ var renderPrintLayout = (container, input, options = {}) => {
226
+ const words = new OrbitWords(options.language, options.strings);
227
+ const map = typeof input === "string" ? parseOrbit2(input, { untitled: words.text("untitledMap") }) : input;
228
+ const fromDocument = documentToRendererOptions2(map.options);
229
+ const theme = printTheme(mergeThemes(fromDocument.theme, options.theme));
230
+ const tokens = resolveThemeTokens(themeName(theme), theme);
231
+ const extensions = new MapExtensions({ nodeTypes: options.nodeTypes, statuses: options.statuses, nodeTypeFor: options.nodeTypeFor });
232
+ const palette = options.palette ?? fromDocument.palette;
233
+ const colors = assignNodeColors(map.root, resolvePalette(palette), (node) => extensions.color(node));
234
+ const nodes = indexNodes(map.root);
235
+ const layout = {
236
+ words,
237
+ tokens,
238
+ extensions,
239
+ colors,
240
+ baseUrl: options.baseUrl ?? fromDocument.baseUrl,
241
+ linked: (href) => findIndexedLinkedNode(nodes, href)
242
+ };
243
+ const title = options.title ?? (map.options.title || map.root.text || words.text("untitledMap"));
244
+ const article = element("article", "orbit-print");
245
+ article.lang = words.locale;
246
+ article.style.cssText = themeProperties(tokens);
247
+ const heading = element("h1", "orbit-print__title");
248
+ appendName(heading, map.root, title, layout);
249
+ article.append(heading, ...entryParts(map.root, layout));
250
+ if (options.picture !== false) {
251
+ const figure = element("figure", "orbit-print__picture");
252
+ const svg = renderToSVGString2(map, {
253
+ width: PICTURE_SIZE,
254
+ height: PICTURE_SIZE,
255
+ viewMode: options.viewMode,
256
+ focus: options.focus,
257
+ theme,
258
+ palette,
259
+ title,
260
+ language: options.language,
261
+ strings: options.strings,
262
+ baseUrl: layout.baseUrl,
263
+ idPrefix: options.idPrefix ?? "orbit-print",
264
+ nodeTypes: options.nodeTypes,
265
+ statuses: options.statuses,
266
+ nodeTypeFor: options.nodeTypeFor
267
+ });
268
+ figure.appendChild(document.importNode(new DOMParser().parseFromString(svg, "image/svg+xml").documentElement, true));
269
+ article.appendChild(figure);
270
+ }
271
+ if (map.root.children.length > 0) {
272
+ const outline = element("div", "orbit-print__outline");
273
+ outline.append(...map.root.children.map((child) => idea(child, 1, layout)));
274
+ article.appendChild(outline);
275
+ }
276
+ container.replaceChildren(article);
277
+ ensureStyles(article);
278
+ return article;
279
+ };
280
+ var idea = (node, depth, layout) => {
281
+ const section = element("section", "orbit-print__idea");
282
+ section.dataset.depth = String(depth);
283
+ const entry = element("div", node.children.length > 0 ? "orbit-print__entry orbit-print__entry--parent" : "orbit-print__entry");
284
+ const level = `h${Math.min(depth + 1, 6)}`;
285
+ const name = element(level, `orbit-print__name orbit-print__name--depth-${Math.min(depth, 3)}`);
286
+ const dot = element("span", "orbit-print__dot");
287
+ dot.setAttribute("aria-hidden", "true");
288
+ dot.style.setProperty("--orbit-print-dot", resolveColor(layout.colors.get(node.id) ?? layout.tokens["color-1"] ?? "#888888", layout.tokens));
289
+ name.appendChild(dot);
290
+ appendName(name, node, node.text || layout.words.text("untitled"), layout);
291
+ entry.append(name, ...entryParts(node, layout));
292
+ section.appendChild(entry);
293
+ if (node.children.length > 0) {
294
+ const children = element("div", "orbit-print__children");
295
+ children.append(...node.children.map((child) => idea(child, depth + 1, layout)));
296
+ section.appendChild(children);
297
+ }
298
+ return section;
299
+ };
300
+ var appendName = (heading, node, text, layout) => {
301
+ const icon = layout.extensions.icon(node);
302
+ if (icon) {
303
+ const glyph = element("span", "orbit-print__icon");
304
+ glyph.textContent = icon;
305
+ heading.append(glyph, document.createTextNode(" "));
306
+ }
307
+ heading.appendChild(document.createTextNode(text));
308
+ };
309
+ var entryParts = (node, layout) => {
310
+ const parts = [];
311
+ const chips = chipsOf(node, layout);
312
+ if (chips) {
313
+ parts.push(chips);
314
+ }
315
+ const link = linkOf(node, layout);
316
+ if (link) {
317
+ parts.push(link);
318
+ }
319
+ const details = layout.extensions.nodeType(node)?.details;
320
+ if (details) {
321
+ const section = element("div", "orbit-print__details");
322
+ guarded("A node type’s details", undefined, () => details(node, section));
323
+ if (section.childNodes.length > 0) {
324
+ parts.push(section);
325
+ }
326
+ }
327
+ if (node.note?.trim()) {
328
+ const note = element("div", "orbit-print__note");
329
+ if (node.noteFormat === "plain") {
330
+ note.classList.add("orbit-print__note--plain");
331
+ note.textContent = node.note;
332
+ } else {
333
+ note.appendChild(renderNoteMarkdown2(node.note, { baseUrl: layout.baseUrl, resolveLink: layout.linked }));
334
+ }
335
+ parts.push(note);
336
+ }
337
+ parts.push(...mediaOf(node, layout));
338
+ return parts;
339
+ };
340
+ var chipsOf = (node, layout) => {
341
+ const chips = [];
342
+ const typeLabel = layout.extensions.nodeType(node)?.label;
343
+ if (typeLabel) {
344
+ chips.push(chip(typeLabel));
345
+ }
346
+ if (node.status) {
347
+ const status = chip(node.status, "orbit-print__chip--status");
348
+ const look = layout.extensions.status(node.status);
349
+ status.style.setProperty("--orbit-print-status", resolveColor(look.color ?? `var(--_orbit-status-${look.tone})`, layout.tokens));
350
+ chips.push(status);
351
+ }
352
+ if (node.priority !== undefined) {
353
+ chips.push(chip(layout.words.text("priority", { priority: node.priority })));
354
+ }
355
+ for (const tag of node.tags ?? []) {
356
+ chips.push(chip(`#${tag}`));
357
+ }
358
+ if (chips.length === 0) {
359
+ return null;
360
+ }
361
+ const container = element("p", "orbit-print__chips");
362
+ container.append(...chips);
363
+ return container;
364
+ };
365
+ var chip = (text, modifier) => {
366
+ const span = element("span", modifier ? `orbit-print__chip ${modifier}` : "orbit-print__chip");
367
+ span.textContent = text;
368
+ return span;
369
+ };
370
+ var linkOf = (node, layout) => {
371
+ if (!node.link) {
372
+ return null;
373
+ }
374
+ const paragraph = element("p", "orbit-print__link");
375
+ if (isNodeLink(node.link)) {
376
+ const target = layout.linked(node.link);
377
+ if (!target) {
378
+ return null;
379
+ }
380
+ paragraph.textContent = `→ ${layout.words.text("seeIdea", { name: target.text || layout.words.text("theIdea") })}`;
381
+ return paragraph;
382
+ }
383
+ const address = guardUrl(node.link, "link");
384
+ if (!address) {
385
+ return null;
386
+ }
387
+ paragraph.appendChild(addressLink(resolveUrl(address, layout.baseUrl)));
388
+ return paragraph;
389
+ };
390
+ var addressLink = (href) => {
391
+ const anchor = element("a", "orbit-print__address");
392
+ anchor.href = href;
393
+ anchor.textContent = href;
394
+ return anchor;
395
+ };
396
+ var mediaOf = (node, layout) => {
397
+ const figures = [];
398
+ if (hasMediaSource(node.image)) {
399
+ const source = guardUrl(node.image.src, "image");
400
+ if (source) {
401
+ const figure = element("figure", "orbit-print__media");
402
+ const image = element("img", "orbit-print__image");
403
+ image.src = resolveUrl(source, layout.baseUrl);
404
+ image.alt = node.image.alt ?? "";
405
+ figure.appendChild(image);
406
+ appendCaption(figure, node.image.caption ?? node.image.alt);
407
+ figures.push(figure);
408
+ }
409
+ }
410
+ if (hasMediaSource(node.video)) {
411
+ const source = guardUrl(node.video.src, "video");
412
+ if (source) {
413
+ const resolved = resolveUrl(source, layout.baseUrl);
414
+ const figure = element("figure", "orbit-print__media orbit-print__media--video");
415
+ const thumbnail = node.video.thumbnail ? guardUrl(node.video.thumbnail, "image") : youtubeThumbnailUrl(resolved);
416
+ if (thumbnail) {
417
+ const image = element("img", "orbit-print__image");
418
+ image.src = resolveUrl(thumbnail, layout.baseUrl);
419
+ image.alt = "";
420
+ figure.appendChild(image);
421
+ }
422
+ const caption = element("figcaption", "orbit-print__caption");
423
+ caption.append(document.createTextNode(`${node.video.caption ?? layout.words.text("video")}: `), addressLink(videoPageUrl(resolved, node.video.provider)));
424
+ figure.appendChild(caption);
425
+ figures.push(figure);
426
+ }
427
+ }
428
+ if (hasMediaSource(node.embed) && !hasMediaSource(node.video)) {
429
+ const source = guardUrl(node.embed.src, "embed");
430
+ if (source) {
431
+ const figure = element("figure", "orbit-print__media orbit-print__media--embed");
432
+ const caption = element("figcaption", "orbit-print__caption");
433
+ if (node.embed.caption) {
434
+ caption.append(document.createTextNode(`${node.embed.caption}: `));
435
+ }
436
+ caption.appendChild(addressLink(resolveUrl(source, layout.baseUrl)));
437
+ figure.appendChild(caption);
438
+ figures.push(figure);
439
+ }
440
+ }
441
+ return figures;
442
+ };
443
+ var appendCaption = (figure, text) => {
444
+ if (text?.trim()) {
445
+ const caption = element("figcaption", "orbit-print__caption");
446
+ caption.textContent = text;
447
+ figure.appendChild(caption);
448
+ }
449
+ };
450
+ var themeProperties = (tokens) => {
451
+ const value = (token, fallback) => resolveColor(tokens[token] ?? fallback, tokens);
452
+ const font = (token, fallback) => resolveFont(tokens[token] ?? fallback, tokens);
453
+ return [
454
+ `--_orbit-print-text: ${value("text", "#1F2733")};`,
455
+ `--_orbit-print-muted-text: ${value("muted-text", "#5B6474")};`,
456
+ `--_orbit-print-link-text: ${value("link-text", value("text", "#1F2733"))};`,
457
+ `--_orbit-print-chip-background: ${value("chip-background", "#EEF0F4")};`,
458
+ `--_orbit-print-chip-text: ${value("chip-text", value("text", "#1F2733"))};`,
459
+ `--_orbit-print-border: ${value("sheet-border", "#D9DDE3")};`,
460
+ `--_orbit-print-font: ${font("font-family", "sans-serif")};`,
461
+ `--_orbit-print-display-font: ${font("display-font", font("font-family", "sans-serif"))};`
462
+ ].join(" ");
463
+ };
464
+ var resolveFont = (value, tokens) => {
465
+ let font = value;
466
+ for (let depth = 0;depth < 4 && font.includes("var("); depth += 1) {
467
+ font = font.replace(/var\(--_?orbit-([a-z0-9-]+)(?:\s*,\s*([^()]+))?\)/gi, (whole, token, fallback) => tokens[token] ?? fallback?.trim() ?? whole);
468
+ }
469
+ return font.includes("var(") || /[;{}]/.test(font) ? "sans-serif" : font;
470
+ };
471
+ var element = (name, className) => {
472
+ const created = document.createElement(name);
473
+ created.className = className;
474
+ return created;
475
+ };
476
+ // src/core/web-page/page.ts
477
+ var ORBIT_ICON = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='18 18 72 72'%3E%3Cdefs%3E%3CradialGradient id='d' gradientUnits='userSpaceOnUse' cx='32.4' cy='23.76' r='102.6'%3E%3Cstop offset='0' stop-color='%234A35A8'/%3E%3Cstop offset='1' stop-color='%2318134A'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect x='18' y='18' width='72' height='72' rx='16' fill='url(%23d)'/%3E%3Cpath d='M67.53,35.36 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M69.84,37.38 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M71.86,39.69 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M76.52,57.07 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M75.92,60.08 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M74.93,62.99 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M62.21,75.71 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M59.3,76.7 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M56.29,77.3 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M38.91,72.64 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M36.6,70.62 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M34.58,68.31 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M29.92,50.93 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M30.52,47.92 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M31.51,45.01 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M44.23,32.29 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M47.14,31.3 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M50.15,30.7 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z' fill='%23E2E6FF' fill-opacity='.5'/%3E%3Ccircle cx='54' cy='54' r='11' fill='%23FAD86F'/%3E%3Ccircle cx='60.08' cy='31.3' r='6.4' fill='%23FF9E96'/%3E%3Ccircle cx='76.7' cy='47.92' r='6.4' fill='%23F8B478'/%3E%3Ccircle cx='70.62' cy='70.62' r='6.4' fill='%237FDAAB'/%3E%3Ccircle cx='47.92' cy='76.7' r='6.4' fill='%2386CDFF'/%3E%3Ccircle cx='31.3' cy='60.08' r='6.4' fill='%23C9B1FE'/%3E%3Ccircle cx='37.38' cy='37.38' r='6.4' fill='%23F7A3D4'/%3E%3C/svg%3E";
478
+ var exportWebPage = (input, options) => {
479
+ const document2 = typeof input === "string" ? parseOrbit2(input) : input;
480
+ const source = typeof input === "string" ? input : serializeOrbitMarkdown2(input);
481
+ const words = new OrbitWords(options.language, options.strings);
482
+ const title = options.title ?? (document2.options.title || document2.root.text || "Orbit");
483
+ const data = {
484
+ source,
485
+ theme: options.theme,
486
+ viewMode: options.viewMode,
487
+ focus: options.focus,
488
+ language: options.language,
489
+ strings: options.strings,
490
+ media: options.media ?? {}
491
+ };
492
+ const theme = options.theme ?? document2.options.theme;
493
+ return `<!doctype html>
494
+ <html lang="${escapeAttribute(words.locale)}">
495
+ <head>
496
+ <meta charset="utf-8">
497
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
498
+ <meta name="generator" content="Orbit">
499
+ <link rel="icon" href="${ORBIT_ICON}">
500
+ <title>${escapeText(title)}</title>
501
+ <style>
502
+ ${pageStyles(theme)}
503
+ </style>
504
+ </head>
505
+ <body>
506
+ <div class="page" id="page">
507
+ <div class="page__map" id="page-map" aria-label="${escapeAttribute(title)}"></div>
508
+ <button class="page__present" type="button" data-present><span aria-hidden="true">▶</span> ${escapeText(words.text("present"))}</button>
509
+ <section class="page__caption" id="page-caption" aria-live="polite" hidden>
510
+ <h1 class="page__title" id="page-title"></h1>
511
+ <div class="page__note" id="page-note"></div>
512
+ </section>
513
+ <nav class="page__controls" id="page-controls" hidden>
514
+ <button class="page__button" type="button" data-step="-1" aria-label="${escapeAttribute(words.text("previous"))}">‹</button>
515
+ <span class="page__count" id="page-count"></span>
516
+ <button class="page__button" type="button" data-step="1" aria-label="${escapeAttribute(words.text("next"))}">›</button>
517
+ <button class="page__button" type="button" data-close aria-label="${escapeAttribute(words.text("close"))}">✕</button>
518
+ </nav>
519
+ </div>
520
+ <div class="page__print" id="page-print"></div>
521
+ <script type="application/json" id="orbit-page">${scriptJson(data)}</script>
522
+ <script type="application/json" id="orbit-script">${scriptJson(options.script)}</script>
523
+ <script>
524
+ // Orbit runs from a script element made here: text inside one is never read as HTML.
525
+ (function () {
526
+ var script = document.createElement('script');
527
+ script.textContent = JSON.parse(document.getElementById('orbit-script').textContent);
528
+ document.body.appendChild(script);
529
+ })();
530
+ </script>
531
+ </body>
532
+ </html>
533
+ `;
534
+ };
535
+ var pageColors = (tokens) => {
536
+ const surface = tokens.surface === "transparent" ? "#ffffff" : tokens.surface;
537
+ return [
538
+ `--page-background: ${surface};`,
539
+ `--page-card: ${tokens["sheet-background"]};`,
540
+ `--page-text: ${tokens["sheet-text"]};`,
541
+ `--page-muted: ${tokens["sheet-muted-text"]};`,
542
+ `--page-border: ${tokens["sheet-border"]};`,
543
+ `--page-shadow: ${tokens["sheet-shadow"]};`
544
+ ].join(" ");
545
+ };
546
+ var tokensOf = (theme) => {
547
+ const expanded = expandTheme(theme);
548
+ const preset = typeof expanded === "string" ? expanded : expanded?.preset;
549
+ const base = themeTokens2(isOrbitThemePreset2(preset) ? preset : "paper");
550
+ return typeof expanded === "object" && expanded?.tokens ? { ...base, ...expanded.tokens } : base;
551
+ };
552
+ var pageStyles = (theme) => {
553
+ const automatic = (typeof theme === "string" ? theme : theme?.preset) === "auto";
554
+ const colors = automatic ? `:root { ${pageColors(themeTokens2("paper"))} }
555
+ @media (prefers-color-scheme: dark) { :root { ${pageColors(themeTokens2("night"))} } }` : `:root { ${pageColors(tokensOf(theme))} }`;
556
+ return `${colors}
557
+ html, body { margin: 0; height: 100%; background: var(--page-background); }
558
+ body { font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--page-text); }
559
+ .page__map { position: fixed; inset: 0; }
560
+ .page__present, .page__controls {
561
+ position: fixed; left: 50%; bottom: max(16px, env(safe-area-inset-bottom)); transform: translateX(-50%);
562
+ display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 18px; border: 1px solid var(--page-border);
563
+ border-radius: 22px; background: var(--page-card); color: var(--page-text); box-shadow: var(--page-shadow); font: inherit; font-weight: 600;
564
+ }
565
+ .page__present { cursor: pointer; }
566
+ .page__controls { padding: 0 6px; transition: opacity 200ms ease; }
567
+ .page__button { min-width: 44px; min-height: 44px; border: 0; border-radius: 22px; background: none; color: inherit; font: inherit; font-size: 20px; cursor: pointer; }
568
+ .page__button:focus-visible, .page__present:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
569
+ .page__count { min-width: 4em; text-align: center; color: var(--page-muted); font-variant-numeric: tabular-nums; }
570
+ .page__caption {
571
+ position: fixed; left: 50%; bottom: calc(max(16px, env(safe-area-inset-bottom)) + 60px); transform: translateX(-50%);
572
+ box-sizing: border-box; width: min(720px, calc(100% - 32px)); max-height: 40vh; overflow: auto; padding: 16px 20px;
573
+ border: 1px solid var(--page-border); border-radius: 16px; background: var(--page-card); color: var(--page-text); box-shadow: var(--page-shadow);
574
+ }
575
+ .page__title { margin: 0; font-size: 20px; line-height: 1.3; }
576
+ .page__note:not(:empty) { margin-top: 8px; }
577
+ .page__note p { margin: 0 0 8px; }
578
+ .page__note img { max-width: 100%; border-radius: 8px; }
579
+ .page__note a { color: inherit; }
580
+ .page--presenting .page__present { display: none; }
581
+ .page--presenting:not(.page--controls) .page__controls { opacity: 0; }
582
+ .page--presenting:not(.page--controls) { cursor: none; }
583
+ [hidden] { display: none !important; }
584
+ .page__print { display: none; }
585
+ @media print {
586
+ @page { margin: 16mm 14mm; }
587
+ html, body { height: auto; background: #ffffff; }
588
+ .page { display: none; }
589
+ .page__print { display: block; }
590
+ }`;
591
+ };
592
+ var scriptJson = (value) => {
593
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
594
+ };
595
+ var escapeText = (text) => {
596
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
597
+ };
598
+ var escapeAttribute = (text) => {
599
+ return escapeText(text).replace(/"/g, "&quot;");
600
+ };
601
+ // src/core/map-tools/input.ts
602
+ var schemaOf = (fields) => {
603
+ const required = Object.keys(fields).filter((name) => fields[name].required);
604
+ return {
605
+ type: "object",
606
+ properties: Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, propertyOf(field)])),
607
+ ...required.length > 0 ? { required } : {}
608
+ };
609
+ };
610
+ var propertyOf = (field) => {
611
+ if (field.type === "list") {
612
+ return { type: "array", description: field.description, items: field.values ? { type: "string", enum: [...field.values] } : { type: "string" } };
613
+ }
614
+ if (field.type === "record") {
615
+ return { ...schemaOf(field.fields ?? {}), description: field.description };
616
+ }
617
+ return { type: field.type, description: field.description, ...field.minimum === undefined ? {} : { minimum: field.minimum } };
618
+ };
619
+
620
+ class InputError extends Error {
621
+ }
622
+ var readInput = (tool, fields, input, synonyms = {}) => {
623
+ const record = inputRecord(tool, fields, input);
624
+ const values = {};
625
+ const ignored = [];
626
+ const names = Object.keys(fields);
627
+ for (const [given, value] of Object.entries(record)) {
628
+ if (value === null || value === undefined) {
629
+ continue;
630
+ }
631
+ const name = owns(fields, given) ? given : synonymOf(given, names, synonyms);
632
+ if (name === undefined) {
633
+ const near = suggest(given, names);
634
+ if (near !== undefined) {
635
+ throw new InputError(`${tool} has no field "${given}". Did you mean "${near}"? It takes ${list(names)}.`);
636
+ }
637
+ ignored.push(given);
638
+ continue;
639
+ }
640
+ if (owns(values, name)) {
641
+ throw new InputError(`${tool} got ${name} twice, as "${name}" and as "${given}": give it once.`);
642
+ }
643
+ values[name] = readValue(name, fields[name], value);
644
+ }
645
+ for (const name of names) {
646
+ if (fields[name].required && !owns(values, name)) {
647
+ throw new InputError(`${tool} needs ${name}: ${lowerFirst(fields[name].description)}`);
648
+ }
649
+ }
650
+ return { values, ignored };
651
+ };
652
+ var inputRecord = (tool, fields, input) => {
653
+ let value = input;
654
+ if (typeof value === "string") {
655
+ if (value.trim() === "") {
656
+ return {};
657
+ }
658
+ try {
659
+ value = JSON.parse(value);
660
+ } catch (error) {
661
+ throw new InputError(`${tool}'s input isn't valid JSON (${error.message}). Send an object, like ${example(fields)}.`);
662
+ }
663
+ }
664
+ if (value === null || value === undefined) {
665
+ return {};
666
+ }
667
+ if (typeof value !== "object" || Array.isArray(value)) {
668
+ throw new InputError(`${tool} takes an object, like ${example(fields)}, not ${describe(value)}.`);
669
+ }
670
+ return value;
671
+ };
672
+ var synonymOf = (given, names, synonyms) => {
673
+ const lowered = given.toLowerCase();
674
+ return names.find((name) => name.toLowerCase() === lowered) ?? (owns(synonyms, lowered) ? synonyms[lowered] : undefined);
675
+ };
676
+ var owns = (record, key) => {
677
+ return Object.prototype.hasOwnProperty.call(record, key);
678
+ };
679
+ var readValue = (name, field, value) => {
680
+ switch (field.type) {
681
+ case "string":
682
+ if (typeof value === "string") {
683
+ return value;
684
+ }
685
+ if (typeof value === "number" || typeof value === "boolean") {
686
+ return String(value);
687
+ }
688
+ throw new InputError(`${name} takes text, not ${describe(value)}.`);
689
+ case "integer":
690
+ case "number": {
691
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
692
+ if (!Number.isFinite(number) || field.type === "integer" && !Number.isInteger(number)) {
693
+ throw new InputError(`${name} takes ${field.type === "integer" ? "a whole number" : "a number"}, like ${field.minimum ?? 1}, not ${describe(value)}.`);
694
+ }
695
+ if (field.minimum !== undefined && number < field.minimum) {
696
+ throw new InputError(`${name} takes ${field.minimum} or more, not ${number}. ${field.description}`);
697
+ }
698
+ return number;
699
+ }
700
+ case "boolean": {
701
+ const word = typeof value === "string" ? value.trim().toLowerCase() : value;
702
+ if (word === true || word === "true" || word === "yes") {
703
+ return true;
704
+ }
705
+ if (word === false || word === "false" || word === "no") {
706
+ return false;
707
+ }
708
+ throw new InputError(`${name} takes true or false, not ${describe(value)}.`);
709
+ }
710
+ case "list": {
711
+ const items = Array.isArray(value) ? value : typeof value === "string" ? value.trim() === "" ? [] : value.split(",") : null;
712
+ if (!items || items.some((item) => typeof item !== "string" && typeof item !== "number")) {
713
+ throw new InputError(`${name} takes a list of words, like ["${field.values?.[0] ?? "design"}"], not ${describe(value)}.`);
714
+ }
715
+ const words = items.map((item) => String(item).trim()).filter((item) => item !== "");
716
+ for (const word of words) {
717
+ if (field.values && !field.values.includes(word)) {
718
+ const near = suggest(word, field.values);
719
+ throw new InputError(`${name} can't take "${word}".${near === undefined ? "" : ` Did you mean "${near}"?`} It takes ${list(field.values)}.`);
720
+ }
721
+ }
722
+ return words;
723
+ }
724
+ case "record": {
725
+ const fields = field.fields ?? {};
726
+ const first = Object.keys(fields)[0];
727
+ if (typeof value === "string") {
728
+ return { [first]: value };
729
+ }
730
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
731
+ throw new InputError(`${name} takes an object, like {"${first}": "…"}, not ${describe(value)}.`);
732
+ }
733
+ return readInput(name, fields, value).values;
734
+ }
735
+ }
736
+ };
737
+ var describe = (value) => {
738
+ if (value === null) {
739
+ return "null";
740
+ }
741
+ if (Array.isArray(value)) {
742
+ return "a list";
743
+ }
744
+ if (typeof value === "string") {
745
+ return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value);
746
+ }
747
+ if (typeof value === "object") {
748
+ return "an object";
749
+ }
750
+ return String(value);
751
+ };
752
+ var list = (words) => {
753
+ return words.length <= 1 ? words.join("") : `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
754
+ };
755
+ var example = (fields) => {
756
+ const name = Object.keys(fields).find((key) => fields[key].required) ?? Object.keys(fields)[0];
757
+ return name === undefined ? "{}" : `{"${name}": "…"}`;
758
+ };
759
+ var lowerFirst = (text) => {
760
+ return text.charAt(0).toLowerCase() + text.slice(1);
761
+ };
762
+
763
+ // src/core/map-tools/definitions.ts
764
+ var PROVIDERS = ["anthropic", "openai", "openai-chat", "gemini"];
765
+ var ATTRIBUTES = ["note", "status", "priority", "tags", "icon", "color", "link", "image", "video", "embed", "collapsed"];
766
+ var NODE_ID = "node_id";
767
+ var NODE_SYNONYMS = { id: NODE_ID, node: NODE_ID, nodeid: NODE_ID };
768
+ var TOOL_SPECS = [
769
+ {
770
+ name: "read_map",
771
+ description: "Read the mind map as an outline: a line for each node (an idea), indented under its parent, with its id after a #, its attributes in parentheses, and the start of its note on the next line after >. Read it before changing the map: the other tools take these ids. A big map is cut short: read a branch with node_id, or search with find_nodes.",
772
+ fields: {
773
+ node_id: { type: "string", description: "Read only this node and the nodes under it. Leave out to read the whole map." },
774
+ depth: { type: "integer", minimum: 1, description: "How many levels under it to read: 1 for its children only. Leave out to read them all." }
775
+ },
776
+ synonyms: NODE_SYNONYMS,
777
+ readOnly: true,
778
+ destructive: false
779
+ },
780
+ {
781
+ name: "find_nodes",
782
+ description: "Search the map for nodes by the words in their titles, notes and tags, by status or by tag. Returns each match with its id and where it is in the map.",
783
+ fields: {
784
+ query: { type: "string", description: "Words to look for, in any case, with or without accents. A node matches when it has every word." },
785
+ status: { type: "string", description: "Only nodes with this status. todo, active, done and blocked also find the words that mean the same, such as planned, in progress, shipped or stuck." },
786
+ tag: { type: "string", description: "Only nodes with this tag." }
787
+ },
788
+ synonyms: { text: "query", search: "query", words: "query", tags: "tag" },
789
+ readOnly: true,
790
+ destructive: false
791
+ },
792
+ {
793
+ name: "read_node",
794
+ description: "Read everything about one node: its title, its whole note, its attributes, its picture, video or embedded page, where it is in the map, its children, and the nodes that link to it.",
795
+ fields: {
796
+ node_id: { type: "string", required: true, description: "The node to read, by the id read_map or find_nodes gave." }
797
+ },
798
+ synonyms: NODE_SYNONYMS,
799
+ readOnly: true,
800
+ destructive: false
801
+ },
802
+ {
803
+ name: "add_nodes",
804
+ description: "Add nodes under a node: one, several, or whole branches at once, written as a markdown list. Each item is a node, items indented under it are its children, at any depth, and indented lines under an item that aren't items are its note. On an item's line, [ ], [/] or [x] first sets its status to todo, active or done, an emoji first sets its icon, words after a # at the end are its tags, and <!-- orbit color: tomato, priority: 1 --> at the end sets other attributes. Returns the new nodes with their ids.",
805
+ fields: {
806
+ parent_id: { type: "string", required: true, description: "The node to add them under." },
807
+ outline: { type: "string", required: true, description: 'The nodes, as a markdown list: "- Social media\\n - Instagram\\n- Newsletter".' },
808
+ index: { type: "integer", minimum: 0, description: "Where they go among the children of parent_id: 0 puts them first. Leave out to put them last." }
809
+ },
810
+ synonyms: { parent: "parent_id", parentid: "parent_id", under: "parent_id", markdown: "outline", nodes: "outline", items: "outline", children: "outline", text: "outline", position: "index" },
811
+ readOnly: false,
812
+ destructive: false
813
+ },
814
+ {
815
+ name: "update_node",
816
+ description: "Change a node's title or attributes. Only the fields you give change; to remove an attribute, name it in clear. A new title can give the node a new id, which the result says.",
817
+ fields: {
818
+ node_id: { type: "string", required: true, description: "The node to change." },
819
+ text: { type: "string", description: "Its title: plain text, a few words." },
820
+ note: { type: "string", description: "Its note, in markdown, for details and longer text. Replaces the note it has." },
821
+ status: { type: "string", description: "todo, active, done or blocked, which show a colored dot, or any other word." },
822
+ priority: { type: "number", description: "A number, such as 1 for the most important." },
823
+ tags: { type: "list", description: "All its tags, as words: they replace the ones it has." },
824
+ icon: { type: "string", description: "An emoji shown before its title." },
825
+ color: { type: "string", description: "A CSS color, such as #E8A33D or tomato: its children take shades of it. Nodes without one take the theme's colors, so set it only when asked." },
826
+ link: { type: "string", description: "A web address it links to, or #id to link to another node of the map." },
827
+ image: {
828
+ type: "record",
829
+ description: "A picture shown in the node.",
830
+ fields: {
831
+ src: { type: "string", required: true, description: "The picture's address." },
832
+ alt: { type: "string", description: "What the picture shows, for people who can't see it." },
833
+ caption: { type: "string", description: "A caption." }
834
+ }
835
+ },
836
+ video: {
837
+ type: "record",
838
+ description: "A video the node plays.",
839
+ fields: {
840
+ src: { type: "string", required: true, description: "The video's address: YouTube, Vimeo or a video file." },
841
+ caption: { type: "string", description: "A caption." }
842
+ }
843
+ },
844
+ embed: {
845
+ type: "record",
846
+ description: "A web page shown in a frame in the node's details.",
847
+ fields: {
848
+ src: { type: "string", required: true, description: "The page's address." },
849
+ caption: { type: "string", description: "A caption." }
850
+ }
851
+ },
852
+ collapsed: { type: "boolean", description: "true hides its children until the reader opens it." },
853
+ clear: { type: "list", values: ATTRIBUTES, description: 'Attributes to remove, such as ["status", "color"].' }
854
+ },
855
+ synonyms: {
856
+ ...NODE_SYNONYMS,
857
+ title: "text",
858
+ name: "text",
859
+ label: "text",
860
+ notes: "note",
861
+ description: "note",
862
+ details: "note",
863
+ state: "status",
864
+ tag: "tags",
865
+ labels: "tags",
866
+ emoji: "icon",
867
+ colour: "color",
868
+ url: "link",
869
+ href: "link",
870
+ picture: "image",
871
+ photo: "image",
872
+ remove: "clear",
873
+ unset: "clear",
874
+ delete: "clear"
875
+ },
876
+ readOnly: false,
877
+ destructive: true
878
+ },
879
+ {
880
+ name: "move_node",
881
+ description: "Move a node, with everything under it, under another node, or to another place among its siblings.",
882
+ fields: {
883
+ node_id: { type: "string", required: true, description: "The node to move." },
884
+ parent_id: { type: "string", description: "The node to move it under. Leave out to keep its parent and change only its place." },
885
+ index: { type: "integer", minimum: 0, description: "Its place among the children of its new parent: 0 puts it first. Leave out to put it last." }
886
+ },
887
+ synonyms: { ...NODE_SYNONYMS, parent: "parent_id", parentid: "parent_id", under: "parent_id", to: "parent_id", position: "index" },
888
+ readOnly: false,
889
+ destructive: false
890
+ },
891
+ {
892
+ name: "reorder_children",
893
+ description: "Put the children of a node in a new order, all at once: to sort them, or to group the ones that go together.",
894
+ fields: {
895
+ parent_id: { type: "string", required: true, description: "The node whose children to reorder." },
896
+ order: { type: "list", required: true, description: "The ids of all its children, in their new order." }
897
+ },
898
+ synonyms: { parent: "parent_id", parentid: "parent_id", node_id: "parent_id", id: "parent_id", ids: "order", children: "order", child_ids: "order" },
899
+ readOnly: false,
900
+ destructive: false
901
+ },
902
+ {
903
+ name: "remove_node",
904
+ description: "Remove a node and everything under it. Remove nodes only when asked to.",
905
+ fields: {
906
+ node_id: { type: "string", required: true, description: "The node to remove. The root can't be removed." }
907
+ },
908
+ synonyms: NODE_SYNONYMS,
909
+ readOnly: false,
910
+ destructive: true
911
+ },
912
+ {
913
+ name: "show_node",
914
+ description: "Show the reader a node: the map flies to it, with its children around it. Use it when they ask to see or go to something, or to show them what you changed.",
915
+ fields: {
916
+ node_id: { type: "string", required: true, description: "The node to show." }
917
+ },
918
+ synonyms: NODE_SYNONYMS,
919
+ readOnly: true,
920
+ destructive: false
921
+ }
922
+ ];
923
+ var definitionOf = (spec) => {
924
+ return {
925
+ name: spec.name,
926
+ description: spec.description,
927
+ inputSchema: schemaOf(spec.fields),
928
+ annotations: { readOnlyHint: spec.readOnly, destructiveHint: spec.destructive }
929
+ };
930
+ };
931
+ var formatDefinitions = (definitions, provider) => {
932
+ const tools = definitions.map(({ name, description, inputSchema }) => ({ name, description, schema: structuredCopy(inputSchema) }));
933
+ switch (provider) {
934
+ case "anthropic":
935
+ return tools.map(({ name, description, schema }) => ({ name, description, input_schema: schema }));
936
+ case "openai":
937
+ return tools.map(({ name, description, schema }) => ({ type: "function", name, description, parameters: schema, strict: false }));
938
+ case "openai-chat":
939
+ return tools.map(({ name, description, schema }) => ({ type: "function", function: { name, description, parameters: schema } }));
940
+ case "gemini":
941
+ return [{ functionDeclarations: tools.map(({ name, description, schema }) => ({ name, description, parametersJsonSchema: schema })) }];
942
+ default:
943
+ throw new TypeError(`Map tools come in the shapes of ${PROVIDERS.map((name) => `"${name}"`).join(", ")}, not ${JSON.stringify(provider)}. For any other, build them from tools.definitions: each has a name, a description and an inputSchema (JSON Schema).`);
944
+ }
945
+ };
946
+ var structuredCopy = (value) => {
947
+ return JSON.parse(JSON.stringify(value));
948
+ };
949
+
950
+ // src/core/map-tools/reading.ts
951
+ var NOTE_PREVIEW = 100;
952
+ var MATCH_LIMIT = 50;
953
+ var nameOf = (node) => {
954
+ return `"${node.text}" #${node.id}`;
955
+ };
956
+ var outlineLine = (node, depth) => {
957
+ const details = attributesOf(node);
958
+ return `${" ".repeat(depth)}- ${node.text} #${node.id}${details.length > 0 ? ` (${details.join("; ")})` : ""}`;
959
+ };
960
+ var attributesOf = (node) => {
961
+ const details = [];
962
+ if (node.icon) {
963
+ details.push(`icon: ${node.icon}`);
964
+ }
965
+ if (node.status) {
966
+ details.push(`status: ${node.status}`);
967
+ }
968
+ if (node.priority !== undefined) {
969
+ details.push(`priority: ${node.priority}`);
970
+ }
971
+ if (node.tags && node.tags.length > 0) {
972
+ details.push(`tags: ${node.tags.join(", ")}`);
973
+ }
974
+ if (node.color) {
975
+ details.push(`color: ${node.color}`);
976
+ }
977
+ if (node.link) {
978
+ details.push(`link: ${node.link}`);
979
+ }
980
+ for (const media of ["image", "video", "embed"]) {
981
+ if (node[media]) {
982
+ details.push(media);
983
+ }
984
+ }
985
+ if (node.type) {
986
+ details.push(`type: ${node.type}`);
987
+ }
988
+ if (node.collapsed) {
989
+ details.push("collapsed");
990
+ }
991
+ return details;
992
+ };
993
+ var notePreview = (note) => {
994
+ const text = note.trim().replace(/\s+/g, " ");
995
+ return text.length > NOTE_PREVIEW ? `${text.slice(0, NOTE_PREVIEW - 1).trimEnd()}…` : text;
996
+ };
997
+ var headOf = (node, depth) => {
998
+ const lines = [outlineLine(node, depth)];
999
+ if (node.note?.trim()) {
1000
+ lines.push(`${" ".repeat(depth + 1)}> ${notePreview(node.note)}`);
1001
+ }
1002
+ return lines;
1003
+ };
1004
+ var outlineOf = (node, depth = 0, levels = Infinity) => {
1005
+ const lines = headOf(node, depth);
1006
+ if (levels > 0) {
1007
+ for (const child of node.children) {
1008
+ lines.push(...outlineOf(child, depth + 1, levels - 1));
1009
+ }
1010
+ } else if (node.children.length > 0) {
1011
+ lines.push(`${" ".repeat(depth + 1)}- … ${count(node.children.length, "child", "children")}, ${count(descendants(node), "node")} in all under it`);
1012
+ }
1013
+ return lines;
1014
+ };
1015
+ var readOutline = (start, levels, limit) => {
1016
+ let shown = 0;
1017
+ let total = 1;
1018
+ let level = [start];
1019
+ while (shown < levels) {
1020
+ const next = level.flatMap((node) => node.children);
1021
+ if (next.length === 0 || total + next.length > limit) {
1022
+ break;
1023
+ }
1024
+ total += next.length;
1025
+ level = next;
1026
+ shown += 1;
1027
+ }
1028
+ const complete = shown === levels || level.every((node) => node.children.length === 0);
1029
+ if (shown === 0 && !complete) {
1030
+ const children = start.children.slice(0, Math.max(1, limit - 1));
1031
+ return {
1032
+ lines: [
1033
+ ...headOf(start, 0),
1034
+ ...children.flatMap((child) => outlineOf(child, 1, 0)),
1035
+ ` - … ${count(start.children.length - children.length, "more child", "more children")} of ${nameOf(start)}`
1036
+ ],
1037
+ complete
1038
+ };
1039
+ }
1040
+ return { lines: outlineOf(start, 0, shown), complete };
1041
+ };
1042
+ var descendants = (node) => {
1043
+ return node.children.reduce((sum, child) => sum + 1 + descendants(child), 0);
1044
+ };
1045
+ var count = (amount, one, many = `${one}s`) => {
1046
+ return `${amount} ${amount === 1 ? one : many}`;
1047
+ };
1048
+ var nodeDetails = (root, node, path) => {
1049
+ const lines = [nameOf(node)];
1050
+ lines.push(path.length > 1 ? `Where: ${path.map((step) => step.text).join(" › ")}` : "Where: the root of the map");
1051
+ const fields = [
1052
+ ["Icon", node.icon],
1053
+ ["Status", node.status],
1054
+ ["Priority", node.priority],
1055
+ ["Tags", node.tags && node.tags.length > 0 ? node.tags.join(", ") : undefined],
1056
+ ["Color", node.color],
1057
+ ["Link", node.link],
1058
+ ["Picture", node.image && mediaLine(node.image.src, { alt: node.image.alt, caption: node.image.caption })],
1059
+ ["Video", node.video && mediaLine(node.video.src, { caption: node.video.caption })],
1060
+ ["Embedded page", node.embed && mediaLine(node.embed.src, { caption: node.embed.caption })],
1061
+ ["Type", node.type],
1062
+ ["Collapsed", node.collapsed ? "yes: its children are hidden until the reader opens it" : undefined]
1063
+ ];
1064
+ for (const [label, value] of fields) {
1065
+ if (value !== undefined && value !== "") {
1066
+ lines.push(`${label}: ${String(value)}`);
1067
+ }
1068
+ }
1069
+ const custom = Object.entries(node.attrs ?? {});
1070
+ if (custom.length > 0) {
1071
+ lines.push(`Other attributes: ${custom.map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("; ")}`);
1072
+ }
1073
+ lines.push(node.note?.trim() ? `Note${node.noteFormat === "plain" ? " (plain text)" : ""}:
1074
+ ${node.note.trim()}` : "Note: none");
1075
+ lines.push(node.children.length > 0 ? `Children (${node.children.length}): ${node.children.map(nameOf).join(", ")}` : "Children: none");
1076
+ const linking = nodesLinkingTo(root, node);
1077
+ if (linking.length > 0) {
1078
+ lines.push(`Linked from: ${linking.map(nameOf).join(", ")}`);
1079
+ }
1080
+ return lines.join(`
1081
+ `);
1082
+ };
1083
+ var mediaLine = (src, extras) => {
1084
+ const described = Object.entries(extras).filter(([, value]) => value).map(([key, value]) => `${key}: ${value}`);
1085
+ return described.length > 0 ? `${src} (${described.join("; ")})` : src;
1086
+ };
1087
+ var nodesLinkingTo = (root, target) => {
1088
+ const nodes = indexNodes(root);
1089
+ const linking = [];
1090
+ const visit = (node) => {
1091
+ if (nodeLinksOf(node).some((href) => findIndexedLinkedNode(nodes, href) === target)) {
1092
+ linking.push(node);
1093
+ }
1094
+ node.children.forEach(visit);
1095
+ };
1096
+ visit(root);
1097
+ return linking;
1098
+ };
1099
+ var searchable = (text) => {
1100
+ return text.normalize("NFKD").replace(/\p{M}+/gu, "").toLowerCase();
1101
+ };
1102
+ var findNodes = (root, search) => {
1103
+ const words = searchable(search.query ?? "").split(/\s+/).filter((word) => word !== "");
1104
+ const status = search.status === undefined ? undefined : searchable(search.status.trim());
1105
+ const tag = search.tag === undefined ? undefined : searchable(search.tag.trim().replace(/^#/, ""));
1106
+ const statusMatches = (node) => {
1107
+ if (status === undefined) {
1108
+ return true;
1109
+ }
1110
+ if (!node.status) {
1111
+ return false;
1112
+ }
1113
+ const own = searchable(node.status.trim());
1114
+ const tone = statusTone(status);
1115
+ return own === status || tone !== "neutral" && statusTone(own) === tone;
1116
+ };
1117
+ const matches = [];
1118
+ const visit = (node, path) => {
1119
+ const title = searchable([node.text, ...node.tags ?? []].join(" "));
1120
+ const everything = `${title} ${searchable(node.note ?? "")}`;
1121
+ if (words.every((word) => everything.includes(word)) && statusMatches(node) && (tag === undefined || (node.tags ?? []).some((own) => searchable(own) === tag))) {
1122
+ matches.push({ node, path, inTitle: words.every((word) => title.includes(word)) });
1123
+ }
1124
+ node.children.forEach((child) => visit(child, [...path, node]));
1125
+ };
1126
+ visit(root, []);
1127
+ if (matches.length === 0) {
1128
+ return "No node matches. Try fewer or other words, or read the map with read_map.";
1129
+ }
1130
+ matches.sort((first, second) => Number(second.inTitle) - Number(first.inTitle));
1131
+ const shown = matches.slice(0, MATCH_LIMIT);
1132
+ const lines = [`${count(matches.length, "node matches", "nodes match")}${matches.length > shown.length ? `; the first ${shown.length}` : ""}:`];
1133
+ for (const { node, path } of shown) {
1134
+ lines.push(`${outlineLine(node, 0)}${path.length > 0 ? ` in ${path.map((step) => step.text).join(" › ")}` : " (the root)"}`);
1135
+ }
1136
+ if (matches.length > shown.length) {
1137
+ lines.push("Narrow the search to see the others.");
1138
+ }
1139
+ return lines.join(`
1140
+ `);
1141
+ };
1142
+
1143
+ // src/core/map-tools/tools.ts
1144
+ var READ_LIMIT = 250;
1145
+ var LONG_TITLE = 60;
1146
+ var COLOR_SYNTAX = /^(?:#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})|[a-z]+|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark|var)\(.+\))$/i;
1147
+ var OUTLINE_CODES_LEFT_OUT = new Set(["synthetic-root", "no-structure", "unknown-link-target"]);
1148
+
1149
+ class CallError extends Error {
1150
+ }
1151
+ var createMapTools = (map, options = {}) => {
1152
+ const target = targetOf(map);
1153
+ const readLimit = typeof options.readLimit === "number" && Number.isFinite(options.readLimit) ? Math.max(10, Math.trunc(options.readLimit)) : READ_LIMIT;
1154
+ const specs = TOOL_SPECS.filter((spec) => (spec.name !== "show_node" || target.live !== null) && (spec.readOnly || !options.readOnly));
1155
+ const definitions = specs.map(definitionOf);
1156
+ const renamed = new Map;
1157
+ const removed = new Map;
1158
+ const notes = [];
1159
+ const resolve = (root, given, field) => {
1160
+ const id = given.trim().replace(/^#/, "");
1161
+ const direct = findNodeById2(root, id);
1162
+ if (direct) {
1163
+ return direct;
1164
+ }
1165
+ let current = id;
1166
+ const seen = new Set;
1167
+ while (renamed.has(current) && !seen.has(current)) {
1168
+ seen.add(current);
1169
+ current = renamed.get(current);
1170
+ }
1171
+ const followed = current === id ? null : findNodeById2(root, current);
1172
+ if (followed) {
1173
+ notes.push(`#${id} is ${nameOf(followed)} now.`);
1174
+ return followed;
1175
+ }
1176
+ if (removed.has(id)) {
1177
+ throw new CallError(`${field} names #${id}, but "${removed.get(id)}" was removed.`);
1178
+ }
1179
+ const wanted = searchable(id);
1180
+ const titled = allNodes(root).filter((node) => searchable(node.text.trim()) === wanted || node.id === nodeSlug(id));
1181
+ if (titled.length === 1) {
1182
+ notes.push(`${field} takes ids: the id of "${titled[0].text}" is #${titled[0].id}.`);
1183
+ return titled[0];
1184
+ }
1185
+ if (titled.length > 1) {
1186
+ throw new CallError(`${titled.length} nodes are called "${given}": ${titled.map((node) => `${nameOf(node)}${node.parent ? ` under "${node.parent.text}"` : ""}`).join(", ")}. Give ${field} one of their ids.`);
1187
+ }
1188
+ const near = suggest(id, allNodes(root).map((node) => node.id));
1189
+ const nearNode = near === undefined ? null : findNodeById2(root, near);
1190
+ throw new CallError(`No node has the id "${id}".${nearNode ? ` Did you mean ${nameOf(nearNode)}?` : ""} read_map and find_nodes give every node's id.`);
1191
+ };
1192
+ const calls = {
1193
+ read_map: (values, root, document2) => {
1194
+ const start = typeof values.node_id === "string" ? resolve(root, values.node_id, "node_id") : root;
1195
+ const levels = typeof values.depth === "number" ? values.depth : Infinity;
1196
+ const { lines, complete } = readOutline(start, levels, readLimit);
1197
+ const header = [];
1198
+ if (start === root) {
1199
+ const title = document2.options.title ?? root.text;
1200
+ header.push(`The map "${title}" has ${count(1 + descendants(root), "node")}.`);
1201
+ } else {
1202
+ const path = getNodePath2(root, start.id);
1203
+ header.push(`${nameOf(start)}, in ${path.slice(0, -1).map((node) => node.text).join(" › ")}, has ${count(descendants(start), "node")} under it.`);
1204
+ }
1205
+ const live = target.live;
1206
+ if (live) {
1207
+ const focused = live.getFocusedNode();
1208
+ header.push(live.getViewMode() === "all" || !focused ? "The reader is looking at the whole map." : `The reader is looking at ${nameOf(focused)}.`);
1209
+ }
1210
+ const footer = complete ? [] : ["Nodes further down aren't shown: read a branch with read_map and its node_id, or search with find_nodes."];
1211
+ return { text: [header.join(" "), ...lines, ...footer].join(`
1212
+ `), changed: false };
1213
+ },
1214
+ find_nodes: (values, root) => {
1215
+ if (values.query === undefined && values.status === undefined && values.tag === undefined) {
1216
+ throw new CallError("find_nodes needs something to look for: query, status, tag, or more than one.");
1217
+ }
1218
+ return { text: findNodes(root, values), changed: false };
1219
+ },
1220
+ read_node: (values, root) => {
1221
+ const node = resolve(root, values.node_id, "node_id");
1222
+ return { text: nodeDetails(root, node, getNodePath2(root, node.id)), changed: false };
1223
+ },
1224
+ add_nodes: (values, root) => {
1225
+ const parent = resolve(root, values.parent_id, "parent_id");
1226
+ const nodes = readNewNodes(values.outline);
1227
+ const index = values.index;
1228
+ const added = [];
1229
+ nodes.forEach((input, offset) => {
1230
+ const node = target.add(parent.id, input, index === undefined ? undefined : index + offset);
1231
+ if (node) {
1232
+ added.push(node);
1233
+ }
1234
+ });
1235
+ if (added.length === 0) {
1236
+ throw new CallError(`Nothing could be added under ${nameOf(parent)}.`);
1237
+ }
1238
+ for (const node of added) {
1239
+ const twin = parent.children.find((sibling) => sibling !== node && !added.includes(sibling) && searchable(sibling.text) === searchable(node.text));
1240
+ if (twin) {
1241
+ notes.push(`${nameOf(node)} has the same title as ${nameOf(twin)} beside it. If it's there twice by mistake, remove one with remove_node.`);
1242
+ }
1243
+ }
1244
+ const all = added.flatMap(allNodes);
1245
+ const long = all.filter((node) => node.text.length > LONG_TITLE);
1246
+ if (long.length > 0) {
1247
+ notes.push(`${long.map(nameOf).join(", ")} ${long.length === 1 ? "has a long title" : "have long titles"}: a few words read best on the map, with the rest in the note.`);
1248
+ }
1249
+ notes.push(...brokenLinks(target.document()?.root ?? root, all));
1250
+ return {
1251
+ text: [`Added ${count(all.length, "node")} under ${nameOf(parent)}${placeOf(added, parent)}:`, ...added.flatMap((node) => outlineOf(node))].join(`
1252
+ `),
1253
+ changed: added.length > 0
1254
+ };
1255
+ },
1256
+ update_node: (values, root) => {
1257
+ const node = resolve(root, values.node_id, "node_id");
1258
+ const previousId = node.id;
1259
+ const { changes, set, cleared, absent } = readChanges(values, root, node);
1260
+ if (set.length === 0 && cleared.length === 0) {
1261
+ if (absent.length === 0) {
1262
+ throw new CallError(`update_node needs something to change: ${list(["text", ...ATTRIBUTES])}, or clear to remove attributes.`);
1263
+ }
1264
+ return { text: `${nameOf(node)} has no ${list(absent)} to remove: nothing changed.`, changed: false };
1265
+ }
1266
+ const updated = target.update(node.id, changes);
1267
+ if (!updated) {
1268
+ throw new CallError(`${nameOf(node)} couldn't be changed.`);
1269
+ }
1270
+ if (changes.note !== undefined) {
1271
+ notes.push(...brokenLinks(root, [updated]));
1272
+ }
1273
+ const described = [set.length > 0 ? `set its ${list(set)}` : "", cleared.length > 0 ? `removed its ${list(cleared)}` : ""].filter((part) => part !== "").join("; ");
1274
+ let text = `Changed ${nameOf(updated)}: ${described}.`;
1275
+ if (updated.id !== previousId) {
1276
+ renamed.set(previousId, updated.id);
1277
+ text += ` Its id is #${updated.id} now: it was #${previousId}.`;
1278
+ }
1279
+ return { text, changed: true };
1280
+ },
1281
+ move_node: (values, root) => {
1282
+ const node = resolve(root, values.node_id, "node_id");
1283
+ if (values.parent_id === undefined && values.index === undefined) {
1284
+ throw new CallError("move_node needs where to move the node: parent_id, index, or both.");
1285
+ }
1286
+ const path = getNodePath2(root, node.id);
1287
+ if (path.length < 2) {
1288
+ throw new CallError(`${nameOf(node)} is the root: it can't move, as every other node is under it.`);
1289
+ }
1290
+ const parent = values.parent_id === undefined ? path[path.length - 2] : resolve(root, values.parent_id, "parent_id");
1291
+ if (parent === node) {
1292
+ throw new CallError(`${nameOf(node)} can't go under itself. Give parent_id another node.`);
1293
+ }
1294
+ if (getNodePath2(root, parent.id).includes(node)) {
1295
+ throw new CallError(`${nameOf(node)} can't go under ${nameOf(parent)}, which is inside it. Move ${nameOf(parent)} out of it first, or choose another parent.`);
1296
+ }
1297
+ const under = descendants(node);
1298
+ const moved = target.move(node.id, parent.id, values.index);
1299
+ if (!moved) {
1300
+ throw new CallError(`${nameOf(node)} couldn't be moved.`);
1301
+ }
1302
+ return { text: `Moved ${nameOf(moved)}${under > 0 ? `, with the ${count(under, "node")} under it,` : ""} under ${nameOf(parent)}${placeOf([moved], parent)}.`, changed: true };
1303
+ },
1304
+ reorder_children: (values, root) => {
1305
+ const parent = resolve(root, values.parent_id, "parent_id");
1306
+ const children = [...parent.children];
1307
+ if (children.length === 0) {
1308
+ throw new CallError(`${nameOf(parent)} has no children to reorder.`);
1309
+ }
1310
+ const order = values.order.map((given) => childNamed(parent, given));
1311
+ const twice = order.find((child, position) => order.indexOf(child) !== position);
1312
+ if (twice) {
1313
+ throw new CallError(`order lists ${nameOf(twice)} twice: list each child once.`);
1314
+ }
1315
+ const missing = children.filter((child) => !order.includes(child));
1316
+ if (missing.length > 0) {
1317
+ throw new CallError(`order needs all ${children.length} children of ${nameOf(parent)}, and leaves out ${missing.map(nameOf).join(", ")}.`);
1318
+ }
1319
+ if (order.every((child, position) => children[position] === child)) {
1320
+ return { text: `The children of ${nameOf(parent)} are in that order already.`, changed: false };
1321
+ }
1322
+ order.forEach((child, position) => {
1323
+ const current = findNodeById2(target.document()?.root ?? root, parent.id) ?? parent;
1324
+ if (current.children[position]?.id !== child.id) {
1325
+ target.move(child.id, parent.id, position);
1326
+ }
1327
+ });
1328
+ return { text: `The children of ${nameOf(parent)} are in this order now: ${order.map(nameOf).join(", ")}.`, changed: true };
1329
+ },
1330
+ remove_node: (values, root) => {
1331
+ const node = resolve(root, values.node_id, "node_id");
1332
+ if (node === root) {
1333
+ throw new CallError(`${nameOf(node)} is the root, and the map is all under it: it can't be removed. Remove the nodes under it one by one, or give it a new title with update_node.`);
1334
+ }
1335
+ const gone = allNodes(node);
1336
+ if (!target.remove(node.id)) {
1337
+ throw new CallError(`${nameOf(node)} couldn't be removed.`);
1338
+ }
1339
+ for (const each of gone) {
1340
+ removed.set(each.id, each.text);
1341
+ }
1342
+ return { text: `Removed ${nameOf(node)}${gone.length > 1 ? ` and the ${count(gone.length - 1, "node")} under it` : ""}.`, changed: true };
1343
+ },
1344
+ show_node: (values, root) => {
1345
+ const node = resolve(root, values.node_id, "node_id");
1346
+ target.live?.flyTo(node.id);
1347
+ return { text: `The map shows ${nameOf(node)} now, with its children around it.`, changed: false };
1348
+ }
1349
+ };
1350
+ const childNamed = (parent, given) => {
1351
+ const id = given.trim().replace(/^#/, "");
1352
+ const child = parent.children.find((each) => each.id === id || each.id === renamed.get(id)) ?? parent.children.find((each) => searchable(each.text.trim()) === searchable(id));
1353
+ if (!child) {
1354
+ throw new CallError(`${given} isn't a child of ${nameOf(parent)}. Its children: ${parent.children.map(nameOf).join(", ")}.`);
1355
+ }
1356
+ return child;
1357
+ };
1358
+ const run = (name, input) => {
1359
+ notes.length = 0;
1360
+ const spec = TOOL_SPECS.find((each) => each.name === name);
1361
+ const offered = specs.find((each) => each.name === name);
1362
+ if (!offered) {
1363
+ if (spec?.name === "show_node") {
1364
+ return failure("show_node only works on a map on screen.");
1365
+ }
1366
+ if (spec) {
1367
+ return failure(`${name} isn't available: this map can only be read.`);
1368
+ }
1369
+ const near = suggest(String(name), specs.map((each) => each.name));
1370
+ return failure(`There's no tool "${String(name)}".${near === undefined ? "" : ` Did you mean ${near}?`} The tools are ${list(specs.map((each) => each.name))}.`);
1371
+ }
1372
+ const document2 = target.document();
1373
+ if (!document2) {
1374
+ return failure("There's no map to read yet: it hasn't been drawn.");
1375
+ }
1376
+ if (!offered.readOnly && target.newer) {
1377
+ return failure("This map was written for a newer version of Orbit, and changing it here could lose what this version doesn't read. It can be read, but not changed.");
1378
+ }
1379
+ try {
1380
+ const { values, ignored } = readInput(offered.name, offered.fields, input, offered.synonyms);
1381
+ const outcome = calls[offered.name](values, document2.root, document2);
1382
+ if (ignored.length > 0) {
1383
+ notes.push(`${offered.name} doesn't take ${list(ignored.map((key) => `"${key}"`))}, so ${ignored.length === 1 ? "it was" : "they were"} left out. It takes ${list(Object.keys(offered.fields))}.`);
1384
+ }
1385
+ return { ok: true, text: [outcome.text, ...notes.map((note) => `Note: ${note}`)].join(`
1386
+ `), changed: outcome.changed };
1387
+ } catch (error) {
1388
+ if (error instanceof CallError || error instanceof InputError) {
1389
+ return failure(error.message, offered);
1390
+ }
1391
+ throw error;
1392
+ }
1393
+ };
1394
+ return {
1395
+ definitions,
1396
+ instructions: instructionsFor(target.live !== null, options.readOnly === true),
1397
+ for: (provider) => formatDefinitions(definitions, provider),
1398
+ run,
1399
+ get document() {
1400
+ return target.document();
1401
+ }
1402
+ };
1403
+ };
1404
+ var failure = (message, spec) => {
1405
+ return { ok: false, text: spec && !spec.readOnly ? `${message} Nothing changed.` : message, changed: false };
1406
+ };
1407
+ var targetOf = (map) => {
1408
+ if (isLiveMap(map)) {
1409
+ return {
1410
+ document: () => map.getDocument(),
1411
+ add: (parentId, input, index) => map.addNode(parentId, input, index),
1412
+ update: (id, changes) => map.updateNode(id, changes),
1413
+ move: (id, parentId, index) => map.moveNode(id, parentId, index),
1414
+ remove: (id) => map.removeNode(id),
1415
+ live: map,
1416
+ newer: false
1417
+ };
1418
+ }
1419
+ const document2 = typeof map === "string" ? parseOrbit2(map) : map;
1420
+ if (!document2 || typeof document2 !== "object" || !document2.root || !Array.isArray(document2.root.children)) {
1421
+ throw new TypeError(`createMapTools() takes a map on screen (createOrbitMap's result, or <orbit-map>'s renderer), a parsed document, or a map's markdown or YAML, but got ${map === null ? "null" : typeof map}.`);
1422
+ }
1423
+ const extensions = new MapExtensions;
1424
+ const colors = () => ({ count: branchPalette(resolvePalette(document2.options?.palette)).length, ownColor: (node) => extensions.color(node) });
1425
+ return {
1426
+ document: () => document2,
1427
+ add: (parentId, input, index) => addToTree(document2.root, parentId, input, index, colors())?.node ?? null,
1428
+ update: (id, changes) => updateInTree(document2.root, id, changes, colors())?.node ?? null,
1429
+ move: (id, parentId, index) => moveInTree(document2.root, id, parentId, index, colors())?.node ?? null,
1430
+ remove: (id) => removeFromTree(document2.root, id, colors())?.node ?? null,
1431
+ live: null,
1432
+ newer: (document2.diagnostics ?? []).some((diagnostic) => diagnostic.code === "unsupported-schema-version")
1433
+ };
1434
+ };
1435
+ var isLiveMap = (map) => {
1436
+ const candidate = map;
1437
+ return typeof candidate === "object" && candidate !== null && typeof candidate.getDocument === "function" && typeof candidate.addNode === "function";
1438
+ };
1439
+ var instructionsFor = (live, readOnly) => {
1440
+ const lines = readOnly ? [
1441
+ "You can read the mind map with the map tools. Each node is an idea, with an id.",
1442
+ "- read_map shows the map as an outline, with every node's id; find_nodes searches a big one, and read_node shows everything about one node, its whole note included.",
1443
+ "- Answer from what the map says, and say when it doesn't say."
1444
+ ] : [
1445
+ "You can read and change the mind map with the map tools. Each node is an idea, with an id.",
1446
+ "- Read the map before you change it: read_map shows it as an outline with every node's id, and find_nodes searches a big one. The other tools take those ids.",
1447
+ "- Keep titles to a few words, and put details and longer text in the note.",
1448
+ "- Write in the language the map is written in.",
1449
+ "- Add several nodes, or whole branches, with one add_nodes call.",
1450
+ "- A new title can give a node a new id: use the ids the latest results give.",
1451
+ "- Remove nodes only when asked to."
1452
+ ];
1453
+ if (live) {
1454
+ lines.push(readOnly ? "- show_node takes the reader to a node." : "- The reader sees each change on the map as you make it, and show_node takes them to a node.");
1455
+ }
1456
+ return lines.join(`
1457
+ `);
1458
+ };
1459
+ var allNodes = (root) => {
1460
+ return [root, ...root.children.flatMap(allNodes)];
1461
+ };
1462
+ var placeOf = (nodes, parent) => {
1463
+ const first = parent.children.indexOf(nodes[0]);
1464
+ const last = parent.children.indexOf(nodes[nodes.length - 1]);
1465
+ if (first > 0) {
1466
+ return `, after ${nameOf(parent.children[first - 1])}`;
1467
+ }
1468
+ if (first === 0 && last < parent.children.length - 1) {
1469
+ return `, first, before ${nameOf(parent.children[last + 1])}`;
1470
+ }
1471
+ return "";
1472
+ };
1473
+ var brokenLinks = (root, nodes) => {
1474
+ const index = indexNodes(root);
1475
+ const ids = [...index.keys()];
1476
+ return nodes.flatMap((node) => nodeLinksOf(node).filter((href) => !findIndexedLinkedNode(index, href)).map((href) => {
1477
+ const near = suggest(href.slice(1), ids);
1478
+ return `${nameOf(node)} links to ${href}, but no node has that id.${near === undefined ? "" : ` Did you mean #${near}?`}`;
1479
+ }));
1480
+ };
1481
+ var readNewNodes = (outline) => {
1482
+ let source = outline.replace(/\r\n?/g, `
1483
+ `);
1484
+ if (/^\s*---\s*\n/.test(source)) {
1485
+ throw new CallError("outline takes the nodes to add as a markdown list, without frontmatter.");
1486
+ }
1487
+ let document2 = parseOrbitMarkdown2(source);
1488
+ if (document2.diagnostics.some((diagnostic) => diagnostic.code === "no-structure")) {
1489
+ source = source.split(`
1490
+ `).map((line) => line.trim()).filter((line) => line !== "").map((line) => `- ${line}`).join(`
1491
+ `);
1492
+ document2 = parseOrbitMarkdown2(source);
1493
+ }
1494
+ const roots = document2.diagnostics.some((diagnostic) => diagnostic.code === "synthetic-root") ? document2.root.children : [document2.root];
1495
+ const untitled = roots.flatMap(allNodes).find((node) => node.text.trim() === "");
1496
+ if (roots.length === 0 || untitled) {
1497
+ throw new CallError(untitled?.source ? `Line ${untitled.source.line} of outline has an item without a title.` : 'outline has no nodes: write them as a markdown list, like "- Social media\\n- Newsletter".');
1498
+ }
1499
+ const problems = document2.diagnostics.filter((diagnostic) => !OUTLINE_CODES_LEFT_OUT.has(diagnostic.code)).map((diagnostic) => formatDiagnostic2(diagnostic).replace(/[,;] kept as (?:a custom attribute|written)(?:, but not used)?/, ""));
1500
+ if (problems.length > 0) {
1501
+ throw new CallError(`outline doesn't read cleanly. Fix ${problems.length === 1 ? "this" : "these"}, and send it again:
1502
+ ${problems.map((problem) => `- ${problem}`).join(`
1503
+ `)}
1504
+ Lines count from the outline's first line.`);
1505
+ }
1506
+ return roots.map(newNodeFrom);
1507
+ };
1508
+ var readChanges = (values, root, node) => {
1509
+ const changes = {};
1510
+ const set = [];
1511
+ const clear = new Set(values.clear ?? []);
1512
+ const cleared = [];
1513
+ const absent = [];
1514
+ const remove = (name) => {
1515
+ if (node[name] === undefined) {
1516
+ absent.push(name);
1517
+ return;
1518
+ }
1519
+ changes[name] = undefined;
1520
+ cleared.push(name);
1521
+ };
1522
+ for (const name of clear) {
1523
+ if (values[name] !== undefined) {
1524
+ throw new CallError(`${name} is both given and in clear: give it a value, or clear it.`);
1525
+ }
1526
+ remove(name);
1527
+ }
1528
+ if (values.text !== undefined) {
1529
+ let text = String(values.text).replace(/\s+/g, " ").trim();
1530
+ if (text === "") {
1531
+ throw new CallError("text can't be empty: every node has a title. To take the node out of the map, use remove_node.");
1532
+ }
1533
+ const emoji = leadingEmoji(text);
1534
+ if (emoji && values.icon === undefined && !clear.has("icon")) {
1535
+ changes.icon = emoji.icon;
1536
+ text = emoji.text;
1537
+ set.push("icon");
1538
+ }
1539
+ changes.text = text;
1540
+ set.push("title");
1541
+ }
1542
+ const text = (name) => {
1543
+ const value = values[name];
1544
+ if (value === undefined) {
1545
+ return;
1546
+ }
1547
+ const written = name === "note" ? String(value).trim() : String(value).replace(/\s+/g, " ").trim();
1548
+ if (written === "") {
1549
+ remove(name);
1550
+ return;
1551
+ }
1552
+ set.push(name);
1553
+ return written;
1554
+ };
1555
+ const note = text("note");
1556
+ if (note !== undefined) {
1557
+ changes.note = note;
1558
+ }
1559
+ const status = text("status");
1560
+ if (status !== undefined) {
1561
+ changes.status = status;
1562
+ }
1563
+ if (values.priority !== undefined) {
1564
+ changes.priority = values.priority;
1565
+ set.push("priority");
1566
+ }
1567
+ if (values.tags !== undefined) {
1568
+ const tags = [...new Set(values.tags.map((tag) => tag.replace(/^#/, "").trim()).filter((tag) => tag !== ""))];
1569
+ if (tags.length === 0) {
1570
+ remove("tags");
1571
+ } else {
1572
+ changes.tags = tags;
1573
+ set.push("tags");
1574
+ }
1575
+ }
1576
+ const icon = text("icon");
1577
+ if (icon !== undefined) {
1578
+ if (/^[\p{L}\p{N}][\p{L}\p{N}\s'-]+$/u.test(icon) || Array.from(icon).length > 8) {
1579
+ throw new CallError(`icon takes an emoji, such as \uD83D\uDE80, not ${JSON.stringify(icon)}.`);
1580
+ }
1581
+ changes.icon = icon;
1582
+ }
1583
+ const color = text("color");
1584
+ if (color !== undefined) {
1585
+ const supports = typeof CSS !== "undefined" && typeof CSS.supports === "function" ? CSS.supports("color", color) : true;
1586
+ if (!COLOR_SYNTAX.test(color) || !supports) {
1587
+ throw new CallError(`"${color}" isn't a CSS color. Use a color name, such as tomato, or a code, such as #E8A33D.`);
1588
+ }
1589
+ changes.color = color;
1590
+ }
1591
+ const link = text("link");
1592
+ if (link !== undefined) {
1593
+ if (isNodeLink(link)) {
1594
+ if (!findLinkedNode(root, link)) {
1595
+ const near = suggest(link.slice(1), allNodes(root).map((each) => each.id));
1596
+ throw new CallError(`link ${link} points at no node.${near === undefined ? "" : ` Did you mean #${near}?`} Node links are # and a node's id.`);
1597
+ }
1598
+ } else if (guardUrl(link, "link", false) === null) {
1599
+ throw new CallError(`"${link}" isn't an address the map can open: use a web address, such as https://example.com.`);
1600
+ }
1601
+ changes.link = link;
1602
+ }
1603
+ for (const name of ["image", "video", "embed"]) {
1604
+ const media = values[name];
1605
+ if (media === undefined) {
1606
+ continue;
1607
+ }
1608
+ const src = media.src.trim();
1609
+ if (src === "") {
1610
+ remove(name);
1611
+ continue;
1612
+ }
1613
+ if (guardUrl(src, name, false) === null) {
1614
+ throw new CallError(`${name} can't show "${src}": use a web address, such as https://example.com/${name === "image" ? "photo.jpg" : name === "video" ? "video.mp4" : "page"}.`);
1615
+ }
1616
+ const extras = Object.fromEntries(Object.entries(media).filter(([key, value]) => key !== "src" && value.trim() !== "").map(([key, value]) => [key, value.trim()]));
1617
+ changes[name] = { src, ...extras };
1618
+ set.push(name === "image" ? "picture" : name === "embed" ? "embedded page" : "video");
1619
+ }
1620
+ if (values.collapsed !== undefined) {
1621
+ if (values.collapsed === true) {
1622
+ changes.collapsed = true;
1623
+ set.push("collapsed");
1624
+ } else {
1625
+ remove("collapsed");
1626
+ }
1627
+ }
1628
+ return { changes, set, cleared, absent };
1629
+ };
1630
+ // src/core/other-apps/icons.ts
1631
+ var FREEMIND_ICONS = new Map(Object.entries({
1632
+ idea: "\uD83D\uDCA1",
1633
+ help: "❓",
1634
+ yes: "❗",
1635
+ messagebox_warning: "⚠️",
1636
+ info: "ℹ️",
1637
+ "stop-sign": "\uD83D\uDED1",
1638
+ closed: "⛔",
1639
+ button_ok: "✅",
1640
+ button_cancel: "❌",
1641
+ stop: "\uD83D\uDD34",
1642
+ prepare: "\uD83D\uDFE1",
1643
+ go: "\uD83D\uDFE2",
1644
+ back: "⬅️",
1645
+ forward: "➡️",
1646
+ up: "⬆️",
1647
+ down: "⬇️",
1648
+ attach: "\uD83D\uDCCE",
1649
+ ksmiletris: "\uD83D\uDE42",
1650
+ "smiley-neutral": "\uD83D\uDE10",
1651
+ "smiley-oh": "\uD83D\uDE2E",
1652
+ "smiley-angry": "\uD83D\uDE20",
1653
+ smily_bad: "\uD83D\uDE1E",
1654
+ clanbomber: "\uD83D\uDCA3",
1655
+ desktop_new: "\uD83D\uDCCC",
1656
+ flag: "\uD83D\uDEA9",
1657
+ "flag-black": "\uD83C\uDFF4",
1658
+ "flag-blue": "\uD83D\uDEA9",
1659
+ "flag-green": "\uD83D\uDEA9",
1660
+ "flag-orange": "\uD83D\uDEA9",
1661
+ "flag-pink": "\uD83D\uDEA9",
1662
+ "flag-yellow": "\uD83D\uDEA9",
1663
+ gohome: "\uD83C\uDFE0",
1664
+ folder: "\uD83D\uDCC1",
1665
+ korn: "\uD83D\uDCEB",
1666
+ Mail: "✉️",
1667
+ kmail: "\uD83D\uDCE7",
1668
+ list: "\uD83D\uDCCB",
1669
+ edit: "✏️",
1670
+ pencil: "✏️",
1671
+ kaddressbook: "☎️",
1672
+ knotify: "\uD83C\uDFB5",
1673
+ password: "\uD83D\uDD11",
1674
+ encrypted: "\uD83D\uDD12",
1675
+ decrypted: "\uD83D\uDD13",
1676
+ wizard: "\uD83E\uDE84",
1677
+ xmag: "\uD83D\uDD0D",
1678
+ bell: "\uD83D\uDD14",
1679
+ bookmark: "\uD83D\uDD16",
1680
+ penguin: "\uD83D\uDC27",
1681
+ licq: "\uD83C\uDF38",
1682
+ freemind_butterfly: "\uD83E\uDD8B",
1683
+ "broken-line": "\uD83D\uDC94",
1684
+ calendar: "\uD83D\uDCC5",
1685
+ clock: "⏰",
1686
+ hourglass: "⏳",
1687
+ launch: "\uD83D\uDE80",
1688
+ family: "\uD83D\uDC6A",
1689
+ female1: "\uD83D\uDC69",
1690
+ female2: "\uD83D\uDC69",
1691
+ male1: "\uD83D\uDC68",
1692
+ male2: "\uD83D\uDC68",
1693
+ fema: "\uD83D\uDC6B",
1694
+ group: "\uD83D\uDC65",
1695
+ bee: "\uD83D\uDC1D",
1696
+ mindmap: "\uD83D\uDDFA️",
1697
+ unchecked: "⬜",
1698
+ checked: "✅"
1699
+ }));
1700
+ var FREEMIND_PROGRESS = new Map(Object.entries({ "0%": "todo", "25%": "active", "50%": "active", "75%": "active", "100%": "done", checked: "done", unchecked: "todo" }));
1701
+ var freeMindIcon = (name) => {
1702
+ const priority = /^full-(\d)$/.exec(name);
1703
+ if (priority) {
1704
+ return { priority: Number(priority[1]) };
1705
+ }
1706
+ const progress = FREEMIND_PROGRESS.get(name);
1707
+ if (progress) {
1708
+ return { status: progress };
1709
+ }
1710
+ const emoji = /^emoji-([0-9A-Fa-f]{2,6}(?:-[0-9A-Fa-f]{2,6})*)$/.exec(name);
1711
+ if (emoji) {
1712
+ const codes = emoji[1].split("-").map((code) => parseInt(code, 16));
1713
+ if (codes.every((code) => code > 0 && code <= 1114111)) {
1714
+ return { icon: String.fromCodePoint(...codes) };
1715
+ }
1716
+ }
1717
+ const icon = FREEMIND_ICONS.get(name);
1718
+ return icon ? { icon } : null;
1719
+ };
1720
+ var XMIND_MARKERS = new Map(Object.entries({
1721
+ "smiley-smile": "\uD83D\uDE42",
1722
+ "smiley-laugh": "\uD83D\uDE04",
1723
+ "smiley-angry": "\uD83D\uDE20",
1724
+ "smiley-cry": "\uD83D\uDE22",
1725
+ "smiley-surprise": "\uD83D\uDE2E",
1726
+ "smiley-boring": "\uD83D\uDE10",
1727
+ "arrow-up": "⬆️",
1728
+ "arrow-down": "⬇️",
1729
+ "arrow-left": "⬅️",
1730
+ "arrow-right": "➡️",
1731
+ "arrow-up-right": "↗️",
1732
+ "arrow-down-right": "↘️",
1733
+ "arrow-down-left": "↙️",
1734
+ "arrow-up-left": "↖️",
1735
+ "arrow-refresh": "\uD83D\uDD04",
1736
+ "symbol-plus": "➕",
1737
+ "symbol-minus": "➖",
1738
+ "symbol-question": "❓",
1739
+ "symbol-exclam": "❗",
1740
+ "symbol-info": "ℹ️",
1741
+ "symbol-wrong": "❌",
1742
+ "symbol-right": "✅",
1743
+ "symbol-attention": "⚠️",
1744
+ "symbol-code": "\uD83D\uDCBB",
1745
+ "symbol-pin": "\uD83D\uDCCC",
1746
+ "symbol-heart": "❤️",
1747
+ "symbol-star": "⭐",
1748
+ "other-calendar": "\uD83D\uDCC5",
1749
+ "other-email": "✉️",
1750
+ "other-phone": "☎️",
1751
+ "other-question": "❓",
1752
+ "other-exclam": "❗",
1753
+ "other-lightbulb": "\uD83D\uDCA1",
1754
+ "other-clock": "⏰",
1755
+ "other-people": "\uD83D\uDC65",
1756
+ "other-money": "\uD83D\uDCB0",
1757
+ "other-books": "\uD83D\uDCDA",
1758
+ "other-music": "\uD83C\uDFB5",
1759
+ "other-yes": "\uD83D\uDC4D",
1760
+ "other-no": "\uD83D\uDC4E",
1761
+ "other-hourglass": "⏳",
1762
+ "other-flag": "\uD83D\uDEA9",
1763
+ "other-star": "⭐",
1764
+ "other-coffee-cup": "☕",
1765
+ "other-bomb": "\uD83D\uDCA3",
1766
+ "other-mic": "\uD83C\uDFA4",
1767
+ "other-phone-call": "\uD83D\uDCDE",
1768
+ "other-map": "\uD83D\uDDFA️",
1769
+ "other-code": "\uD83D\uDCBB",
1770
+ "other-search": "\uD83D\uDD0D",
1771
+ "other-dollar": "\uD83D\uDCB5",
1772
+ "other-heart": "❤️",
1773
+ c_symbol_like: "\uD83D\uDC4D",
1774
+ c_symbol_dislike: "\uD83D\uDC4E",
1775
+ c_symbol_heart: "❤️",
1776
+ c_symbol_star: "⭐",
1777
+ c_symbol_question: "❓",
1778
+ c_symbol_exclam: "❗",
1779
+ c_symbol_pin: "\uD83D\uDCCC",
1780
+ c_symbol_idea: "\uD83D\uDCA1",
1781
+ c_symbol_check: "✅",
1782
+ c_symbol_cross: "❌",
1783
+ c_symbol_fire: "\uD83D\uDD25",
1784
+ c_symbol_trophy: "\uD83C\uDFC6"
1785
+ }));
1786
+ var XMIND_FAMILIES = new Map(Object.entries({
1787
+ flag: "\uD83D\uDEA9",
1788
+ star: "⭐",
1789
+ people: "\uD83D\uDC64",
1790
+ tag: "\uD83C\uDFF7️",
1791
+ month: "\uD83D\uDCC5",
1792
+ week: "\uD83D\uDCC5",
1793
+ "half-star": "⭐"
1794
+ }));
1795
+ var XMIND_TASKS = new Map(Object.entries({
1796
+ "task-start": "todo",
1797
+ "task-oct": "active",
1798
+ "task-quarter": "active",
1799
+ "task-3oct": "active",
1800
+ "task-half": "active",
1801
+ "task-5oct": "active",
1802
+ "task-3quar": "active",
1803
+ "task-7oct": "active",
1804
+ "task-done": "done",
1805
+ "task-pause": "paused"
1806
+ }));
1807
+ var xmindMarker = (markerId) => {
1808
+ const priority = /^priority-(\d)$/.exec(markerId);
1809
+ if (priority) {
1810
+ return { priority: Number(priority[1]) };
1811
+ }
1812
+ const status = XMIND_TASKS.get(markerId);
1813
+ if (status) {
1814
+ return { status };
1815
+ }
1816
+ const icon = XMIND_MARKERS.get(markerId);
1817
+ if (icon) {
1818
+ return { icon };
1819
+ }
1820
+ const family = [...XMIND_FAMILIES.keys()].sort((first, second) => second.length - first.length).find((name) => markerId.startsWith(`${name}-`));
1821
+ return family ? { icon: XMIND_FAMILIES.get(family) } : null;
1822
+ };
1823
+
1824
+ // src/core/other-apps/markup.ts
1825
+ class MarkupError extends Error {
1826
+ line;
1827
+ constructor(message, line) {
1828
+ super(`${message} (line ${line})`);
1829
+ this.name = "MarkupError";
1830
+ this.line = line;
1831
+ }
1832
+ }
1833
+ var MAX_MARKUP_LENGTH = 64 * 1024 * 1024;
1834
+ var MAX_MARKUP_DEPTH = 1000;
1835
+ var XML_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
1836
+ var HTML_ENTITIES = {
1837
+ ...XML_ENTITIES,
1838
+ nbsp: " ",
1839
+ copy: "©",
1840
+ reg: "®",
1841
+ trade: "™",
1842
+ hellip: "…",
1843
+ mdash: "—",
1844
+ ndash: "–",
1845
+ lsquo: "‘",
1846
+ rsquo: "’",
1847
+ ldquo: "“",
1848
+ rdquo: "”",
1849
+ laquo: "«",
1850
+ raquo: "»",
1851
+ bull: "•",
1852
+ middot: "·",
1853
+ deg: "°",
1854
+ plusmn: "±",
1855
+ times: "×",
1856
+ divide: "÷",
1857
+ euro: "€",
1858
+ pound: "£",
1859
+ yen: "¥",
1860
+ cent: "¢",
1861
+ sect: "§",
1862
+ para: "¶",
1863
+ iexcl: "¡",
1864
+ iquest: "¿",
1865
+ ordf: "ª",
1866
+ ordm: "º",
1867
+ acute: "´",
1868
+ uml: "¨",
1869
+ shy: "­",
1870
+ frac12: "½",
1871
+ frac14: "¼",
1872
+ frac34: "¾",
1873
+ sup1: "¹",
1874
+ sup2: "²",
1875
+ sup3: "³",
1876
+ micro: "µ",
1877
+ larr: "←",
1878
+ rarr: "→",
1879
+ uarr: "↑",
1880
+ darr: "↓",
1881
+ harr: "↔",
1882
+ rArr: "⇒",
1883
+ lArr: "⇐",
1884
+ hArr: "⇔",
1885
+ aacute: "á",
1886
+ eacute: "é",
1887
+ iacute: "í",
1888
+ oacute: "ó",
1889
+ uacute: "ú",
1890
+ yacute: "ý",
1891
+ Aacute: "Á",
1892
+ Eacute: "É",
1893
+ Iacute: "Í",
1894
+ Oacute: "Ó",
1895
+ Uacute: "Ú",
1896
+ Yacute: "Ý",
1897
+ agrave: "à",
1898
+ egrave: "è",
1899
+ igrave: "ì",
1900
+ ograve: "ò",
1901
+ ugrave: "ù",
1902
+ Agrave: "À",
1903
+ Egrave: "È",
1904
+ Igrave: "Ì",
1905
+ Ograve: "Ò",
1906
+ Ugrave: "Ù",
1907
+ acirc: "â",
1908
+ ecirc: "ê",
1909
+ icirc: "î",
1910
+ ocirc: "ô",
1911
+ ucirc: "û",
1912
+ Acirc: "Â",
1913
+ Ecirc: "Ê",
1914
+ Icirc: "Î",
1915
+ Ocirc: "Ô",
1916
+ Ucirc: "Û",
1917
+ auml: "ä",
1918
+ euml: "ë",
1919
+ iuml: "ï",
1920
+ ouml: "ö",
1921
+ uuml: "ü",
1922
+ yuml: "ÿ",
1923
+ Auml: "Ä",
1924
+ Euml: "Ë",
1925
+ Iuml: "Ï",
1926
+ Ouml: "Ö",
1927
+ Uuml: "Ü",
1928
+ atilde: "ã",
1929
+ otilde: "õ",
1930
+ ntilde: "ñ",
1931
+ Atilde: "Ã",
1932
+ Otilde: "Õ",
1933
+ Ntilde: "Ñ",
1934
+ ccedil: "ç",
1935
+ Ccedil: "Ç",
1936
+ aring: "å",
1937
+ Aring: "Å",
1938
+ aelig: "æ",
1939
+ AElig: "Æ",
1940
+ oslash: "ø",
1941
+ Oslash: "Ø",
1942
+ szlig: "ß",
1943
+ thorn: "þ",
1944
+ THORN: "Þ",
1945
+ eth: "ð",
1946
+ ETH: "Ð",
1947
+ oelig: "œ",
1948
+ OElig: "Œ"
1949
+ };
1950
+ var VOID_ELEMENTS = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
1951
+ var RAW_TEXT_ELEMENTS = new Set(["script", "style"]);
1952
+ var NAME = /^[^\s/>=]+/;
1953
+ var ATTRIBUTE = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/y;
1954
+ var decodeMarkup = (bytes) => {
1955
+ if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
1956
+ return new TextDecoder("utf-8").decode(bytes.subarray(3));
1957
+ }
1958
+ if (bytes[0] === 255 && bytes[1] === 254) {
1959
+ return new TextDecoder("utf-16le").decode(bytes.subarray(2));
1960
+ }
1961
+ if (bytes[0] === 254 && bytes[1] === 255) {
1962
+ return new TextDecoder("utf-16be").decode(bytes.subarray(2));
1963
+ }
1964
+ if (bytes[0] === 60 && bytes[1] === 0) {
1965
+ return new TextDecoder("utf-16le").decode(bytes);
1966
+ }
1967
+ if (bytes[0] === 0 && bytes[1] === 60) {
1968
+ return new TextDecoder("utf-16be").decode(bytes);
1969
+ }
1970
+ const start = String.fromCharCode(...bytes.subarray(0, 200));
1971
+ const declared = /^\s*<\?xml[^>]*\bencoding\s*=\s*["']([A-Za-z0-9._:-]+)["']/.exec(start)?.[1];
1972
+ if (declared && !/^utf-?8$/i.test(declared)) {
1973
+ try {
1974
+ return new TextDecoder(declared).decode(bytes);
1975
+ } catch {}
1976
+ }
1977
+ try {
1978
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1979
+ } catch {
1980
+ return new TextDecoder(declared ? "utf-8" : "windows-1252").decode(bytes);
1981
+ }
1982
+ };
1983
+ var parseXml = (text) => {
1984
+ const root = readMarkup(text, { html: false });
1985
+ const elements = root.children.filter(isElement);
1986
+ if (elements.length !== 1) {
1987
+ throw new MarkupError(elements.length === 0 ? "No element in the file" : "More than one top element", 1);
1988
+ }
1989
+ return elements[0];
1990
+ };
1991
+ var parseHtml = (text) => {
1992
+ return readMarkup(text, { html: true });
1993
+ };
1994
+ var isElement = (node) => {
1995
+ return typeof node !== "string";
1996
+ };
1997
+ var childElements = (element, localName) => {
1998
+ return element.children.filter((child) => isElement(child) && (localName === undefined || child.localName === localName));
1999
+ };
2000
+ var childElement = (element, localName) => {
2001
+ return element.children.find((child) => isElement(child) && child.localName === localName);
2002
+ };
2003
+ var attributeOf = (element, localName) => {
2004
+ if (Object.prototype.hasOwnProperty.call(element.attributes, localName)) {
2005
+ return element.attributes[localName];
2006
+ }
2007
+ for (const [name, value] of Object.entries(element.attributes)) {
2008
+ if (localPart(name) === localName) {
2009
+ return value;
2010
+ }
2011
+ }
2012
+ return;
2013
+ };
2014
+ var textContent = (node) => {
2015
+ if (!isElement(node)) {
2016
+ return node;
2017
+ }
2018
+ let text = "";
2019
+ const pending = [node];
2020
+ while (pending.length > 0) {
2021
+ const next = pending.pop();
2022
+ if (isElement(next)) {
2023
+ for (let index = next.children.length - 1;index >= 0; index--) {
2024
+ pending.push(next.children[index]);
2025
+ }
2026
+ } else {
2027
+ text += next;
2028
+ }
2029
+ }
2030
+ return text;
2031
+ };
2032
+ var localPart = (name) => {
2033
+ const colon = name.indexOf(":");
2034
+ return colon === -1 ? name : name.slice(colon + 1);
2035
+ };
2036
+ var lineAt = (text, index) => {
2037
+ let line = 1;
2038
+ for (let position = text.indexOf(`
2039
+ `);position !== -1 && position < index; position = text.indexOf(`
2040
+ `, position + 1)) {
2041
+ line += 1;
2042
+ }
2043
+ return line;
2044
+ };
2045
+ var readMarkup = (input, mode) => {
2046
+ if (input.length > MAX_MARKUP_LENGTH) {
2047
+ throw new MarkupError(`The file is larger than ${MAX_MARKUP_LENGTH / 1024 / 1024} MB of text`, 1);
2048
+ }
2049
+ const text = input.replace(/^\ufeff/, "").replace(/\r\n?/g, `
2050
+ `);
2051
+ const fragment = { name: "#fragment", localName: "#fragment", attributes: {}, children: [] };
2052
+ const open = [fragment];
2053
+ const entities = mode.html ? HTML_ENTITIES : XML_ENTITIES;
2054
+ let index = 0;
2055
+ const fail = (message, at) => {
2056
+ throw new MarkupError(message, lineAt(text, at));
2057
+ };
2058
+ const current = () => open[open.length - 1];
2059
+ const addText = (value) => {
2060
+ if (value === "") {
2061
+ return;
2062
+ }
2063
+ const children = current().children;
2064
+ const last = children.length - 1;
2065
+ if (last >= 0 && typeof children[last] === "string") {
2066
+ children[last] = children[last] + value;
2067
+ } else {
2068
+ children.push(value);
2069
+ }
2070
+ };
2071
+ while (index < text.length) {
2072
+ const tag = text.indexOf("<", index);
2073
+ if (tag === -1) {
2074
+ addText(decodeEntities(text.slice(index), entities));
2075
+ break;
2076
+ }
2077
+ if (tag > index) {
2078
+ addText(decodeEntities(text.slice(index, tag), entities));
2079
+ }
2080
+ index = tag;
2081
+ if (text.startsWith("<!--", index)) {
2082
+ const end = text.indexOf("-->", index + 4);
2083
+ if (end === -1) {
2084
+ if (mode.html) {
2085
+ break;
2086
+ }
2087
+ fail("A comment that never ends", index);
2088
+ }
2089
+ index = end + 3;
2090
+ continue;
2091
+ }
2092
+ if (text.startsWith("<![CDATA[", index)) {
2093
+ const end = text.indexOf("]]>", index + 9);
2094
+ if (end === -1) {
2095
+ fail("A CDATA section that never ends", index);
2096
+ }
2097
+ addText(text.slice(index + 9, end));
2098
+ index = end + 3;
2099
+ continue;
2100
+ }
2101
+ if (text.startsWith("<?", index)) {
2102
+ const end = text.indexOf("?>", index + 2);
2103
+ if (end === -1) {
2104
+ fail("A processing instruction that never ends", index);
2105
+ }
2106
+ index = end + 2;
2107
+ continue;
2108
+ }
2109
+ if (text.startsWith("<!", index)) {
2110
+ index = skipDeclaration(text, index, fail);
2111
+ continue;
2112
+ }
2113
+ if (text.startsWith("</", index)) {
2114
+ const end = text.indexOf(">", index + 2);
2115
+ if (end === -1) {
2116
+ fail("An end tag that never closes", index);
2117
+ }
2118
+ const name = text.slice(index + 2, end).trim();
2119
+ closeElement(open, name, mode, () => fail(`</${name}> doesn't match <${current().name}>`, index));
2120
+ index = end + 1;
2121
+ continue;
2122
+ }
2123
+ const nameMatch = NAME.exec(text.slice(index + 1, index + 257));
2124
+ if (!nameMatch || !/^[\p{L}_:]/u.test(nameMatch[0])) {
2125
+ if (mode.html) {
2126
+ addText("<");
2127
+ index += 1;
2128
+ continue;
2129
+ }
2130
+ fail('A "<" that starts no tag', index);
2131
+ }
2132
+ const name = nameMatch[0];
2133
+ const [attributes, afterAttributes, selfClosing] = readAttributes(text, index + 1 + name.length, entities, mode, fail);
2134
+ const localName = mode.html ? localPart(name).toLowerCase() : localPart(name);
2135
+ const element = { name, localName, attributes, children: [] };
2136
+ current().children.push(element);
2137
+ index = afterAttributes;
2138
+ if (mode.html && RAW_TEXT_ELEMENTS.has(localName)) {
2139
+ const end = text.toLowerCase().indexOf(`</${localName}`, index);
2140
+ index = end === -1 ? text.length : text.indexOf(">", end) + 1 || text.length;
2141
+ continue;
2142
+ }
2143
+ if (selfClosing || mode.html && VOID_ELEMENTS.has(localName)) {
2144
+ continue;
2145
+ }
2146
+ if (open.length > MAX_MARKUP_DEPTH) {
2147
+ fail(`Elements nested more than ${MAX_MARKUP_DEPTH} deep`, index);
2148
+ }
2149
+ open.push(element);
2150
+ }
2151
+ if (!mode.html && open.length > 1) {
2152
+ fail(`<${current().name}> is never closed`, text.length);
2153
+ }
2154
+ return fragment;
2155
+ };
2156
+ var readAttributes = (text, start, entities, mode, fail) => {
2157
+ const attributes = {};
2158
+ let index = start;
2159
+ while (index < text.length) {
2160
+ while (index < text.length && /\s/.test(text[index])) {
2161
+ index += 1;
2162
+ }
2163
+ if (text[index] === ">") {
2164
+ return [attributes, index + 1, false];
2165
+ }
2166
+ if (text.startsWith("/>", index)) {
2167
+ return [attributes, index + 2, true];
2168
+ }
2169
+ if (text[index] === "/") {
2170
+ index += 1;
2171
+ continue;
2172
+ }
2173
+ ATTRIBUTE.lastIndex = index;
2174
+ const match = ATTRIBUTE.exec(text);
2175
+ if (!match || match[0].length === 0) {
2176
+ fail("A tag that never closes", start);
2177
+ }
2178
+ const [whole, name, doubleQuoted, singleQuoted, unquoted] = match;
2179
+ if (unquoted !== undefined && !mode.html) {
2180
+ fail(`The value of ${name} has no quotes`, index);
2181
+ }
2182
+ const raw = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
2183
+ attributes[name] = decodeEntities(mode.html ? raw : raw.replace(/[\t\n]/g, " "), entities);
2184
+ index += whole.length;
2185
+ }
2186
+ return fail("A tag that never closes", start);
2187
+ };
2188
+ var skipDeclaration = (text, start, fail) => {
2189
+ let depth = 0;
2190
+ let quote = null;
2191
+ for (let index = start + 2;index < text.length; index++) {
2192
+ const character = text[index];
2193
+ if (quote) {
2194
+ if (character === quote) {
2195
+ quote = null;
2196
+ }
2197
+ } else if (character === '"' || character === "'") {
2198
+ quote = character;
2199
+ } else if (character === "[") {
2200
+ depth += 1;
2201
+ } else if (character === "]") {
2202
+ depth -= 1;
2203
+ } else if (character === ">" && depth <= 0) {
2204
+ return index + 1;
2205
+ }
2206
+ }
2207
+ return fail("A declaration that never ends", start);
2208
+ };
2209
+ var closeElement = (open, name, mode, mismatch) => {
2210
+ const top = open[open.length - 1];
2211
+ if (open.length > 1 && (top.name === name || mode.html && top.localName === localPart(name).toLowerCase())) {
2212
+ open.pop();
2213
+ return;
2214
+ }
2215
+ if (!mode.html) {
2216
+ mismatch();
2217
+ }
2218
+ const wanted = localPart(name).toLowerCase();
2219
+ for (let depth = open.length - 1;depth > 0; depth--) {
2220
+ if (open[depth].localName === wanted) {
2221
+ open.length = depth;
2222
+ return;
2223
+ }
2224
+ }
2225
+ };
2226
+ var decodeEntities = (text, entities) => {
2227
+ if (!text.includes("&")) {
2228
+ return text;
2229
+ }
2230
+ return text.replace(/&(#[xX][0-9a-fA-F]{1,6}|#[0-9]{1,7}|[A-Za-z][A-Za-z0-9]{1,31});/g, (whole, reference) => {
2231
+ if (reference.startsWith("#")) {
2232
+ const code = reference[1] === "x" || reference[1] === "X" ? parseInt(reference.slice(2), 16) : parseInt(reference.slice(1), 10);
2233
+ return code > 0 && code <= 1114111 && !(code >= 55296 && code <= 57343) ? String.fromCodePoint(code) : whole;
2234
+ }
2235
+ return Object.prototype.hasOwnProperty.call(entities, reference) ? entities[reference] : whole;
2236
+ });
2237
+ };
2238
+
2239
+ // src/core/other-apps/notes.ts
2240
+ var BLOCK_CONTAINERS = new Set([
2241
+ "html",
2242
+ "body",
2243
+ "div",
2244
+ "p",
2245
+ "section",
2246
+ "article",
2247
+ "header",
2248
+ "footer",
2249
+ "main",
2250
+ "aside",
2251
+ "nav",
2252
+ "center",
2253
+ "figure",
2254
+ "figcaption",
2255
+ "address",
2256
+ "details",
2257
+ "summary",
2258
+ "dl",
2259
+ "dt",
2260
+ "dd",
2261
+ "form",
2262
+ "fieldset",
2263
+ "tbody",
2264
+ "thead",
2265
+ "tfoot",
2266
+ "caption",
2267
+ "#fragment"
2268
+ ]);
2269
+ var SKIPPED = new Set(["head", "title", "script", "style", "meta", "link", "template", "object", "iframe", "svg", "math"]);
2270
+ var HEADINGS = new Map([["h1", 1], ["h2", 2], ["h3", 3], ["h4", 4], ["h5", 5], ["h6", 6]]);
2271
+ var INLINE_KINDS = new Map([
2272
+ ["strong", "strong"],
2273
+ ["b", "strong"],
2274
+ ["em", "emphasis"],
2275
+ ["i", "emphasis"],
2276
+ ["cite", "emphasis"],
2277
+ ["s", "strike"],
2278
+ ["strike", "strike"],
2279
+ ["del", "strike"]
2280
+ ]);
2281
+ var CODE_ELEMENTS = new Set(["code", "tt", "kbd", "samp", "var"]);
2282
+ var isKeptLink = (href) => {
2283
+ return /^(https?:\/\/|mailto:)\S+$/i.test(href) || /^#\S+$/.test(href);
2284
+ };
2285
+ var keptAddress = (href) => {
2286
+ const address = href.trim();
2287
+ if (/^www\.[^\s/]+\.\S+$/i.test(address)) {
2288
+ return `https://${address}`;
2289
+ }
2290
+ return isKeptLink(address) ? address : null;
2291
+ };
2292
+ var isWebAddress = (src) => {
2293
+ return /^https?:\/\/\S+$/i.test(src);
2294
+ };
2295
+ var noteFromHtml = (html, addresses = {}) => {
2296
+ const element = typeof html === "string" ? parseHtml(html) : html;
2297
+ return noteFromBlocks(readBlocks(element.children, addresses), addresses);
2298
+ };
2299
+ var noteFromPlainText = (text) => {
2300
+ const cleaned = (text ?? "").replace(/\r\n?/g, `
2301
+ `).split(`
2302
+ `).map((line) => line.replace(/[ \t]+$/, "")).join(`
2303
+ `).replace(/^\n+|\n+$/g, "");
2304
+ if (cleaned.trim() === "") {
2305
+ return;
2306
+ }
2307
+ const paragraphs = cleaned.split(/\n{2,}/);
2308
+ const readsTheSame = paragraphs.every((paragraph) => !paragraph.includes(`
2309
+ `) && isInertText(paragraph) && isInertLineStart(paragraph));
2310
+ return { text: cleaned, format: readsTheSame ? "markdown" : "plain" };
2311
+ };
2312
+ var noteFromBlocks = (blocks, addresses = {}) => {
2313
+ const kept = blocks.filter((block) => !isEmptyBlock(block));
2314
+ if (kept.length === 0) {
2315
+ return;
2316
+ }
2317
+ const writer = new MarkdownWriter(addresses);
2318
+ const markdown = writer.blocks(kept);
2319
+ if (writer.inert && markdown.trim() !== "") {
2320
+ return { text: markdown, format: "markdown" };
2321
+ }
2322
+ const plain = plainBlocks(kept).trim();
2323
+ return plain === "" ? undefined : { text: plain, format: "plain" };
2324
+ };
2325
+ var joinNotes = (notes) => {
2326
+ const present = notes.filter((note) => note !== undefined && note.text.trim() !== "");
2327
+ if (present.length <= 1) {
2328
+ return present[0];
2329
+ }
2330
+ return {
2331
+ text: present.map((note) => note.text).join(`
2332
+
2333
+ `),
2334
+ format: present.every((note) => note.format === "markdown") ? "markdown" : "plain"
2335
+ };
2336
+ };
2337
+ var titleFromHtml = (html) => {
2338
+ const element = typeof html === "string" ? parseHtml(html) : html;
2339
+ const pieces = inlineFrom(element.children, {});
2340
+ const link = firstLink(pieces);
2341
+ return { text: plainInline(pieces, false, false).replace(/\s+/g, " ").trim(), ...link ? { link } : {} };
2342
+ };
2343
+ var firstLink = (pieces) => {
2344
+ for (const piece of pieces) {
2345
+ if (piece.kind === "link") {
2346
+ return piece.href;
2347
+ }
2348
+ if (piece.kind === "strong" || piece.kind === "emphasis" || piece.kind === "strike") {
2349
+ const inner = firstLink(piece.children);
2350
+ if (inner) {
2351
+ return inner;
2352
+ }
2353
+ }
2354
+ }
2355
+ return;
2356
+ };
2357
+ var readBlocks = (nodes, addresses) => {
2358
+ const blocks = [];
2359
+ let inline = [];
2360
+ const flush = () => {
2361
+ const trimmed = trimInline(inline);
2362
+ if (trimmed.length > 0) {
2363
+ blocks.push({ kind: "paragraph", inline: trimmed });
2364
+ }
2365
+ inline = [];
2366
+ };
2367
+ for (const node of nodes) {
2368
+ if (!isElement(node)) {
2369
+ inline.push(...inlineFrom([node], addresses));
2370
+ continue;
2371
+ }
2372
+ const name = node.localName.toLowerCase();
2373
+ if (SKIPPED.has(name)) {
2374
+ continue;
2375
+ }
2376
+ const headingLevel = HEADINGS.get(name);
2377
+ if (headingLevel !== undefined) {
2378
+ flush();
2379
+ const content = trimInline(inlineFrom(node.children, addresses).map((piece) => piece.kind === "break" ? { kind: "text", text: " " } : piece));
2380
+ if (content.length > 0) {
2381
+ blocks.push({ kind: "heading", level: headingLevel, inline: content });
2382
+ }
2383
+ } else if (name === "ul" || name === "ol" || name === "menu" || name === "dir") {
2384
+ flush();
2385
+ const items = node.children.filter((child) => isElement(child) || child.trim() !== "").map((child) => readBlocks(isElement(child) && child.localName.toLowerCase() === "li" ? child.children : [child], addresses)).filter((item) => item.some((block) => !isEmptyBlock(block)));
2386
+ if (items.length > 0) {
2387
+ blocks.push({ kind: "list", ordered: name === "ol", items });
2388
+ }
2389
+ } else if (name === "li") {
2390
+ flush();
2391
+ blocks.push({ kind: "list", ordered: false, items: [readBlocks(node.children, addresses)] });
2392
+ } else if (name === "blockquote") {
2393
+ flush();
2394
+ const quoted = readBlocks(node.children, addresses);
2395
+ if (quoted.length > 0) {
2396
+ blocks.push({ kind: "quote", blocks: quoted });
2397
+ }
2398
+ } else if (name === "pre") {
2399
+ flush();
2400
+ const code = textContent(node).replace(/^\n/, "").replace(/\s+$/, "");
2401
+ if (code !== "") {
2402
+ blocks.push({ kind: "code", text: code });
2403
+ }
2404
+ } else if (name === "table") {
2405
+ flush();
2406
+ blocks.push(...tableRows(node, addresses));
2407
+ } else if (name === "hr") {
2408
+ flush();
2409
+ } else if (BLOCK_CONTAINERS.has(name)) {
2410
+ flush();
2411
+ blocks.push(...readBlocks(node.children, addresses));
2412
+ } else {
2413
+ inline.push(...inlineFrom([node], addresses));
2414
+ }
2415
+ }
2416
+ flush();
2417
+ return blocks;
2418
+ };
2419
+ var tableRows = (table, addresses) => {
2420
+ const rows = [];
2421
+ const pending = [table];
2422
+ while (pending.length > 0) {
2423
+ const element = pending.shift();
2424
+ for (const child of childElements(element)) {
2425
+ const name = child.localName.toLowerCase();
2426
+ if (name === "tr") {
2427
+ const cells = childElements(child).filter((cell) => /^t[dh]$/.test(cell.localName.toLowerCase()));
2428
+ const inline = [];
2429
+ for (const cell of cells) {
2430
+ const content = trimInline(inlineFrom(cell.children, addresses).map((piece) => piece.kind === "break" ? { kind: "text", text: " " } : piece));
2431
+ if (content.length > 0) {
2432
+ if (inline.length > 0) {
2433
+ inline.push({ kind: "text", text: " · " });
2434
+ }
2435
+ inline.push(...content);
2436
+ }
2437
+ }
2438
+ if (inline.length > 0) {
2439
+ rows.push({ kind: "paragraph", inline });
2440
+ }
2441
+ } else if (name !== "td" && name !== "th") {
2442
+ pending.push(child);
2443
+ }
2444
+ }
2445
+ }
2446
+ return rows;
2447
+ };
2448
+ var inlineFrom = (nodes, addresses) => {
2449
+ const pieces = [];
2450
+ for (const node of nodes) {
2451
+ if (!isElement(node)) {
2452
+ const text = node.replace(/[ \t\n\r\f]+/g, " ");
2453
+ if (text !== "") {
2454
+ pieces.push({ kind: "text", text });
2455
+ }
2456
+ continue;
2457
+ }
2458
+ const name = node.localName.toLowerCase();
2459
+ if (SKIPPED.has(name)) {
2460
+ continue;
2461
+ }
2462
+ if (name === "br") {
2463
+ pieces.push({ kind: "break" });
2464
+ } else if (name === "img") {
2465
+ const src = node.attributes.src ?? Object.entries(node.attributes).find(([key]) => key.endsWith(":src"))?.[1] ?? "";
2466
+ pieces.push({ kind: "image", src, alt: (node.attributes.alt ?? node.attributes.title ?? "").trim() });
2467
+ } else if (INLINE_KINDS.has(name)) {
2468
+ pieces.push({ kind: INLINE_KINDS.get(name), children: inlineFrom(node.children, addresses) });
2469
+ } else if (CODE_ELEMENTS.has(name)) {
2470
+ pieces.push({ kind: "code", text: textContent(node).replace(/\s+/g, " ") });
2471
+ } else if (name === "a") {
2472
+ const href = (node.attributes.href ?? Object.entries(node.attributes).find(([key]) => key.endsWith(":href"))?.[1] ?? "").trim();
2473
+ const children = inlineFrom(node.children, addresses);
2474
+ if (href) {
2475
+ pieces.push({ kind: "link", href, children });
2476
+ } else {
2477
+ pieces.push(...children);
2478
+ }
2479
+ } else {
2480
+ const block = BLOCK_CONTAINERS.has(name) || HEADINGS.has(name) || name === "li" || name === "tr";
2481
+ const content = inlineFrom(node.children, addresses);
2482
+ if (block && pieces.length > 0) {
2483
+ pieces.push({ kind: "break" });
2484
+ }
2485
+ pieces.push(...content);
2486
+ }
2487
+ }
2488
+ return pieces;
2489
+ };
2490
+ var trimInline = (pieces) => {
2491
+ const result = [...pieces];
2492
+ while (result.length > 0 && isBlankPiece(result[0])) {
2493
+ result.shift();
2494
+ }
2495
+ while (result.length > 0 && isBlankPiece(result[result.length - 1])) {
2496
+ result.pop();
2497
+ }
2498
+ if (result.length > 0 && result[0].kind === "text") {
2499
+ result[0] = { kind: "text", text: result[0].text.replace(/^ +/, "") };
2500
+ }
2501
+ const last = result.length - 1;
2502
+ if (last >= 0 && result[last].kind === "text") {
2503
+ result[last] = { kind: "text", text: result[last].text.replace(/ +$/, "") };
2504
+ }
2505
+ return result;
2506
+ };
2507
+ var isBlankPiece = (piece) => {
2508
+ return piece.kind === "break" || piece.kind === "text" && piece.text.trim() === "";
2509
+ };
2510
+ var isEmptyBlock = (block) => {
2511
+ switch (block.kind) {
2512
+ case "paragraph":
2513
+ case "heading":
2514
+ return plainInline(block.inline, false).trim() === "" && !block.inline.some((piece) => piece.kind === "image" && piece.src !== "");
2515
+ case "list":
2516
+ return block.items.every((item) => item.every(isEmptyBlock));
2517
+ case "quote":
2518
+ return block.blocks.every(isEmptyBlock);
2519
+ case "code":
2520
+ return block.text.trim() === "";
2521
+ }
2522
+ };
2523
+ var ACTIVE_CHARACTERS = /[`*_~[\]<\\]|&#?[A-Za-z0-9]+;/;
2524
+ var ACTIVE_LINE_START = /^(\s{4,}|\s*(#{1,6}(\s|$)|>|[-*+](\s|$)|\d{1,9}[.)](\s|$)|```|~~~|[-=_]{3,}\s*$))/;
2525
+ var isInertText = (text) => {
2526
+ return !ACTIVE_CHARACTERS.test(text);
2527
+ };
2528
+ var isInertLineStart = (line) => {
2529
+ return !ACTIVE_LINE_START.test(line);
2530
+ };
2531
+
2532
+ class MarkdownWriter {
2533
+ inert = true;
2534
+ addresses;
2535
+ constructor(addresses) {
2536
+ this.addresses = addresses;
2537
+ }
2538
+ blocks(blocks) {
2539
+ return blocks.map((block) => this.block(block)).filter((text) => text !== "").join(`
2540
+
2541
+ `);
2542
+ }
2543
+ block(block) {
2544
+ switch (block.kind) {
2545
+ case "paragraph": {
2546
+ const lines = this.inlineLines(block.inline);
2547
+ return lines.map((line) => this.lineStart(line)).filter((line) => line !== "").join(`
2548
+
2549
+ `);
2550
+ }
2551
+ case "heading": {
2552
+ const text = this.inlineLines(block.inline).join(" ").trim();
2553
+ return text === "" ? "" : `${"#".repeat(block.level)} ${text}`;
2554
+ }
2555
+ case "list":
2556
+ return this.list(block, 0);
2557
+ case "quote": {
2558
+ const inner = this.blocks(block.blocks);
2559
+ return inner.split(`
2560
+ `).map((line) => line === "" ? ">" : `> ${line}`).join(`
2561
+ `);
2562
+ }
2563
+ case "code": {
2564
+ if (/^\s*(```|~~~)/m.test(block.text)) {
2565
+ this.inert = false;
2566
+ }
2567
+ return `\`\`\`
2568
+ ${block.text}
2569
+ \`\`\``;
2570
+ }
2571
+ }
2572
+ }
2573
+ list(list, indent) {
2574
+ const lines = [];
2575
+ list.items.forEach((item, index) => {
2576
+ const marker = list.ordered ? `${index + 1}.` : "-";
2577
+ const nested = [];
2578
+ const words = [];
2579
+ for (const block of item) {
2580
+ if (block.kind === "list") {
2581
+ nested.push(this.list(block, indent + marker.length + 1));
2582
+ } else {
2583
+ words.push(this.flatText(block));
2584
+ }
2585
+ }
2586
+ const text = words.filter((word) => word !== "").join(" ");
2587
+ lines.push(`${" ".repeat(indent)}${marker} ${text}`.trimEnd());
2588
+ lines.push(...nested);
2589
+ });
2590
+ return lines.join(`
2591
+ `);
2592
+ }
2593
+ flatText(block) {
2594
+ switch (block.kind) {
2595
+ case "paragraph":
2596
+ case "heading":
2597
+ return this.inlineLines(block.inline).join(" ").trim();
2598
+ case "quote":
2599
+ return block.blocks.map((inner) => this.flatText(inner)).join(" ");
2600
+ case "code":
2601
+ return this.code(block.text.replace(/\s+/g, " "));
2602
+ case "list":
2603
+ return this.list(block, 0);
2604
+ }
2605
+ }
2606
+ lineStart(line) {
2607
+ if (!isInertLineStart(line)) {
2608
+ this.inert = false;
2609
+ }
2610
+ return line.trim();
2611
+ }
2612
+ inlineLines(pieces) {
2613
+ return this.inlineText(pieces).split(`
2614
+ `).map((line) => line.replace(/ {2,}/g, " ").trim());
2615
+ }
2616
+ inlineText(pieces) {
2617
+ return mergeNeighbors(pieces).map((piece) => piece.kind === "break" ? `
2618
+ ` : this.inline(piece)).join("");
2619
+ }
2620
+ inline(piece) {
2621
+ switch (piece.kind) {
2622
+ case "text":
2623
+ if (!isInertText(piece.text)) {
2624
+ this.inert = false;
2625
+ }
2626
+ return piece.text;
2627
+ case "break":
2628
+ return " ";
2629
+ case "strong":
2630
+ case "emphasis":
2631
+ case "strike": {
2632
+ const inner = this.inlineText(piece.children).replace(/\n/g, " ").replace(/ {2,}/g, " ");
2633
+ const [, before, middle, after] = /^(\s*)([\s\S]*?)(\s*)$/.exec(inner);
2634
+ if (middle === "") {
2635
+ return inner;
2636
+ }
2637
+ const mark = piece.kind === "strong" ? "**" : piece.kind === "emphasis" ? "*" : "~~";
2638
+ return `${before}${mark}${middle}${mark}${after}`;
2639
+ }
2640
+ case "code":
2641
+ return this.code(piece.text);
2642
+ case "link": {
2643
+ const label = this.inlineLines(piece.children).join(" ").trim();
2644
+ const href = (this.addresses.link ?? defaultLink)(piece.href);
2645
+ if (!href) {
2646
+ return label;
2647
+ }
2648
+ return label === "" || label === href ? `<${encodeAddress(href)}>` : `[${label}](${encodeAddress(href)})`;
2649
+ }
2650
+ case "image": {
2651
+ const src = piece.src ? (this.addresses.picture ?? defaultPicture)(piece.src) : null;
2652
+ if (!src) {
2653
+ if (!isInertText(piece.alt)) {
2654
+ this.inert = false;
2655
+ }
2656
+ return piece.alt;
2657
+ }
2658
+ if (!isInertText(piece.alt)) {
2659
+ this.inert = false;
2660
+ }
2661
+ return `![${piece.alt}](${encodeAddress(src)})`;
2662
+ }
2663
+ }
2664
+ }
2665
+ code(text) {
2666
+ const trimmed = text.trim();
2667
+ if (trimmed === "") {
2668
+ return "";
2669
+ }
2670
+ if (trimmed.includes("`")) {
2671
+ this.inert = false;
2672
+ }
2673
+ return `\`${trimmed}\``;
2674
+ }
2675
+ }
2676
+ var mergeNeighbors = (pieces) => {
2677
+ const merged = [];
2678
+ for (const piece of pieces) {
2679
+ const last = merged[merged.length - 1];
2680
+ if (last && last.kind === piece.kind && (piece.kind === "strong" || piece.kind === "emphasis" || piece.kind === "strike")) {
2681
+ merged[merged.length - 1] = { kind: piece.kind, children: [...last.children, ...piece.children] };
2682
+ } else {
2683
+ merged.push(piece);
2684
+ }
2685
+ }
2686
+ return merged;
2687
+ };
2688
+ var defaultLink = (href) => {
2689
+ return keptAddress(href);
2690
+ };
2691
+ var defaultPicture = (src) => {
2692
+ return isWebAddress(src) ? src : null;
2693
+ };
2694
+ var encodeAddress = (address) => {
2695
+ return address.replace(/ /g, "%20").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/</g, "%3C").replace(/>/g, "%3E");
2696
+ };
2697
+ var plainBlocks = (blocks, indent = "") => {
2698
+ return blocks.map((block) => plainBlock(block, indent)).filter((text) => text.trim() !== "").join(`
2699
+
2700
+ `);
2701
+ };
2702
+ var plainBlock = (block, indent) => {
2703
+ switch (block.kind) {
2704
+ case "paragraph":
2705
+ return prefixLines(plainInline(block.inline, true), indent);
2706
+ case "heading":
2707
+ return prefixLines(plainInline(block.inline, false).trim(), indent);
2708
+ case "list":
2709
+ return block.items.map((item, index) => {
2710
+ const marker = block.ordered ? `${index + 1}. ` : "- ";
2711
+ const text = plainBlocks(item.filter((inner) => inner.kind !== "list"), "").replace(/\n+/g, " ").trim();
2712
+ const nested = item.filter((inner) => inner.kind === "list").map((inner) => plainBlock(inner, `${indent}${" ".repeat(marker.length)}`));
2713
+ return [`${indent}${marker}${text}`.trimEnd(), ...nested].join(`
2714
+ `);
2715
+ }).join(`
2716
+ `);
2717
+ case "quote":
2718
+ return prefixLines(plainBlocks(block.blocks), `${indent}> `);
2719
+ case "code":
2720
+ return prefixLines(block.text, indent);
2721
+ }
2722
+ };
2723
+ var prefixLines = (text, prefix) => {
2724
+ return prefix === "" ? text : text.split(`
2725
+ `).map((line) => `${prefix}${line}`).join(`
2726
+ `);
2727
+ };
2728
+ var plainInline = (pieces, breaks, addresses = true) => {
2729
+ let text = "";
2730
+ for (const piece of pieces) {
2731
+ switch (piece.kind) {
2732
+ case "text":
2733
+ text += piece.text;
2734
+ break;
2735
+ case "break":
2736
+ text += breaks ? `
2737
+ ` : " ";
2738
+ break;
2739
+ case "strong":
2740
+ case "emphasis":
2741
+ case "strike":
2742
+ text += plainInline(piece.children, breaks, addresses);
2743
+ break;
2744
+ case "code":
2745
+ text += piece.text;
2746
+ break;
2747
+ case "link": {
2748
+ const label = plainInline(piece.children, breaks, addresses).trim();
2749
+ const href = addresses && isKeptLink(piece.href) && !piece.href.startsWith("#") ? piece.href : "";
2750
+ text += href && label && label !== href ? `${label} (${href})` : label || href;
2751
+ break;
2752
+ }
2753
+ case "image":
2754
+ text += piece.alt;
2755
+ break;
2756
+ }
2757
+ }
2758
+ return text.split(`
2759
+ `).map((line) => line.replace(/ {2,}/g, " ").trim()).join(`
2760
+ `);
2761
+ };
2762
+
2763
+ // src/core/other-apps/ideas.ts
2764
+ var IMPORT_WARNING_CODES = [
2765
+ "floating-ideas-moved",
2766
+ "summaries-moved",
2767
+ "callouts-moved",
2768
+ "boundaries-in-notes",
2769
+ "links-in-notes",
2770
+ "missing-link-targets",
2771
+ "icons-not-kept",
2772
+ "pictures-not-kept",
2773
+ "attachments-not-kept",
2774
+ "local-links-not-kept",
2775
+ "encrypted-branches-not-read",
2776
+ "formatting-not-kept"
2777
+ ];
2778
+ var IMPORT_ERROR_CODES = ["unknown-format", "unreadable", "empty", "password-protected", "too-large"];
2779
+
2780
+ class OrbitImportError extends Error {
2781
+ code;
2782
+ constructor(code, message) {
2783
+ super(message);
2784
+ this.name = "OrbitImportError";
2785
+ this.code = code;
2786
+ }
2787
+ }
2788
+ var MAX_IMPORTED_IDEAS = 1e5;
2789
+ var MAX_IMPORTED_DEPTH = 500;
2790
+ var newIdea = (text) => {
2791
+ return { text, notes: [], ideaLinks: [], tags: [], icons: [], children: [] };
2792
+ };
2793
+ var MESSAGES = {
2794
+ "floating-ideas-moved": (count, examples) => `${plural(count, "floating topic")}${examples} had no place in the tree: ${count === 1 ? "it's" : "they're"} now under the center.`,
2795
+ "summaries-moved": (count, examples) => `${plural(count, "summary")}${examples} became ${count === 1 ? "an idea" : "ideas"} beside the ideas ${count === 1 ? "it sums" : "they sum"} up.`,
2796
+ "callouts-moved": (count, examples) => `${plural(count, "callout")}${examples} became ${count === 1 ? "an idea" : "ideas"} under the topic ${count === 1 ? "it points" : "they point"} at.`,
2797
+ "boundaries-in-notes": (count, examples) => `${plural(count, "boundary title")}${examples} ${count === 1 ? "is" : "are"} now in the note of the idea whose ideas ${count === 1 ? "it surrounds" : "they surround"}.`,
2798
+ "links-in-notes": (count, examples) => `${plural(count, "link")}${examples} beyond an idea's first ${count === 1 ? "is" : "are"} now in its note.`,
2799
+ "missing-link-targets": (count, examples) => `${plural(count, "link")}${examples} ${count === 1 ? "points" : "point"} at an idea that isn't in this map, so ${count === 1 ? "it was" : "they were"} left out.`,
2800
+ "icons-not-kept": (count, examples) => `${plural(count, "icon")}${examples} ${count === 1 ? "was" : "were"} left out: an idea in Orbit has one icon, an emoji.`,
2801
+ "pictures-not-kept": (count, examples) => `${plural(count, "picture")}${examples} ${count === 1 ? "isn't" : "aren't"} in the file, so ${count === 1 ? "it was" : "they were"} left out.`,
2802
+ "attachments-not-kept": (count, examples) => `${plural(count, "attached file")}${examples} ${count === 1 ? "was" : "were"} left out; the ideas keep their names.`,
2803
+ "local-links-not-kept": (count, examples) => `${plural(count, "link")}${examples} to files on the computer the map was made on ${count === 1 ? "was" : "were"} left out.`,
2804
+ "encrypted-branches-not-read": (count, examples) => `${plural(count, "encrypted branch")}${examples} couldn't be read: the ideas in ${count === 1 ? "it" : "them"} were left out.`,
2805
+ "formatting-not-kept": (count, examples) => `${plural(count, "note")}${examples} ${count === 1 ? "is" : "are"} kept as plain text, without bold, italics or lists: markdown would have shown ${count === 1 ? "it" : "them"} differently.`
2806
+ };
2807
+ var plural = (count, noun) => {
2808
+ const word = count === 1 ? noun : noun.endsWith("y") ? `${noun.slice(0, -1)}ies` : /(ch|sh|s|x)$/.test(noun) ? `${noun}es` : `${noun}s`;
2809
+ return `${count === 1 ? "One" : count} ${word}`;
2810
+ };
2811
+
2812
+ class ImportReport {
2813
+ found = new Map;
2814
+ add(code, example) {
2815
+ const entry = this.found.get(code) ?? { count: 0, examples: [] };
2816
+ const shown = example === undefined ? "" : oneLine(example);
2817
+ entry.count += 1;
2818
+ if (shown !== "" && entry.examples.length < 3 && !entry.examples.includes(shown)) {
2819
+ entry.examples.push(shown);
2820
+ }
2821
+ this.found.set(code, entry);
2822
+ }
2823
+ get warnings() {
2824
+ const order = (code) => IMPORT_WARNING_CODES.indexOf(code);
2825
+ return [...this.found.entries()].sort(([first], [second]) => order(first) - order(second)).map(([code, { count, examples }]) => {
2826
+ const shown = examples.map((example) => `“${example.length > 40 ? `${example.slice(0, 39)}…` : example}”`);
2827
+ const more = count > examples.length && examples.length > 0 ? ", …" : "";
2828
+ return { code, count, message: MESSAGES[code](count, shown.length > 0 ? ` (${shown.join(", ")}${more})` : "") };
2829
+ });
2830
+ }
2831
+ }
2832
+ var oneLine = (text) => {
2833
+ return text.replace(/[\t\n\v\f\r\u2028\u2029 ]+/g, " ").replace(/^ | $/g, "");
2834
+ };
2835
+ var SOURCE_LINK = "#orbit-source:";
2836
+ var sourceLink = (sourceId) => {
2837
+ return `${SOURCE_LINK}${encodeURIComponent(sourceId)}`;
2838
+ };
2839
+ var buildDocument = (rootIdea, report) => {
2840
+ let count = 0;
2841
+ const bySource = new Map;
2842
+ const pending = [];
2843
+ const build = (idea, level) => {
2844
+ count += 1;
2845
+ if (count > MAX_IMPORTED_IDEAS) {
2846
+ throw new OrbitImportError("too-large", `The map has more than ${MAX_IMPORTED_IDEAS.toLocaleString("en")} ideas.`);
2847
+ }
2848
+ if (level > MAX_IMPORTED_DEPTH) {
2849
+ throw new OrbitImportError("too-large", `The map goes more than ${MAX_IMPORTED_DEPTH} levels deep.`);
2850
+ }
2851
+ let text = oneLine(idea.text);
2852
+ const icons = idea.icons.filter((icon) => icon !== "");
2853
+ let icon = icons[0];
2854
+ const emoji = icon === undefined ? leadingEmoji(text) : null;
2855
+ if (emoji) {
2856
+ icon = emoji.icon;
2857
+ text = emoji.text;
2858
+ }
2859
+ for (const extra of icons.slice(1)) {
2860
+ report.add("icons-not-kept", extra);
2861
+ }
2862
+ const node = { id: nodeSlug(text), text, level, children: [] };
2863
+ if (icon) {
2864
+ node.icon = icon;
2865
+ }
2866
+ if (idea.collapsed && idea.children.length > 0) {
2867
+ node.collapsed = true;
2868
+ }
2869
+ if (idea.status) {
2870
+ node.status = idea.status;
2871
+ }
2872
+ if (idea.priority !== undefined && Number.isFinite(idea.priority)) {
2873
+ node.priority = idea.priority;
2874
+ }
2875
+ const tags = [...new Set(idea.tags.map((tag) => oneLine(tag)).filter((tag) => tag !== ""))];
2876
+ if (tags.length > 0) {
2877
+ node.tags = tags;
2878
+ }
2879
+ if (idea.color) {
2880
+ node.color = idea.color;
2881
+ }
2882
+ if (idea.link) {
2883
+ node.link = idea.link;
2884
+ }
2885
+ if (idea.image) {
2886
+ node.image = { src: idea.image };
2887
+ }
2888
+ if (idea.sourceId !== undefined && !bySource.has(idea.sourceId)) {
2889
+ bySource.set(idea.sourceId, node);
2890
+ }
2891
+ pending.push([idea, node]);
2892
+ for (const child of idea.children) {
2893
+ const childNode = build(child, level + 1);
2894
+ setParent(childNode, node);
2895
+ node.children.push(childNode);
2896
+ }
2897
+ return node;
2898
+ };
2899
+ const root = build(rootIdea, 0);
2900
+ ensureUniqueNodeIds(root);
2901
+ for (const [idea, node] of pending) {
2902
+ const extraLines = [];
2903
+ for (const link of idea.ideaLinks) {
2904
+ const target = bySource.get(link.target);
2905
+ if (!target) {
2906
+ report.add("missing-link-targets", link.label ?? node.text);
2907
+ continue;
2908
+ }
2909
+ if (!node.link && !link.label) {
2910
+ node.link = `#${target.id}`;
2911
+ } else {
2912
+ report.add("links-in-notes", link.label ?? target.text);
2913
+ extraLines.push({ label: link.label ?? target.text, target });
2914
+ }
2915
+ }
2916
+ const note = joinNotes([...idea.notes, linkNote(extraLines, idea.notes)]);
2917
+ if (note) {
2918
+ node.note = resolveSourceLinks(note.text, bySource, note.format);
2919
+ if (note.format === "plain") {
2920
+ node.noteFormat = "plain";
2921
+ }
2922
+ }
2923
+ }
2924
+ const nodes = indexNodes(root);
2925
+ for (const [, node] of pending) {
2926
+ if (isNodeLink(node.link) && !findIndexedLinkedNode(nodes, node.link)) {
2927
+ report.add("missing-link-targets", node.link);
2928
+ delete node.link;
2929
+ }
2930
+ }
2931
+ return { root, options: {}, warnings: [], diagnostics: [] };
2932
+ };
2933
+ var linkNote = (lines, notes) => {
2934
+ if (lines.length === 0) {
2935
+ return;
2936
+ }
2937
+ const plain = notes.some((note) => note.format === "plain") || lines.some(({ label }) => /[[\]`*_~<\\]/.test(label));
2938
+ if (plain) {
2939
+ return { text: lines.map(({ label, target }) => label === target.text ? `→ ${label}` : `→ ${label}: ${target.text}`).join(`
2940
+ `), format: "plain" };
2941
+ }
2942
+ return { text: lines.map(({ label, target }) => `→ [${label || target.text}](#${target.id})`).join(`
2943
+
2944
+ `), format: "markdown" };
2945
+ };
2946
+ var resolveSourceLinks = (text, bySource, format) => {
2947
+ if (format !== "markdown" || !text.includes(SOURCE_LINK)) {
2948
+ return text;
2949
+ }
2950
+ const target = (encoded) => {
2951
+ try {
2952
+ return bySource.get(decodeURIComponent(encoded));
2953
+ } catch {
2954
+ return;
2955
+ }
2956
+ };
2957
+ return text.replace(/\[([^\]]*)\]\(#orbit-source:([^)\s]+)\)/g, (_, label, encoded) => {
2958
+ const node = target(encoded);
2959
+ return node ? `[${label || node.text}](#${node.id})` : label;
2960
+ }).replace(/<#orbit-source:([^>\s]+)>/g, (_, encoded) => {
2961
+ const node = target(encoded);
2962
+ return node ? `[${node.text}](#${node.id})` : "";
2963
+ });
2964
+ };
2965
+
2966
+ // src/core/other-apps/reading.ts
2967
+ var readXmlFile = (data, rootName, what) => {
2968
+ const text = typeof data === "string" ? data : decodeMarkup(data);
2969
+ let root;
2970
+ try {
2971
+ root = parseXml(text);
2972
+ } catch (error) {
2973
+ if (error instanceof MarkupError) {
2974
+ throw new OrbitImportError("unreadable", `The file isn't ${what} Orbit can read: ${error.message}.`);
2975
+ }
2976
+ throw error;
2977
+ }
2978
+ if (root.localName !== rootName) {
2979
+ throw new OrbitImportError("unreadable", `The file isn't ${what}: it starts with <${root.name}>, not <${rootName}>.`);
2980
+ }
2981
+ return root;
2982
+ };
2983
+ var baseName = (name) => {
2984
+ return (name ?? "").replace(/^.*[/\\]/, "").replace(/\.(opml|xml|mm|xmind|txt|md)$/i, "").trim();
2985
+ };
2986
+
2987
+ // src/core/other-apps/freemind.ts
2988
+ var importFreeMind = (data, options = {}) => {
2989
+ const map = readXmlFile(data, "map", "a FreeMind or Freeplane map");
2990
+ const nodes = childElements(map, "node");
2991
+ if (nodes.length === 0) {
2992
+ throw new OrbitImportError("empty", "The map has no nodes.");
2993
+ }
2994
+ const report = new ImportReport;
2995
+ let root;
2996
+ if (nodes.length === 1) {
2997
+ root = readNode(nodes[0], report);
2998
+ } else {
2999
+ root = newIdea(baseName(options.name) || options.untitled || "Untitled map");
3000
+ root.children = nodes.map((node) => readNode(node, report));
3001
+ }
3002
+ return { format: "freemind", maps: [{ document: buildDocument(root, report), media: {}, warnings: report.warnings }] };
3003
+ };
3004
+ var readNode = (node, report) => {
3005
+ const idea = newIdea("");
3006
+ const id = node.attributes.ID;
3007
+ if (id) {
3008
+ idea.sourceId = id;
3009
+ }
3010
+ readTitle(node, idea, report);
3011
+ const addresses = noteAddresses(report);
3012
+ const details = [];
3013
+ const notes = [];
3014
+ for (const content of childElements(node, "richcontent")) {
3015
+ const type = (content.attributes.TYPE ?? "").toUpperCase();
3016
+ if (type === "NOTE" || type === "DETAILS") {
3017
+ const note = richContentNote(content, addresses, report, idea.text);
3018
+ if (note) {
3019
+ (type === "DETAILS" ? details : notes).push(note);
3020
+ }
3021
+ }
3022
+ }
3023
+ for (const hook of childElements(node, "hook")) {
3024
+ if (/NodeNote/.test(hook.attributes.NAME ?? "")) {
3025
+ const text = childElement(hook, "text");
3026
+ const note = noteFromPlainText(text ? textContent(text) : undefined);
3027
+ if (note) {
3028
+ notes.push(note);
3029
+ }
3030
+ }
3031
+ }
3032
+ idea.notes.push(...details, ...notes);
3033
+ const attributes = attributeNote(node);
3034
+ if (attributes) {
3035
+ idea.notes.push(attributes);
3036
+ }
3037
+ readLink(node.attributes.LINK, idea, report);
3038
+ for (const arrow of childElements(node, "arrowlink")) {
3039
+ const target = arrow.attributes.DESTINATION;
3040
+ if (target) {
3041
+ const label = oneLine(arrow.attributes.MIDDLE_LABEL || arrow.attributes.SOURCE_LABEL || arrow.attributes.TARGET_LABEL || "");
3042
+ idea.ideaLinks.push(label ? { target, label } : { target });
3043
+ }
3044
+ }
3045
+ if (node.attributes.FOLDED === "true") {
3046
+ idea.collapsed = true;
3047
+ }
3048
+ const background = node.attributes.BACKGROUND_COLOR;
3049
+ if (background && /^#[0-9A-Fa-f]{6}$/.test(background)) {
3050
+ idea.color = background.toUpperCase();
3051
+ }
3052
+ for (const icon of childElements(node, "icon")) {
3053
+ const name = icon.attributes.BUILTIN ?? "";
3054
+ const meaning = freeMindIcon(name);
3055
+ if (!meaning) {
3056
+ report.add("icons-not-kept", name.replace(/^.*\//, ""));
3057
+ } else if ("icon" in meaning) {
3058
+ idea.icons.push(meaning.icon);
3059
+ } else if ("priority" in meaning) {
3060
+ idea.priority ??= meaning.priority;
3061
+ } else {
3062
+ idea.status ??= meaning.status;
3063
+ }
3064
+ }
3065
+ for (const hook of childElements(node, "hook")) {
3066
+ if (hook.attributes.NAME === "ExternalObject" && hook.attributes.URI) {
3067
+ readPicture(hook.attributes.URI, idea, report);
3068
+ }
3069
+ }
3070
+ if (node.attributes.ENCRYPTED_CONTENT) {
3071
+ report.add("encrypted-branches-not-read", idea.text || undefined);
3072
+ }
3073
+ idea.children = childElements(node, "node").map((child) => readNode(child, report));
3074
+ return idea;
3075
+ };
3076
+ var readTitle = (node, idea, report) => {
3077
+ const rich = childElements(node, "richcontent").find((content) => (content.attributes.TYPE ?? "").toUpperCase() === "NODE");
3078
+ const text = node.attributes.TEXT ?? node.attributes.LOCALIZED_TEXT;
3079
+ if (rich) {
3080
+ const html = childElement(rich, "html");
3081
+ const plain = childElement(rich, "text");
3082
+ if (html) {
3083
+ const title = titleFromHtml(html);
3084
+ idea.text = title.text;
3085
+ readLink(title.link, idea, report);
3086
+ for (const image of findElements(html, "img")) {
3087
+ readPicture(image.attributes.src ?? "", idea, report);
3088
+ }
3089
+ } else {
3090
+ idea.text = plain ? textContent(plain) : textContent(rich);
3091
+ }
3092
+ return;
3093
+ }
3094
+ if (text !== undefined && /^\s*<html>/i.test(text)) {
3095
+ const title = titleFromHtml(text);
3096
+ idea.text = title.text;
3097
+ readLink(title.link, idea, report);
3098
+ return;
3099
+ }
3100
+ idea.text = text ?? "";
3101
+ };
3102
+ var readLink = (link, idea, report) => {
3103
+ const address = link?.trim();
3104
+ if (!address || idea.link) {
3105
+ return;
3106
+ }
3107
+ const kept = keptAddress(address);
3108
+ if (address.startsWith("#") || /^(ID_\d+|_?Freemind_Link_\d+)$/.test(address)) {
3109
+ idea.ideaLinks.push({ target: address.replace(/^#/, "") });
3110
+ } else if (kept) {
3111
+ idea.link = kept;
3112
+ } else {
3113
+ report.add("local-links-not-kept", address);
3114
+ }
3115
+ };
3116
+ var readPicture = (address, idea, report) => {
3117
+ if (/^https?:\/\/\S+$/i.test(address)) {
3118
+ idea.image ??= address;
3119
+ } else if (address !== "") {
3120
+ report.add("pictures-not-kept", address.replace(/^.*[/\\]/, ""));
3121
+ }
3122
+ };
3123
+ var noteAddresses = (report) => {
3124
+ return {
3125
+ link: (href) => {
3126
+ if (href.startsWith("#")) {
3127
+ return sourceLink(href.slice(1));
3128
+ }
3129
+ const kept = keptAddress(href);
3130
+ if (kept) {
3131
+ return kept;
3132
+ }
3133
+ report.add("local-links-not-kept", href);
3134
+ return null;
3135
+ },
3136
+ picture: (src) => {
3137
+ if (/^https?:\/\/\S+$/i.test(src)) {
3138
+ return src;
3139
+ }
3140
+ report.add("pictures-not-kept", src.replace(/^.*[/\\]/, ""));
3141
+ return null;
3142
+ }
3143
+ };
3144
+ };
3145
+ var richContentNote = (content, addresses, report, title) => {
3146
+ const html = childElement(content, "html");
3147
+ if (html) {
3148
+ const note = noteFromHtml(html, addresses);
3149
+ if (note?.format === "plain") {
3150
+ report.add("formatting-not-kept", title || undefined);
3151
+ }
3152
+ return note;
3153
+ }
3154
+ const plain = childElement(content, "text");
3155
+ const text = plain ? textContent(plain) : textContent(content);
3156
+ if (/\/markdown$/i.test(content.attributes["CONTENT-TYPE"] ?? "") && text.trim() !== "") {
3157
+ return { text: text.replace(/\r\n?/g, `
3158
+ `).trim(), format: "markdown" };
3159
+ }
3160
+ return noteFromPlainText(text);
3161
+ };
3162
+ var attributeNote = (node) => {
3163
+ const items = [];
3164
+ for (const attribute of childElements(node, "attribute")) {
3165
+ const name = (attributeOf(attribute, "NAME") ?? "").trim();
3166
+ const value = (attributeOf(attribute, "VALUE") ?? "").trim();
3167
+ if (name !== "" || value !== "") {
3168
+ items.push([{ kind: "paragraph", inline: name === "" ? [{ kind: "text", text: value }] : [{ kind: "strong", children: [{ kind: "text", text: `${name}:` }] }, { kind: "text", text: ` ${value}` }] }]);
3169
+ }
3170
+ }
3171
+ return items.length === 0 ? undefined : noteFromBlocks([{ kind: "list", ordered: false, items }]);
3172
+ };
3173
+ var findElements = (element, localName) => {
3174
+ const found = [];
3175
+ const pending = [element];
3176
+ while (pending.length > 0) {
3177
+ const next = pending.pop();
3178
+ for (const child of childElements(next)) {
3179
+ if (child.localName.toLowerCase() === localName) {
3180
+ found.push(child);
3181
+ }
3182
+ pending.push(child);
3183
+ }
3184
+ }
3185
+ return found;
3186
+ };
3187
+
3188
+ // src/core/other-apps/opml.ts
3189
+ var HTML_TAGS = /<\/?(b|i|u|s|em|strong|a|span|br|code|mark|strike|del|sup|sub|font|p|div|ul|ol|li)\b[^>]*>/i;
3190
+ var importOPML = (data, options = {}) => {
3191
+ const opml = readXmlFile(data, "opml", "an OPML file");
3192
+ const head = childElement(opml, "head");
3193
+ const titleElement = head ? childElement(head, "title") : undefined;
3194
+ const title = titleElement ? baseName(textContent(titleElement).replace(/\s+/g, " ").trim()) : "";
3195
+ const body = childElement(opml, "body");
3196
+ const outlines = body ? childElements(body, "outline") : [];
3197
+ if (outlines.length === 0) {
3198
+ throw new OrbitImportError("empty", "The OPML file has no outlines.");
3199
+ }
3200
+ const report = new ImportReport;
3201
+ let root;
3202
+ if (outlines.length === 1) {
3203
+ root = readOutline2(outlines[0], report);
3204
+ } else {
3205
+ root = newIdea(title || baseName(options.name) || options.untitled || "Untitled map");
3206
+ root.children = outlines.map((outline) => readOutline2(outline, report));
3207
+ }
3208
+ return { format: "opml", maps: [{ document: buildDocument(root, report), media: {}, warnings: report.warnings }] };
3209
+ };
3210
+ var readOutline2 = (outline, report) => {
3211
+ const text = attributeOf(outline, "text") ?? attributeOf(outline, "title") ?? "";
3212
+ const idea = newIdea(text);
3213
+ if (HTML_TAGS.test(text) && outline.attributes._textFormat !== "plain") {
3214
+ const title = titleFromHtml(text);
3215
+ idea.text = title.text;
3216
+ if (title.link && isKeptLink(title.link) && !title.link.startsWith("#")) {
3217
+ idea.link = title.link;
3218
+ }
3219
+ }
3220
+ const note = readNote(outline, idea, report);
3221
+ if (note) {
3222
+ idea.notes.push(note);
3223
+ }
3224
+ idea.status = readStatus(outline);
3225
+ const link = readLink2(outline);
3226
+ const kept = link ? keptAddress(link) : null;
3227
+ if (kept) {
3228
+ idea.link = kept;
3229
+ } else if (link) {
3230
+ report.add("local-links-not-kept", link);
3231
+ }
3232
+ const categories = attributeOf(outline, "category");
3233
+ if (categories) {
3234
+ idea.tags.push(...categories.split(",").map((category) => category.trim().replace(/^\/+/, "")).filter((category) => category !== ""));
3235
+ }
3236
+ idea.children = childElements(outline, "outline").map((child) => readOutline2(child, report));
3237
+ return idea;
3238
+ };
3239
+ var readNote = (outline, idea, report) => {
3240
+ const text = attributeOf(outline, "_note") ?? outline.attributes.note ?? attributeOf(outline, "description");
3241
+ if (text === undefined || text.trim() === "") {
3242
+ return;
3243
+ }
3244
+ const format = outline.attributes._noteFormat;
3245
+ if (format === "markdown" || format === "plain") {
3246
+ return { text: text.replace(/\r\n?/g, `
3247
+ `).trim(), format };
3248
+ }
3249
+ if (HTML_TAGS.test(text)) {
3250
+ const note = noteFromHtml(text.replace(/\r\n?|\n/g, "<br>"));
3251
+ if (note?.format === "plain") {
3252
+ report.add("formatting-not-kept", idea.text || undefined);
3253
+ }
3254
+ return note;
3255
+ }
3256
+ return noteFromPlainText(text);
3257
+ };
3258
+ var readStatus = (outline) => {
3259
+ const status = attributeOf(outline, "_status")?.trim();
3260
+ if (status) {
3261
+ return CHECKBOX_STATUSES.get(status.toLowerCase()) ?? status;
3262
+ }
3263
+ const complete = (outline.attributes._complete ?? outline.attributes.complete ?? outline.attributes.checked ?? outline.attributes._checked)?.trim().toLowerCase();
3264
+ if (complete === "true") {
3265
+ return "done";
3266
+ }
3267
+ return outline.attributes.checkbox === "true" ? "todo" : undefined;
3268
+ };
3269
+ var readLink2 = (outline) => {
3270
+ const url = outline.attributes.url ?? outline.attributes.htmlUrl ?? outline.attributes.xmlUrl;
3271
+ return url?.trim() || undefined;
3272
+ };
3273
+ var STATUS_CHECKBOXES = new Map([["done", "checked"], ["todo", "unchecked"], ["active", "indeterminate"]]);
3274
+ var CHECKBOX_STATUSES = new Map([...STATUS_CHECKBOXES].map(([status, checkbox]) => [checkbox, status]));
3275
+ var DONE_WORDS = new Set(["done", "complete", "completed", "shipped"]);
3276
+ var exportOPML = (input, options = {}) => {
3277
+ const document2 = typeof input === "string" ? parseOrbit2(input) : input;
3278
+ const title = options.title ?? (document2.options.title || document2.root.text);
3279
+ const lines = [
3280
+ '<?xml version="1.0" encoding="UTF-8"?>',
3281
+ '<opml version="2.0">',
3282
+ " <head>",
3283
+ ` <title>${escapeXml(title)}</title>`,
3284
+ " </head>",
3285
+ " <body>"
3286
+ ];
3287
+ const write = (node, depth) => {
3288
+ const indent = " ".repeat(depth + 2);
3289
+ const attributes = outlineAttributes(node).map(([name, value]) => ` ${name}="${escapeXml(value, true)}"`).join("");
3290
+ if (node.children.length === 0) {
3291
+ lines.push(`${indent}<outline${attributes}/>`);
3292
+ return;
3293
+ }
3294
+ lines.push(`${indent}<outline${attributes}>`);
3295
+ for (const child of node.children) {
3296
+ write(child, depth + 1);
3297
+ }
3298
+ lines.push(`${indent}</outline>`);
3299
+ };
3300
+ write(document2.root, 0);
3301
+ lines.push(" </body>", "</opml>", "");
3302
+ return lines.join(`
3303
+ `);
3304
+ };
3305
+ var outlineAttributes = (node) => {
3306
+ const text = node.icon ? `${node.icon} ${node.text}` : node.text;
3307
+ const attributes = [["text", text]];
3308
+ if (HTML_TAGS.test(text)) {
3309
+ attributes.push(["_textFormat", "plain"]);
3310
+ }
3311
+ if (node.note) {
3312
+ attributes.push(["_note", node.note]);
3313
+ const format = node.noteFormat ?? "markdown";
3314
+ if (noteFromPlainText(node.note)?.format !== format) {
3315
+ attributes.push(["_noteFormat", format]);
3316
+ }
3317
+ }
3318
+ const status = node.status?.trim();
3319
+ if (status) {
3320
+ attributes.push(["_status", STATUS_CHECKBOXES.get(status) ?? status]);
3321
+ if (DONE_WORDS.has(status.toLowerCase())) {
3322
+ attributes.push(["_complete", "true"]);
3323
+ }
3324
+ }
3325
+ if (node.link && (isKeptLink(node.link) || isNodeLink(node.link))) {
3326
+ attributes.push(["type", "link"], ["url", node.link]);
3327
+ }
3328
+ if (node.tags && node.tags.length > 0) {
3329
+ attributes.push(["category", node.tags.map((tag) => tag.replace(/,/g, " ")).join(",")]);
3330
+ }
3331
+ return attributes;
3332
+ };
3333
+ var escapeXml = (text, attribute = false) => {
3334
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3335
+ return attribute ? escaped.replace(/"/g, "&quot;").replace(/\n/g, "&#10;").replace(/\r/g, "&#13;").replace(/\t/g, "&#9;") : escaped;
3336
+ };
3337
+
3338
+ // src/core/other-apps/inflate.ts
3339
+ class InflateError extends Error {
3340
+ constructor(message) {
3341
+ super(message);
3342
+ this.name = "InflateError";
3343
+ }
3344
+ }
3345
+ var LENGTH_BASES = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
3346
+ var LENGTH_EXTRA_BITS = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
3347
+ var DISTANCE_BASES = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
3348
+ var DISTANCE_EXTRA_BITS = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
3349
+ var CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
3350
+ var MAX_BITS = 15;
3351
+ var buildCode = (lengths) => {
3352
+ const counts = new Uint16Array(MAX_BITS + 1);
3353
+ for (const length of lengths) {
3354
+ counts[length] += 1;
3355
+ }
3356
+ counts[0] = 0;
3357
+ let left = 1;
3358
+ for (let length = 1;length <= MAX_BITS; length++) {
3359
+ left = left * 2 - counts[length];
3360
+ if (left < 0) {
3361
+ throw new InflateError("A Huffman code with more codes than its lengths allow");
3362
+ }
3363
+ }
3364
+ const offsets = new Uint16Array(MAX_BITS + 2);
3365
+ for (let length = 1;length <= MAX_BITS; length++) {
3366
+ offsets[length + 1] = offsets[length] + counts[length];
3367
+ }
3368
+ const symbols = new Uint16Array(lengths.length);
3369
+ for (let symbol = 0;symbol < lengths.length; symbol++) {
3370
+ if (lengths[symbol] !== 0) {
3371
+ symbols[offsets[lengths[symbol]]++] = symbol;
3372
+ }
3373
+ }
3374
+ return { counts, symbols };
3375
+ };
3376
+ var fixedLengths = new Uint8Array(288);
3377
+ fixedLengths.fill(8, 0, 144);
3378
+ fixedLengths.fill(9, 144, 256);
3379
+ fixedLengths.fill(7, 256, 280);
3380
+ fixedLengths.fill(8, 280, 288);
3381
+ var FIXED_LITERALS = buildCode(fixedLengths);
3382
+ var FIXED_DISTANCES = buildCode(new Uint8Array(30).fill(5));
3383
+
3384
+ class BitReader {
3385
+ data;
3386
+ position;
3387
+ buffer = 0;
3388
+ count = 0;
3389
+ constructor(data, start) {
3390
+ this.data = data;
3391
+ this.position = start;
3392
+ }
3393
+ bits(needed) {
3394
+ while (this.count < needed) {
3395
+ if (this.position >= this.data.length) {
3396
+ throw new InflateError("The data ends in the middle of a block");
3397
+ }
3398
+ this.buffer |= this.data[this.position++] << this.count;
3399
+ this.count += 8;
3400
+ }
3401
+ const value = this.buffer & (1 << needed) - 1;
3402
+ this.buffer >>>= needed;
3403
+ this.count -= needed;
3404
+ return value;
3405
+ }
3406
+ bytes(length) {
3407
+ this.buffer = 0;
3408
+ this.count = 0;
3409
+ if (this.position + length > this.data.length) {
3410
+ throw new InflateError("The data ends in the middle of a stored block");
3411
+ }
3412
+ const bytes = this.data.subarray(this.position, this.position + length);
3413
+ this.position += length;
3414
+ return bytes;
3415
+ }
3416
+ word() {
3417
+ const [low, high] = this.bytes(2);
3418
+ return low | high << 8;
3419
+ }
3420
+ decode(code) {
3421
+ let value = 0;
3422
+ let first = 0;
3423
+ let index = 0;
3424
+ for (let length = 1;length <= MAX_BITS; length++) {
3425
+ value |= this.bits(1);
3426
+ const count = code.counts[length];
3427
+ if (value - first < count) {
3428
+ return code.symbols[index + value - first];
3429
+ }
3430
+ index += count;
3431
+ first = first + count << 1;
3432
+ value <<= 1;
3433
+ }
3434
+ throw new InflateError("A code that no Huffman table has");
3435
+ }
3436
+ }
3437
+
3438
+ class Output {
3439
+ bytes;
3440
+ length = 0;
3441
+ limit;
3442
+ constructor(expected, limit) {
3443
+ this.limit = limit;
3444
+ this.bytes = new Uint8Array(Math.max(16, Math.min(expected ?? 65536, limit)));
3445
+ }
3446
+ room(extra) {
3447
+ const needed = this.length + extra;
3448
+ if (needed > this.limit) {
3449
+ throw new InflateError(`The data holds more than the ${this.limit} bytes it may`);
3450
+ }
3451
+ if (needed > this.bytes.length) {
3452
+ const grown = new Uint8Array(Math.min(this.limit, Math.max(needed, this.bytes.length * 2)));
3453
+ grown.set(this.bytes.subarray(0, this.length));
3454
+ this.bytes = grown;
3455
+ }
3456
+ }
3457
+ push(byte) {
3458
+ this.room(1);
3459
+ this.bytes[this.length++] = byte;
3460
+ }
3461
+ append(bytes) {
3462
+ this.room(bytes.length);
3463
+ this.bytes.set(bytes, this.length);
3464
+ this.length += bytes.length;
3465
+ }
3466
+ copy(distance, length) {
3467
+ if (distance > this.length) {
3468
+ throw new InflateError("A back reference to before the start of the data");
3469
+ }
3470
+ this.room(length);
3471
+ for (let index = 0;index < length; index++) {
3472
+ this.bytes[this.length] = this.bytes[this.length - distance];
3473
+ this.length += 1;
3474
+ }
3475
+ }
3476
+ }
3477
+ var inflate = (data, options = {}) => {
3478
+ const limit = options.limit ?? Math.max(options.expected ?? 0, 64 * 1024 * 1024);
3479
+ const reader = new BitReader(data, 0);
3480
+ const output = new Output(options.expected, limit);
3481
+ let last = 0;
3482
+ while (last === 0) {
3483
+ last = reader.bits(1);
3484
+ const type = reader.bits(2);
3485
+ if (type === 0) {
3486
+ const length = reader.word();
3487
+ const complement = reader.word();
3488
+ if ((length ^ 65535) !== complement) {
3489
+ throw new InflateError("A stored block whose length doesn't match its complement");
3490
+ }
3491
+ output.append(reader.bytes(length));
3492
+ } else if (type === 1) {
3493
+ inflateBlock(reader, output, FIXED_LITERALS, FIXED_DISTANCES);
3494
+ } else if (type === 2) {
3495
+ const [literals, distances] = readDynamicCodes(reader);
3496
+ inflateBlock(reader, output, literals, distances);
3497
+ } else {
3498
+ throw new InflateError("A block of an unknown type");
3499
+ }
3500
+ }
3501
+ return output.bytes.subarray(0, output.length);
3502
+ };
3503
+ var readDynamicCodes = (reader) => {
3504
+ const literalCount = reader.bits(5) + 257;
3505
+ const distanceCount = reader.bits(5) + 1;
3506
+ const codeLengthCount = reader.bits(4) + 4;
3507
+ if (literalCount > 286 || distanceCount > 30) {
3508
+ throw new InflateError("A block with more codes than deflate has");
3509
+ }
3510
+ const codeLengths = new Uint8Array(19);
3511
+ for (let index = 0;index < codeLengthCount; index++) {
3512
+ codeLengths[CODE_LENGTH_ORDER[index]] = reader.bits(3);
3513
+ }
3514
+ const codeLengthCode = buildCode(codeLengths);
3515
+ const lengths = new Uint8Array(literalCount + distanceCount);
3516
+ let index = 0;
3517
+ while (index < lengths.length) {
3518
+ const symbol = reader.decode(codeLengthCode);
3519
+ if (symbol < 16) {
3520
+ lengths[index++] = symbol;
3521
+ continue;
3522
+ }
3523
+ let repeat;
3524
+ let value = 0;
3525
+ if (symbol === 16) {
3526
+ if (index === 0) {
3527
+ throw new InflateError("A repeat with nothing before it");
3528
+ }
3529
+ value = lengths[index - 1];
3530
+ repeat = 3 + reader.bits(2);
3531
+ } else if (symbol === 17) {
3532
+ repeat = 3 + reader.bits(3);
3533
+ } else {
3534
+ repeat = 11 + reader.bits(7);
3535
+ }
3536
+ if (index + repeat > lengths.length) {
3537
+ throw new InflateError("Code lengths that run past their table");
3538
+ }
3539
+ lengths.fill(value, index, index + repeat);
3540
+ index += repeat;
3541
+ }
3542
+ if (lengths[256] === 0) {
3543
+ throw new InflateError("A block with no code for its end");
3544
+ }
3545
+ return [buildCode(lengths.subarray(0, literalCount)), buildCode(lengths.subarray(literalCount))];
3546
+ };
3547
+ var inflateBlock = (reader, output, literals, distances) => {
3548
+ for (;; ) {
3549
+ const symbol = reader.decode(literals);
3550
+ if (symbol < 256) {
3551
+ output.push(symbol);
3552
+ } else if (symbol === 256) {
3553
+ return;
3554
+ } else {
3555
+ const lengthIndex = symbol - 257;
3556
+ if (lengthIndex >= LENGTH_BASES.length) {
3557
+ throw new InflateError("A length code deflate has no length for");
3558
+ }
3559
+ const length = LENGTH_BASES[lengthIndex] + reader.bits(LENGTH_EXTRA_BITS[lengthIndex]);
3560
+ const distanceSymbol = reader.decode(distances);
3561
+ if (distanceSymbol >= DISTANCE_BASES.length) {
3562
+ throw new InflateError("A distance code deflate has no distance for");
3563
+ }
3564
+ output.copy(DISTANCE_BASES[distanceSymbol] + reader.bits(DISTANCE_EXTRA_BITS[distanceSymbol]), length);
3565
+ }
3566
+ }
3567
+ };
3568
+ var crcTable = null;
3569
+ var crc32 = (bytes) => {
3570
+ if (!crcTable) {
3571
+ crcTable = new Uint32Array(256);
3572
+ for (let index = 0;index < 256; index++) {
3573
+ let value = index;
3574
+ for (let bit = 0;bit < 8; bit++) {
3575
+ value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
3576
+ }
3577
+ crcTable[index] = value >>> 0;
3578
+ }
3579
+ }
3580
+ let crc = 4294967295;
3581
+ for (const byte of bytes) {
3582
+ crc = crcTable[(crc ^ byte) & 255] ^ crc >>> 8;
3583
+ }
3584
+ return (crc ^ 4294967295) >>> 0;
3585
+ };
3586
+
3587
+ // src/core/other-apps/zip.ts
3588
+ class ZipError extends Error {
3589
+ constructor(message) {
3590
+ super(message);
3591
+ this.name = "ZipError";
3592
+ }
3593
+ }
3594
+ var DEFAULT_ZIP_LIMITS = {
3595
+ entries: 20000,
3596
+ entrySize: 64 * 1024 * 1024,
3597
+ totalSize: 256 * 1024 * 1024
3598
+ };
3599
+ var END_OF_DIRECTORY = 101010256;
3600
+ var DIRECTORY_ENTRY = 33639248;
3601
+ var LOCAL_HEADER = 67324752;
3602
+ var isZip = (bytes) => {
3603
+ return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
3604
+ };
3605
+
3606
+ class ZipArchive {
3607
+ bytes;
3608
+ view;
3609
+ limits;
3610
+ entries = new Map;
3611
+ unpacked = 0;
3612
+ constructor(bytes, limits = {}) {
3613
+ this.bytes = bytes;
3614
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3615
+ this.limits = { ...DEFAULT_ZIP_LIMITS, ...limits };
3616
+ this.readDirectory();
3617
+ }
3618
+ get names() {
3619
+ return [...this.entries.keys()].filter((name) => !name.endsWith("/"));
3620
+ }
3621
+ has(name) {
3622
+ return this.entries.has(name);
3623
+ }
3624
+ read(name) {
3625
+ const entry = this.entries.get(name);
3626
+ if (!entry) {
3627
+ return null;
3628
+ }
3629
+ if (entry.flags & 1) {
3630
+ throw new ZipError(`${name} is encrypted`);
3631
+ }
3632
+ if (entry.size > this.limits.entrySize || this.unpacked + entry.size > this.limits.totalSize) {
3633
+ throw new ZipError(`${name} is too large to unpack`);
3634
+ }
3635
+ const offset = entry.headerOffset;
3636
+ if (offset + 30 > this.bytes.length || this.view.getUint32(offset, true) !== LOCAL_HEADER) {
3637
+ throw new ZipError(`The archive has no data where ${name} should start`);
3638
+ }
3639
+ const start = offset + 30 + this.view.getUint16(offset + 26, true) + this.view.getUint16(offset + 28, true);
3640
+ const end = start + entry.compressedSize;
3641
+ if (end > this.bytes.length) {
3642
+ throw new ZipError(`${name} runs past the end of the archive`);
3643
+ }
3644
+ const stored = this.bytes.subarray(start, end);
3645
+ let data;
3646
+ if (entry.method === 0) {
3647
+ data = stored;
3648
+ } else if (entry.method === 8) {
3649
+ try {
3650
+ data = inflate(stored, { expected: entry.size, limit: entry.size });
3651
+ } catch (error) {
3652
+ throw new ZipError(`${name} can't be unpacked: ${error instanceof InflateError ? error.message : String(error)}`);
3653
+ }
3654
+ } else {
3655
+ throw new ZipError(`${name} is compressed a way Orbit can't read (method ${entry.method})`);
3656
+ }
3657
+ if (data.length !== entry.size || crc32(data) !== entry.crc) {
3658
+ throw new ZipError(`${name} is damaged: its contents don't match its checksum`);
3659
+ }
3660
+ this.unpacked += data.length;
3661
+ return data;
3662
+ }
3663
+ text(name) {
3664
+ const data = this.read(name);
3665
+ return data === null ? null : new TextDecoder("utf-8").decode(data);
3666
+ }
3667
+ readDirectory() {
3668
+ const bytes = this.bytes;
3669
+ const view = this.view;
3670
+ let end = -1;
3671
+ for (let index = bytes.length - 22;index >= Math.max(0, bytes.length - 22 - 65535); index--) {
3672
+ if (view.getUint32(index, true) === END_OF_DIRECTORY) {
3673
+ end = index;
3674
+ break;
3675
+ }
3676
+ }
3677
+ if (end === -1) {
3678
+ throw new ZipError("The file is not a zip archive, or its end is missing");
3679
+ }
3680
+ const disk = view.getUint16(end + 4, true);
3681
+ const count = view.getUint16(end + 10, true);
3682
+ const size = view.getUint32(end + 12, true);
3683
+ const offset = view.getUint32(end + 16, true);
3684
+ if (disk !== 0) {
3685
+ throw new ZipError("The archive spans several disks");
3686
+ }
3687
+ if (count === 65535 || size === 4294967295 || offset === 4294967295) {
3688
+ throw new ZipError("The archive needs ZIP64, which Orbit reads no map from");
3689
+ }
3690
+ if (count > this.limits.entries) {
3691
+ throw new ZipError(`The archive lists more than ${this.limits.entries} files`);
3692
+ }
3693
+ if (offset + size > end) {
3694
+ throw new ZipError("The archive's directory runs past its end");
3695
+ }
3696
+ const decoder = new TextDecoder("utf-8");
3697
+ let position = offset;
3698
+ for (let index = 0;index < count; index++) {
3699
+ if (position + 46 > end || view.getUint32(position, true) !== DIRECTORY_ENTRY) {
3700
+ throw new ZipError("The archive's directory is damaged");
3701
+ }
3702
+ const nameLength = view.getUint16(position + 28, true);
3703
+ const extraLength = view.getUint16(position + 30, true);
3704
+ const commentLength = view.getUint16(position + 32, true);
3705
+ if (position + 46 + nameLength > end) {
3706
+ throw new ZipError("The archive's directory is damaged");
3707
+ }
3708
+ const name = decoder.decode(bytes.subarray(position + 46, position + 46 + nameLength));
3709
+ const entry = {
3710
+ name,
3711
+ flags: view.getUint16(position + 8, true),
3712
+ method: view.getUint16(position + 10, true),
3713
+ crc: view.getUint32(position + 16, true),
3714
+ compressedSize: view.getUint32(position + 20, true),
3715
+ size: view.getUint32(position + 24, true),
3716
+ headerOffset: view.getUint32(position + 42, true)
3717
+ };
3718
+ if (!this.entries.has(name)) {
3719
+ this.entries.set(name, entry);
3720
+ }
3721
+ position += 46 + nameLength + extraLength + commentLength;
3722
+ }
3723
+ }
3724
+ }
3725
+
3726
+ // src/core/other-apps/xmind.ts
3727
+ var PICTURE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "heic", "heif"]);
3728
+ var importXMind = (data, options = {}) => {
3729
+ const workbook = openWorkbook(data);
3730
+ const maps = [];
3731
+ if (workbook.json !== undefined) {
3732
+ for (const sheet of workbook.json) {
3733
+ if (isObject(sheet) && isObject(sheet.rootTopic)) {
3734
+ maps.push(new JsonSheetReader(workbook.archive).read(sheet));
3735
+ }
3736
+ }
3737
+ } else if (workbook.xml) {
3738
+ const styles = workbook.archive ? readStyles(workbook.archive) : new Map;
3739
+ const comments = workbook.archive ? readComments(workbook.archive) : new Map;
3740
+ for (const sheet of childElements(workbook.xml, "sheet")) {
3741
+ const topic = childElement(sheet, "topic");
3742
+ if (topic) {
3743
+ maps.push(new XmlSheetReader(workbook.archive, styles, comments).read(sheet, topic));
3744
+ }
3745
+ }
3746
+ }
3747
+ if (maps.length === 0) {
3748
+ throw new OrbitImportError("empty", `The workbook${options.name ? ` ${options.name}` : ""} has no sheet with a topic.`);
3749
+ }
3750
+ return { format: "xmind", maps };
3751
+ };
3752
+ var openWorkbook = (data) => {
3753
+ if (typeof data === "string") {
3754
+ return readContent(data, null, data.trimStart().startsWith("[") ? "json" : "xml");
3755
+ }
3756
+ if (!isZip(data)) {
3757
+ return readContent(new TextDecoder("utf-8").decode(data), null, "xml");
3758
+ }
3759
+ let archive;
3760
+ try {
3761
+ archive = new ZipArchive(data);
3762
+ } catch (error) {
3763
+ throw unreadable(error);
3764
+ }
3765
+ if (isEncrypted(archive)) {
3766
+ throw new OrbitImportError("password-protected", "The workbook is protected with a password: open it in XMind and save it without one first.");
3767
+ }
3768
+ try {
3769
+ if (archive.has("content.json")) {
3770
+ return readContent(archive.text("content.json") ?? "", archive, "json");
3771
+ }
3772
+ if (archive.has("content.xml")) {
3773
+ return readContent(archive.text("content.xml") ?? "", archive, "xml");
3774
+ }
3775
+ } catch (error) {
3776
+ throw unreadable(error);
3777
+ }
3778
+ throw new OrbitImportError("unreadable", "The file is a zip archive, but not an XMind workbook: it has no content.json or content.xml.");
3779
+ };
3780
+ var readContent = (text, archive, kind) => {
3781
+ if (kind === "json") {
3782
+ let json;
3783
+ try {
3784
+ json = JSON.parse(text);
3785
+ } catch (error) {
3786
+ throw new OrbitImportError("unreadable", `The workbook's content.json can't be read: ${error.message}.`);
3787
+ }
3788
+ if (!Array.isArray(json)) {
3789
+ throw new OrbitImportError("unreadable", "The workbook's content.json isn't a list of sheets.");
3790
+ }
3791
+ return { archive, json };
3792
+ }
3793
+ let xml;
3794
+ try {
3795
+ xml = parseXml(text);
3796
+ } catch (error) {
3797
+ throw unreadable(error);
3798
+ }
3799
+ if (xml.localName !== "xmap-content") {
3800
+ throw new OrbitImportError("unreadable", `The file isn't an XMind workbook: it starts with <${xml.name}>, not <xmap-content>.`);
3801
+ }
3802
+ return { archive, xml };
3803
+ };
3804
+ var unreadable = (error) => {
3805
+ if (error instanceof ZipError && /encrypted/.test(error.message)) {
3806
+ return new OrbitImportError("password-protected", "The workbook is protected with a password: open it in XMind and save it without one first.");
3807
+ }
3808
+ if (error instanceof ZipError || error instanceof MarkupError) {
3809
+ return new OrbitImportError("unreadable", `The workbook can't be read: ${error.message}.`);
3810
+ }
3811
+ return error instanceof OrbitImportError ? error : new OrbitImportError("unreadable", `The workbook can't be read: ${error.message}.`);
3812
+ };
3813
+ var isEncrypted = (archive) => {
3814
+ try {
3815
+ const manifest = archive.has("META-INF/manifest.xml") ? archive.text("META-INF/manifest.xml") ?? "" : "";
3816
+ const json = archive.has("manifest.json") ? archive.text("manifest.json") ?? "" : "";
3817
+ return /<encryption-data/.test(manifest) || /"encryption/.test(json);
3818
+ } catch {
3819
+ return false;
3820
+ }
3821
+ };
3822
+ var isObject = (value) => {
3823
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3824
+ };
3825
+ var asString = (value) => {
3826
+ return typeof value === "string" ? value : undefined;
3827
+ };
3828
+
3829
+ class SheetReader {
3830
+ archive;
3831
+ report = new ImportReport;
3832
+ media = {};
3833
+ topics = new Map;
3834
+ pictureNames = new Map;
3835
+ constructor(archive) {
3836
+ this.archive = archive;
3837
+ }
3838
+ finish(root) {
3839
+ return { document: buildDocument(root, this.report), media: this.media, warnings: this.report.warnings };
3840
+ }
3841
+ readLink(href, idea) {
3842
+ const address = href?.trim();
3843
+ if (!address) {
3844
+ return;
3845
+ }
3846
+ const topic = /^xmind:#(.+)$/.exec(address);
3847
+ const kept = address.startsWith("#") ? null : keptAddress(address);
3848
+ if (topic) {
3849
+ idea.ideaLinks.push({ target: topic[1] });
3850
+ } else if (kept) {
3851
+ idea.link = kept;
3852
+ } else if (/^xap:/.test(address)) {
3853
+ if (PICTURE_EXTENSIONS.has(extensionOf(address)) && !idea.image) {
3854
+ idea.image = this.picture(address, idea.text) ?? undefined;
3855
+ } else {
3856
+ this.report.add("attachments-not-kept", idea.text || address.replace(/^.*\//, ""));
3857
+ }
3858
+ } else {
3859
+ this.report.add("local-links-not-kept", address);
3860
+ }
3861
+ }
3862
+ picture(src, example) {
3863
+ if (/^https?:\/\/\S+$/i.test(src)) {
3864
+ return src;
3865
+ }
3866
+ const inside = /^xap:(.+)$/.exec(src)?.[1];
3867
+ if (!inside || !this.archive) {
3868
+ this.report.add("pictures-not-kept", example || src.replace(/^.*[/\\]/, ""));
3869
+ return null;
3870
+ }
3871
+ if (!this.pictureNames.has(inside)) {
3872
+ this.pictureNames.set(inside, this.unpackPicture(inside));
3873
+ }
3874
+ const name = this.pictureNames.get(inside) ?? null;
3875
+ if (!name) {
3876
+ this.report.add("pictures-not-kept", example || inside.replace(/^.*\//, ""));
3877
+ }
3878
+ return name;
3879
+ }
3880
+ unpackPicture(path) {
3881
+ const extension = extensionOf(path);
3882
+ if (!PICTURE_EXTENSIONS.has(extension)) {
3883
+ return null;
3884
+ }
3885
+ let bytes;
3886
+ try {
3887
+ bytes = this.archive?.read(path) ?? null;
3888
+ } catch {
3889
+ bytes = null;
3890
+ }
3891
+ if (!bytes) {
3892
+ return null;
3893
+ }
3894
+ const base = path.replace(/^.*\//, "").replace(/\.[^.]*$/, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "") || "picture";
3895
+ let name = `media/${base}.${extension}`;
3896
+ for (let suffix = 2;this.media[name] !== undefined; suffix++) {
3897
+ name = `media/${base}-${suffix}.${extension}`;
3898
+ }
3899
+ this.media[name] = bytes;
3900
+ return name;
3901
+ }
3902
+ noteAddresses(example) {
3903
+ return {
3904
+ link: (href) => {
3905
+ const topic = /^xmind:#(.+)$/.exec(href);
3906
+ if (topic) {
3907
+ return sourceLink(topic[1]);
3908
+ }
3909
+ const kept = href.startsWith("#") ? null : keptAddress(href);
3910
+ if (kept) {
3911
+ return kept;
3912
+ }
3913
+ this.report.add("local-links-not-kept", href);
3914
+ return null;
3915
+ },
3916
+ picture: (src) => this.picture(src, example)
3917
+ };
3918
+ }
3919
+ htmlNote(html, idea) {
3920
+ const note = noteFromHtml(html, this.noteAddresses(idea.text));
3921
+ if (note?.format === "plain") {
3922
+ this.report.add("formatting-not-kept", idea.text || undefined);
3923
+ }
3924
+ return note;
3925
+ }
3926
+ readMarker(markerId, idea) {
3927
+ const meaning = xmindMarker(markerId);
3928
+ if (!meaning) {
3929
+ this.report.add("icons-not-kept", markerId);
3930
+ } else if ("icon" in meaning) {
3931
+ idea.icons.push(meaning.icon);
3932
+ } else if ("priority" in meaning) {
3933
+ idea.priority ??= meaning.priority;
3934
+ } else {
3935
+ idea.status ??= meaning.status;
3936
+ }
3937
+ }
3938
+ relate(fromId, toId, title) {
3939
+ const from = fromId ? this.topics.get(fromId) : undefined;
3940
+ if (!from || !toId) {
3941
+ this.report.add("missing-link-targets", title || undefined);
3942
+ return;
3943
+ }
3944
+ const label = title?.trim();
3945
+ from.ideaLinks.push(label ? { target: toId, label } : { target: toId });
3946
+ }
3947
+ adopt(idea, children, code) {
3948
+ for (const child of children) {
3949
+ this.report.add(code, child.text || undefined);
3950
+ idea.children.push(child);
3951
+ }
3952
+ }
3953
+ boundaryNote(titles, idea) {
3954
+ for (const title of titles) {
3955
+ const text = title.trim();
3956
+ if (text !== "") {
3957
+ this.report.add("boundaries-in-notes", text);
3958
+ const note = noteFromPlainText(text);
3959
+ if (note) {
3960
+ idea.notes.push(note);
3961
+ }
3962
+ }
3963
+ }
3964
+ }
3965
+ }
3966
+ var extensionOf = (path) => {
3967
+ return /\.([A-Za-z0-9]+)$/.exec(path)?.[1].toLowerCase() ?? "";
3968
+ };
3969
+
3970
+ class JsonSheetReader extends SheetReader {
3971
+ read(sheet) {
3972
+ const root = this.topic(sheet.rootTopic, 0);
3973
+ for (const relationship of Array.isArray(sheet.relationships) ? sheet.relationships : []) {
3974
+ if (isObject(relationship)) {
3975
+ this.relate(asString(relationship.end1Id), asString(relationship.end2Id), asString(relationship.title));
3976
+ }
3977
+ }
3978
+ return this.finish(root);
3979
+ }
3980
+ topic(topic, depth) {
3981
+ if (depth > 500) {
3982
+ throw new OrbitImportError("too-large", "The sheet goes more than 500 levels deep.");
3983
+ }
3984
+ const idea = newIdea(asString(topic.title) ?? "");
3985
+ const id = asString(topic.id);
3986
+ if (id) {
3987
+ idea.sourceId = id;
3988
+ this.topics.set(id, idea);
3989
+ }
3990
+ const note = this.note(topic.notes, idea);
3991
+ if (note) {
3992
+ idea.notes.push(note);
3993
+ }
3994
+ if (Array.isArray(topic.labels)) {
3995
+ idea.tags.push(...topic.labels.filter((label) => typeof label === "string"));
3996
+ }
3997
+ for (const marker of Array.isArray(topic.markers) ? topic.markers : []) {
3998
+ const markerId = isObject(marker) ? asString(marker.markerId) : undefined;
3999
+ if (markerId) {
4000
+ this.readMarker(markerId, idea);
4001
+ }
4002
+ }
4003
+ this.readLink(asString(topic.href), idea);
4004
+ if (isObject(topic.image)) {
4005
+ const src = asString(topic.image.src);
4006
+ if (src) {
4007
+ idea.image ??= this.picture(src, idea.text) ?? undefined;
4008
+ }
4009
+ }
4010
+ if (topic.branch === "folded") {
4011
+ idea.collapsed = true;
4012
+ }
4013
+ const style = isObject(topic.style) && isObject(topic.style.properties) ? topic.style.properties : {};
4014
+ const fill = asString(style["svg:fill"]);
4015
+ if (fill && /^#[0-9A-Fa-f]{6}$/.test(fill)) {
4016
+ idea.color = fill.toUpperCase();
4017
+ }
4018
+ const children = isObject(topic.children) ? topic.children : {};
4019
+ const kind = (name) => (Array.isArray(children[name]) ? children[name] : []).filter(isObject).map((child) => this.topic(child, depth + 1));
4020
+ idea.children.push(...kind("attached"));
4021
+ this.adopt(idea, kind("summary"), "summaries-moved");
4022
+ this.adopt(idea, kind("callout"), "callouts-moved");
4023
+ this.adopt(idea, kind("detached"), "floating-ideas-moved");
4024
+ const boundaries = Array.isArray(topic.boundaries) ? topic.boundaries.filter(isObject).map((boundary) => asString(boundary.title) ?? "") : [];
4025
+ this.boundaryNote(boundaries, idea);
4026
+ return idea;
4027
+ }
4028
+ note(notes, idea) {
4029
+ if (!isObject(notes)) {
4030
+ return;
4031
+ }
4032
+ const html = isObject(notes.realHTML) ? asString(notes.realHTML.content) : undefined;
4033
+ if (html) {
4034
+ return this.htmlNote(html, idea);
4035
+ }
4036
+ const paragraphs = isObject(notes.html) && isObject(notes.html.content) && Array.isArray(notes.html.content.paragraphs) ? notes.html.content.paragraphs : null;
4037
+ if (paragraphs) {
4038
+ const blocks = paragraphs.filter(isObject).map((paragraph) => ({
4039
+ kind: "paragraph",
4040
+ inline: (Array.isArray(paragraph.spans) ? paragraph.spans : []).filter(isObject).map((span) => styledSpan(span))
4041
+ }));
4042
+ const note = noteFromBlocks(blocks);
4043
+ if (note?.format === "plain") {
4044
+ this.report.add("formatting-not-kept", idea.text || undefined);
4045
+ }
4046
+ return note;
4047
+ }
4048
+ return noteFromPlainText(isObject(notes.plain) ? asString(notes.plain.content) : undefined);
4049
+ }
4050
+ }
4051
+ var styledSpan = (span) => {
4052
+ const properties = isObject(span.style) && isObject(span.style.properties) ? span.style.properties : {};
4053
+ let piece = { kind: "text", text: (asString(span.text) ?? "").replace(/\s+/g, " ") };
4054
+ if (/line-through/.test(asString(properties["fo:text-decoration"]) ?? "")) {
4055
+ piece = { kind: "strike", children: [piece] };
4056
+ }
4057
+ if (asString(properties["fo:font-style"]) === "italic") {
4058
+ piece = { kind: "emphasis", children: [piece] };
4059
+ }
4060
+ if (/^(bold|[6-9]00)$/.test(asString(properties["fo:font-weight"]) ?? "")) {
4061
+ piece = { kind: "strong", children: [piece] };
4062
+ }
4063
+ return piece;
4064
+ };
4065
+
4066
+ class XmlSheetReader extends SheetReader {
4067
+ styles;
4068
+ comments;
4069
+ constructor(archive, styles, comments) {
4070
+ super(archive);
4071
+ this.styles = styles;
4072
+ this.comments = comments;
4073
+ }
4074
+ read(sheet, topic) {
4075
+ const root = this.topic(topic);
4076
+ for (const relationships of childElements(sheet, "relationships")) {
4077
+ for (const relationship of childElements(relationships, "relationship")) {
4078
+ const title = childElement(relationship, "title");
4079
+ this.relate(relationship.attributes.end1, relationship.attributes.end2, title ? textContent(title) : undefined);
4080
+ }
4081
+ }
4082
+ return this.finish(root);
4083
+ }
4084
+ topic(topic) {
4085
+ const title = childElement(topic, "title");
4086
+ const idea = newIdea(title ? textContent(title) : "");
4087
+ const id = topic.attributes.id;
4088
+ if (id) {
4089
+ idea.sourceId = id;
4090
+ this.topics.set(id, idea);
4091
+ }
4092
+ const notes = childElement(topic, "notes");
4093
+ if (notes) {
4094
+ const html = childElement(notes, "html");
4095
+ const plain = childElement(notes, "plain");
4096
+ const note = html ? this.htmlNote(html, idea) : noteFromPlainText(plain ? textContent(plain) : undefined);
4097
+ if (note) {
4098
+ idea.notes.push(note);
4099
+ }
4100
+ }
4101
+ for (const comment of id ? this.comments.get(id) ?? [] : []) {
4102
+ const note = noteFromPlainText(comment);
4103
+ if (note) {
4104
+ idea.notes.push(note);
4105
+ }
4106
+ }
4107
+ for (const labels of childElements(topic, "labels")) {
4108
+ idea.tags.push(...childElements(labels, "label").map((label) => textContent(label)));
4109
+ }
4110
+ for (const markers of childElements(topic, "marker-refs")) {
4111
+ for (const marker of childElements(markers, "marker-ref")) {
4112
+ const markerId = marker.attributes["marker-id"];
4113
+ if (markerId) {
4114
+ this.readMarker(markerId, idea);
4115
+ }
4116
+ }
4117
+ }
4118
+ this.readLink(attributeOf(topic, "href"), idea);
4119
+ const image = childElement(topic, "img");
4120
+ const src = image ? attributeOf(image, "src") : undefined;
4121
+ if (src) {
4122
+ idea.image ??= this.picture(src, idea.text) ?? undefined;
4123
+ }
4124
+ if (topic.attributes.branch === "folded") {
4125
+ idea.collapsed = true;
4126
+ }
4127
+ const fill = this.styles.get(topic.attributes["style-id"] ?? "");
4128
+ if (fill) {
4129
+ idea.color = fill;
4130
+ }
4131
+ const children = childElement(topic, "children");
4132
+ const kind = (type) => (children ? childElements(children, "topics") : []).filter((topics) => (topics.attributes.type ?? "attached") === type).flatMap((topics) => childElements(topics, "topic").map((child) => this.topic(child)));
4133
+ idea.children.push(...kind("attached"));
4134
+ this.adopt(idea, kind("summary"), "summaries-moved");
4135
+ this.adopt(idea, kind("callout"), "callouts-moved");
4136
+ this.adopt(idea, kind("detached"), "floating-ideas-moved");
4137
+ const boundaries = childElements(topic, "boundaries").flatMap((list) => childElements(list, "boundary")).map((boundary) => {
4138
+ const boundaryTitle = childElement(boundary, "title");
4139
+ return boundaryTitle ? textContent(boundaryTitle) : "";
4140
+ });
4141
+ this.boundaryNote(boundaries, idea);
4142
+ return idea;
4143
+ }
4144
+ }
4145
+ var readStyles = (archive) => {
4146
+ const fills = new Map;
4147
+ try {
4148
+ const text = archive.has("styles.xml") ? archive.text("styles.xml") : null;
4149
+ if (!text) {
4150
+ return fills;
4151
+ }
4152
+ const pending = [parseXml(text)];
4153
+ while (pending.length > 0) {
4154
+ const element = pending.pop();
4155
+ for (const child of element.children) {
4156
+ if (!isElement(child)) {
4157
+ continue;
4158
+ }
4159
+ if (child.localName === "style" && child.attributes.type === "topic" && child.attributes.id) {
4160
+ const properties = childElement(child, "topic-properties");
4161
+ const fill = properties ? attributeOf(properties, "fill") : undefined;
4162
+ if (fill && /^#[0-9A-Fa-f]{6}$/.test(fill)) {
4163
+ fills.set(child.attributes.id, fill.toUpperCase());
4164
+ }
4165
+ }
4166
+ pending.push(child);
4167
+ }
4168
+ }
4169
+ } catch {}
4170
+ return fills;
4171
+ };
4172
+ var readComments = (archive) => {
4173
+ const comments = new Map;
4174
+ try {
4175
+ const text = archive.has("comments.xml") ? archive.text("comments.xml") : null;
4176
+ if (!text) {
4177
+ return comments;
4178
+ }
4179
+ for (const comment of childElements(parseXml(text), "comment")) {
4180
+ const topic = comment.attributes["object-id"];
4181
+ const content = childElement(comment, "content");
4182
+ const words = content ? textContent(content).replace(/\r\n?/g, `
4183
+ `).trim() : "";
4184
+ if (topic && words !== "") {
4185
+ const author = (comment.attributes.author ?? "").trim();
4186
+ comments.set(topic, [...comments.get(topic) ?? [], author ? `${author}: ${words}` : words]);
4187
+ }
4188
+ }
4189
+ } catch {}
4190
+ return comments;
4191
+ };
4192
+
4193
+ // src/core/other-apps/import.ts
4194
+ var EXTENSIONS = { opml: "opml", mm: "freemind", xmind: "xmind" };
4195
+ var detectImportFormat = (data, name) => {
4196
+ const extension = /\.([A-Za-z0-9]+)$/.exec(name ?? "")?.[1].toLowerCase() ?? "";
4197
+ return formatInside(data) ?? (Object.prototype.hasOwnProperty.call(EXTENSIONS, extension) ? EXTENSIONS[extension] : null);
4198
+ };
4199
+ var formatInside = (data) => {
4200
+ if (typeof data !== "string" && isZip(data)) {
4201
+ try {
4202
+ const archive = new ZipArchive(data);
4203
+ return archive.has("content.json") || archive.has("content.xml") ? "xmind" : null;
4204
+ } catch {
4205
+ return null;
4206
+ }
4207
+ }
4208
+ const text = typeof data === "string" ? data.slice(0, 4096) : decodeMarkup(data.subarray(0, 4096));
4209
+ const root = /^(?:\s|<\?[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[^>]*>)*<([A-Za-z_][\w.:-]*)/.exec(text.replace(/^\ufeff/, ""))?.[1];
4210
+ const formats = { opml: "opml", map: "freemind", "xmap-content": "xmind" };
4211
+ return root && Object.prototype.hasOwnProperty.call(formats, root) ? formats[root] : null;
4212
+ };
4213
+ var importMap = (data, options = {}) => {
4214
+ const format = options.format ?? detectImportFormat(data, options.name);
4215
+ switch (format) {
4216
+ case "opml":
4217
+ return importOPML(data, options);
4218
+ case "freemind":
4219
+ return importFreeMind(data, options);
4220
+ case "xmind":
4221
+ return importXMind(data, options);
4222
+ default:
4223
+ throw new OrbitImportError("unknown-format", `${options.name ? `${options.name} isn't` : "The file isn't"} a map Orbit can bring in from another app: it reads OPML, FreeMind and Freeplane (.mm), and XMind (.xmind).`);
4224
+ }
4225
+ };
4226
+ // src/core/material.ts
4227
+ var MATERIAL_SEED = "#6750A4";
4228
+ var BRANCH_HUES = [0, 3, 6, 2, 5, 1, 4].map((step) => step * 360 / 7);
4229
+ var MOST_CHROMA = 0.16;
4230
+ var BRANCH_CHROMA = 0.11;
4231
+ var ERROR = { hue: 25, chroma: 0.19 };
4232
+ var SUCCESS = { hue: 145, chroma: 0.13 };
4233
+ var materialTheme = (seed, options = {}) => {
4234
+ const dark = options.dark === true;
4235
+ return { preset: dark ? "material-night" : "material", tokens: materialColors(seed, dark) };
4236
+ };
4237
+ var materialColors = (seed, dark) => {
4238
+ const parsed = parseColor(seed);
4239
+ if (!parsed) {
4240
+ throw new Error(`A Material theme needs a color Orbit can read, such as "#0B57D0" or "oklch(0.55 0.15 260)": got "${seed}".`);
4241
+ }
4242
+ const { hue, chroma } = toOklch(parsed);
4243
+ const primaryChroma = Math.min(chroma, MOST_CHROMA);
4244
+ const palette = (paletteHue, paletteChroma) => (tone) => toHex(toneColor(paletteHue, paletteChroma, tone));
4245
+ const primary = palette(hue, primaryChroma);
4246
+ const secondary = palette(hue, Math.min(primaryChroma / 3, 0.05));
4247
+ const neutral = palette(hue, Math.min(primaryChroma / 10, 0.012));
4248
+ const neutralVariant = palette(hue, Math.min(primaryChroma / 5, 0.024));
4249
+ const error = palette(ERROR.hue, ERROR.chroma);
4250
+ const success = palette(SUCCESS.hue, SUCCESS.chroma);
4251
+ const branches = BRANCH_HUES.map((offset) => palette((hue + offset) % 360, BRANCH_CHROMA));
4252
+ const [accent, container, onContainer] = dark ? [80, 30, 90] : [40, 90, 10];
4253
+ const branchTone = dark ? 32 : 88;
4254
+ const [colorRoot, ...colors] = [primary(accent), ...branches.map((branch) => branch(branchTone))];
4255
+ return {
4256
+ surface: neutral(dark ? 6 : 98),
4257
+ text: neutral(dark ? 90 : 10),
4258
+ "muted-text": neutralVariant(dark ? 80 : 30),
4259
+ "root-text": neutral(10),
4260
+ "node-text": neutral(10),
4261
+ "node-text-inverse": neutral(dark ? 90 : 100),
4262
+ "color-root": colorRoot,
4263
+ "color-1": colors[0],
4264
+ "color-2": colors[1],
4265
+ "color-3": colors[2],
4266
+ "color-4": colors[3],
4267
+ "color-5": colors[4],
4268
+ "color-6": colors[5],
4269
+ "color-7": colors[6],
4270
+ "shade-light": dark ? neutral(40) : neutralVariant(80),
4271
+ "shade-dark": dark ? neutral(12) : neutralVariant(50),
4272
+ "node-stroke": neutralVariant(dark ? 30 : 80),
4273
+ "badge-stroke": neutral(dark ? 6 : 98),
4274
+ "sun-halo": primary(accent),
4275
+ "ring-stroke": neutralVariant(dark ? 30 : 80),
4276
+ "link-stroke": neutralVariant(dark ? 60 : 50),
4277
+ "focus-ring": primary(accent),
4278
+ "note-indicator": primary(accent),
4279
+ "control-background": neutral(dark ? 12 : 94),
4280
+ "control-text": neutral(dark ? 90 : 10),
4281
+ "control-muted-text": neutralVariant(dark ? 80 : 30),
4282
+ "control-active-background": secondary(container),
4283
+ "control-active-text": secondary(onContainer),
4284
+ "tooltip-background": neutral(dark ? 90 : 20),
4285
+ "tooltip-text": neutral(dark ? 20 : 95),
4286
+ "action-background": neutral(dark ? 17 : 92),
4287
+ "action-text": neutral(dark ? 90 : 10),
4288
+ "sheet-background": neutral(dark ? 10 : 96),
4289
+ "sheet-text": neutral(dark ? 90 : 10),
4290
+ "sheet-muted-text": neutralVariant(dark ? 80 : 30),
4291
+ "sheet-border": neutralVariant(dark ? 30 : 80),
4292
+ "chip-background": secondary(container),
4293
+ "chip-text": secondary(onContainer),
4294
+ "link-text": primary(accent),
4295
+ "status-active": primary(accent),
4296
+ "status-done": success(accent),
4297
+ "status-blocked": error(accent),
4298
+ "status-todo": neutralVariant(60),
4299
+ "status-neutral": neutralVariant(dark ? 70 : 50),
4300
+ "error-background": error(container),
4301
+ "error-border": error(accent),
4302
+ "error-text": error(onContainer)
4303
+ };
4304
+ };
4305
+ export {
4306
+ DIAGNOSTIC_CODES2 as DIAGNOSTIC_CODES,
4307
+ IMPORT_ERROR_CODES,
4308
+ IMPORT_WARNING_CODES,
4309
+ MATERIAL_SEED,
4310
+ ORBIT_THEME_PRESETS2 as ORBIT_THEME_PRESETS,
4311
+ OrbitImportError,
4312
+ OrbitParseError2 as OrbitParseError,
4313
+ OrbitSVGRenderer2 as OrbitSVGRenderer,
4314
+ PRINT_THEMES,
4315
+ SUPPORTED_SCHEMA_VERSION2 as SUPPORTED_SCHEMA_VERSION,
4316
+ THEME_TOKENS2 as THEME_TOKENS,
4317
+ createMapTools,
4318
+ createOrbitMap,
4319
+ defineOrbitMapElement2 as defineOrbitMapElement,
4320
+ detectImportFormat,
4321
+ documentToRendererOptions2 as documentToRendererOptions,
4322
+ exportOPML,
4323
+ exportWebPage,
4324
+ findNodeById2 as findNodeById,
4325
+ formatDiagnostic2 as formatDiagnostic,
4326
+ getNodePath2 as getNodePath,
4327
+ importFreeMind,
4328
+ importMap,
4329
+ importOPML,
4330
+ importXMind,
4331
+ isOrbitThemePreset2 as isOrbitThemePreset,
4332
+ isThemeName2 as isThemeName,
4333
+ languageTags2 as languageTags,
4334
+ materialTheme,
4335
+ parseOrbit2 as parseOrbit,
4336
+ parseOrbitMarkdown2 as parseOrbitMarkdown,
4337
+ parseOrbitYAML2 as parseOrbitYAML,
4338
+ printTheme,
4339
+ registerAction2 as registerAction,
4340
+ registerLanguage2 as registerLanguage,
4341
+ registerNodeType2 as registerNodeType,
4342
+ registerStatus2 as registerStatus,
4343
+ registerTheme2 as registerTheme,
4344
+ renderNoteMarkdown2 as renderNoteMarkdown,
4345
+ renderOrbitElement2 as renderOrbitElement,
4346
+ renderOrbitMarkdownBlocks2 as renderOrbitMarkdownBlocks,
4347
+ renderPrintLayout,
4348
+ renderToSVGString2 as renderToSVGString,
4349
+ serializeOrbitMarkdown2 as serializeOrbitMarkdown,
4350
+ serializeOrbitYAML,
4351
+ themeNames2 as themeNames,
4352
+ themeTokens2 as themeTokens,
4353
+ upgradeNodeTypes2 as upgradeNodeTypes
4354
+ };
4355
+
4356
+ //# debugId=0EF60DBE164EC11364756E2164756E21
4357
+ //# sourceMappingURL=index.js.map