@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.
- package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
- package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
- package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
- package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-attachment/index.js +1 -0
- package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
- package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
- package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
- package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
- package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
- package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
- package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
- package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
- package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-frame/index.js +1 -0
- package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
- package/dist/_pkgs/affine-block-frame/translations.js +16 -1
- package/dist/_pkgs/affine-block-frame/view.js +6 -0
- package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
- package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
- package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
- package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
- package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
- package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
- package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
- package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
- package/dist/_pkgs/affine-block-image/effects.js +2 -0
- package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-block.js +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
- package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
- package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-image/index.js +1 -0
- package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
- package/dist/_pkgs/affine-block-image/translations.js +43 -0
- package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
- package/dist/_pkgs/affine-block-image/utils.js +10 -1
- package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
- package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-root/index.js +1 -0
- package/dist/_pkgs/affine-block-root/view.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
- package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
- package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
- package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
- package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
- package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
- package/dist/_pkgs/affine-block-surface/view.js +22 -2
- package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
- package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
- package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
- package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
- package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
- package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
- package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
- package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
- package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
- package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
- package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
- package/dist/_pkgs/affine-model/consts/shape.js +5 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
- package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
- package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
- package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
- package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
- package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
- package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
- package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
- package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
- package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
- package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
- package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
- package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
- package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
- package/dist/_pkgs/affine-model/headless.d.ts +19 -0
- package/dist/_pkgs/affine-model/headless.js +19 -0
- package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
- package/dist/_pkgs/affine-shared/headless.js +10 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
- package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
- package/dist/_pkgs/affine-shared/services/index.js +2 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
- package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
- package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
- package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
- package/dist/commands.js +5 -2
- package/dist/host-panels.d.ts +21 -1
- package/dist/host-panels.js +50 -10
- package/dist/interchange.d.ts +1 -0
- package/dist/interchange.js +1 -0
- package/dist/model/headless.d.ts +1 -0
- package/dist/model/headless.js +1 -0
- package/dist/shared/headless.d.ts +1 -0
- package/dist/shared/headless.js +1 -0
- package/dist/translations.js +8 -1
- 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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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 {
|
|
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;
|