@formicoidea/labre-core 0.46.2 → 0.47.1

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 (91) hide show
  1. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +2 -2
  2. package/dist/_pkgs/affine-block-attachment/translations.js +2 -2
  3. package/dist/_pkgs/affine-block-data-view/columns/index.d.ts +1 -1
  4. package/dist/_pkgs/affine-block-database/data-source.d.ts +1 -1
  5. package/dist/_pkgs/affine-block-database/properties/converts.d.ts +4 -4
  6. package/dist/_pkgs/affine-block-database/properties/index.d.ts +1 -1
  7. package/dist/_pkgs/affine-block-embed/embed-html-block/configs/toolbar.js +1 -1
  8. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.d.ts +1 -1
  9. package/dist/_pkgs/affine-block-embed/embed-iframe-block/configs/toolbar.js +1 -1
  10. package/dist/_pkgs/affine-block-embed/translations.d.ts +1 -1
  11. package/dist/_pkgs/affine-block-embed/translations.js +2 -2
  12. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/slash-menu.js +6 -6
  13. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/configs/toolbar.js +1 -1
  14. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/configs/toolbar.js +3 -3
  15. package/dist/_pkgs/affine-block-embed-doc/translations.d.ts +1 -1
  16. package/dist/_pkgs/affine-block-embed-doc/translations.js +8 -8
  17. package/dist/_pkgs/affine-block-frame/commands.js +41 -1
  18. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +1 -1
  19. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +2 -2
  20. package/dist/_pkgs/affine-block-frame/translations.js +1 -1
  21. package/dist/_pkgs/affine-block-frame/view.js +7 -2
  22. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.d.ts +7 -0
  23. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.js +15 -1
  24. package/dist/_pkgs/affine-block-note/translations.js +8 -8
  25. package/dist/_pkgs/affine-block-root/commands/copy-link-commands.js +2 -2
  26. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  27. package/dist/_pkgs/affine-block-root/configs/toolbar.js +1 -1
  28. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/misc.d.ts +1 -1
  29. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.d.ts +1 -1
  30. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +1 -1
  31. package/dist/_pkgs/affine-block-root/translations.d.ts +1 -1
  32. package/dist/_pkgs/affine-block-root/translations.js +2 -2
  33. package/dist/_pkgs/affine-block-surface/extensions/canvas-grid/commands.js +21 -7
  34. package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.d.ts +2 -0
  35. package/dist/_pkgs/affine-block-surface/extensions/export-manager/commands.js +79 -0
  36. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.d.ts +18 -1
  37. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +53 -30
  38. package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.d.ts +1 -0
  39. package/dist/_pkgs/affine-block-surface/extensions/export-manager/index.js +1 -0
  40. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +1 -1
  41. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.js +1 -1
  42. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.d.ts +8 -2
  43. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.js +11 -13
  44. package/dist/_pkgs/affine-block-surface/view.js +11 -2
  45. package/dist/_pkgs/affine-block-surface-ref/translations.js +8 -8
  46. package/dist/_pkgs/affine-components/translations.js +6 -6
  47. package/dist/_pkgs/affine-fragment-doc-title/doc-title.d.ts +8 -0
  48. package/dist/_pkgs/affine-fragment-doc-title/doc-title.js +65 -4
  49. package/dist/_pkgs/affine-fragment-doc-title/effects.d.ts +2 -0
  50. package/dist/_pkgs/affine-fragment-doc-title/effects.js +2 -0
  51. package/dist/_pkgs/affine-fragment-doc-title/index.d.ts +2 -0
  52. package/dist/_pkgs/affine-fragment-doc-title/index.js +2 -0
  53. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.d.ts +69 -0
  54. package/dist/_pkgs/affine-fragment-doc-title/title-band/format.js +182 -0
  55. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +62 -0
  56. package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +649 -0
  57. package/dist/_pkgs/affine-fragment-doc-title/translations.d.ts +21 -0
  58. package/dist/_pkgs/affine-fragment-doc-title/translations.js +58 -0
  59. package/dist/_pkgs/affine-gfx-group/toolbar/config.d.ts +2 -2
  60. package/dist/_pkgs/affine-gfx-group/toolbar/config.js +2 -2
  61. package/dist/_pkgs/affine-gfx-group/translations.js +1 -1
  62. package/dist/_pkgs/affine-gfx-note/translations.js +2 -2
  63. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  64. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  65. package/dist/_pkgs/affine-shared/services/linked-doc-creation-service.d.ts +1 -1
  66. package/dist/_pkgs/affine-shared/services/properties-panel-service.d.ts +50 -0
  67. package/dist/_pkgs/affine-shared/services/properties-panel-service.js +17 -0
  68. package/dist/_pkgs/affine-shared/services/title-band-service.d.ts +124 -0
  69. package/dist/_pkgs/affine-shared/services/title-band-service.js +17 -0
  70. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +3 -3
  71. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +17 -17
  72. package/dist/_pkgs/affine-widget-edgeless-auto-connect/translations.js +2 -2
  73. package/dist/_pkgs/affine-widget-edgeless-selected-rect/translations.js +1 -1
  74. package/dist/_pkgs/affine-widget-linked-doc/translations.js +3 -3
  75. package/dist/_pkgs/affine-widget-slash-menu/translations.js +1 -1
  76. package/dist/_pkgs/data-view/property-presets/converts.d.ts +2 -2
  77. package/dist/_pkgs/data-view/property-presets/index.d.ts +1 -1
  78. package/dist/_pkgs/data-view/property-presets/number/cell-renderer.d.ts +1 -1
  79. package/dist/_pkgs/data-view/property-presets/number/define.d.ts +1 -1
  80. package/dist/_pkgs/data-view/property-presets/number/types.d.ts +2 -2
  81. package/dist/_pkgs/data-view/property-presets/pure-index.d.ts +2 -1
  82. package/dist/_pkgs/data-view/property-presets/pure-index.js +4 -0
  83. package/dist/_pkgs/std/extension/command-registry.d.ts +50 -1
  84. package/dist/_pkgs/std/extension/command-registry.js +26 -7
  85. package/dist/commands.js +2 -1
  86. package/dist/host-panels.d.ts +83 -2
  87. package/dist/host-panels.js +82 -1
  88. package/dist/translations.js +1 -1
  89. package/package.json +1 -1
  90. package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.d.ts +0 -26
  91. package/dist/_pkgs/affine-block-surface/extensions/export-manager/file-exporter.js +0 -73
@@ -10,7 +10,7 @@ export declare const propertyModelPresets: {
10
10
  }[];
11
11
  }, string[], string[]>;
12
12
  numberPropertyModelConfig: import("../index.js").PropertyModel<"number", {
13
- format: "number" | "numberWithCommas" | "percent" | "currencyYen" | "currencyINR" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
13
+ format: "number" | "percent" | "numberWithCommas" | "currencyINR" | "currencyYen" | "currencyCNY" | "currencyUSD" | "currencyEUR" | "currencyGBP";
14
14
  decimal?: number | undefined;
15
15
  }, number | null, number | null>;
16
16
  progressPropertyModelConfig: import("../index.js").PropertyModel<"progress", {}, number, number>;
@@ -23,3 +23,4 @@ export declare const propertyModelPresets: {
23
23
  }, string | null, string | null>;
24
24
  textPropertyModelConfig: import("../index.js").PropertyModel<"text", {}, string, string>;
25
25
  };
26
+ export { formatNumber, type NumberFormat, NumberFormatSchema, } from './number/utils/formatter.js';
@@ -16,3 +16,7 @@ export const propertyModelPresets = {
16
16
  selectPropertyModelConfig,
17
17
  textPropertyModelConfig,
18
18
  };
19
+ // The number preset's formats and formatter, so the title band's own copy of
20
+ // the table (`fragments/doc-title/src/title-band/format.ts`) can be held to
21
+ // it by a parity spec (`title-band-number-parity.unit.spec.ts`).
22
+ export { formatNumber, NumberFormatSchema, } from './number/utils/formatter.js';
@@ -57,8 +57,16 @@ export type Availability = 'always' | 'selection' | 'selection:framework' | 'edi
57
57
  * command, so the behaviour, the availability rule and the single telemetry
58
58
  * emission stay in one place, and `CommandInvocation.surface` reports where the
59
59
  * user actually clicked instead of borrowing another surface's name.
60
+ *
61
+ * `'view-menu'` joined with the view actions (ADR 0034, amendment of
62
+ * 2026-10-13): what is proper to the view the editor shows — the canvas grid,
63
+ * its PNG and PDF export, the presentation — listed for a HOST menu (Labre's
64
+ * "This view" section of its header ⋯). The library draws no such menu; the
65
+ * host reads the projection `getViewActions` (`@labre/affine/host-panels`)
66
+ * and runs an entry through `runCommand`, so the behaviour and the one
67
+ * telemetry emission stay in the command.
60
68
  */
61
- export type CommandSurface = 'senior-menu' | 'catalogue' | 'palette' | 'contextual-toolbar' | 'agent';
69
+ export type CommandSurface = 'senior-menu' | 'catalogue' | 'palette' | 'contextual-toolbar' | 'agent' | 'view-menu';
62
70
  export type CommandKind = 'artefact' | 'tool' | 'toggle' | 'legend' | 'action';
63
71
  /** Where the invocation came from — feeds `segment` / `module` / `control`. */
64
72
  export interface CommandInvocation {
@@ -130,6 +138,15 @@ export interface CommandDescriptor<P = void> {
130
138
  * only narrowing. Not projected into either manifest.
131
139
  */
132
140
  when?: (std: BlockStdScope) => boolean;
141
+ /**
142
+ * A toggle's current state, for a surface that draws a check mark (the
143
+ * host's view menu reads it through `getViewActions`). Absent = not a
144
+ * toggle. It may read signals: a caller that reads it inside an `effect`
145
+ * or a `computed` follows the state, whoever changes it. Not projected into
146
+ * either manifest — a state is a fact of the open editor, not of the
147
+ * catalogue.
148
+ */
149
+ checked?: (std: BlockStdScope) => boolean;
133
150
  /** THE new capability: invocable without a keyboard event. */
134
151
  run: (std: BlockStdScope, invocation: CommandInvocation, params?: P) => void | Promise<void>;
135
152
  /** Agent-facing parameter contract. */
@@ -472,6 +489,26 @@ export declare const isEditingOnCanvas: (std: BlockStdScope) => boolean;
472
489
  * in `when`. See ADR 0008 § Availability.
473
490
  */
474
491
  export declare function isCommandAvailable(std: BlockStdScope, command: AnyCommandDescriptor): boolean;
492
+ /**
493
+ * A file a command hands back to its caller instead of downloading it — the
494
+ * PNG and PDF exports of the canvas. The library renders, the host decides
495
+ * where the bytes go (a download, a share sheet, an upload).
496
+ */
497
+ export interface CommandFile {
498
+ blob: Blob;
499
+ /** Sanitised, extension included: `Order to cash.png`. */
500
+ filename: string;
501
+ }
502
+ /**
503
+ * The params of a command that produces a {@link CommandFile}. Given
504
+ * `deliver`, the command hands the file to it and downloads nothing; without
505
+ * it (the palette, a bound chord) the command downloads the file itself. Not
506
+ * declared as a zod `params`: a callback crosses no serializable seam, and an
507
+ * agent gets the download.
508
+ */
509
+ export interface CommandFileParams {
510
+ deliver?: (file: CommandFile) => void;
511
+ }
475
512
  /**
476
513
  * THE bottleneck: the one place a command runs, and therefore the one place
477
514
  * its telemetry is emitted and its usage measured. Every surface (sub-menu,
@@ -479,6 +516,18 @@ export declare function isCommandAvailable(std: BlockStdScope, command: AnyComma
479
516
  * the per-menu `track()` duplicates.
480
517
  */
481
518
  export declare function runCommand(std: BlockStdScope, command: AnyCommandDescriptor, invocation: CommandInvocation, params?: unknown): void;
519
+ /**
520
+ * {@link runCommand} for a caller that waits: the same bottleneck (usage and
521
+ * telemetry recorded once, synchronously, as there), but the returned promise
522
+ * settles when the command's own work does and REJECTS when it fails, where
523
+ * `runCommand` only logs. For a gesture that shows progress — a menu entry
524
+ * that spins while the canvas export renders, then reports a failure.
525
+ *
526
+ * A separate function rather than a return value on `runCommand`: a promise
527
+ * returned there would read as a floating promise at every existing call site
528
+ * of every host.
529
+ */
530
+ export declare function runCommandAsync(std: BlockStdScope, command: AnyCommandDescriptor, invocation: CommandInvocation, params?: unknown): Promise<void>;
482
531
  /**
483
532
  * One of the three permanent projections out of {@link CommandDescriptor} —
484
533
  * NOT a migration shim. It is TOTAL: keyless commands yield a descriptor with
@@ -82,6 +82,17 @@ export function isCommandAvailable(std, command) {
82
82
  return !std.store.readonly;
83
83
  }
84
84
  }
85
+ /** Shared by the two entry points below: run, then measure, then report. */
86
+ function invokeCommand(std, command, invocation, params) {
87
+ const result = command.run(std, invocation, params);
88
+ // Before the telemetry gate on purpose: usage is measured for EVERY command,
89
+ // including the ones with no `telemetry` field and the self-emitting ones.
90
+ std.getOptional(CommandUsageIdentifier)?.record(command, invocation);
91
+ if (command.telemetry) {
92
+ std.getOptional(CommandTelemetryIdentifier)?.({ std, command, invocation });
93
+ }
94
+ return result;
95
+ }
85
96
  /**
86
97
  * THE bottleneck: the one place a command runs, and therefore the one place
87
98
  * its telemetry is emitted and its usage measured. Every surface (sub-menu,
@@ -89,15 +100,23 @@ export function isCommandAvailable(std, command) {
89
100
  * the per-menu `track()` duplicates.
90
101
  */
91
102
  export function runCommand(std, command, invocation, params) {
92
- const result = command.run(std, invocation, params);
103
+ const result = invokeCommand(std, command, invocation, params);
93
104
  if (result instanceof Promise)
94
105
  result.catch(console.error);
95
- // Before the telemetry gate on purpose: usage is measured for EVERY command,
96
- // including the ones with no `telemetry` field and the self-emitting ones.
97
- std.getOptional(CommandUsageIdentifier)?.record(command, invocation);
98
- if (!command.telemetry)
99
- return;
100
- std.getOptional(CommandTelemetryIdentifier)?.({ std, command, invocation });
106
+ }
107
+ /**
108
+ * {@link runCommand} for a caller that waits: the same bottleneck (usage and
109
+ * telemetry recorded once, synchronously, as there), but the returned promise
110
+ * settles when the command's own work does and REJECTS when it fails, where
111
+ * `runCommand` only logs. For a gesture that shows progress — a menu entry
112
+ * that spins while the canvas export renders, then reports a failure.
113
+ *
114
+ * A separate function rather than a return value on `runCommand`: a promise
115
+ * returned there would read as a floating promise at every existing call site
116
+ * of every host.
117
+ */
118
+ export async function runCommandAsync(std, command, invocation, params) {
119
+ await invokeCommand(std, command, invocation, params);
101
120
  }
102
121
  /**
103
122
  * One of the three permanent projections out of {@link CommandDescriptor} —
package/dist/commands.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { frameCommands } from './_pkgs/affine-block-frame/index.js';
2
2
  import { copyLinkCommands, coreCommands, pivotCommands, readingCommands, tagCommands, } from './_pkgs/affine-block-root/index.js';
3
- import { auditCommands, canvasGridCommands, exportSvgCommands, mapQualityCommands, selectionPaneCommands, userLayerCommands, } from './_pkgs/affine-block-surface/index.js';
3
+ import { auditCommands, canvasExportCommands, canvasGridCommands, exportSvgCommands, mapQualityCommands, selectionPaneCommands, userLayerCommands, } from './_pkgs/affine-block-surface/index.js';
4
4
  import { edgeDirectionCommands } from './_pkgs/affine-gfx-connector/index.js';
5
5
  import { shapeCommands } from './_pkgs/affine-gfx-shape/index.js';
6
6
  import { toCommandManifestEntry, } from './_pkgs/std/index.js';
@@ -58,6 +58,7 @@ export function getCommands(flags) {
58
58
  ...selectionPaneCommands,
59
59
  ...frameCommands,
60
60
  ...canvasGridCommands,
61
+ ...canvasExportCommands,
61
62
  ...userLayerCommands,
62
63
  ...edgeDirectionCommands,
63
64
  ...(isCapabilityEnabled(flags, 'ai-audit') ? auditCommands : []),
@@ -1,6 +1,6 @@
1
1
  import { type FramePreview } from './_pkgs/affine-fragment-frame-panel/index.js';
2
2
  import type { FrameBlockModel } from './_pkgs/affine-model/index.js';
3
- import type { BlockStdScope } from './_pkgs/std/index.js';
3
+ import { type BlockStdScope, type CommandFile } from './_pkgs/std/index.js';
4
4
  /**
5
5
  * `@labre/affine/host-panels` — the verbs a host's own panel calls (ADR 0034
6
6
  * §4, docs/integrate/08-host-panels.md).
@@ -40,14 +40,23 @@ import type { BlockStdScope } from './_pkgs/std/index.js';
40
40
  * cannot find here is a gap in this module, not a reason to import a widget
41
41
  * package.
42
42
  *
43
+ * **The view actions** (amendment of 2026-10-13) are the same idea for a
44
+ * host's MENU rather than its panel: `getViewActions` projects the commands
45
+ * on the `'view-menu'` surface — the grid, the PNG and PDF export, Present —
46
+ * into rows of data (label, icon key, check, disabled), and `runViewAction`
47
+ * runs one and hands back the file an export produced. The host draws the
48
+ * rows (Labre's header ⋯, section "This view"); nothing here renders.
49
+ *
43
50
  * ponytail: `frameList` is a plain read, not a signal of the frame list; a
44
51
  * host follows reorders through `std.store.slots.blockUpdated` (08 says how).
45
52
  * A signal of the frame list is the upgrade path if two hosts ask.
46
53
  */
47
- export { type AnyCommandDescriptor, type CommandInvocation, type CommandOwner, type CommandSurface, getCommandIcon, getCommandsForSurface, getRegisteredCommands, runCommand, } from './_pkgs/std/index.js';
54
+ export { type AnyCommandDescriptor, type CommandFile, type CommandInvocation, type CommandOwner, type CommandSurface, getCommandIcon, getCommandsForSurface, getRegisteredCommands, runCommand, runCommandAsync, } from './_pkgs/std/index.js';
48
55
  export { armArtefact } from './_pkgs/affine-widget-edgeless-toolbar/index.js';
49
56
  export { type SelectionPaneNode, selectionPaneTree, } from './_pkgs/affine-block-surface/index.js';
50
57
  export { frameCommands, type ReorderFramesParams, reorderFramesParams, } from './_pkgs/affine-block-frame/index.js';
58
+ export { formatPropertyValue, isPropertyValueEmpty, } from './_pkgs/affine-fragment-doc-title/index.js';
59
+ export type { PropertiesPanelRequest, PropertiesPanelService, TitleBandNotShownPage, TitleBandProperty, TitleBandService, TitleBandSheetState, } from './_pkgs/affine-shared/services/index.js';
51
60
  /**
52
61
  * The document's frames in presentation order, first slide first — the order
53
62
  * `canvas.frame.reorder` writes and the presentation walks. Works in page mode
@@ -88,3 +97,75 @@ export declare function createFramePreview(std: BlockStdScope, frame: FrameBlock
88
97
  height?: number;
89
98
  fillScreen?: boolean;
90
99
  }): FramePreview;
100
+ /**
101
+ * One entry of the current view's own actions — what a host draws in the
102
+ * "This view" section of its menu (ADR 0034, amendment of 2026-10-13). Data
103
+ * only: the host owns the menu, the row, the spinner and the toast; the
104
+ * library owns what the entry does.
105
+ */
106
+ export interface ViewAction {
107
+ /**
108
+ * The command id, stable across versions (`canvas.grid.toggle`): the key a
109
+ * host maps its own icon, test id or placement to, and what it passes back
110
+ * to {@link runViewAction}.
111
+ */
112
+ id: string;
113
+ /**
114
+ * The group the entry belongs to, from the command's `category`:
115
+ * `'display'` (the grid), `'export'` (PNG, PDF), `'present'`. A host draws
116
+ * a separator between groups, or places a group elsewhere (Labre keeps
117
+ * "Present" in its header). Open: a later minor may add a group.
118
+ */
119
+ section: string;
120
+ /** Resolved through the host's `TranslationExtension`, else English. */
121
+ label: string;
122
+ /** The `com.labre.*` key behind {@link label}, for a host that translates itself. */
123
+ labelKey: string;
124
+ /** Stable asset key; `getCommandIcon(std, iconKey)` is the library's icon. */
125
+ iconKey?: string;
126
+ /** Present only on a toggle: the state a check mark shows. */
127
+ checked?: boolean;
128
+ /**
129
+ * Listed but refused now (a write on a read-only document): draw it
130
+ * disabled. An entry that does not apply at all is not listed.
131
+ */
132
+ disabled: boolean;
133
+ }
134
+ /** What {@link runViewAction} hands back. */
135
+ export interface ViewActionResult {
136
+ /**
137
+ * The file an export produced — the host downloads it, shares it, uploads
138
+ * it. Absent for an action that makes none, and for an export that found
139
+ * nothing to draw.
140
+ */
141
+ file?: CommandFile;
142
+ }
143
+ /**
144
+ * The actions proper to the view the editor shows now, in their declared
145
+ * order: the commands registered on the `'view-menu'` surface whose `scope`
146
+ * is this mode (or `'global'`) and whose `when` holds.
147
+ *
148
+ * `mode` is the view the host shows; omitted, it is read from
149
+ * `DocModeProvider`, and a provider that does not know (the library default
150
+ * answers `null`) leaves the choice to registration — the canvas commands are
151
+ * registered on the edgeless scope only. Flags apply by construction: a
152
+ * `frame: false` assembly registers no "Present", so it is not listed.
153
+ *
154
+ * Reactive when read inside an `effect` or a `computed`: `checked`, `when`
155
+ * and the read-only refusal read signals, so the list follows the grid, a
156
+ * toggle, a peer's save or a document turning read-only. Read it again after a
157
+ * mode switch: the `std` is rebuilt.
158
+ */
159
+ export declare function getViewActions(std: BlockStdScope, mode?: 'page' | 'edgeless'): ViewAction[];
160
+ /**
161
+ * Run one entry of {@link getViewActions} by its id, through `runCommand`'s
162
+ * bottleneck (usage and telemetry recorded once), and wait for it.
163
+ *
164
+ * Resolves when the action is done — after the export has rendered — with the
165
+ * file it produced, if any: the library renders, the host decides where the
166
+ * bytes go, and nothing is downloaded behind its back. Rejects when the action
167
+ * fails, so the host can say so. An id that is not a view action here, an
168
+ * entry that no longer applies or a refused one runs nothing and resolves
169
+ * with no file.
170
+ */
171
+ export declare function runViewAction(std: BlockStdScope, id: string): Promise<ViewActionResult>;
@@ -1,5 +1,7 @@
1
1
  import { EdgelessFrameManagerIdentifier } from './_pkgs/affine-block-frame/index.js';
2
2
  import { AFFINE_FRAME_PREVIEW, } from './_pkgs/affine-fragment-frame-panel/index.js';
3
+ import { DocModeProvider, translateKey } from './_pkgs/affine-shared/services/index.js';
4
+ import { getRegisteredCommands, isCommandAvailable, runCommandAsync, } from './_pkgs/std/index.js';
3
5
  import { GfxControllerIdentifier } from './_pkgs/std/gfx/index.js';
4
6
  /**
5
7
  * `@labre/affine/host-panels` — the verbs a host's own panel calls (ADR 0034
@@ -40,14 +42,32 @@ import { GfxControllerIdentifier } from './_pkgs/std/gfx/index.js';
40
42
  * cannot find here is a gap in this module, not a reason to import a widget
41
43
  * package.
42
44
  *
45
+ * **The view actions** (amendment of 2026-10-13) are the same idea for a
46
+ * host's MENU rather than its panel: `getViewActions` projects the commands
47
+ * on the `'view-menu'` surface — the grid, the PNG and PDF export, Present —
48
+ * into rows of data (label, icon key, check, disabled), and `runViewAction`
49
+ * runs one and hands back the file an export produced. The host draws the
50
+ * rows (Labre's header ⋯, section "This view"); nothing here renders.
51
+ *
43
52
  * ponytail: `frameList` is a plain read, not a signal of the frame list; a
44
53
  * host follows reorders through `std.store.slots.blockUpdated` (08 says how).
45
54
  * A signal of the frame list is the upgrade path if two hosts ask.
46
55
  */
47
- export { getCommandIcon, getCommandsForSurface, getRegisteredCommands, runCommand, } from './_pkgs/std/index.js';
56
+ export { getCommandIcon, getCommandsForSurface, getRegisteredCommands, runCommand, runCommandAsync, } from './_pkgs/std/index.js';
48
57
  export { armArtefact } from './_pkgs/affine-widget-edgeless-toolbar/index.js';
49
58
  export { selectionPaneTree, } from './_pkgs/affine-block-surface/index.js';
50
59
  export { frameCommands, reorderFramesParams, } from './_pkgs/affine-block-frame/index.js';
60
+ /*
61
+ * The properties panel (ADR 0035 §9b and §11). The editor asks the host to
62
+ * show it through `PropertiesPanelExtension` and feeds the title band through
63
+ * `TitleBandExtension` (both registered from `@labre/affine/shared/services`,
64
+ * docs/integrate/04-host-seams.md); a host's own panel reads and writes the
65
+ * same sheet it hands the band, from its own store. What it takes from here
66
+ * is the band's vocabulary, so the panel and the chips read the same: the
67
+ * types of a sheet and of a panel request, and the text a chip draws for a
68
+ * value.
69
+ */
70
+ export { formatPropertyValue, isPropertyValueEmpty, } from './_pkgs/affine-fragment-doc-title/index.js';
51
71
  /**
52
72
  * The document's frames in presentation order, first slide first — the order
53
73
  * `canvas.frame.reorder` writes and the presentation walks. Works in page mode
@@ -109,3 +129,64 @@ export function createFramePreview(std, frame, options = {}) {
109
129
  preview.fillScreen = options.fillScreen;
110
130
  return preview;
111
131
  }
132
+ /**
133
+ * The actions proper to the view the editor shows now, in their declared
134
+ * order: the commands registered on the `'view-menu'` surface whose `scope`
135
+ * is this mode (or `'global'`) and whose `when` holds.
136
+ *
137
+ * `mode` is the view the host shows; omitted, it is read from
138
+ * `DocModeProvider`, and a provider that does not know (the library default
139
+ * answers `null`) leaves the choice to registration — the canvas commands are
140
+ * registered on the edgeless scope only. Flags apply by construction: a
141
+ * `frame: false` assembly registers no "Present", so it is not listed.
142
+ *
143
+ * Reactive when read inside an `effect` or a `computed`: `checked`, `when`
144
+ * and the read-only refusal read signals, so the list follows the grid, a
145
+ * toggle, a peer's save or a document turning read-only. Read it again after a
146
+ * mode switch: the `std` is rebuilt.
147
+ */
148
+ export function getViewActions(std, mode) {
149
+ const view = mode ?? std.getOptional(DocModeProvider)?.getEditorMode();
150
+ return getRegisteredCommands(std)
151
+ .filter(command => command.surfaces.includes('view-menu'))
152
+ .filter(command => command.scope === 'global' || !view || command.scope === view)
153
+ .filter(command => !command.when || command.when(std))
154
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
155
+ .map(command => ({
156
+ id: command.id,
157
+ section: command.category ?? 'display',
158
+ label: translateKey(std, command.labelKey, command.labelFallback),
159
+ labelKey: command.labelKey,
160
+ ...(command.iconKey ? { iconKey: command.iconKey } : {}),
161
+ ...(command.checked ? { checked: command.checked(std) } : {}),
162
+ disabled: !isCommandAvailable(std, command),
163
+ }));
164
+ }
165
+ /**
166
+ * Run one entry of {@link getViewActions} by its id, through `runCommand`'s
167
+ * bottleneck (usage and telemetry recorded once), and wait for it.
168
+ *
169
+ * Resolves when the action is done — after the export has rendered — with the
170
+ * file it produced, if any: the library renders, the host decides where the
171
+ * bytes go, and nothing is downloaded behind its back. Rejects when the action
172
+ * fails, so the host can say so. An id that is not a view action here, an
173
+ * entry that no longer applies or a refused one runs nothing and resolves
174
+ * with no file.
175
+ */
176
+ export async function runViewAction(std, id) {
177
+ const command = getRegisteredCommands(std).find(candidate => candidate.id === id && candidate.surfaces.includes('view-menu'));
178
+ if (!command)
179
+ return {};
180
+ if (command.when && !command.when(std))
181
+ return {};
182
+ if (!isCommandAvailable(std, command))
183
+ return {};
184
+ const result = {};
185
+ const params = {
186
+ deliver: file => {
187
+ result.file = file;
188
+ },
189
+ };
190
+ await runCommandAsync(std, command, { surface: 'view-menu', source: 'context-menu' }, params);
191
+ return result;
192
+ }
@@ -231,7 +231,7 @@ const CHROME_KEYS = [
231
231
  // person who picked it.
232
232
  [
233
233
  'com.labre.interchange.import.readonly',
234
- 'This document became read-only while the file was being read, so nothing was imported.',
234
+ 'This page became read-only while the file was being read, so nothing was imported.',
235
235
  ],
236
236
  [
237
237
  'com.labre.interchange.import.remarks',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-core",
3
3
  "description": "Labre editor — the full BlockSuite-derived editor as one package.",
4
- "version": "0.46.2",
4
+ "version": "0.47.1",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -1,26 +0,0 @@
1
- /** Tools for exporting files to device. For example, via browser download. */
2
- export declare const FileExporter: {
3
- /**
4
- * Create a download for the user's browser.
5
- *
6
- * @param filename
7
- * @param text
8
- * @param mimeType like `"text/plain"`, `"text/html"`, `"application/javascript"`, etc. See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types mdn docs List of MIME types}.
9
- *
10
- * @remarks
11
- * Only accepts data in utf-8 encoding (html files, javascript source, text files, etc).
12
- *
13
- * @example
14
- * const todoMDText = `# Todo items
15
- * [ ] Item 1
16
- * [ ] Item 2
17
- * `
18
- * FileExporter.exportFile("Todo list.md", todoMDText, "text/plain")
19
- *
20
- * @example
21
- * const stateJsonContent = JSON.stringify({ a: 1, b: 2, c: 3 })
22
- * FileExporter.exportFile("state.json", jsonContent, "application/json")
23
- */
24
- exportFile(filename: string, dataURL: string): void;
25
- exportPng(docTitle: string | undefined, dataURL: string): void;
26
- };
@@ -1,73 +0,0 @@
1
- /* oxlint-disable no-control-regex */
2
- // Context: Lean towards breaking out any localizable content into constants so it's
3
- // easier to track content we may need to localize in the future. (i18n)
4
- const UNTITLED_PAGE_NAME = 'Untitled';
5
- /** Tools for exporting files to device. For example, via browser download. */
6
- export const FileExporter = {
7
- /**
8
- * Create a download for the user's browser.
9
- *
10
- * @param filename
11
- * @param text
12
- * @param mimeType like `"text/plain"`, `"text/html"`, `"application/javascript"`, etc. See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types mdn docs List of MIME types}.
13
- *
14
- * @remarks
15
- * Only accepts data in utf-8 encoding (html files, javascript source, text files, etc).
16
- *
17
- * @example
18
- * const todoMDText = `# Todo items
19
- * [ ] Item 1
20
- * [ ] Item 2
21
- * `
22
- * FileExporter.exportFile("Todo list.md", todoMDText, "text/plain")
23
- *
24
- * @example
25
- * const stateJsonContent = JSON.stringify({ a: 1, b: 2, c: 3 })
26
- * FileExporter.exportFile("state.json", jsonContent, "application/json")
27
- */
28
- exportFile(filename, dataURL) {
29
- const element = document.createElement('a');
30
- element.setAttribute('href', dataURL);
31
- const safeFilename = getSafeFileName(filename);
32
- element.setAttribute('download', safeFilename);
33
- element.style.display = 'none';
34
- document.body.append(element);
35
- element.click();
36
- element.remove();
37
- },
38
- exportPng(docTitle, dataURL) {
39
- const title = docTitle?.trim() || UNTITLED_PAGE_NAME;
40
- FileExporter.exportFile(title + '.png', dataURL);
41
- },
42
- };
43
- function getSafeFileName(string) {
44
- const replacement = ' ';
45
- const filenameReservedRegex = /[<>:"/\\|?*\u0000-\u001F]/g;
46
- const windowsReservedNameRegex = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
47
- const reControlChars = /[\u0000-\u001F\u0080-\u009F]/g;
48
- const reTrailingPeriods = /\.+$/;
49
- const allowedLength = 50;
50
- function trimRepeated(string, target) {
51
- const escapeStringRegexp = target
52
- .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
53
- .replace(/-/g, '\\x2d');
54
- const regex = new RegExp(`(?:${escapeStringRegexp}){2,}`, 'g');
55
- return string.replace(regex, target);
56
- }
57
- string = string
58
- .normalize('NFD')
59
- .replace(filenameReservedRegex, replacement)
60
- .replace(reControlChars, replacement)
61
- .replace(reTrailingPeriods, '');
62
- string = trimRepeated(string, replacement);
63
- string = windowsReservedNameRegex.test(string)
64
- ? string + replacement
65
- : string;
66
- const extIndex = string.lastIndexOf('.');
67
- const filename = string.slice(0, extIndex).trim();
68
- const extension = string.slice(extIndex);
69
- string =
70
- filename.slice(0, Math.max(1, allowedLength - extension.length)) +
71
- extension;
72
- return string;
73
- }