@formicoidea/labre-core 0.50.0 → 0.51.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 (191) hide show
  1. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
  2. package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
  3. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
  4. package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
  5. package/dist/_pkgs/affine-block-attachment/index.js +1 -0
  6. package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
  7. package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
  8. package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
  9. package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
  10. package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
  11. package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
  12. package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
  13. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
  14. package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
  15. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
  16. package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
  17. package/dist/_pkgs/affine-block-frame/index.js +1 -0
  18. package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
  19. package/dist/_pkgs/affine-block-frame/translations.js +16 -1
  20. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  21. package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
  22. package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
  23. package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
  24. package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
  25. package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
  26. package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
  27. package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
  28. package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
  29. package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
  30. package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
  31. package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
  32. package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
  33. package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
  34. package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
  35. package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
  36. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
  37. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
  38. package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
  39. package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
  40. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
  41. package/dist/_pkgs/affine-block-image/effects.js +2 -0
  42. package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
  43. package/dist/_pkgs/affine-block-image/image-block.js +2 -0
  44. package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
  45. package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
  46. package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
  47. package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
  48. package/dist/_pkgs/affine-block-image/index.js +1 -0
  49. package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
  50. package/dist/_pkgs/affine-block-image/translations.js +43 -0
  51. package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
  52. package/dist/_pkgs/affine-block-image/utils.js +10 -1
  53. package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
  54. package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
  55. package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
  56. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  57. package/dist/_pkgs/affine-block-root/index.js +1 -0
  58. package/dist/_pkgs/affine-block-root/view.js +8 -0
  59. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
  60. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
  61. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
  62. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
  63. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
  64. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
  65. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
  66. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
  67. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
  68. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
  69. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
  70. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
  71. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
  72. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
  73. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
  74. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
  75. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
  76. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
  77. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
  78. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
  79. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
  80. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
  81. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
  82. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
  83. package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
  84. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
  85. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
  86. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
  87. package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
  88. package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
  89. package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
  90. package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
  91. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
  92. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
  93. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
  94. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
  95. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
  96. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
  97. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
  98. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
  99. package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
  100. package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
  101. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
  102. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
  103. package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
  104. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
  105. package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
  106. package/dist/_pkgs/affine-block-surface/view.js +22 -2
  107. package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
  108. package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
  109. package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
  110. package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
  111. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
  112. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
  113. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
  114. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
  115. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
  116. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
  117. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
  118. package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
  119. package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
  120. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
  121. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
  122. package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
  123. package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
  124. package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
  125. package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
  126. package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
  127. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
  128. package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
  129. package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
  130. package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
  131. package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
  132. package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
  133. package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
  134. package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
  135. package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
  136. package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
  137. package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
  138. package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
  139. package/dist/_pkgs/affine-model/consts/shape.js +5 -0
  140. package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
  141. package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
  142. package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
  143. package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
  144. package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
  145. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
  146. package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
  147. package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
  148. package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
  149. package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
  150. package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
  151. package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
  152. package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
  153. package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
  154. package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
  155. package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
  156. package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
  157. package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
  158. package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
  159. package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
  160. package/dist/_pkgs/affine-model/headless.d.ts +19 -0
  161. package/dist/_pkgs/affine-model/headless.js +19 -0
  162. package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
  163. package/dist/_pkgs/affine-shared/headless.js +10 -0
  164. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
  165. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
  166. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
  167. package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
  168. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  169. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  170. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
  171. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
  172. package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
  173. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
  174. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
  175. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
  176. package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
  177. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
  178. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
  179. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
  180. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
  181. package/dist/commands.js +5 -2
  182. package/dist/host-panels.d.ts +21 -1
  183. package/dist/host-panels.js +50 -10
  184. package/dist/interchange.d.ts +1 -0
  185. package/dist/interchange.js +1 -0
  186. package/dist/model/headless.d.ts +1 -0
  187. package/dist/model/headless.js +1 -0
  188. package/dist/shared/headless.d.ts +1 -0
  189. package/dist/shared/headless.js +1 -0
  190. package/dist/translations.js +8 -1
  191. package/package.json +13 -1
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Paint an image block's picture into its box for the raster export
3
+ * (ADR 0037): the cropped part when there is a crop and the source has a
4
+ * pixel size, otherwise the whole picture — the 5-argument call every
5
+ * uncropped image, and an SVG with no intrinsic size, always took.
6
+ */
7
+ export declare function drawImageBlock(ctx: Pick<CanvasRenderingContext2D, 'drawImage'>, image: HTMLImageElement, crop: unknown, box: {
8
+ x: number;
9
+ y: number;
10
+ w: number;
11
+ h: number;
12
+ }): void;
@@ -0,0 +1,15 @@
1
+ import { imageCropSourceRect, readImageCrop } from '../../../affine-model/index.js';
2
+ /**
3
+ * Paint an image block's picture into its box for the raster export
4
+ * (ADR 0037): the cropped part when there is a crop and the source has a
5
+ * pixel size, otherwise the whole picture — the 5-argument call every
6
+ * uncropped image, and an SVG with no intrinsic size, always took.
7
+ */
8
+ export function drawImageBlock(ctx, image, crop, box) {
9
+ const source = imageCropSourceRect(readImageCrop(crop), image.naturalWidth, image.naturalHeight);
10
+ if (!source) {
11
+ ctx.drawImage(image, box.x, box.y, box.w, box.h);
12
+ return;
13
+ }
14
+ ctx.drawImage(image, source.x, source.y, source.w, source.h, box.x, box.y, box.w, box.h);
15
+ }
@@ -9,6 +9,7 @@ import { StdIdentifier, } from '../../../std/index.js';
9
9
  import { GfxBlockElementModel, GfxControllerIdentifier, GfxPrimitiveElementModel, isGfxGroupCompatibleModel, } from '../../../std/gfx/index.js';
10
10
  import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
11
11
  import { getBgGridGap } from '../../utils/get-bg-grip-gap.js';
12
+ import { drawImageBlock } from './draw-image-block.js';
12
13
  /**
13
14
  * The SVG the PNG export paints the canvas background from: the background
14
15
  * colour, plus the dotted grid unless `gridColor` is `null` — what this
@@ -364,7 +365,12 @@ export class ExportManager {
364
365
  img.src = URL.createObjectURL(blob);
365
366
  });
366
367
  const blockBound = xywhArrayToObject(block);
367
- ctx.drawImage(await blobToImage(blob), blockBound.x - bound.x, blockBound.y - bound.y, blockBound.w, blockBound.h);
368
+ drawImageBlock(ctx, await blobToImage(blob), block.props.crop, {
369
+ x: blockBound.x - bound.x,
370
+ y: blockBound.y - bound.y,
371
+ w: blockBound.w,
372
+ h: blockBound.h,
373
+ });
368
374
  }
369
375
  const blockComponent = this.editorHost.view.getBlock(block.id);
370
376
  if (blockComponent) {
@@ -1,5 +1,5 @@
1
1
  import { FrameworkBackgroundElementModel } from '../../../affine-model/index.js';
2
- import type { AnyCommandDescriptor, BlockStdScope } from '../../../std/index.js';
2
+ import type { AnyCommandDescriptor, BlockStdScope, CommandFile } from '../../../std/index.js';
3
3
  import { type BoardSvgExportOptions } from './parts.js';
4
4
  /**
5
5
  * The framework boards in the current canvas selection.
@@ -22,3 +22,9 @@ export declare function selectedBoards(std: BlockStdScope): FrameworkBackgroundE
22
22
  */
23
23
  export declare function exportBoardSvg(std: BlockStdScope, options?: Readonly<BoardSvgExportOptions>): void;
24
24
  export declare const exportSvgCommands: AnyCommandDescriptor[];
25
+ /**
26
+ * The whole canvas as an SVG file, or `null` when there is nothing to draw.
27
+ * Named after the page, like the PNG.
28
+ */
29
+ export declare function canvasSvgFile(std: BlockStdScope): CommandFile | null;
30
+ export declare const canvasSvgExportCommands: AnyCommandDescriptor[];
@@ -3,7 +3,7 @@ import { NotificationProvider, translateKey, } from '../../../affine-shared/serv
3
3
  import { downloadBlob, safeFilename } from '../../../affine-shared/utils/index.js';
4
4
  import { GfxControllerIdentifier } from '../../../std/gfx/index.js';
5
5
  import { boardSvgExportOptions, DEFAULT_BOARD_SVG_EXPORT_OPTIONS, } from './parts.js';
6
- import { renderBoardSvg } from './render.js';
6
+ import { canvasRendererOf, renderBoardSvg, renderCanvasSvg } from './render.js';
7
7
  import { EXPORT_SVG_NOTHING_TO_EXPORT } from './translations.js';
8
8
  /** What the file is, told to the browser. */
9
9
  const SVG_MIME = 'image/svg+xml';
@@ -106,3 +106,62 @@ const exportSvg = {
106
106
  run: (std, _invocation, params) => exportBoardSvg(std, boardSvgExportOptions(params)),
107
107
  };
108
108
  export const exportSvgCommands = [exportSvg];
109
+ /**
110
+ * The whole canvas as an SVG file, or `null` when there is nothing to draw.
111
+ * Named after the page, like the PNG.
112
+ */
113
+ export function canvasSvgFile(std) {
114
+ const rendered = renderCanvasSvg(std);
115
+ if (!rendered)
116
+ return null;
117
+ const title = std.store.workspace.meta.getDocMeta(std.store.id)?.title;
118
+ return {
119
+ blob: new Blob([rendered.svg], { type: `${SVG_MIME};charset=utf-8` }),
120
+ filename: `${safeFilename(title, SVG_FILENAME_FALLBACK)}.svg`,
121
+ };
122
+ }
123
+ /**
124
+ * "Export as SVG" of the WHOLE canvas, the fourth export of the canvas's
125
+ * "This view" (ADR 0034, amendment of 2026-10-14), on the model of
126
+ * `canvas.export.png`: given `params.deliver` (`runViewAction`) it hands the
127
+ * file over and downloads nothing; without it (the palette) it downloads.
128
+ *
129
+ * `export.svg` exports ONE selected board; this exports everything this
130
+ * viewer sees, with the same renderer replay and the same `data-labre-*`
131
+ * markers (ADR 0025, ADR 0032). What the replay cannot draw is left out and
132
+ * the description says so: the canvas' DOM blocks — notes, frames, images,
133
+ * embeds — have no SVG painter; edgeless texts do.
134
+ *
135
+ * `owner: 'core'`, never gated, no `telemetry` (it names no framework,
136
+ * like the PNG). Listed only where a `CanvasRenderer` paints the canvas.
137
+ */
138
+ const exportCanvasSvg = {
139
+ id: 'canvas.export.svg',
140
+ owner: 'core',
141
+ kind: 'action',
142
+ labelKey: 'com.labre.command.canvas.export.svg',
143
+ labelFallback: 'Export as SVG',
144
+ descriptionKey: 'com.labre.command.canvas.export.svg.description',
145
+ descriptionFallback: 'Download the whole canvas as a vector SVG file: shapes, lines, texts and boards. Notes, frames and images are not drawn.',
146
+ category: 'export',
147
+ iconKey: 'canvas.export.svg',
148
+ keywords: ['svg', 'vector', 'image', 'export', 'download'],
149
+ surfaces: ['view-menu', 'palette'],
150
+ order: 13,
151
+ scope: 'edgeless',
152
+ defaultKeys: { mac: [], other: [] },
153
+ when: std => canvasRendererOf(std) !== null,
154
+ run: (std, _invocation, params) => {
155
+ const file = canvasSvgFile(std);
156
+ if (!file)
157
+ return;
158
+ const deliver = params?.deliver;
159
+ if (typeof deliver === 'function')
160
+ deliver(file);
161
+ else
162
+ downloadBlob(file.blob, file.filename);
163
+ },
164
+ };
165
+ export const canvasSvgExportCommands = [
166
+ exportCanvasSvg,
167
+ ];
@@ -1,8 +1,8 @@
1
- export { exportBoardSvg, exportSvgCommands, selectedBoards, } from './command.js';
1
+ export { canvasSvgExportCommands, canvasSvgFile, exportBoardSvg, exportSvgCommands, selectedBoards, } from './command.js';
2
2
  export type { BoardSvgExportOptions, BoardSvgExportPart, SvgExportCandidate, } from './parts.js';
3
3
  export { boardSvgExportOptions, boardSvgExportPartOf, DEFAULT_BOARD_SVG_EXPORT_OPTIONS, selectBoardSvgParts, } from './parts.js';
4
4
  export type { BlockSvgPainter, BoardSvgExport } from './render.js';
5
- export { BlockSvgPainterExtension, BlockSvgPainterIdentifier, boardSvgMarkers, exportBoundOf, renderBoardSvg, selectBoardElements, } from './render.js';
5
+ export { BlockSvgPainterExtension, BlockSvgPainterIdentifier, boardSvgMarkers, canvasRendererOf, canvasSvgModels, exportBoundOf, renderBoardSvg, renderCanvasSvg, selectBoardElements, } from './render.js';
6
6
  export { taggedPath2D } from './tagged-path.js';
7
7
  export type { SvgContext } from './svg-context.js';
8
8
  export { BOARD_SVG_MARKER_VERSION, createSvgContext, runWithRecordingPath2D, } from './svg-context.js';
@@ -1,6 +1,6 @@
1
- export { exportBoardSvg, exportSvgCommands, selectedBoards, } from './command.js';
1
+ export { canvasSvgExportCommands, canvasSvgFile, exportBoardSvg, exportSvgCommands, selectedBoards, } from './command.js';
2
2
  export { boardSvgExportOptions, boardSvgExportPartOf, DEFAULT_BOARD_SVG_EXPORT_OPTIONS, selectBoardSvgParts, } from './parts.js';
3
- export { BlockSvgPainterExtension, BlockSvgPainterIdentifier, boardSvgMarkers, exportBoundOf, renderBoardSvg, selectBoardElements, } from './render.js';
3
+ export { BlockSvgPainterExtension, BlockSvgPainterIdentifier, boardSvgMarkers, canvasRendererOf, canvasSvgModels, exportBoundOf, renderBoardSvg, renderCanvasSvg, selectBoardElements, } from './render.js';
4
4
  export { taggedPath2D } from './tagged-path.js';
5
5
  export { BOARD_SVG_MARKER_VERSION, createSvgContext, runWithRecordingPath2D, } from './svg-context.js';
6
6
  export { exportSvgToolbarConfig, exportSvgToolbarExtension, } from './toolbar.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The marker version an export writes on its root (`data-labre-svg`), and the
3
+ * one a reader of it must know (ADR 0032 §6). Bumped only when the meaning of
4
+ * a marker changes; adding one is not a new version.
5
+ */
6
+ export declare const BOARD_SVG_MARKER_VERSION = "1";
@@ -0,0 +1,8 @@
1
+ // Its own module so the headless interchange entry (`./interchange`) can read
2
+ // it without the SVG export's graph (ADR 0012, P3 as amended 2026-10-14).
3
+ /**
4
+ * The marker version an export writes on its root (`data-labre-svg`), and the
5
+ * one a reader of it must know (ADR 0032 §6). Bumped only when the meaning of
6
+ * a marker changes; adding one is not a new version.
7
+ */
8
+ export const BOARD_SVG_MARKER_VERSION = '1';
@@ -1,7 +1,7 @@
1
1
  import { FrameworkBackgroundElementModel } from '../../../affine-model/index.js';
2
2
  import { Bound } from '../../../global/gfx/index.js';
3
3
  import type { BlockStdScope } from '../../../std/index.js';
4
- import { type GfxBlockElementModel, type GfxPrimitiveElementModel } from '../../../std/gfx/index.js';
4
+ import { GfxBlockElementModel, GfxPrimitiveElementModel } from '../../../std/gfx/index.js';
5
5
  import type { ExtensionType } from '../../../store/index.js';
6
6
  import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
7
7
  import { type BoardSvgExportOptions } from './parts.js';
@@ -70,6 +70,33 @@ export declare function exportBoundOf(frame: {
70
70
  */
71
71
  export declare function renderBoardSvg(std: BlockStdScope, board: FrameworkBackgroundElementModel): BoardSvgExport;
72
72
  export declare function renderBoardSvg(std: BlockStdScope, board: FrameworkBackgroundElementModel, options: Readonly<BoardSvgExportOptions>): BoardSvgExport | null;
73
+ /**
74
+ * The surface's painter, or `null` when the canvas is not painted by a
75
+ * {@link CanvasRenderer} (no surface mounted, a DOM renderer): what the SVG
76
+ * export replays into its own context.
77
+ */
78
+ export declare function canvasRendererOf(std: BlockStdScope): CanvasRenderer | null;
79
+ /**
80
+ * What the WHOLE canvas export draws: every canvas element and every edgeless
81
+ * text block this viewer sees (ADR 0031: what they hid is neither painted nor
82
+ * allowed to widen the picture), in the canvas' own z-order.
83
+ *
84
+ * The canvas' other blocks — notes, frames, images, embeds, attachments — are
85
+ * DOM, and no painter draws them into the 2D context (ADR 0025,
86
+ * Consequences): they are not in the file. A block that registers a
87
+ * {@link BlockSvgPainterExtension} joins without a change here.
88
+ */
89
+ export declare function canvasSvgModels(std: BlockStdScope): {
90
+ elements: GfxPrimitiveElementModel[];
91
+ textBlocks: GfxBlockElementModel[];
92
+ };
93
+ /**
94
+ * The whole canvas as one SVG document: {@link renderBoardSvg}'s replay of
95
+ * the canvas renderers, over everything {@link canvasSvgModels} keeps, framed
96
+ * by the union of what is painted. `null` when there is nothing to draw, or
97
+ * no {@link CanvasRenderer} to draw it with.
98
+ */
99
+ export declare function renderCanvasSvg(std: BlockStdScope): BoardSvgExport | null;
73
100
  /**
74
101
  * What an element's group says about it in the file (ADR 0032 §6): its id, its
75
102
  * type, its role, its stored bound in the FILE's coordinates, the two ends of
@@ -1,7 +1,7 @@
1
1
  import { FrameworkBackgroundElementModel } from '../../../affine-model/index.js';
2
2
  import { createIdentifier } from '../../../global/di/index.js';
3
3
  import { Bound } from '../../../global/gfx/index.js';
4
- import { GfxControllerIdentifier, } from '../../../std/gfx/index.js';
4
+ import { GfxBlockElementModel, GfxControllerIdentifier, GfxPrimitiveElementModel, } from '../../../std/gfx/index.js';
5
5
  import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
6
6
  import { RoughCanvas } from '../../utils/rough/canvas.js';
7
7
  import { DEFAULT_BOARD_SVG_EXPORT_OPTIONS, selectBoardSvgParts, } from './parts.js';
@@ -61,8 +61,8 @@ export function exportBoundOf(frame, elements) {
61
61
  export function renderBoardSvg(std, board, given) {
62
62
  const options = given ?? DEFAULT_BOARD_SVG_EXPORT_OPTIONS;
63
63
  const gfx = std.get(GfxControllerIdentifier);
64
- const renderer = gfx.surfaceComponent?.renderer;
65
- if (!(renderer instanceof CanvasRenderer)) {
64
+ const renderer = canvasRendererOf(std);
65
+ if (!renderer) {
66
66
  throw new Error('SVG export needs the surface to be painted by a CanvasRenderer.');
67
67
  }
68
68
  const frame = Bound.deserialize(board.xywh);
@@ -89,13 +89,73 @@ export function renderBoardSvg(std, board, given) {
89
89
  return null;
90
90
  }
91
91
  const bound = exportBoundOf(options.framework ? board : drawn[0], drawn);
92
+ return paintSvg(std, renderer, elements, texts, bound);
93
+ }
94
+ /**
95
+ * The surface's painter, or `null` when the canvas is not painted by a
96
+ * {@link CanvasRenderer} (no surface mounted, a DOM renderer): what the SVG
97
+ * export replays into its own context.
98
+ */
99
+ export function canvasRendererOf(std) {
100
+ const renderer = std.get(GfxControllerIdentifier).surfaceComponent?.renderer;
101
+ return renderer instanceof CanvasRenderer ? renderer : null;
102
+ }
103
+ /**
104
+ * What the WHOLE canvas export draws: every canvas element and every edgeless
105
+ * text block this viewer sees (ADR 0031: what they hid is neither painted nor
106
+ * allowed to widen the picture), in the canvas' own z-order.
107
+ *
108
+ * The canvas' other blocks — notes, frames, images, embeds, attachments — are
109
+ * DOM, and no painter draws them into the 2D context (ADR 0025,
110
+ * Consequences): they are not in the file. A block that registers a
111
+ * {@link BlockSvgPainterExtension} joins without a change here.
112
+ */
113
+ export function canvasSvgModels(std) {
114
+ const gfx = std.get(GfxControllerIdentifier);
115
+ const elements = [];
116
+ const textBlocks = [];
117
+ for (const model of [...gfx.gfxElements].sort(gfx.layer.compare)) {
118
+ if (gfx.localVisibility.isHidden(model))
119
+ continue;
120
+ if (model instanceof GfxPrimitiveElementModel) {
121
+ elements.push(model);
122
+ }
123
+ else if (model instanceof GfxBlockElementModel &&
124
+ std.getOptional(BlockSvgPainterIdentifier(model.flavour))) {
125
+ textBlocks.push(model);
126
+ }
127
+ }
128
+ return { elements, textBlocks };
129
+ }
130
+ /**
131
+ * The whole canvas as one SVG document: {@link renderBoardSvg}'s replay of
132
+ * the canvas renderers, over everything {@link canvasSvgModels} keeps, framed
133
+ * by the union of what is painted. `null` when there is nothing to draw, or
134
+ * no {@link CanvasRenderer} to draw it with.
135
+ */
136
+ export function renderCanvasSvg(std) {
137
+ const renderer = canvasRendererOf(std);
138
+ if (!renderer)
139
+ return null;
140
+ const { elements, textBlocks } = canvasSvgModels(std);
141
+ const drawn = [...elements, ...textBlocks];
142
+ const [first] = drawn;
143
+ if (!first)
144
+ return null;
145
+ return paintSvg(std, renderer, elements, textBlocks, exportBoundOf(first, drawn));
146
+ }
147
+ /**
148
+ * Replay `elements` and `texts` into an SVG-emitting 2D context framed by
149
+ * `bound`, one model per pass, each inside a group carrying its markers (ADR
150
+ * 0032 §6), in the canvas' own z-order: a text block is painted at its layer
151
+ * index, between the elements it sits between on screen.
152
+ */
153
+ function paintSvg(std, renderer, elements, texts, bound) {
154
+ const gfx = std.get(GfxControllerIdentifier);
92
155
  return runWithRecordingPath2D(() => {
93
156
  const { ctx, canvas, serialize, markRoot, markedGroup } = createSvgContext(bound.w, bound.h);
94
157
  const rc = new RoughCanvas(canvas);
95
158
  markRoot();
96
- // One model per pass, each inside a group carrying its markers (ADR 0032
97
- // §6), in the canvas' own z-order: a text block is painted at its layer
98
- // index, between the elements it sits between on screen.
99
159
  const blocks = new Set(texts);
100
160
  for (const model of [...elements, ...texts].sort(gfx.layer.compare)) {
101
161
  if (!blocks.has(model)) {
@@ -1,3 +1,4 @@
1
+ import { BOARD_SVG_MARKER_VERSION } from './marker-version.js';
1
2
  /**
2
3
  * A 2D context that records an SVG document instead of pixels, shaped so the
3
4
  * existing canvas renderers can paint into it unchanged.
@@ -54,10 +55,4 @@ export declare function readLineDash(lineDash: unknown): number[];
54
55
  * handed to {@link CanvasRenderer.renderBoundTo}.
55
56
  */
56
57
  export declare function createSvgContext(width: number, height: number): SvgContext;
57
- /**
58
- * The marker version an export writes on its root (`data-labre-svg`), and the
59
- * one a reader of it must know (ADR 0032 §6). Bumped only when the meaning of
60
- * a marker changes; adding one is not a new version.
61
- */
62
- export declare const BOARD_SVG_MARKER_VERSION = "1";
63
- export {};
58
+ export { BOARD_SVG_MARKER_VERSION };
@@ -1,5 +1,6 @@
1
1
  import { parseCssColor } from '../../../affine-shared/adapters/index.js';
2
2
  import { Context } from 'svgcanvas/dist/svgcanvas.esm.js';
3
+ import { BOARD_SVG_MARKER_VERSION } from './marker-version.js';
3
4
  const PATH_OPS = ['fill', 'stroke', 'clip'];
4
5
  /** Marks a {@link RecordingPath2D} and carries what it recorded. */
5
6
  const RECORDED_D = Symbol('labre.export-svg.recorded-d');
@@ -330,9 +331,4 @@ export function createSvgContext(width, height) {
330
331
  },
331
332
  };
332
333
  }
333
- /**
334
- * The marker version an export writes on its root (`data-labre-svg`), and the
335
- * one a reader of it must know (ADR 0032 §6). Bumped only when the meaning of
336
- * a marker changes; adding one is not a new version.
337
- */
338
- export const BOARD_SVG_MARKER_VERSION = '1';
334
+ export { BOARD_SVG_MARKER_VERSION };
@@ -3,12 +3,14 @@ export * from './canvas-grid/index.js';
3
3
  export * from './clipboard-config.js';
4
4
  export * from './crud-extension.js';
5
5
  export * from './dom-element-renderer.js';
6
+ export * from './drawio-export/index.js';
6
7
  export * from './edit-props-middleware-builder.js';
7
8
  export * from './editor-anchored-panel.js';
8
9
  export * from './element-renderer.js';
9
10
  export * from './export-manager/index.js';
10
11
  export * from './export-svg/index.js';
11
12
  export * from './interchange.js';
13
+ export * from './interchange-export.js';
12
14
  export * from './interchange-import.js';
13
15
  export * from './legacy-slot-extension.js';
14
16
  export * from './legend.js';
@@ -3,12 +3,14 @@ export * from './canvas-grid/index.js';
3
3
  export * from './clipboard-config.js';
4
4
  export * from './crud-extension.js';
5
5
  export * from './dom-element-renderer.js';
6
+ export * from './drawio-export/index.js';
6
7
  export * from './edit-props-middleware-builder.js';
7
8
  export * from './editor-anchored-panel.js';
8
9
  export * from './element-renderer.js';
9
10
  export * from './export-manager/index.js';
10
11
  export * from './export-svg/index.js';
11
12
  export * from './interchange.js';
13
+ export * from './interchange-export.js';
12
14
  export * from './interchange-import.js';
13
15
  export * from './legacy-slot-extension.js';
14
16
  export * from './legend.js';
@@ -0,0 +1,84 @@
1
+ import { type ChromeWording } from '../../affine-shared/services/index.js';
2
+ import { type AnyCommandDescriptor, type BlockStdScope, type CommandFile, type FrameworkId } from '../../std/index.js';
3
+ import { type InterchangeExportCapability } from './interchange.js';
4
+ /**
5
+ * **One "This view" export per `*:export` capability a framework registered**
6
+ * (ADR 0034, amendment of 2026-10-14; ADR 0012).
7
+ *
8
+ * The registry already answers "what can Labre write, for which framework";
9
+ * this projects that answer into view actions, the way the senior sub-menu is
10
+ * a projection of the command descriptors. Nothing is declared per framework:
11
+ * a capability registered from a framework's FLAG-GATED view extension yields
12
+ * its action, a framework switched off registers no capability and therefore
13
+ * lists no action (ADR 0009: a flag gates tooling), and the next framework that
14
+ * declares an exporter is listed with no change here.
15
+ *
16
+ * Read off the registry at the moment the host lists the view's actions, NOT
17
+ * registered as commands: a command enters the static registry
18
+ * (`getCommands`), and from there every projection of it — the catalogue that
19
+ * must hold every framework command, the per-framework counts, the senior
20
+ * row's cap, Settings › Shortcuts — would grow by one row per format for an
21
+ * entry that only a host menu draws. Each framework keeps its own selection
22
+ * export (`bpmn.exportXml`, `uml.exportXmi`…) on its toolbar, catalogue and
23
+ * palette; this is the whole-board twin a page-level "Export…" lists.
24
+ *
25
+ * The canvas's own capabilities (owner `'canvas'`, `canvas:drawio:export`)
26
+ * are left out: they have their hand-written command (`canvas.export.drawio`),
27
+ * and a second entry would list draw.io twice.
28
+ */
29
+ /** "Export BPMN as .bpmn" — the framework's name, the format's extension. */
30
+ export declare const INTERCHANGE_EXPORT_LABEL: ChromeWording;
31
+ /** The notice's title when the writer could not say everything. */
32
+ export declare const INTERCHANGE_EXPORT_WARNINGS: ChromeWording;
33
+ export declare const INTERCHANGE_EXPORT_WORDINGS: readonly ChromeWording[];
34
+ /** One icon for every format: the registry declares none of its own. */
35
+ export declare const INTERCHANGE_EXPORT_ICON_KEY = "canvas.export.interchange";
36
+ /**
37
+ * `canvas.export.<framework>.<format>` — `canvas.export.bpmn.bpmn`,
38
+ * `canvas.export.uml.xmi`. The framework is part of the id because a format
39
+ * id is not unique across owners (`drawio` is UML's reader and the canvas's
40
+ * writer). Stable: a host keys its test ids and icons on it, and usage is
41
+ * recorded under it.
42
+ */
43
+ export declare function interchangeExportActionId(capability: InterchangeExportCapability): string;
44
+ /**
45
+ * The whole board as the capability writes it, or `null` when the canvas
46
+ * holds nothing of its framework.
47
+ *
48
+ * The WHOLE surface in document order, never the selection and never what
49
+ * this viewer hid: a semantic export is a statement about the board, and
50
+ * logic keeps what a viewer hid (ADR 0031) — the selection exports stay the
51
+ * framework's own commands. The raw page title goes in, the capability makes
52
+ * it safe to write to disk (`InterchangeExportContext.name`).
53
+ */
54
+ export declare function interchangeExportFile(std: BlockStdScope, capability: InterchangeExportCapability): {
55
+ file: CommandFile;
56
+ warnings: readonly string[];
57
+ } | null;
58
+ /**
59
+ * The view action of one capability. `owner` and `telemetry` name the
60
+ * framework, so `runCommand` reports the export as that framework's
61
+ * (`FrameworkElementAdded`, element `canvas:export-<format>`, module
62
+ * `<framework> view-menu`). It READS: offered on a read-only page. Listed
63
+ * only while the canvas holds an element of that framework (its role's
64
+ * namespace): an export of nothing is not an export.
65
+ *
66
+ * A warning is never an error — the file is valid. What the writer could not
67
+ * say goes to the notification seam, in its own English words (the
68
+ * capability's `warnings` carry no key); with no seam, it is dropped.
69
+ */
70
+ export declare function interchangeExportCommand(capability: InterchangeExportCapability & {
71
+ framework: FrameworkId;
72
+ }): AnyCommandDescriptor;
73
+ /** A derived view action and the capability it runs. */
74
+ export interface InterchangeExportAction {
75
+ command: AnyCommandDescriptor;
76
+ capability: InterchangeExportCapability & {
77
+ framework: FrameworkId;
78
+ };
79
+ }
80
+ /**
81
+ * Every framework export this editor offers now, in the registry's id order:
82
+ * what the registry holds, so what the flags let through.
83
+ */
84
+ export declare function interchangeExportActions(std: BlockStdScope): InterchangeExportAction[];
@@ -0,0 +1,150 @@
1
+ import { NotificationProvider, translateKey, } from '../../affine-shared/services/index.js';
2
+ import { downloadBlob } from '../../affine-shared/utils/index.js';
3
+ import { FRAMEWORK_IDS, frameworkOfRole, } from '../../std/index.js';
4
+ import { GfxControllerIdentifier } from '../../std/gfx/index.js';
5
+ import { interchangeCapabilities, } from './interchange.js';
6
+ /**
7
+ * **One "This view" export per `*:export` capability a framework registered**
8
+ * (ADR 0034, amendment of 2026-10-14; ADR 0012).
9
+ *
10
+ * The registry already answers "what can Labre write, for which framework";
11
+ * this projects that answer into view actions, the way the senior sub-menu is
12
+ * a projection of the command descriptors. Nothing is declared per framework:
13
+ * a capability registered from a framework's FLAG-GATED view extension yields
14
+ * its action, a framework switched off registers no capability and therefore
15
+ * lists no action (ADR 0009: a flag gates tooling), and the next framework that
16
+ * declares an exporter is listed with no change here.
17
+ *
18
+ * Read off the registry at the moment the host lists the view's actions, NOT
19
+ * registered as commands: a command enters the static registry
20
+ * (`getCommands`), and from there every projection of it — the catalogue that
21
+ * must hold every framework command, the per-framework counts, the senior
22
+ * row's cap, Settings › Shortcuts — would grow by one row per format for an
23
+ * entry that only a host menu draws. Each framework keeps its own selection
24
+ * export (`bpmn.exportXml`, `uml.exportXmi`…) on its toolbar, catalogue and
25
+ * palette; this is the whole-board twin a page-level "Export…" lists.
26
+ *
27
+ * The canvas's own capabilities (owner `'canvas'`, `canvas:drawio:export`)
28
+ * are left out: they have their hand-written command (`canvas.export.drawio`),
29
+ * and a second entry would list draw.io twice.
30
+ */
31
+ /** "Export BPMN as .bpmn" — the framework's name, the format's extension. */
32
+ export const INTERCHANGE_EXPORT_LABEL = [
33
+ 'com.labre.command.canvas.export.interchange',
34
+ 'Export {{framework}} as {{extension}}',
35
+ ];
36
+ /** The notice's title when the writer could not say everything. */
37
+ export const INTERCHANGE_EXPORT_WARNINGS = [
38
+ 'com.labre.command.canvas.export.interchange.warnings',
39
+ 'What this export could not write down',
40
+ ];
41
+ export const INTERCHANGE_EXPORT_WORDINGS = [
42
+ INTERCHANGE_EXPORT_LABEL,
43
+ INTERCHANGE_EXPORT_WARNINGS,
44
+ ];
45
+ /** One icon for every format: the registry declares none of its own. */
46
+ export const INTERCHANGE_EXPORT_ICON_KEY = 'canvas.export.interchange';
47
+ /**
48
+ * `canvas.export.<framework>.<format>` — `canvas.export.bpmn.bpmn`,
49
+ * `canvas.export.uml.xmi`. The framework is part of the id because a format
50
+ * id is not unique across owners (`drawio` is UML's reader and the canvas's
51
+ * writer). Stable: a host keys its test ids and icons on it, and usage is
52
+ * recorded under it.
53
+ */
54
+ export function interchangeExportActionId(capability) {
55
+ return `canvas.export.${capability.framework}.${capability.format.id}`;
56
+ }
57
+ const isFrameworkId = (id) => FRAMEWORK_IDS.includes(id);
58
+ function surfaceElementsOf(std) {
59
+ return std.get(GfxControllerIdentifier).surface?.elementModels ?? [];
60
+ }
61
+ /**
62
+ * The whole board as the capability writes it, or `null` when the canvas
63
+ * holds nothing of its framework.
64
+ *
65
+ * The WHOLE surface in document order, never the selection and never what
66
+ * this viewer hid: a semantic export is a statement about the board, and
67
+ * logic keeps what a viewer hid (ADR 0031) — the selection exports stay the
68
+ * framework's own commands. The raw page title goes in, the capability makes
69
+ * it safe to write to disk (`InterchangeExportContext.name`).
70
+ */
71
+ export function interchangeExportFile(std, capability) {
72
+ const elements = surfaceElementsOf(std);
73
+ if (!elements.some(e => frameworkOfRole(e.role) === capability.framework)) {
74
+ return null;
75
+ }
76
+ const title = std.store.workspace.meta.getDocMeta(std.store.id)?.title;
77
+ const { text, filename, mime, warnings } = capability.run(elements, {
78
+ name: title,
79
+ });
80
+ return {
81
+ // The charset is the browser's business, not the format's.
82
+ file: {
83
+ blob: new Blob([text], { type: `${mime};charset=utf-8` }),
84
+ filename,
85
+ },
86
+ warnings: warnings ?? [],
87
+ };
88
+ }
89
+ /**
90
+ * The view action of one capability. `owner` and `telemetry` name the
91
+ * framework, so `runCommand` reports the export as that framework's
92
+ * (`FrameworkElementAdded`, element `canvas:export-<format>`, module
93
+ * `<framework> view-menu`). It READS: offered on a read-only page. Listed
94
+ * only while the canvas holds an element of that framework (its role's
95
+ * namespace): an export of nothing is not an export.
96
+ *
97
+ * A warning is never an error — the file is valid. What the writer could not
98
+ * say goes to the notification seam, in its own English words (the
99
+ * capability's `warnings` carry no key); with no seam, it is dropped.
100
+ */
101
+ export function interchangeExportCommand(capability) {
102
+ return {
103
+ id: interchangeExportActionId(capability),
104
+ owner: capability.framework,
105
+ kind: 'action',
106
+ labelKey: INTERCHANGE_EXPORT_LABEL[0],
107
+ labelFallback: INTERCHANGE_EXPORT_LABEL[1],
108
+ category: 'export',
109
+ iconKey: INTERCHANGE_EXPORT_ICON_KEY,
110
+ surfaces: ['view-menu'],
111
+ order: 14,
112
+ scope: 'edgeless',
113
+ defaultKeys: { mac: [], other: [] },
114
+ telemetry: {
115
+ framework: capability.framework,
116
+ element: `canvas:export-${capability.format.id}`,
117
+ },
118
+ when: std => surfaceElementsOf(std).some(e => frameworkOfRole(e.role) === capability.framework),
119
+ run: (std, _invocation, params) => {
120
+ const exported = interchangeExportFile(std, capability);
121
+ if (!exported)
122
+ return;
123
+ const deliver = params?.deliver;
124
+ if (typeof deliver === 'function')
125
+ deliver(exported.file);
126
+ else
127
+ downloadBlob(exported.file.blob, exported.file.filename);
128
+ if (exported.warnings.length === 0)
129
+ return;
130
+ std.getOptional(NotificationProvider)?.notify({
131
+ title: translateKey(std, ...INTERCHANGE_EXPORT_WARNINGS),
132
+ message: exported.warnings.join('\n'),
133
+ accent: 'warning',
134
+ duration: 8000,
135
+ });
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * Every framework export this editor offers now, in the registry's id order:
141
+ * what the registry holds, so what the flags let through.
142
+ */
143
+ export function interchangeExportActions(std) {
144
+ return interchangeCapabilities(std.provider, { direction: 'export' })
145
+ .filter((capability) => isFrameworkId(capability.framework))
146
+ .map(capability => ({
147
+ capability,
148
+ command: interchangeExportCommand(capability),
149
+ }));
150
+ }