@formicoidea/labre-core 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
  2. package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
  3. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
  4. package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
  5. package/dist/_pkgs/affine-block-attachment/index.js +1 -0
  6. package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
  7. package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
  8. package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
  9. package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
  10. package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
  11. package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
  12. package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
  13. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
  14. package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
  15. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
  16. package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
  17. package/dist/_pkgs/affine-block-frame/index.js +1 -0
  18. package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
  19. package/dist/_pkgs/affine-block-frame/translations.js +16 -1
  20. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  21. package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
  22. package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
  23. package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
  24. package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
  25. package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
  26. package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
  27. package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
  28. package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
  29. package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
  30. package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
  31. package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
  32. package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
  33. package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
  34. package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
  35. package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
  36. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
  37. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
  38. package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
  39. package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
  40. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
  41. package/dist/_pkgs/affine-block-image/effects.js +2 -0
  42. package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
  43. package/dist/_pkgs/affine-block-image/image-block.js +2 -0
  44. package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
  45. package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
  46. package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
  47. package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
  48. package/dist/_pkgs/affine-block-image/index.js +1 -0
  49. package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
  50. package/dist/_pkgs/affine-block-image/translations.js +43 -0
  51. package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
  52. package/dist/_pkgs/affine-block-image/utils.js +10 -1
  53. package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
  54. package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
  55. package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
  56. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  57. package/dist/_pkgs/affine-block-root/index.js +1 -0
  58. package/dist/_pkgs/affine-block-root/view.js +8 -0
  59. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
  60. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
  61. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
  62. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
  63. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
  64. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
  65. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
  66. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
  67. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
  68. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
  69. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
  70. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
  71. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
  72. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
  73. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
  74. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
  75. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
  76. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
  77. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
  78. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
  79. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
  80. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
  81. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
  82. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
  83. package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
  84. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
  85. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
  86. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
  87. package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
  88. package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
  89. package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
  90. package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
  91. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
  92. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
  93. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
  94. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
  95. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
  96. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
  97. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
  98. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
  99. package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
  100. package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
  101. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
  102. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
  103. package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
  104. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  105. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  106. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
  107. package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
  108. package/dist/_pkgs/affine-block-surface/view.js +22 -2
  109. package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
  110. package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
  111. package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
  112. package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
  113. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
  114. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
  115. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
  116. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
  117. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
  118. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
  119. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
  120. package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
  121. package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
  122. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
  123. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
  124. package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
  125. package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
  126. package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
  127. package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
  128. package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
  129. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
  130. package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
  131. package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
  132. package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
  133. package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
  134. package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
  135. package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
  136. package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
  137. package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
  138. package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
  139. package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
  140. package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
  141. package/dist/_pkgs/affine-model/consts/shape.js +5 -0
  142. package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
  143. package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
  144. package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
  145. package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
  146. package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
  147. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
  148. package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
  149. package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
  150. package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
  151. package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
  152. package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
  153. package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
  154. package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
  155. package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
  156. package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
  157. package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
  158. package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
  159. package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
  160. package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
  161. package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
  162. package/dist/_pkgs/affine-model/headless.d.ts +19 -0
  163. package/dist/_pkgs/affine-model/headless.js +19 -0
  164. package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
  165. package/dist/_pkgs/affine-shared/headless.js +10 -0
  166. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
  167. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
  168. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
  169. package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
  170. package/dist/_pkgs/affine-shared/services/index.d.ts +3 -0
  171. package/dist/_pkgs/affine-shared/services/index.js +3 -0
  172. package/dist/_pkgs/affine-shared/services/thread-marks-service.d.ts +69 -0
  173. package/dist/_pkgs/affine-shared/services/thread-marks-service.js +20 -0
  174. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
  175. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
  176. package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
  177. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
  178. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
  179. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
  180. package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
  181. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
  182. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
  183. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
  184. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
  185. package/dist/_pkgs/affine-widget-thread-marks/effects.d.ts +1 -0
  186. package/dist/_pkgs/affine-widget-thread-marks/effects.js +4 -0
  187. package/dist/_pkgs/affine-widget-thread-marks/excerpt.d.ts +19 -0
  188. package/dist/_pkgs/affine-widget-thread-marks/excerpt.js +68 -0
  189. package/dist/_pkgs/affine-widget-thread-marks/index.d.ts +5 -0
  190. package/dist/_pkgs/affine-widget-thread-marks/index.js +8 -0
  191. package/dist/_pkgs/affine-widget-thread-marks/toolbar.d.ts +49 -0
  192. package/dist/_pkgs/affine-widget-thread-marks/toolbar.js +108 -0
  193. package/dist/_pkgs/affine-widget-thread-marks/translations.d.ts +9 -0
  194. package/dist/_pkgs/affine-widget-thread-marks/translations.js +11 -0
  195. package/dist/_pkgs/affine-widget-thread-marks/view.d.ts +12 -0
  196. package/dist/_pkgs/affine-widget-thread-marks/view.js +25 -0
  197. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +51 -0
  198. package/dist/_pkgs/affine-widget-thread-marks/widget.js +214 -0
  199. package/dist/commands.js +5 -2
  200. package/dist/extensions/view.js +2 -0
  201. package/dist/host-panels.d.ts +24 -1
  202. package/dist/host-panels.js +61 -10
  203. package/dist/interchange.d.ts +1 -0
  204. package/dist/interchange.js +1 -0
  205. package/dist/model/headless.d.ts +1 -0
  206. package/dist/model/headless.js +1 -0
  207. package/dist/shared/headless.d.ts +1 -0
  208. package/dist/shared/headless.js +1 -0
  209. package/dist/translations.js +10 -1
  210. package/package.json +13 -1
@@ -0,0 +1,24 @@
1
+ import { type AssetRef } from '../affine-shared/services/index.js';
2
+ import type { IVec } from '../global/gfx/index.js';
3
+ import type { BlockStdScope } from '../std/index.js';
4
+ import type { BlockModel } from '../store/index.js';
5
+ /**
6
+ * Adds one attachment card per ref next to `targetModel`, as one undo step.
7
+ * Nothing is written on a read-only document.
8
+ */
9
+ export declare function addSiblingAttachmentBlocksFromAssets(std: BlockStdScope, assets: readonly AssetRef[], targetModel: BlockModel, placement?: 'before' | 'after'): string[];
10
+ /**
11
+ * The canvas counterpart of {@link addSiblingAttachmentBlocksFromAssets}:
12
+ * places the cards like `addAttachments` does (around `point`, active layer,
13
+ * selected).
14
+ */
15
+ export declare function addAttachmentsFromAssets(std: BlockStdScope, assets: readonly AssetRef[], point?: IVec, shouldTransformPoint?: boolean): string[];
16
+ /**
17
+ * The attachment entries of the slash menu (Attachment, PDF) and of the
18
+ * mobile keyboard toolbar: the host's asset dialog when there is one, the
19
+ * system file chooser otherwise (the upload path, unchanged). An emptied
20
+ * line the gesture was typed on goes away once a card is in.
21
+ */
22
+ export declare function pickAndAddSiblingAttachmentBlocks(std: BlockStdScope, targetModel: BlockModel): Promise<string[]>;
23
+ /** The canvas's file button: the host's asset dialog, or the file chooser. */
24
+ export declare function pickAndAddAttachments(std: BlockStdScope): Promise<string[]>;
@@ -0,0 +1,87 @@
1
+ import { AssetPickerProvider, } from '../affine-shared/services/index.js';
2
+ import { openSingleFileWith } from '../affine-shared/utils/index.js';
3
+ import { addAttachments, addSiblingAttachmentBlocks, placeAttachmentsOnCanvas, } from './utils.js';
4
+ /**
5
+ * Attachments from the host's asset library (`AssetPickerProvider`, labre
6
+ * P4): the card references a blob the host already stores, by its
7
+ * `sourceId`, with the ref's name, type and size. Nothing here calls
8
+ * `BlobSource.set`. Without a host every entry point keeps the system file
9
+ * chooser and the upload path of `utils.ts`; pasting and dropping never come
10
+ * here.
11
+ */
12
+ function attachmentPropsFrom(assets) {
13
+ return assets.map(({ sourceId, name, type, size }) => ({
14
+ sourceId,
15
+ name,
16
+ type,
17
+ size,
18
+ }));
19
+ }
20
+ /**
21
+ * Adds one attachment card per ref next to `targetModel`, as one undo step.
22
+ * Nothing is written on a read-only document.
23
+ */
24
+ export function addSiblingAttachmentBlocksFromAssets(std, assets, targetModel, placement = 'after') {
25
+ if (!assets.length || std.store.readonly)
26
+ return [];
27
+ std.store.captureSync();
28
+ return std.store.addSiblingBlocks(targetModel, attachmentPropsFrom(assets).map(props => ({
29
+ ...props,
30
+ flavour: 'affine:attachment',
31
+ })), placement);
32
+ }
33
+ /**
34
+ * The canvas counterpart of {@link addSiblingAttachmentBlocksFromAssets}:
35
+ * places the cards like `addAttachments` does (around `point`, active layer,
36
+ * selected).
37
+ */
38
+ export function addAttachmentsFromAssets(std, assets, point, shouldTransformPoint) {
39
+ if (!assets.length || std.store.readonly)
40
+ return [];
41
+ std.store.captureSync();
42
+ return placeAttachmentsOnCanvas(std, attachmentPropsFrom(assets), point, shouldTransformPoint);
43
+ }
44
+ function pickFiles(std) {
45
+ const picker = std.getOptional(AssetPickerProvider);
46
+ if (!picker)
47
+ return null;
48
+ return picker.pick({
49
+ kind: 'file',
50
+ mode: 'insert',
51
+ multiple: false,
52
+ docId: std.store.id,
53
+ });
54
+ }
55
+ /**
56
+ * The attachment entries of the slash menu (Attachment, PDF) and of the
57
+ * mobile keyboard toolbar: the host's asset dialog when there is one, the
58
+ * system file chooser otherwise (the upload path, unchanged). An emptied
59
+ * line the gesture was typed on goes away once a card is in.
60
+ */
61
+ export async function pickAndAddSiblingAttachmentBlocks(std, targetModel) {
62
+ const picked = pickFiles(std);
63
+ let ids;
64
+ if (picked) {
65
+ ids = addSiblingAttachmentBlocksFromAssets(std, (await picked) ?? [], targetModel);
66
+ }
67
+ else {
68
+ const file = await openSingleFileWith();
69
+ if (!file)
70
+ return [];
71
+ ids = await addSiblingAttachmentBlocks(std, [file], targetModel);
72
+ }
73
+ if (ids.length && targetModel.text?.length === 0) {
74
+ std.store.deleteBlock(targetModel);
75
+ }
76
+ return ids;
77
+ }
78
+ /** The canvas's file button: the host's asset dialog, or the file chooser. */
79
+ export async function pickAndAddAttachments(std) {
80
+ const picked = pickFiles(std);
81
+ if (picked)
82
+ return addAttachmentsFromAssets(std, (await picked) ?? []);
83
+ const file = await openSingleFileWith();
84
+ if (!file)
85
+ return [];
86
+ return addAttachments(std, [file]);
87
+ }
@@ -1,8 +1,7 @@
1
- import { openSingleFileWith } from '../../affine-shared/utils/index.js';
2
1
  import {} from '../../affine-widget-slash-menu/index.js';
3
2
  import { ExportToPdfIcon, FileIcon } from '@blocksuite/icons/lit';
3
+ import { pickAndAddSiblingAttachmentBlocks } from '../asset-picker.js';
4
4
  import { ATTACHMENT_SLASH_DESCRIPTION, ATTACHMENT_SLASH_NAME, ATTACHMENT_SLASH_PDF_DESCRIPTION, ATTACHMENT_SLASH_PDF_NAME, } from '../translations.js';
5
- import { addSiblingAttachmentBlocks } from '../utils.js';
6
5
  import { AttachmentTooltip, PDFTooltip } from './tooltips.js';
7
6
  export const attachmentSlashMenuConfig = {
8
7
  items: [
@@ -21,15 +20,7 @@ export const attachmentSlashMenuConfig = {
21
20
  group: '4_Content & Media@3',
22
21
  when: ({ model }) => model.store.schema.flavourSchemaMap.has('affine:attachment'),
23
22
  action: ({ std, model }) => {
24
- (async () => {
25
- const file = await openSingleFileWith();
26
- if (!file)
27
- return;
28
- await addSiblingAttachmentBlocks(std, [file], model);
29
- if (model.text?.length === 0) {
30
- std.store.deleteBlock(model);
31
- }
32
- })().catch(console.error);
23
+ pickAndAddSiblingAttachmentBlocks(std, model).catch(console.error);
33
24
  },
34
25
  },
35
26
  {
@@ -46,15 +37,7 @@ export const attachmentSlashMenuConfig = {
46
37
  group: '4_Content & Media@4',
47
38
  when: ({ model }) => model.store.schema.flavourSchemaMap.has('affine:attachment'),
48
39
  action: ({ std, model }) => {
49
- (async () => {
50
- const file = await openSingleFileWith();
51
- if (!file)
52
- return;
53
- await addSiblingAttachmentBlocks(std, [file], model);
54
- if (model.text?.length === 0) {
55
- std.store.deleteBlock(model);
56
- }
57
- })().catch(console.error);
40
+ pickAndAddSiblingAttachmentBlocks(std, model).catch(console.error);
58
41
  },
59
42
  },
60
43
  ],
@@ -1,4 +1,5 @@
1
1
  export * from './adapters/index.js';
2
+ export { addAttachmentsFromAssets, addSiblingAttachmentBlocksFromAssets, pickAndAddAttachments, pickAndAddSiblingAttachmentBlocks, } from './asset-picker.js';
2
3
  export * from './attachment-block.js';
3
4
  export * from './attachment-service.js';
4
5
  export { attachmentViewDropdownMenu } from './configs/toolbar.js';
@@ -1,4 +1,5 @@
1
1
  export * from './adapters/index.js';
2
+ export { addAttachmentsFromAssets, addSiblingAttachmentBlocksFromAssets, pickAndAddAttachments, pickAndAddSiblingAttachmentBlocks, } from './asset-picker.js';
2
3
  export * from './attachment-block.js';
3
4
  export * from './attachment-service.js';
4
5
  export { attachmentViewDropdownMenu } from './configs/toolbar.js';
@@ -1,4 +1,4 @@
1
- import { type AttachmentBlockModel } from '../affine-model/index.js';
1
+ import { type AttachmentBlockModel, type AttachmentBlockProps } from '../affine-model/index.js';
2
2
  import { type IVec } from '../global/gfx/index.js';
3
3
  import type { BlockStdScope } from '../std/index.js';
4
4
  import type { BlockModel } from '../store/index.js';
@@ -16,3 +16,11 @@ export declare function getFileType(file: File): Promise<string>;
16
16
  */
17
17
  export declare function addSiblingAttachmentBlocks(std: BlockStdScope, files: File[], targetModel: BlockModel, placement?: 'before' | 'after', embed?: boolean): Promise<string[]>;
18
18
  export declare function addAttachments(std: BlockStdScope, files: File[], point?: IVec, shouldTransformPoint?: boolean): Promise<string[]>;
19
+ export type AttachmentBlockSource = Pick<AttachmentBlockProps, 'name' | 'size' | 'type' | 'sourceId'>;
20
+ /**
21
+ * Lays attachment cards out on the canvas around `point` (the viewport's
22
+ * centre without one), in the viewer's active layer, and selects them. Shared
23
+ * by the files path (`addAttachments`) and the asset path
24
+ * (`addAttachmentsFromAssets`).
25
+ */
26
+ export declare function placeAttachmentsOnCanvas(std: BlockStdScope, propsArray: AttachmentBlockSource[], point?: IVec, shouldTransformPoint?: boolean): string[];
@@ -121,6 +121,15 @@ export async function addAttachments(std, files, point, shouldTransformPoint //
121
121
  if (hasExceeded(std, files))
122
122
  return [];
123
123
  const propsArray = await Promise.all(files.map(file => buildPropsWith(std, file, undefined, 'whiteboard')));
124
+ return placeAttachmentsOnCanvas(std, propsArray, point, shouldTransformPoint);
125
+ }
126
+ /**
127
+ * Lays attachment cards out on the canvas around `point` (the viewport's
128
+ * centre without one), in the viewer's active layer, and selects them. Shared
129
+ * by the files path (`addAttachments`) and the asset path
130
+ * (`addAttachmentsFromAssets`).
131
+ */
132
+ export function placeAttachmentsOnCanvas(std, propsArray, point, shouldTransformPoint) {
124
133
  const gfx = std.get(GfxControllerIdentifier);
125
134
  let { x, y } = gfx.viewport.center;
126
135
  if (point) {
@@ -1,14 +1,27 @@
1
1
  import { createEmbedBlockMarkdownAdapterMatcher } from '../../../affine-block-embed/index.js';
2
2
  import { BookmarkBlockSchema, FootNoteReferenceParamsSchema, } from '../../../affine-model/index.js';
3
3
  import { BlockMarkdownAdapterExtension, FOOTNOTE_DEFINITION_PREFIX, getFootnoteDefinitionText, isFootnoteDefinitionNode, } from '../../../affine-shared/adapters/index.js';
4
+ import { isSafeLinkUrl } from '../../../affine-shared/utils/index.js';
4
5
  import { nanoid } from '../../../store/index.js';
6
+ const safeDecode = (value) => {
7
+ try {
8
+ return decodeURIComponent(value);
9
+ }
10
+ catch {
11
+ return value;
12
+ }
13
+ };
5
14
  const isUrlFootnoteDefinitionNode = (node) => {
6
15
  if (!isFootnoteDefinitionNode(node))
7
16
  return false;
8
17
  const footnoteDefinition = getFootnoteDefinitionText(node);
9
18
  try {
10
19
  const footnoteDefinitionJson = FootNoteReferenceParamsSchema.parse(JSON.parse(footnoteDefinition));
11
- return (footnoteDefinitionJson.type === 'url' && !!footnoteDefinitionJson.url);
20
+ // A refused scheme makes no bookmark: the definition is left to the
21
+ // reader's default, and no `javascript:` is stored as a bookmark `url`.
22
+ return (footnoteDefinitionJson.type === 'url' &&
23
+ !!footnoteDefinitionJson.url &&
24
+ isSafeLinkUrl(safeDecode(footnoteDefinitionJson.url)));
12
25
  }
13
26
  catch {
14
27
  return false;
@@ -1,5 +1,6 @@
1
1
  import { BookmarkBlockSchema } from '../../affine-model/index.js';
2
2
  import { BlockNotionHtmlAdapterExtension, HastUtils, } from '../../affine-shared/adapters/index.js';
3
+ import { isSafeLinkUrl } from '../../affine-shared/utils/index.js';
3
4
  import { nanoid } from '../../store/index.js';
4
5
  export const bookmarkBlockNotionHtmlAdapterMatcher = {
5
6
  flavour: BookmarkBlockSchema.model.flavour,
@@ -33,7 +34,10 @@ export const bookmarkBlockNotionHtmlAdapterMatcher = {
33
34
  flavour: BookmarkBlockSchema.model.flavour,
34
35
  props: {
35
36
  type: 'card',
36
- url: bookmarkURL ?? '',
37
+ // A refused scheme is not stored, as a missing href is not.
38
+ url: typeof bookmarkURL === 'string' && isSafeLinkUrl(bookmarkURL)
39
+ ? bookmarkURL
40
+ : '',
37
41
  title: bookmarkTitle,
38
42
  description: bookmarkDescription,
39
43
  icon: bookmarkIconURL,
@@ -1,11 +1,13 @@
1
1
  import { DatabaseBlockSchema, } from '../../affine-model/index.js';
2
2
  import { BlockMarkdownAdapterExtension, } from '../../affine-shared/adapters/index.js';
3
3
  import { processTable } from './utils.js';
4
- const DATABASE_NODE_TYPES = new Set(['table', 'tableRow']);
5
- const isDatabaseNode = (node) => DATABASE_NODE_TYPES.has(node.type);
6
4
  export const databaseBlockMarkdownAdapterMatcher = {
7
5
  flavour: DatabaseBlockSchema.model.flavour,
8
- toMatch: o => isDatabaseNode(o.node),
6
+ // A Markdown table is read by the table block only: pasted or imported,
7
+ // it becomes an inline table, never a database (labre REQ-IM-2). The
8
+ // database writes tables on export and reads none, like its HTML and
9
+ // plain-text matchers.
10
+ toMatch: () => false,
9
11
  fromMatch: o => o.node.flavour === DatabaseBlockSchema.model.flavour,
10
12
  toBlockSnapshot: {},
11
13
  fromBlockSnapshot: {
@@ -1,3 +1,4 @@
1
+ import { isSafeLinkUrl } from '../../../affine-shared/utils/index.js';
1
2
  export function createEmbedBlockHtmlAdapterMatcher(flavour, { toMatch = () => false, fromMatch = o => o.node.flavour === flavour, toBlockSnapshot = {}, fromBlockSnapshot = {
2
3
  enter: (o, context) => {
3
4
  const { walkerContext } = context;
@@ -18,9 +19,10 @@ export function createEmbedBlockHtmlAdapterMatcher(flavour, { toMatch = () => fa
18
19
  .openNode({
19
20
  type: 'element',
20
21
  tagName: 'a',
21
- properties: {
22
- href: o.node.props.url,
23
- },
22
+ // A refused scheme keeps the title and loses the href.
23
+ properties: isSafeLinkUrl(o.node.props.url)
24
+ ? { href: o.node.props.url }
25
+ : {},
24
26
  children: [
25
27
  {
26
28
  type: 'text',
@@ -1,3 +1,4 @@
1
+ import { isSafeLinkUrl } from '../../../affine-shared/utils/index.js';
1
2
  export function createEmbedBlockMarkdownAdapterMatcher(flavour, { toMatch = () => false, fromMatch = o => o.node.flavour === flavour, toBlockSnapshot = {}, fromBlockSnapshot = {
2
3
  enter: (o, context) => {
3
4
  const { walkerContext } = context;
@@ -6,6 +7,16 @@ export function createEmbedBlockMarkdownAdapterMatcher(flavour, { toMatch = () =
6
7
  typeof o.node.props.url !== 'string') {
7
8
  return;
8
9
  }
10
+ // A url with a refused scheme is not written back out as a link: the
11
+ // title stays, as text.
12
+ if (!isSafeLinkUrl(o.node.props.url)) {
13
+ walkerContext
14
+ .openNode({ type: 'paragraph', children: [] }, 'children')
15
+ .openNode({ type: 'text', value: o.node.props.title }, 'children')
16
+ .closeNode()
17
+ .closeNode();
18
+ return;
19
+ }
9
20
  walkerContext
10
21
  .openNode({
11
22
  type: 'paragraph',
@@ -0,0 +1,58 @@
1
+ import { type FrameBlockModel } from '../affine-model/index.js';
2
+ import { type FrameTemplateService } from '../affine-shared/services/index.js';
3
+ import { type BlockStdScope, LifeCycleWatcher } from '../std/index.js';
4
+ import type { DocSnapshot, Store } from '../store/index.js';
5
+ /**
6
+ * Serialize `frame` and everything it holds as a template snapshot, through
7
+ * the surface TRANSFORMER: every key of every element's Y.Map crosses as is
8
+ * (`SurfaceBlockTransformer._elementToJSON`), texts in their snapshot form,
9
+ * where `element.serialize()` would flatten them to strings. What is changed:
10
+ *
11
+ * - `layer` is dropped everywhere (see {@link dropLayer});
12
+ * - a member connector whose end is not a member gets that end as a free
13
+ * position, as a copy-paste does (`serializeConnector`), since the template
14
+ * job could not remap the id;
15
+ * - the frame's `childElementIds` keep only the members that exist.
16
+ *
17
+ * Read only: nothing is written to the store. `null` when the document has no
18
+ * surface or no root.
19
+ */
20
+ export declare function frameTemplateSnapshot(store: Store, frame: FrameBlockModel): DocSnapshot | null;
21
+ type BlockUpdate = {
22
+ type: 'add' | 'delete' | 'update';
23
+ id: string;
24
+ flavour: string;
25
+ isLocal: boolean;
26
+ };
27
+ /**
28
+ * The frame deletion cascade towards the host, pure so a test drives it with
29
+ * any payload: `frameDeleted` for a frame THIS viewer deleted on a writable
30
+ * document, `frameRestored` when the same viewer brings it back (an undo
31
+ * re-adds the block under its own id).
32
+ *
33
+ * `if (!local || readonly) return;` — a deletion that arrives from another
34
+ * peer was reported by that peer, and reporting it again here would make the
35
+ * host act once per connected viewer.
36
+ */
37
+ export declare class FrameDeletionCascade {
38
+ private readonly _store;
39
+ private readonly _service;
40
+ private readonly _deleted;
41
+ constructor(_store: Pick<Store, 'id' | 'readonly'>, _service: FrameTemplateService);
42
+ handle(payload: BlockUpdate): void;
43
+ }
44
+ /** Mounts {@link FrameDeletionCascade} on the editor's store, if a host keeps templates. */
45
+ export declare class FrameTemplateWatcher extends LifeCycleWatcher {
46
+ static readonly key = "frame-template-watcher";
47
+ private _subscription?;
48
+ mounted(): void;
49
+ unmounted(): void;
50
+ }
51
+ /**
52
+ * "Save as template": serialize the frame and hand it to the host. Reads the
53
+ * document and writes nothing to it, so a read-only viewer may save too: the
54
+ * host decides where the template goes and whether this viewer may write it.
55
+ * The viewer always learns the outcome, a rejection included.
56
+ */
57
+ export declare function saveFrameAsTemplate(std: BlockStdScope, frame: FrameBlockModel): Promise<void>;
58
+ export {};
@@ -0,0 +1,207 @@
1
+ import { getSurfaceBlock } from '../affine-block-surface/index.js';
2
+ import { toast } from '../affine-components/toast/index.js';
3
+ import { ConnectorElementModel, } from '../affine-model/index.js';
4
+ import { FrameTemplateProvider, NotificationProvider, translateKey, } from '../affine-shared/services/index.js';
5
+ import { LifeCycleWatcher } from '../std/index.js';
6
+ import { GfxBlockElementModel } from '../std/gfx/index.js';
7
+ import { FRAME_TOAST_TEMPLATE_SAVE_FAILED, FRAME_TOAST_TEMPLATE_SAVED, } from './translations.js';
8
+ /**
9
+ * Narrow a transformer to one frame's members: the surface keeps only the
10
+ * elements and child blocks in `ids` — the surface transformer's own
11
+ * `selectedElements` filter, the same one the drag handle uses
12
+ * (`widgets/drag-handle/src/middleware/blocks-filter.ts`).
13
+ */
14
+ function onlyMembers(ids) {
15
+ return ({ slots, transformerConfigs }) => {
16
+ const subscription = slots.beforeExport.subscribe(payload => {
17
+ if (payload.type !== 'block')
18
+ return;
19
+ if (payload.model.flavour !== 'affine:surface')
20
+ return;
21
+ transformerConfigs.set('selectedElements', ids);
22
+ payload.model.children = payload.model.children.filter(child => ids.has(child.id));
23
+ });
24
+ return () => subscription.unsubscribe();
25
+ };
26
+ }
27
+ /**
28
+ * A layer id names a record of the SOURCE surface only. Dropped, so the
29
+ * template lands in the inserting viewer's active layer wherever it goes
30
+ * (ADR 0031 §6) — kept, it would land in its old layer when inserted back
31
+ * into its own document, possibly a hidden one.
32
+ */
33
+ function dropLayer(props) {
34
+ delete props['layer'];
35
+ }
36
+ /**
37
+ * Serialize `frame` and everything it holds as a template snapshot, through
38
+ * the surface TRANSFORMER: every key of every element's Y.Map crosses as is
39
+ * (`SurfaceBlockTransformer._elementToJSON`), texts in their snapshot form,
40
+ * where `element.serialize()` would flatten them to strings. What is changed:
41
+ *
42
+ * - `layer` is dropped everywhere (see {@link dropLayer});
43
+ * - a member connector whose end is not a member gets that end as a free
44
+ * position, as a copy-paste does (`serializeConnector`), since the template
45
+ * job could not remap the id;
46
+ * - the frame's `childElementIds` keep only the members that exist.
47
+ *
48
+ * Read only: nothing is written to the store. `null` when the document has no
49
+ * surface or no root.
50
+ */
51
+ export function frameTemplateSnapshot(store, frame) {
52
+ const surface = getSurfaceBlock(store);
53
+ const root = store.root;
54
+ if (!surface || !root)
55
+ return null;
56
+ const members = [frame, ...frame.descendantElements];
57
+ const ids = new Set(members.map(member => member.id));
58
+ const job = store.getTransformer([onlyMembers(ids)]);
59
+ const surfaceSnapshot = job.blockToSnapshot(surface);
60
+ if (!surfaceSnapshot)
61
+ return null;
62
+ const elements = surfaceSnapshot.props['elements'];
63
+ for (const member of members) {
64
+ if (!(member instanceof ConnectorElementModel))
65
+ continue;
66
+ const json = elements[member.id];
67
+ if (!json)
68
+ continue;
69
+ const path = member.absolutePath;
70
+ const sourceId = member.source?.id;
71
+ if (sourceId && !ids.has(sourceId) && path.length) {
72
+ json['source'] = { position: path[0].slice(0, 2) };
73
+ }
74
+ const targetId = member.target?.id;
75
+ if (targetId && !ids.has(targetId) && path.length) {
76
+ json['target'] = { position: path[path.length - 1].slice(0, 2) };
77
+ }
78
+ }
79
+ Object.values(elements).forEach(dropLayer);
80
+ // A note sits under the page, not the surface: serialized on its own.
81
+ const pageMembers = members
82
+ .filter(member => member instanceof GfxBlockElementModel &&
83
+ store.getParent(member.id)?.id === root.id)
84
+ .map(member => job.blockToSnapshot(member))
85
+ .filter((snapshot) => !!snapshot);
86
+ for (const block of [...surfaceSnapshot.children, ...pageMembers]) {
87
+ dropLayer(block.props);
88
+ if (block.id === frame.id) {
89
+ const childIds = (block.props['childElementIds'] ?? {});
90
+ block.props['childElementIds'] = Object.fromEntries(Object.entries(childIds).filter(([id]) => ids.has(id)));
91
+ }
92
+ }
93
+ const title = frame.props.title.toString();
94
+ return {
95
+ type: 'page',
96
+ meta: { id: store.id, title, createDate: Date.now(), tags: [] },
97
+ blocks: {
98
+ type: 'block',
99
+ id: root.id,
100
+ flavour: 'affine:page',
101
+ props: {
102
+ title: {
103
+ '$blocksuite:internal:text$': true,
104
+ delta: title ? [{ insert: title }] : [],
105
+ },
106
+ },
107
+ children: [surfaceSnapshot, ...pageMembers],
108
+ },
109
+ };
110
+ }
111
+ /**
112
+ * The frame deletion cascade towards the host, pure so a test drives it with
113
+ * any payload: `frameDeleted` for a frame THIS viewer deleted on a writable
114
+ * document, `frameRestored` when the same viewer brings it back (an undo
115
+ * re-adds the block under its own id).
116
+ *
117
+ * `if (!local || readonly) return;` — a deletion that arrives from another
118
+ * peer was reported by that peer, and reporting it again here would make the
119
+ * host act once per connected viewer.
120
+ */
121
+ export class FrameDeletionCascade {
122
+ constructor(_store, _service) {
123
+ this._store = _store;
124
+ this._service = _service;
125
+ this._deleted = new Set();
126
+ }
127
+ handle(payload) {
128
+ if (payload.flavour !== 'affine:frame')
129
+ return;
130
+ if (!payload.isLocal || this._store.readonly)
131
+ return;
132
+ const ref = {
133
+ docId: this._store.id,
134
+ frameId: payload.id,
135
+ };
136
+ if (payload.type === 'delete') {
137
+ this._deleted.add(payload.id);
138
+ this._service.frameDeleted?.(ref);
139
+ return;
140
+ }
141
+ if (payload.type === 'add' && this._deleted.delete(payload.id)) {
142
+ this._service.frameRestored?.(ref);
143
+ }
144
+ }
145
+ }
146
+ /** Mounts {@link FrameDeletionCascade} on the editor's store, if a host keeps templates. */
147
+ export class FrameTemplateWatcher extends LifeCycleWatcher {
148
+ static { this.key = 'frame-template-watcher'; }
149
+ mounted() {
150
+ const service = this.std.getOptional(FrameTemplateProvider);
151
+ if (!service)
152
+ return;
153
+ const cascade = new FrameDeletionCascade(this.std.store, service);
154
+ this._subscription = this.std.store.slots.blockUpdated.subscribe(payload => cascade.handle(payload));
155
+ }
156
+ unmounted() {
157
+ this._subscription?.unsubscribe();
158
+ this._subscription = undefined;
159
+ }
160
+ }
161
+ /** Frames whose save is in flight, per document: a double click saves once. */
162
+ const saving = new Set();
163
+ function tell(std, wording, success) {
164
+ const text = translateKey(std, ...wording);
165
+ const notification = std.getOptional(NotificationProvider);
166
+ if (notification) {
167
+ notification.notify({ title: text, accent: success ? 'success' : 'error' });
168
+ return;
169
+ }
170
+ toast(std.host, text);
171
+ }
172
+ /**
173
+ * "Save as template": serialize the frame and hand it to the host. Reads the
174
+ * document and writes nothing to it, so a read-only viewer may save too: the
175
+ * host decides where the template goes and whether this viewer may write it.
176
+ * The viewer always learns the outcome, a rejection included.
177
+ */
178
+ export async function saveFrameAsTemplate(std, frame) {
179
+ const service = std.getOptional(FrameTemplateProvider);
180
+ if (!service)
181
+ return;
182
+ const key = `${std.store.id}:${frame.id}`;
183
+ if (saving.has(key))
184
+ return;
185
+ const snapshot = frameTemplateSnapshot(std.store, frame);
186
+ if (!snapshot) {
187
+ tell(std, FRAME_TOAST_TEMPLATE_SAVE_FAILED, false);
188
+ return;
189
+ }
190
+ saving.add(key);
191
+ try {
192
+ await service.save({
193
+ docId: std.store.id,
194
+ frameId: frame.id,
195
+ title: snapshot.meta.title,
196
+ snapshot,
197
+ });
198
+ tell(std, FRAME_TOAST_TEMPLATE_SAVED, true);
199
+ }
200
+ catch (error) {
201
+ console.error(error);
202
+ tell(std, FRAME_TOAST_TEMPLATE_SAVE_FAILED, false);
203
+ }
204
+ finally {
205
+ saving.delete(key);
206
+ }
207
+ }
@@ -2,16 +2,17 @@ import { EdgelessCRUDIdentifier } from '../affine-block-surface/index.js';
2
2
  import { frameworkPaletteGroups, packColor, } from '../affine-components/color-picker/index.js';
3
3
  import { toast } from '../affine-components/toast/index.js';
4
4
  import { DEFAULT_NOTE_HEIGHT, DefaultTheme, FrameBlockModel, FrameBlockSchema, NoteBlockModel, NoteBlockSchema, NoteDisplayMode, resolveColor, SurfaceRefBlockSchema, } from '../affine-model/index.js';
5
- import { NotificationProvider, TOAST_FRAME_INSERTED_INTO_PAGE, TOOLBAR_RENAME, ToolbarModuleExtension, translateKey, } from '../affine-shared/services/index.js';
5
+ import { FrameTemplateProvider, NotificationProvider, TOAST_FRAME_INSERTED_INTO_PAGE, TOOLBAR_RENAME, ToolbarModuleExtension, translateKey, } from '../affine-shared/services/index.js';
6
6
  import { getMostCommonResolvedValue, matchModels, } from '../affine-shared/utils/index.js';
7
7
  import { mountFrameTitleEditor } from '../affine-widget-frame-title/index.js';
8
8
  import { Bound } from '../global/gfx/index.js';
9
- import { EditIcon, InsertIntoPageIcon, UngroupIcon, } from '@blocksuite/icons/lit';
9
+ import { EditIcon, InsertIntoPageIcon, TemplateIcon, UngroupIcon, } from '@blocksuite/icons/lit';
10
10
  import { BlockFlavourIdentifier } from '../std/index.js';
11
11
  import { GfxControllerIdentifier } from '../std/gfx/index.js';
12
12
  import { html } from 'lit';
13
13
  import { EdgelessFrameManagerIdentifier } from './frame-manager.js';
14
- import { FRAME_TOAST_INSERTED_MESSAGE, FRAME_TOOLBAR_BACKGROUND, FRAME_TOOLBAR_INSERT_INTO_PAGE, FRAME_TOOLBAR_UNGROUP, } from './translations.js';
14
+ import { saveFrameAsTemplate } from './frame-template.js';
15
+ import { FRAME_TOAST_INSERTED_MESSAGE, FRAME_TOOLBAR_BACKGROUND, FRAME_TOOLBAR_INSERT_INTO_PAGE, FRAME_TOOLBAR_SAVE_AS_TEMPLATE, FRAME_TOOLBAR_UNGROUP, } from './translations.js';
15
16
  function getRootBlock(ctx) {
16
17
  const rootModel = ctx.store.root;
17
18
  if (!rootModel)
@@ -75,6 +76,21 @@ const builtinSurfaceToolbarConfig = {
75
76
  mountFrameTitleEditor(model, rootBlock);
76
77
  },
77
78
  },
79
+ {
80
+ id: 'b.save-as-template',
81
+ tooltip: 'Save as template',
82
+ tooltipWording: FRAME_TOOLBAR_SAVE_AS_TEMPLATE,
83
+ icon: TemplateIcon(),
84
+ // No host keeping templates, no button: a save that goes nowhere is a lie.
85
+ when: ctx => ctx.getSurfaceModelsByType(FrameBlockModel).length === 1 &&
86
+ !!ctx.std.getOptional(FrameTemplateProvider),
87
+ run(ctx) {
88
+ const model = ctx.getCurrentModelByType(FrameBlockModel);
89
+ if (!model)
90
+ return;
91
+ saveFrameAsTemplate(ctx.std, model).catch(console.error);
92
+ },
93
+ },
78
94
  {
79
95
  id: 'b.ungroup',
80
96
  tooltip: 'Ungroup',
@@ -6,6 +6,7 @@ export * from './frame-highlight-manager.js';
6
6
  export * from './frame-manager.js';
7
7
  export * from './frame-order.js';
8
8
  export * from './frame-spec.js';
9
+ export * from './frame-template.js';
9
10
  export * from './frame-tool.js';
10
11
  export * from './frame-toolbar.js';
11
12
  export * from './present-tool.js';
@@ -6,6 +6,7 @@ export * from './frame-highlight-manager.js';
6
6
  export * from './frame-manager.js';
7
7
  export * from './frame-order.js';
8
8
  export * from './frame-spec.js';
9
+ export * from './frame-template.js';
9
10
  export * from './frame-tool.js';
10
11
  export * from './frame-toolbar.js';
11
12
  export * from './present-tool.js';
@@ -34,6 +34,13 @@ export declare const FRAME_TOOLBAR_INSERT_INTO_PAGE: ChromeWording;
34
34
  */
35
35
  export declare const FRAME_TOAST_INSERTED_MESSAGE: ChromeWording;
36
36
  export declare const FRAME_TOOLBAR_UNGROUP: ChromeWording;
37
+ /**
38
+ * "Save as template" (the `FrameTemplateProvider` seam): the button, then
39
+ * the notification that says how the host's save went.
40
+ */
41
+ export declare const FRAME_TOOLBAR_SAVE_AS_TEMPLATE: ChromeWording;
42
+ export declare const FRAME_TOAST_TEMPLATE_SAVED: ChromeWording;
43
+ export declare const FRAME_TOAST_TEMPLATE_SAVE_FAILED: ChromeWording;
37
44
  export declare const FRAME_TOOLBAR_BACKGROUND: ChromeWording;
38
45
  export declare const FRAME_PRESENT_TOOL_TOOLTIP: ChromeWording;
39
46
  export declare const FRAME_PRESENT_PREVIOUS: ChromeWording;