@formicoidea/labre-core 0.46.2 → 0.47.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-data-view/columns/index.d.ts +1 -1
- package/dist/_pkgs/affine-block-database/data-source.d.ts +1 -1
- package/dist/_pkgs/affine-block-database/properties/converts.d.ts +4 -4
- package/dist/_pkgs/affine-block-database/properties/index.d.ts +1 -1
- package/dist/_pkgs/affine-block-frame/commands.js +41 -1
- package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +1 -1
- package/dist/_pkgs/affine-block-frame/view.js +7 -2
- package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.d.ts +7 -0
- package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.js +15 -1
- package/dist/_pkgs/affine-block-surface/extensions/canvas-grid/commands.js +20 -6
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.d.ts +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.js +79 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.d.ts +18 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +53 -30
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.js +1 -0
- package/dist/_pkgs/affine-block-surface/view.js +11 -2
- package/dist/_pkgs/affine-fragment-doc-title/doc-title.d.ts +8 -0
- package/dist/_pkgs/affine-fragment-doc-title/doc-title.js +65 -4
- package/dist/_pkgs/affine-fragment-doc-title/effects.d.ts +2 -0
- package/dist/_pkgs/affine-fragment-doc-title/effects.js +2 -0
- package/dist/_pkgs/affine-fragment-doc-title/index.d.ts +2 -0
- package/dist/_pkgs/affine-fragment-doc-title/index.js +2 -0
- package/dist/_pkgs/affine-fragment-doc-title/title-band/format.d.ts +69 -0
- package/dist/_pkgs/affine-fragment-doc-title/title-band/format.js +182 -0
- package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +62 -0
- package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +649 -0
- package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +21 -0
- package/dist/_pkgs/affine-fragment-doc-title/translations.js +58 -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/properties-panel-service.d.ts +50 -0
- package/dist/_pkgs/affine-shared/services/properties-panel-service.js +17 -0
- package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +124 -0
- package/dist/_pkgs/affine-shared/services/title-band-service.js +17 -0
- package/dist/_pkgs/data-view/property-presets/converts.d.ts +2 -2
- package/dist/_pkgs/data-view/property-presets/index.d.ts +1 -1
- package/dist/_pkgs/data-view/property-presets/number/cell-renderer.d.ts +1 -1
- package/dist/_pkgs/data-view/property-presets/number/define.d.ts +1 -1
- package/dist/_pkgs/data-view/property-presets/number/types.d.ts +2 -2
- package/dist/_pkgs/data-view/property-presets/pure-index.d.ts +2 -1
- package/dist/_pkgs/data-view/property-presets/pure-index.js +4 -0
- package/dist/_pkgs/std/extension/command-registry.d.ts +50 -1
- package/dist/_pkgs/std/extension/command-registry.js +26 -7
- package/dist/commands.js +2 -1
- package/dist/host-panels.d.ts +83 -2
- package/dist/host-panels.js +82 -1
- package/package.json +1 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.d.ts +0 -26
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.js +0 -73
|
@@ -6,7 +6,7 @@ export declare const queryBlockColumns: (PropertyMetaConfig<"checkbox", {}, bool
|
|
|
6
6
|
color: string;
|
|
7
7
|
}[];
|
|
8
8
|
}, string[], string[]> | PropertyMetaConfig<"number", {
|
|
9
|
-
format: "number" | "
|
|
9
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
10
10
|
decimal?: number | undefined;
|
|
11
11
|
}, number | null, number | null> | PropertyMetaConfig<"progress", {}, number, number> | PropertyMetaConfig<"select", {
|
|
12
12
|
options: {
|
|
@@ -25,7 +25,7 @@ export declare class DatabaseBlockDataSource extends ExternalDataSourceBase {
|
|
|
25
25
|
color: string;
|
|
26
26
|
}[];
|
|
27
27
|
}, string[], string[]> | PropertyMetaConfig<"number", {
|
|
28
|
-
format: "number" | "
|
|
28
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
29
29
|
decimal?: number | undefined;
|
|
30
30
|
}, number | null, number | null> | PropertyMetaConfig<"progress", {}, number, number> | PropertyMetaConfig<"select", {
|
|
31
31
|
options: {
|
|
@@ -18,14 +18,14 @@ export declare const databasePropertyConverts: ({
|
|
|
18
18
|
from: any;
|
|
19
19
|
to: any;
|
|
20
20
|
convert: import("../index.js").ConvertFunction<import("../index.js").PropertyModel<"number", {
|
|
21
|
-
format: "number" | "
|
|
21
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
22
22
|
decimal?: number | undefined;
|
|
23
23
|
}, number | null, number | null>, import("../index.js").PropertyModel<"progress", {}, number, number>>;
|
|
24
24
|
} | {
|
|
25
25
|
from: any;
|
|
26
26
|
to: any;
|
|
27
27
|
convert: import("../index.js").ConvertFunction<import("../index.js").PropertyModel<"progress", {}, number, number>, import("../index.js").PropertyModel<"number", {
|
|
28
|
-
format: "number" | "
|
|
28
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
29
29
|
decimal?: number | undefined;
|
|
30
30
|
}, number | null, number | null>>;
|
|
31
31
|
} | {
|
|
@@ -68,7 +68,7 @@ export declare const databasePropertyConverts: ({
|
|
|
68
68
|
from: any;
|
|
69
69
|
to: any;
|
|
70
70
|
convert: import("../index.js").ConvertFunction<import("../index.js").PropertyModel<"rich-text", {}, import("./rich-text/define.js").RichTextCellType | undefined, string>, import("../index.js").PropertyModel<"number", {
|
|
71
|
-
format: "number" | "
|
|
71
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
72
72
|
decimal?: number | undefined;
|
|
73
73
|
}, number | null, number | null>>;
|
|
74
74
|
} | {
|
|
@@ -97,7 +97,7 @@ export declare const databasePropertyConverts: ({
|
|
|
97
97
|
from: any;
|
|
98
98
|
to: any;
|
|
99
99
|
convert: import("../index.js").ConvertFunction<import("../index.js").PropertyModel<"number", {
|
|
100
|
-
format: "number" | "
|
|
100
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
101
101
|
decimal?: number | undefined;
|
|
102
102
|
}, number | null, number | null>, import("../index.js").PropertyModel<"rich-text", {}, import("./rich-text/define.js").RichTextCellType | undefined, string>>;
|
|
103
103
|
} | {
|
|
@@ -10,7 +10,7 @@ export declare const databaseBlockProperties: {
|
|
|
10
10
|
}[];
|
|
11
11
|
}, string[], string[]>;
|
|
12
12
|
numberColumnConfig: import("../index.js").PropertyMetaConfig<"number", {
|
|
13
|
-
format: "number" | "
|
|
13
|
+
format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
|
|
14
14
|
decimal?: number | undefined;
|
|
15
15
|
}, number | null, number | null>;
|
|
16
16
|
progressColumnConfig: import("../index.js").PropertyMetaConfig<"progress", {}, number, number>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { EditPropsStore } from '../affine-shared/services/index.js';
|
|
2
|
+
import { GfxControllerIdentifier } from '../std/gfx/index.js';
|
|
1
3
|
import { z } from 'zod';
|
|
2
4
|
import { reorderFramePresentation } from './frame-order.js';
|
|
5
|
+
import { PresentTool } from './present-tool.js';
|
|
3
6
|
/**
|
|
4
7
|
* The frames' commands, `owner: 'core'`: a frame belongs to no framework, and
|
|
5
8
|
* `'core'` owners are exempt from the id-prefix rule (ADR 0008).
|
|
@@ -46,4 +49,41 @@ const reorderFrames = {
|
|
|
46
49
|
reorderFramePresentation(std, parsed.data.ids, parsed.data.before);
|
|
47
50
|
},
|
|
48
51
|
};
|
|
49
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Start the slideshow (ADR 0034, amendment of 2026-10-13): the frames in
|
|
54
|
+
* presentation order, full screen, Esc to leave — the presentation tool owns
|
|
55
|
+
* all of it from here. The entry a host's view menu (or its header's
|
|
56
|
+
* "Present" button) runs; a document without frames gets the tool's own
|
|
57
|
+
* "no frame" notice.
|
|
58
|
+
*
|
|
59
|
+
* Edgeless only, like the tool it arms: a host showing the page view switches
|
|
60
|
+
* to the canvas with its OWN mode toggle first (it owns the toggle's state and
|
|
61
|
+
* the persisted mode), then runs this on the new editor scope once the canvas
|
|
62
|
+
* has mounted. It reads, so a reader may present. The fit or fill choice is the
|
|
63
|
+
* viewer's, stored by the frame panel's setting (`presentFillScreen`).
|
|
64
|
+
*/
|
|
65
|
+
const present = {
|
|
66
|
+
id: 'canvas.present',
|
|
67
|
+
owner: 'core',
|
|
68
|
+
kind: 'action',
|
|
69
|
+
labelKey: 'com.labre.command.canvas.present',
|
|
70
|
+
labelFallback: 'Present',
|
|
71
|
+
descriptionKey: 'com.labre.command.canvas.present.description',
|
|
72
|
+
descriptionFallback: 'Show the frames one after the other, full screen, in presentation order.',
|
|
73
|
+
category: 'present',
|
|
74
|
+
iconKey: 'canvas.present',
|
|
75
|
+
keywords: ['present', 'slides', 'slideshow', 'frames', 'presentation'],
|
|
76
|
+
surfaces: ['view-menu', 'palette'],
|
|
77
|
+
order: 20,
|
|
78
|
+
scope: 'edgeless',
|
|
79
|
+
defaultKeys: { mac: [], other: [] },
|
|
80
|
+
run: std => {
|
|
81
|
+
const fill = std
|
|
82
|
+
.getOptional(EditPropsStore)
|
|
83
|
+
?.getStorage('presentFillScreen');
|
|
84
|
+
std.get(GfxControllerIdentifier).tool.setTool(PresentTool, {
|
|
85
|
+
mode: fill ? 'fill' : 'fit',
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
export const frameCommands = [reorderFrames, present];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PresentationIcon } from '@blocksuite/icons/lit';
|
|
1
2
|
import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
|
|
2
3
|
import { CommandExtension } from '../std/index.js';
|
|
3
4
|
import { frameCommands } from './commands.js';
|
|
@@ -34,8 +35,12 @@ export class FrameViewExtension extends ViewExtensionProvider {
|
|
|
34
35
|
context.register(FrameBlockInteraction);
|
|
35
36
|
// The presentation order's one write (ADR 0034). The frame panel also
|
|
36
37
|
// mounts in page mode: it runs the imported descriptor, not this
|
|
37
|
-
// registration, so its reorder works in both modes.
|
|
38
|
-
|
|
38
|
+
// registration, so its reorder works in both modes. `canvas.present`
|
|
39
|
+
// rides along: a host's view menu lists it in edgeless mode, and a
|
|
40
|
+
// `frame: false` assembly loses it with the rest of the frame tooling.
|
|
41
|
+
context.register(CommandExtension(frameCommands, {
|
|
42
|
+
'canvas.present': PresentationIcon(),
|
|
43
|
+
}));
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { NoteBlockModel } from '../../affine-model/index.js';
|
|
2
2
|
import { type BlockStdScope, ShadowlessElement } from '../../std/index.js';
|
|
3
3
|
declare const EdgelessPageBlockTitle_base: typeof ShadowlessElement & import("../../global/utils/types.js").Constructor<import("../../global/lit/with-disposable.js").DisposableClass>;
|
|
4
|
+
/**
|
|
5
|
+
* The head of the page's note on the canvas: the host's title, if its
|
|
6
|
+
* `NoteConfigExtension` draws one, then the title band (ADR 0035 §11). The
|
|
7
|
+
* band sits inside the note's DOM so it follows the zoom like the rest of the
|
|
8
|
+
* note (REQ-PR-7); it draws nothing when the host registers no
|
|
9
|
+
* `TitleBandProvider`.
|
|
10
|
+
*/
|
|
4
11
|
export declare class EdgelessPageBlockTitle extends EdgelessPageBlockTitle_base {
|
|
5
12
|
render(): import("lit-html").TemplateResult<1> | undefined;
|
|
6
13
|
accessor std: BlockStdScope;
|
|
@@ -40,6 +40,13 @@ import { html } from 'lit';
|
|
|
40
40
|
import { property } from 'lit/decorators.js';
|
|
41
41
|
import { NoteConfigExtension } from '../config.js';
|
|
42
42
|
import * as styles from './edgeless-page-block-title.css.js';
|
|
43
|
+
/**
|
|
44
|
+
* The head of the page's note on the canvas: the host's title, if its
|
|
45
|
+
* `NoteConfigExtension` draws one, then the title band (ADR 0035 §11). The
|
|
46
|
+
* band sits inside the note's DOM so it follows the zoom like the rest of the
|
|
47
|
+
* note (REQ-PR-7); it draws nothing when the host registers no
|
|
48
|
+
* `TitleBandProvider`.
|
|
49
|
+
*/
|
|
43
50
|
let EdgelessPageBlockTitle = (() => {
|
|
44
51
|
let _classDecorators = [requiredProperties({
|
|
45
52
|
note: PropTypes.instanceOf(NoteBlockModel),
|
|
@@ -76,7 +83,14 @@ let EdgelessPageBlockTitle = (() => {
|
|
|
76
83
|
note: this.note,
|
|
77
84
|
std: this.std,
|
|
78
85
|
});
|
|
79
|
-
return html `<div class=${styles.pageBlockTitle}
|
|
86
|
+
return html `<div class=${styles.pageBlockTitle}>
|
|
87
|
+
${title}
|
|
88
|
+
<labre-title-band
|
|
89
|
+
.std=${this.std}
|
|
90
|
+
.docId=${this.std.store.id}
|
|
91
|
+
.surface=${'canvas'}
|
|
92
|
+
></labre-title-band>
|
|
93
|
+
</div>`;
|
|
80
94
|
}
|
|
81
95
|
#std_accessor_storage = __runInitializers(this, _std_initializers, void 0);
|
|
82
96
|
get std() { return this.#std_accessor_storage; }
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CanvasGrid, TelemetryProvider } from '../../../affine-shared/services/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* The canvas grid's two commands (ADR 0031 §11, §12), `owner: 'core'`, on the
|
|
4
|
-
* palette
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* palette, the agent and the host's view menu (`'view-menu'`, ADR 0034
|
|
5
|
+
* amendment of 2026-10-13). The library has no page "more" menu — that
|
|
6
|
+
* belongs to the host — so these are the grid's whole UI here; a host lists
|
|
7
|
+
* them with `getViewActions` and runs them through `runCommand`.
|
|
7
8
|
*
|
|
8
9
|
* Both self-emit `CanvasGridToggled` (see the telemetry README): `scope` is
|
|
9
10
|
* which gesture ran, and a save that changes nothing reports nothing.
|
|
@@ -27,11 +28,17 @@ const toggleGrid = {
|
|
|
27
28
|
labelFallback: 'Show or hide the grid',
|
|
28
29
|
descriptionKey: 'com.labre.command.canvas.grid.toggle.description',
|
|
29
30
|
descriptionFallback: 'Show or hide the canvas grid on your screen only. Nothing changes for anyone else.',
|
|
31
|
+
category: 'display',
|
|
32
|
+
iconKey: 'canvas.grid',
|
|
30
33
|
keywords: ['grid', 'dots', 'background'],
|
|
31
|
-
surfaces: ['palette', 'agent'],
|
|
34
|
+
surfaces: ['palette', 'agent', 'view-menu'],
|
|
35
|
+
order: 0,
|
|
32
36
|
scope: 'edgeless',
|
|
33
37
|
defaultKeys: { mac: [], other: [] },
|
|
34
38
|
when: std => !!std.getOptional(CanvasGrid),
|
|
39
|
+
// What this viewer sees now. A signal read, so a menu that reads it inside
|
|
40
|
+
// an effect ticks and unticks with the grid, whoever flips it.
|
|
41
|
+
checked: std => std.getOptional(CanvasGrid)?.visible$.value ?? false,
|
|
35
42
|
run: std => {
|
|
36
43
|
const grid = std.getOptional(CanvasGrid);
|
|
37
44
|
if (!grid)
|
|
@@ -52,12 +59,19 @@ const saveGridForEveryone = {
|
|
|
52
59
|
labelFallback: 'Save the grid for everyone',
|
|
53
60
|
descriptionKey: 'com.labre.command.canvas.grid.save-for-everyone.description',
|
|
54
61
|
descriptionFallback: 'Make the grid as you see it now the setting of this document, for everyone who opens it.',
|
|
62
|
+
category: 'display',
|
|
55
63
|
keywords: ['grid', 'dots', 'background', 'document'],
|
|
56
|
-
surfaces: ['palette', 'agent'],
|
|
64
|
+
surfaces: ['palette', 'agent', 'view-menu'],
|
|
65
|
+
order: 1,
|
|
57
66
|
scope: 'edgeless',
|
|
58
67
|
defaultKeys: { mac: [], other: [] },
|
|
59
68
|
availability: 'editable',
|
|
60
|
-
|
|
69
|
+
// Offered once this viewer has a grid of their own (`source$` is
|
|
70
|
+
// `'local'`, i.e. after a toggle): that is what the gesture saves, and the
|
|
71
|
+
// follow-up the hosts' menus already showed after the toggle. A signal
|
|
72
|
+
// read, so a view menu read inside an effect shows the entry right after a
|
|
73
|
+
// toggle and drops it after the save.
|
|
74
|
+
when: std => std.getOptional(CanvasGrid)?.source$.value === 'local',
|
|
61
75
|
run: std => {
|
|
62
76
|
const saved = std.getOptional(CanvasGrid)?.saveForEveryone() ?? null;
|
|
63
77
|
if (saved === null)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { downloadBlob } from '../../../affine-shared/utils/index.js';
|
|
2
|
+
import { ExportManager } from './export-manager.js';
|
|
3
|
+
/**
|
|
4
|
+
* "Export as PNG" and "Export as PDF" of the canvas, in the command registry
|
|
5
|
+
* (ADR 0034, amendment of 2026-10-13). `owner: 'core'`: the picture is the
|
|
6
|
+
* canvas's, whatever drew on it.
|
|
7
|
+
*
|
|
8
|
+
* On the `'view-menu'` surface because the export is proper to the canvas
|
|
9
|
+
* VIEW (the host's "This view" section reads them through `getViewActions`),
|
|
10
|
+
* and on the palette. Not `'agent'`: an agent turn would drop a file on the
|
|
11
|
+
* user's disk they did not ask for.
|
|
12
|
+
*
|
|
13
|
+
* The library renders and the host decides where the bytes go: given
|
|
14
|
+
* `params.deliver` (`CommandFileParams`), the command hands the file over and
|
|
15
|
+
* downloads nothing — `runViewAction` passes one and returns the file. Without
|
|
16
|
+
* it (the palette, a bound chord) the command downloads the file itself, which
|
|
17
|
+
* is what a standalone editor needs. Nothing to draw, no file, no error.
|
|
18
|
+
*
|
|
19
|
+
* It READS, so there is no read-only guard: a reader exports what they see.
|
|
20
|
+
* No `telemetry` field, for `export.svg`'s reason — it names no framework —
|
|
21
|
+
* and usage is still measured by `runCommand`.
|
|
22
|
+
*/
|
|
23
|
+
function deliverOf(params) {
|
|
24
|
+
const deliver = params?.deliver;
|
|
25
|
+
return typeof deliver === 'function' ? deliver : undefined;
|
|
26
|
+
}
|
|
27
|
+
async function exportCanvas(std, render, params) {
|
|
28
|
+
const exporter = std.getOptional(ExportManager);
|
|
29
|
+
if (!exporter)
|
|
30
|
+
return;
|
|
31
|
+
const file = await render(exporter);
|
|
32
|
+
if (!file)
|
|
33
|
+
return;
|
|
34
|
+
const deliver = deliverOf(params);
|
|
35
|
+
if (deliver)
|
|
36
|
+
deliver(file);
|
|
37
|
+
else
|
|
38
|
+
downloadBlob(file.blob, file.filename);
|
|
39
|
+
}
|
|
40
|
+
const exportPng = {
|
|
41
|
+
id: 'canvas.export.png',
|
|
42
|
+
owner: 'core',
|
|
43
|
+
kind: 'action',
|
|
44
|
+
labelKey: 'com.labre.command.canvas.export.png',
|
|
45
|
+
labelFallback: 'Export as PNG',
|
|
46
|
+
descriptionKey: 'com.labre.command.canvas.export.png.description',
|
|
47
|
+
descriptionFallback: 'Download the whole canvas as an image, as you see it on your screen.',
|
|
48
|
+
category: 'export',
|
|
49
|
+
iconKey: 'canvas.export.png',
|
|
50
|
+
keywords: ['png', 'image', 'export', 'download', 'picture'],
|
|
51
|
+
surfaces: ['view-menu', 'palette'],
|
|
52
|
+
order: 10,
|
|
53
|
+
scope: 'edgeless',
|
|
54
|
+
defaultKeys: { mac: [], other: [] },
|
|
55
|
+
when: std => !!std.getOptional(ExportManager),
|
|
56
|
+
run: (std, _invocation, params) => exportCanvas(std, exporter => exporter.pngFile(), params),
|
|
57
|
+
};
|
|
58
|
+
const exportPdf = {
|
|
59
|
+
id: 'canvas.export.pdf',
|
|
60
|
+
owner: 'core',
|
|
61
|
+
kind: 'action',
|
|
62
|
+
labelKey: 'com.labre.command.canvas.export.pdf',
|
|
63
|
+
labelFallback: 'Export as PDF',
|
|
64
|
+
descriptionKey: 'com.labre.command.canvas.export.pdf.description',
|
|
65
|
+
descriptionFallback: 'Download the whole canvas as a one-page PDF holding its image.',
|
|
66
|
+
category: 'export',
|
|
67
|
+
iconKey: 'canvas.export.pdf',
|
|
68
|
+
keywords: ['pdf', 'export', 'download', 'print'],
|
|
69
|
+
surfaces: ['view-menu', 'palette'],
|
|
70
|
+
order: 11,
|
|
71
|
+
scope: 'edgeless',
|
|
72
|
+
defaultKeys: { mac: [], other: [] },
|
|
73
|
+
when: std => !!std.getOptional(ExportManager),
|
|
74
|
+
run: (std, _invocation, params) => exportCanvas(std, exporter => exporter.pdfFile(), params),
|
|
75
|
+
};
|
|
76
|
+
export const canvasExportCommands = [
|
|
77
|
+
exportPng,
|
|
78
|
+
exportPdf,
|
|
79
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IBound } from '../../../global/gfx/index.js';
|
|
2
|
-
import { type BlockStdScope, type EditorHost } from '../../../std/index.js';
|
|
2
|
+
import { type BlockStdScope, type CommandFile, type EditorHost } from '../../../std/index.js';
|
|
3
3
|
import { GfxBlockElementModel, type GfxController, GfxPrimitiveElementModel } from '../../../std/gfx/index.js';
|
|
4
4
|
import type { ExtensionType, Store } from '../../../store/index.js';
|
|
5
5
|
import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
|
|
@@ -39,7 +39,24 @@ export declare class ExportManager {
|
|
|
39
39
|
edgelessToCanvas(surfaceRenderer: CanvasRenderer, bound: IBound, gfx: GfxController, blocks?: GfxBlockElementModel[], elements?: GfxPrimitiveElementModel[], edgelessBackground?: {
|
|
40
40
|
zoom: number;
|
|
41
41
|
}): Promise<HTMLCanvasElement | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* The canvas (or, in page mode, the document) as a PNG file, or `null` when
|
|
44
|
+
* there is nothing to draw. Renders, never downloads: the caller decides
|
|
45
|
+
* where the bytes go (`canvas.export.png` hands them to a host, or to the
|
|
46
|
+
* browser).
|
|
47
|
+
*/
|
|
48
|
+
pngFile(): Promise<CommandFile | null>;
|
|
49
|
+
/**
|
|
50
|
+
* The same picture in a one-page PDF (`pdf-lib`, loaded on demand), or
|
|
51
|
+
* `null` when there is nothing to draw. The PDF is an image: its text is
|
|
52
|
+
* not selectable.
|
|
53
|
+
*/
|
|
54
|
+
pdfFile(): Promise<CommandFile | null>;
|
|
55
|
+
/** Render, then hand the file to the browser. Nothing to draw, no file. */
|
|
42
56
|
exportPdf(): Promise<void>;
|
|
57
|
+
/** Render, then hand the file to the browser. Nothing to draw, no file. */
|
|
43
58
|
exportPng(): Promise<void>;
|
|
59
|
+
/** The document title, sanitised; the translated "Untitled" without one. */
|
|
60
|
+
private _fileStem;
|
|
44
61
|
}
|
|
45
62
|
export declare const ExportManagerExtension: ExtensionType;
|
|
@@ -2,14 +2,13 @@ import { ImageBlockModel } from '../../../affine-model/index.js';
|
|
|
2
2
|
import { FetchUtils, ImageProxyService } from '../../../affine-shared/adapters/index.js';
|
|
3
3
|
import { CANVAS_EXPORT_IGNORE_TAGS } from '../../../affine-shared/consts/index.js';
|
|
4
4
|
import { CanvasGrid, CHROME_UNTITLED, translateKey, } from '../../../affine-shared/services/index.js';
|
|
5
|
-
import { isInsidePageEditor, matchModels, normalizeCanvasExportColors, toLegacyColors, } from '../../../affine-shared/utils/index.js';
|
|
5
|
+
import { downloadBlob, isInsidePageEditor, matchModels, normalizeCanvasExportColors, safeFilename, toLegacyColors, } from '../../../affine-shared/utils/index.js';
|
|
6
6
|
import { BlockSuiteError, ErrorCode } from '../../../global/exceptions/index.js';
|
|
7
7
|
import { deserializeXYWH } from '../../../global/gfx/index.js';
|
|
8
8
|
import { StdIdentifier, } from '../../../std/index.js';
|
|
9
9
|
import { GfxBlockElementModel, GfxControllerIdentifier, GfxPrimitiveElementModel, isGfxGroupCompatibleModel, } from '../../../std/gfx/index.js';
|
|
10
10
|
import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
|
|
11
11
|
import { getBgGridGap } from '../../utils/get-bg-grip-gap.js';
|
|
12
|
-
import { FileExporter } from './file-exporter.js';
|
|
13
12
|
/**
|
|
14
13
|
* The SVG the PNG export paints the canvas background from: the background
|
|
15
14
|
* colour, plus the dotted grid unless `gridColor` is `null` — what this
|
|
@@ -379,38 +378,62 @@ export class ExportManager {
|
|
|
379
378
|
ctx.drawImage(surfaceCanvas, 50, 50, bound.w, bound.h);
|
|
380
379
|
return canvas;
|
|
381
380
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
381
|
+
/**
|
|
382
|
+
* The canvas (or, in page mode, the document) as a PNG file, or `null` when
|
|
383
|
+
* there is nothing to draw. Renders, never downloads: the caller decides
|
|
384
|
+
* where the bytes go (`canvas.export.png` hands them to a host, or to the
|
|
385
|
+
* browser).
|
|
386
|
+
*/
|
|
387
|
+
async pngFile() {
|
|
388
|
+
if (!this.doc.root)
|
|
389
|
+
return null;
|
|
390
|
+
const canvas = await this._toCanvas();
|
|
391
|
+
if (!canvas)
|
|
392
|
+
return null;
|
|
393
|
+
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
|
394
|
+
if (!blob)
|
|
395
|
+
return null;
|
|
396
|
+
return { blob, filename: `${this._fileStem()}.png` };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The same picture in a one-page PDF (`pdf-lib`, loaded on demand), or
|
|
400
|
+
* `null` when there is nothing to draw. The PDF is an image: its text is
|
|
401
|
+
* not selectable.
|
|
402
|
+
*/
|
|
403
|
+
async pdfFile() {
|
|
404
|
+
if (!this.doc.root)
|
|
405
|
+
return null;
|
|
406
|
+
const canvas = await this._toCanvas();
|
|
407
|
+
if (!canvas)
|
|
408
|
+
return null;
|
|
390
409
|
const PDFLib = await import('pdf-lib');
|
|
391
410
|
const pdfDoc = await PDFLib.PDFDocument.create();
|
|
392
|
-
const page = pdfDoc.addPage([
|
|
393
|
-
const
|
|
394
|
-
const { width, height } =
|
|
395
|
-
page.drawImage(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
|
|
402
|
-
FileExporter.exportFile(rootModel.props.title.toString() + '.pdf', pdfBase64);
|
|
411
|
+
const page = pdfDoc.addPage([canvas.width, canvas.height]);
|
|
412
|
+
const image = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
|
|
413
|
+
const { width, height } = image.scale(1);
|
|
414
|
+
page.drawImage(image, { x: 0, y: 0, width, height });
|
|
415
|
+
const bytes = await pdfDoc.save();
|
|
416
|
+
return {
|
|
417
|
+
blob: new Blob([bytes], { type: 'application/pdf' }),
|
|
418
|
+
filename: `${this._fileStem()}.pdf`,
|
|
419
|
+
};
|
|
403
420
|
}
|
|
421
|
+
/** Render, then hand the file to the browser. Nothing to draw, no file. */
|
|
422
|
+
async exportPdf() {
|
|
423
|
+
const file = await this.pdfFile();
|
|
424
|
+
if (file)
|
|
425
|
+
downloadBlob(file.blob, file.filename);
|
|
426
|
+
}
|
|
427
|
+
/** Render, then hand the file to the browser. Nothing to draw, no file. */
|
|
404
428
|
async exportPng() {
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
FileExporter.exportPng(title.trim() || translateKey(this.std, ...CHROME_UNTITLED), canvasImage.toDataURL('image/png'));
|
|
429
|
+
const file = await this.pngFile();
|
|
430
|
+
if (file)
|
|
431
|
+
downloadBlob(file.blob, file.filename);
|
|
432
|
+
}
|
|
433
|
+
/** The document title, sanitised; the translated "Untitled" without one. */
|
|
434
|
+
_fileStem() {
|
|
435
|
+
const root = this.doc.root;
|
|
436
|
+
return safeFilename(root?.props.title.toString(), translateKey(this.std, ...CHROME_UNTITLED));
|
|
414
437
|
}
|
|
415
438
|
}
|
|
416
439
|
export const ExportManagerExtension = {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { ExportToPdfIcon, ExportToPngIcon, GridIcon, } from '@blocksuite/icons/lit';
|
|
1
2
|
import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
|
|
2
3
|
import { CanvasGrid, CanvasLocalVisibility, } from '../affine-shared/services/index.js';
|
|
3
4
|
import { BlockViewExtension, CommandExtension, FlavourExtension, } from '../std/index.js';
|
|
4
5
|
import { literal } from 'lit/static-html.js';
|
|
5
6
|
import { effects } from './effects.js';
|
|
6
|
-
import { auditCommands, CanvasActiveLayer, canvasGridCommands, EdgelessCRUDExtension, EdgelessLegacySlotExtension, EditPropsMiddlewareBuilder, exportSvgCommands, exportSvgToolbarExtension, mapQualityCommands, mapQualityWidget, ReadingManager, readingProposalWidget, readingToolbarExtension, selectionPaneCommands, SelectionPaneModel, SpotlightManager, UserLayerMiddlewareBuilder, userLayerCommands, ValidationManager, validationExceptionToolbarExtension, ValidationOverlay, violationDetailWidget, } from './extensions/index.js';
|
|
7
|
+
import { auditCommands, CanvasActiveLayer, canvasExportCommands, canvasGridCommands, EdgelessCRUDExtension, EdgelessLegacySlotExtension, EditPropsMiddlewareBuilder, exportSvgCommands, exportSvgToolbarExtension, mapQualityCommands, mapQualityWidget, ReadingManager, readingProposalWidget, readingToolbarExtension, selectionPaneCommands, SelectionPaneModel, SpotlightManager, UserLayerMiddlewareBuilder, userLayerCommands, ValidationManager, validationExceptionToolbarExtension, ValidationOverlay, violationDetailWidget, } from './extensions/index.js';
|
|
7
8
|
import { ExportManagerExtension } from './extensions/export-manager/export-manager.js';
|
|
8
9
|
import { BackgroundStackingExtension } from './framework-background/stacking.js';
|
|
9
10
|
import { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
|
|
@@ -87,7 +88,15 @@ export class SurfaceViewExtension extends ViewExtensionProvider {
|
|
|
87
88
|
// the edgeless root's background, the surface container and the PNG
|
|
88
89
|
// export; previews follow the document setting alone.
|
|
89
90
|
context.register(CanvasGrid);
|
|
90
|
-
context.register(CommandExtension(canvasGridCommands));
|
|
91
|
+
context.register(CommandExtension(canvasGridCommands, { 'canvas.grid': GridIcon() }));
|
|
92
|
+
// The canvas's PNG and PDF export (ADR 0034, amendment of 2026-10-13):
|
|
93
|
+
// the "This view" entries a host lists with `getViewActions`. Edgeless
|
|
94
|
+
// only — the page-mode export was never offered — and never gated: a
|
|
95
|
+
// picture of what is on the canvas is not a framework's tooling.
|
|
96
|
+
context.register(CommandExtension(canvasExportCommands, {
|
|
97
|
+
'canvas.export.png': ExportToPngIcon(),
|
|
98
|
+
'canvas.export.pdf': ExportToPdfIcon(),
|
|
99
|
+
}));
|
|
91
100
|
// User layers (ADR 0031 §2-§6): the viewer's active layer (session
|
|
92
101
|
// only), the `beforeAdd` middleware that stamps it on what is created,
|
|
93
102
|
// and the commands the pane's layer rows run. Content, never gated.
|
|
@@ -13,6 +13,14 @@ export declare class DocTitle extends DocTitle_base {
|
|
|
13
13
|
get inlineEditor(): import("../affine-shared/types/index.js").AffineInlineEditor | null;
|
|
14
14
|
get inlineEditorContainer(): HTMLDivElement;
|
|
15
15
|
connectedCallback(): void;
|
|
16
|
+
/**
|
|
17
|
+
* The `std` the title band reads its seams from, when the band belongs
|
|
18
|
+
* here: in the page view, never inside a note on the canvas, where
|
|
19
|
+
* `edgeless-page-block-title` draws the band itself (ADR 0035 §11), so a
|
|
20
|
+
* host that renders this title in the note header does not get two.
|
|
21
|
+
*/
|
|
22
|
+
private get _bandStd();
|
|
23
|
+
firstUpdated(): void;
|
|
16
24
|
render(): import("lit-html").TemplateResult<1>;
|
|
17
25
|
private accessor _isComposing;
|
|
18
26
|
private accessor _isReadonly;
|