@formicoidea/labre-core 0.45.0 → 0.46.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 (73) hide show
  1. package/dist/_pkgs/affine-block-callout/commands/turn-callout-into.d.ts +43 -0
  2. package/dist/_pkgs/affine-block-callout/commands/turn-callout-into.js +109 -0
  3. package/dist/_pkgs/affine-block-callout/commands/turn-into-callout.d.ts +1 -1
  4. package/dist/_pkgs/affine-block-callout/commands/turn-into-callout.js +1 -1
  5. package/dist/_pkgs/affine-block-callout/configs/slash-menu.js +4 -2
  6. package/dist/_pkgs/affine-block-callout/configs/toolbar.d.ts +65 -0
  7. package/dist/_pkgs/affine-block-callout/configs/toolbar.js +178 -0
  8. package/dist/_pkgs/affine-block-callout/view.d.ts +30 -1
  9. package/dist/_pkgs/affine-block-callout/view.js +45 -3
  10. package/dist/_pkgs/affine-block-code/code-toolbar/components/lang-button.js +1 -1
  11. package/dist/_pkgs/affine-block-frame/commands.d.ts +27 -0
  12. package/dist/_pkgs/affine-block-frame/commands.js +49 -0
  13. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +11 -6
  14. package/dist/_pkgs/affine-block-frame/frame-manager.js +10 -5
  15. package/dist/_pkgs/affine-block-frame/frame-order.d.ts +43 -0
  16. package/dist/_pkgs/affine-block-frame/frame-order.js +93 -0
  17. package/dist/_pkgs/affine-block-frame/index.d.ts +2 -0
  18. package/dist/_pkgs/affine-block-frame/index.js +2 -0
  19. package/dist/_pkgs/affine-block-frame/present/frame-order-menu.d.ts +10 -2
  20. package/dist/_pkgs/affine-block-frame/present/frame-order-menu.js +32 -18
  21. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  22. package/dist/_pkgs/affine-block-note/configs/toolbar.d.ts +19 -0
  23. package/dist/_pkgs/affine-block-note/configs/toolbar.js +38 -23
  24. package/dist/_pkgs/affine-block-note/translations.d.ts +2 -0
  25. package/dist/_pkgs/affine-block-note/translations.js +14 -1
  26. package/dist/_pkgs/affine-block-root/configs/toolbar.js +31 -2
  27. package/dist/_pkgs/affine-block-root/translations.d.ts +7 -2
  28. package/dist/_pkgs/affine-block-root/translations.js +10 -13
  29. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/actions.d.ts +20 -9
  30. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/actions.js +57 -20
  31. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.d.ts +6 -8
  32. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/commands.js +21 -8
  33. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/index.d.ts +1 -1
  34. package/dist/_pkgs/affine-block-surface/extensions/selection-pane/index.js +1 -1
  35. package/dist/_pkgs/affine-components/portal/portal.d.ts +1 -1
  36. package/dist/_pkgs/affine-foundation/view.d.ts +8 -8
  37. package/dist/_pkgs/affine-fragment-frame-panel/body/frame-panel-body.d.ts +34 -2
  38. package/dist/_pkgs/affine-fragment-frame-panel/body/frame-panel-body.js +194 -81
  39. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card.d.ts +7 -4
  40. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card.js +9 -24
  41. package/dist/_pkgs/affine-fragment-frame-panel/index.d.ts +1 -0
  42. package/dist/_pkgs/affine-fragment-frame-panel/index.js +1 -0
  43. package/dist/_pkgs/affine-model/utils/types.d.ts +2 -2
  44. package/dist/_pkgs/affine-shared/services/edit-props-store.d.ts +4 -4
  45. package/dist/_pkgs/affine-shared/services/font-loader/config.d.ts +2 -2
  46. package/dist/_pkgs/affine-shared/services/font-loader/font-loader-service.d.ts +3 -3
  47. package/dist/_pkgs/affine-shared/services/index.d.ts +1 -1
  48. package/dist/_pkgs/affine-shared/services/index.js +1 -1
  49. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-storage.d.ts +4 -4
  50. package/dist/_pkgs/affine-shared/services/outline-panel-service.d.ts +31 -0
  51. package/dist/_pkgs/affine-shared/services/outline-panel-service.js +17 -0
  52. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +11 -0
  53. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +19 -0
  54. package/dist/_pkgs/affine-shared/utils/index.d.ts +1 -0
  55. package/dist/_pkgs/affine-shared/utils/index.js +1 -0
  56. package/dist/_pkgs/affine-shared/utils/panel-reorder-drag.d.ts +85 -0
  57. package/dist/_pkgs/affine-shared/utils/panel-reorder-drag.js +162 -0
  58. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/drop.d.ts +7 -6
  59. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/drop.js +11 -8
  60. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/selection-pane-widget.d.ts +31 -15
  61. package/dist/_pkgs/affine-widget-edgeless-toolbar/selection-pane/selection-pane-widget.js +178 -142
  62. package/dist/_pkgs/std/scope/std-scope.d.ts +1 -1
  63. package/dist/commands.js +2 -0
  64. package/dist/extensions/view.js +5 -1
  65. package/dist/flags.d.ts +3 -0
  66. package/dist/flags.js +3 -0
  67. package/dist/host-panels.d.ts +90 -0
  68. package/dist/host-panels.js +111 -0
  69. package/package.json +5 -1
  70. package/dist/_pkgs/affine-fragment-frame-panel/utils/drag.d.ts +0 -27
  71. package/dist/_pkgs/affine-fragment-frame-panel/utils/drag.js +0 -96
  72. package/dist/_pkgs/affine-shared/services/sidebar-service.d.ts +0 -8
  73. package/dist/_pkgs/affine-shared/services/sidebar-service.js +0 -7
@@ -24,9 +24,9 @@ export declare class EdgelessFrameManager extends GfxExtension {
24
24
  private readonly _pendingAdoptions;
25
25
  private _pendingAdoptionTicks;
26
26
  /**
27
- * Get all sorted frames by presentation orderer,
28
- * the legacy frame that uses `index` as presentation order
29
- * will be put at the beginning of the array.
27
+ * Every frame, in presentation order (`framePresentationComparator`): by
28
+ * `presentationIndex`, and the legacy frames that have none AFTER the keyed
29
+ * ones, ordered among themselves by their stacking `index`.
30
30
  */
31
31
  get frames(): FrameBlockModel[];
32
32
  constructor(gfx: GfxController);
@@ -35,7 +35,7 @@ export declare class EdgelessFrameManager extends GfxExtension {
35
35
  index: string;
36
36
  presentationIndex?: string;
37
37
  };
38
- }>(a: T, b: T): 0 | 1 | -1;
38
+ }>(a: T, b: T): 1 | 0 | -1;
39
39
  private _addChildrenToLegacyFrame;
40
40
  private _addFrameBlock;
41
41
  /**
@@ -124,8 +124,13 @@ export declare class EdgelessFrameManager extends GfxExtension {
124
124
  getFrameFromPoint([x, y]: IVec, ignoreFrames?: FrameBlockModel[]): FrameBlockModel | null;
125
125
  getParentFrame(element: GfxModel): FrameBlockModel | null;
126
126
  /**
127
- * This method will populate `presentationIndex` for all legacy frames,
128
- * and keep the orderer of the legacy frames.
127
+ * Give a `presentationIndex` to legacy frames, keeping their order — but
128
+ * only on a document where NO frame has one yet. The comparator sorts
129
+ * keyless frames AFTER keyed ones, so as soon as one frame is keyed the first
130
+ * frame is keyed and this returns at once: a mix of keyed and keyless frames
131
+ * is left as it is. In practice a loaded frame always carries a key (the
132
+ * schema's default is written on load), so this only acts on a hand-built
133
+ * store. Idempotent; writes through the props proxy.
129
134
  */
130
135
  refreshLegacyFrameOrder(): void;
131
136
  removeAllChildrenFromFrame(frame: FrameBlockModel): void;
@@ -90,9 +90,9 @@ export const EdgelessFrameManagerIdentifier = GfxExtensionIdentifier('frame-mana
90
90
  export class EdgelessFrameManager extends GfxExtension {
91
91
  static { this.key = 'frame-manager'; }
92
92
  /**
93
- * Get all sorted frames by presentation orderer,
94
- * the legacy frame that uses `index` as presentation order
95
- * will be put at the beginning of the array.
93
+ * Every frame, in presentation order (`framePresentationComparator`): by
94
+ * `presentationIndex`, and the legacy frames that have none AFTER the keyed
95
+ * ones, ordered among themselves by their stacking `index`.
96
96
  */
97
97
  get frames() {
98
98
  return Object.values(this.gfx.doc.blocks.value)
@@ -484,8 +484,13 @@ export class EdgelessFrameManager extends GfxExtension {
484
484
  return container && isFrameBlock(container) ? container : null;
485
485
  }
486
486
  /**
487
- * This method will populate `presentationIndex` for all legacy frames,
488
- * and keep the orderer of the legacy frames.
487
+ * Give a `presentationIndex` to legacy frames, keeping their order — but
488
+ * only on a document where NO frame has one yet. The comparator sorts
489
+ * keyless frames AFTER keyed ones, so as soon as one frame is keyed the first
490
+ * frame is keyed and this returns at once: a mix of keyed and keyless frames
491
+ * is left as it is. In practice a loaded frame always carries a key (the
492
+ * schema's default is written on load), so this only acts on a hand-built
493
+ * store. Idempotent; writes through the props proxy.
489
494
  */
490
495
  refreshLegacyFrameOrder() {
491
496
  const frames = this.frames.splice(0, this.frames.length);
@@ -0,0 +1,43 @@
1
+ import type { BlockStdScope } from '../std/index.js';
2
+ /**
3
+ * The one write of the frames' PRESENTATION order (ADR 0034): the
4
+ * order a presentation walks the frames in, held by each frame's
5
+ * `presentationIndex` — a fractional key, never a position.
6
+ *
7
+ * Why one function: two sites wrote that order by hand — the frame panel's
8
+ * drag and the presentation toolbar's order menu — each its own way, neither
9
+ * refusing a read-only document, and the panel taking its undo checkpoint
10
+ * AFTER the write, so the reorder merged into whatever gesture came before.
11
+ * Both now reach this through `canvas.frame.reorder`, and so does a host's own
12
+ * slide panel: the guards below live here, once.
13
+ *
14
+ * Mirrors the selection pane's `reorderPaneElement`
15
+ * (`blocks/surface/src/extensions/selection-pane/actions.ts`):
16
+ *
17
+ * - refuses on a read-only document before reading anything else;
18
+ * - writes nothing when nothing would change — an unchanged write is an empty
19
+ * undo step, and an undo that "does nothing" reads as a broken undo;
20
+ * - takes ONE `captureSync()` first, so one gesture is one undo step, the
21
+ * legacy keys `refreshLegacyFrameOrder` may write included (it writes in
22
+ * the same capture window, so it joins the gesture's step);
23
+ * - writes through `gfx.updateElement`, which is `store.updateBlock` for a
24
+ * frame — not the CRUD `updateElement`, which would also record the moved
25
+ * frame's props as the flavour's last-used props.
26
+ */
27
+ /**
28
+ * Move the frames `ids` so they sit, as one block, directly BEFORE `before` in
29
+ * the presentation order — `null` puts them at the end. The moved frames keep
30
+ * their current relative order, whatever order `ids` lists them in. Answers
31
+ * whether anything was written.
32
+ *
33
+ * Refused, with nothing written: a read-only document; an id that is not a
34
+ * frame of this document; a `before` that is one of the moved frames, or not
35
+ * a frame; and a target that is where the frames already are.
36
+ *
37
+ * Each moved frame gets a key between its new neighbours, so only the moved
38
+ * frames are written and nothing else shifts. Two neighbours with equal (or
39
+ * inverted) keys cannot be split — `generateKeyBetweenV2` throws on them, and
40
+ * the model's default key is random per frame, so a pasted, imported or
41
+ * hand-made document can carry them: refusing beats throwing in a drop.
42
+ */
43
+ export declare function reorderFramePresentation(std: BlockStdScope, ids: readonly string[], before: string | null): boolean;
@@ -0,0 +1,93 @@
1
+ import { generateKeyBetweenV2, GfxControllerIdentifier } from '../std/gfx/index.js';
2
+ import { EdgelessFrameManagerIdentifier } from './frame-manager.js';
3
+ /**
4
+ * The one write of the frames' PRESENTATION order (ADR 0034): the
5
+ * order a presentation walks the frames in, held by each frame's
6
+ * `presentationIndex` — a fractional key, never a position.
7
+ *
8
+ * Why one function: two sites wrote that order by hand — the frame panel's
9
+ * drag and the presentation toolbar's order menu — each its own way, neither
10
+ * refusing a read-only document, and the panel taking its undo checkpoint
11
+ * AFTER the write, so the reorder merged into whatever gesture came before.
12
+ * Both now reach this through `canvas.frame.reorder`, and so does a host's own
13
+ * slide panel: the guards below live here, once.
14
+ *
15
+ * Mirrors the selection pane's `reorderPaneElement`
16
+ * (`blocks/surface/src/extensions/selection-pane/actions.ts`):
17
+ *
18
+ * - refuses on a read-only document before reading anything else;
19
+ * - writes nothing when nothing would change — an unchanged write is an empty
20
+ * undo step, and an undo that "does nothing" reads as a broken undo;
21
+ * - takes ONE `captureSync()` first, so one gesture is one undo step, the
22
+ * legacy keys `refreshLegacyFrameOrder` may write included (it writes in
23
+ * the same capture window, so it joins the gesture's step);
24
+ * - writes through `gfx.updateElement`, which is `store.updateBlock` for a
25
+ * frame — not the CRUD `updateElement`, which would also record the moved
26
+ * frame's props as the flavour's last-used props.
27
+ */
28
+ /**
29
+ * Move the frames `ids` so they sit, as one block, directly BEFORE `before` in
30
+ * the presentation order — `null` puts them at the end. The moved frames keep
31
+ * their current relative order, whatever order `ids` lists them in. Answers
32
+ * whether anything was written.
33
+ *
34
+ * Refused, with nothing written: a read-only document; an id that is not a
35
+ * frame of this document; a `before` that is one of the moved frames, or not
36
+ * a frame; and a target that is where the frames already are.
37
+ *
38
+ * Each moved frame gets a key between its new neighbours, so only the moved
39
+ * frames are written and nothing else shifts. Two neighbours with equal (or
40
+ * inverted) keys cannot be split — `generateKeyBetweenV2` throws on them, and
41
+ * the model's default key is random per frame, so a pasted, imported or
42
+ * hand-made document can carry them: refusing beats throwing in a drop.
43
+ */
44
+ export function reorderFramePresentation(std, ids, before) {
45
+ if (std.store.readonly)
46
+ return false;
47
+ const manager = std.get(EdgelessFrameManagerIdentifier);
48
+ const frames = manager.frames;
49
+ const moving = new Set(ids);
50
+ if (moving.size === 0)
51
+ return false;
52
+ if (before !== null && moving.has(before))
53
+ return false;
54
+ const known = new Set(frames.map(frame => frame.id));
55
+ for (const id of moving)
56
+ if (!known.has(id))
57
+ return false;
58
+ if (before !== null && !known.has(before))
59
+ return false;
60
+ // `frames` is already in presentation order, so filtering keeps the moved
61
+ // frames' relative order.
62
+ const moved = frames.filter(frame => moving.has(frame.id));
63
+ const rest = frames.filter(frame => !moving.has(frame.id));
64
+ const position = before === null
65
+ ? rest.length
66
+ : rest.findIndex(frame => frame.id === before);
67
+ const target = [
68
+ ...rest.slice(0, position),
69
+ ...moved,
70
+ ...rest.slice(position),
71
+ ];
72
+ if (target.every((frame, i) => frame === frames[i]))
73
+ return false;
74
+ std.store.captureSync();
75
+ // Gives a key to legacy frames that have none, keeping their order, so the
76
+ // neighbours' keys below mean what the list shows. A no-op on any document
77
+ // where a frame already carries one.
78
+ manager.refreshLegacyFrameOrder();
79
+ const lower = rest[position - 1]?.props.presentationIndex || null;
80
+ const upper = rest[position]?.props.presentationIndex || null;
81
+ if (lower !== null && upper !== null && lower >= upper)
82
+ return false;
83
+ const gfx = std.get(GfxControllerIdentifier);
84
+ std.store.transact(() => {
85
+ let previous = lower;
86
+ for (const frame of moved) {
87
+ const presentationIndex = generateKeyBetweenV2(previous, upper);
88
+ gfx.updateElement(frame, { presentationIndex });
89
+ previous = presentationIndex;
90
+ }
91
+ });
92
+ return true;
93
+ }
@@ -1,8 +1,10 @@
1
+ export * from './commands.js';
1
2
  export * from './edgeless-clipboard-config.js';
2
3
  export * from './edgeless-toolbar/index.js';
3
4
  export * from './frame-block.js';
4
5
  export * from './frame-highlight-manager.js';
5
6
  export * from './frame-manager.js';
7
+ export * from './frame-order.js';
6
8
  export * from './frame-spec.js';
7
9
  export * from './frame-tool.js';
8
10
  export * from './frame-toolbar.js';
@@ -1,8 +1,10 @@
1
+ export * from './commands.js';
1
2
  export * from './edgeless-clipboard-config.js';
2
3
  export * from './edgeless-toolbar/index.js';
3
4
  export * from './frame-block.js';
4
5
  export * from './frame-highlight-manager.js';
5
6
  export * from './frame-manager.js';
7
+ export * from './frame-order.js';
6
8
  export * from './frame-spec.js';
7
9
  export * from './frame-tool.js';
8
10
  export * from './frame-toolbar.js';
@@ -1,9 +1,17 @@
1
- import type { BlockComponent } from '../../std/index.js';
1
+ import { type BlockComponent } from '../../std/index.js';
2
2
  import { LitElement } from 'lit';
3
3
  declare const EdgelessFrameOrderMenu_base: typeof LitElement & import("../../global/utils/types.js").Constructor<import("../../global/lit/with-disposable.js").DisposableClass>;
4
+ /**
5
+ * The presentation toolbar's order menu. Its WRITE is `canvas.frame.reorder`
6
+ * (ADR 0034); its drag gesture is its own.
7
+ *
8
+ * ponytail: a drag-to-reorder gesture of its own, beside the frame panel's and
9
+ * the selection pane's — its ceiling is a 256px popover, which the panels' one
10
+ * controller (`createPanelReorderDrag`, ADR 0034) is not built for. If the
11
+ * menu ever becomes a panel, move it onto that controller and delete this one.
12
+ */
4
13
  export declare class EdgelessFrameOrderMenu extends EdgelessFrameOrderMenu_base {
5
14
  static styles: import("lit").CSSResult;
6
- get crud(): import("../../affine-block-surface/index.js").EdgelessCRUDExtension;
7
15
  private get _frameMgr();
8
16
  private get _frames();
9
17
  private _bindEvent;
@@ -32,14 +32,37 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { EdgelessCRUDIdentifier } from '../../affine-block-surface/index.js';
36
35
  import { DisposableGroup } from '../../global/disposable/index.js';
37
36
  import { SignalWatcher, WithDisposable } from '../../global/lit/index.js';
38
- import { generateKeyBetweenV2 } from '../../std/gfx/index.js';
37
+ import { runCommand, } from '../../std/index.js';
39
38
  import { css, html, LitElement, nothing } from 'lit';
40
39
  import { property, query, state } from 'lit/decorators.js';
41
40
  import { repeat } from 'lit/directives/repeat.js';
41
+ import { frameCommands } from '../commands.js';
42
42
  import { EdgelessFrameManagerIdentifier } from '../frame-manager.js';
43
+ /**
44
+ * How the menu invokes the registry: from the editor's chrome, on the frames
45
+ * it names, like the selection pane's rows.
46
+ */
47
+ const MENU_INVOCATION = {
48
+ surface: 'contextual-toolbar',
49
+ source: 'toolbar:general',
50
+ };
51
+ function reorderCommand() {
52
+ const command = frameCommands.find(c => c.id === 'canvas.frame.reorder');
53
+ if (!command)
54
+ throw new Error('frame order menu: no canvas.frame.reorder');
55
+ return command;
56
+ }
57
+ /**
58
+ * The presentation toolbar's order menu. Its WRITE is `canvas.frame.reorder`
59
+ * (ADR 0034); its drag gesture is its own.
60
+ *
61
+ * ponytail: a drag-to-reorder gesture of its own, beside the frame panel's and
62
+ * the selection pane's — its ceiling is a 256px popover, which the panels' one
63
+ * controller (`createPanelReorderDrag`, ADR 0034) is not built for. If the
64
+ * menu ever becomes a panel, move it onto that controller and delete this one.
65
+ */
43
66
  let EdgelessFrameOrderMenu = (() => {
44
67
  let _classSuper = SignalWatcher(WithDisposable(LitElement));
45
68
  let __clone_decorators;
@@ -161,9 +184,6 @@ let EdgelessFrameOrderMenu = (() => {
161
184
  width: 90%;
162
185
  }
163
186
  `; }
164
- get crud() {
165
- return this.edgeless.std.get(EdgelessCRUDIdentifier);
166
- }
167
187
  get _frameMgr() {
168
188
  return this.edgeless.std.get(EdgelessFrameManagerIdentifier);
169
189
  }
@@ -225,20 +245,14 @@ let EdgelessFrameOrderMenu = (() => {
225
245
  this._disposables.addFromEvent(document, 'pointerup', () => {
226
246
  clone.style.visibility = 'hidden';
227
247
  indicatorLine.style.visibility = 'hidden';
228
- if (newIndex >= 0 &&
229
- newIndex <= this._frames.length &&
230
- newIndex !== index &&
231
- newIndex !== index + 1) {
232
- const frameMgr = this._frameMgr;
233
- // Legacy compatibility
234
- frameMgr.refreshLegacyFrameOrder();
235
- const before = this._frames[newIndex - 1]?.props.presentationIndex || null;
236
- const after = this._frames[newIndex]?.props.presentationIndex || null;
237
- const frame = this._frames[index];
238
- this.crud.updateElement(frame.id, {
239
- presentationIndex: generateKeyBetweenV2(before, after),
248
+ const frames = this._frames;
249
+ // A drop on its own slot (before itself) or on the next one is refused
250
+ // by the command: nothing is written, no undo step is pushed.
251
+ if (newIndex >= 0 && newIndex <= frames.length && frames[index]) {
252
+ runCommand(this.edgeless.std, reorderCommand(), MENU_INVOCATION, {
253
+ ids: [frames[index].id],
254
+ before: frames[newIndex]?.id ?? null,
240
255
  });
241
- this.edgeless.store.captureSync();
242
256
  this.requestUpdate();
243
257
  }
244
258
  this._disposables.dispose();
@@ -1,4 +1,6 @@
1
1
  import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
2
+ import { CommandExtension } from '../std/index.js';
3
+ import { frameCommands } from './commands.js';
2
4
  import { EdgelessClipboardFrameConfig } from './edgeless-clipboard-config.js';
3
5
  import { frameQuickTool } from './edgeless-toolbar/index.js';
4
6
  import { effects } from './effects.js';
@@ -30,6 +32,10 @@ export class FrameViewExtension extends ViewExtensionProvider {
30
32
  context.register(edgelessNavigatorBgWidget);
31
33
  context.register(EdgelessClipboardFrameConfig);
32
34
  context.register(FrameBlockInteraction);
35
+ // The presentation order's one write (ADR 0034). The frame panel also
36
+ // mounts in page mode: it runs the imported descriptor, not this
37
+ // registration, so its reorder works in both modes.
38
+ context.register(CommandExtension(frameCommands));
33
39
  }
34
40
  }
35
41
  }
@@ -1,2 +1,21 @@
1
+ import { NoteDisplayMode } from '../../affine-model/index.js';
2
+ import { type BlockStdScope } from '../../std/index.js';
1
3
  import type { ExtensionType } from '../../store/index.js';
4
+ /**
5
+ * The toast shown after a note's display mode changes. Its "View in TOC" link
6
+ * is offered only when the host registered an outline panel
7
+ * (`OutlinePanelExtension`, ADR 0034 §1): a link that opens nothing is a lie,
8
+ * so a host with no outline, or one that passed `null`, gets a toast with no
9
+ * link, and a body without the "Find it in the TOC" sentence that points at
10
+ * it.
11
+ */
12
+ export declare function displayModeToast(std: BlockStdScope, newMode: NoteDisplayMode): {
13
+ title: string;
14
+ message: string;
15
+ actions: {
16
+ key: string;
17
+ label: string;
18
+ onClick: () => void;
19
+ }[];
20
+ };
2
21
  export declare const createBuiltinToolbarConfigExtension: (flavour: string) => ExtensionType[];
@@ -1,6 +1,6 @@
1
1
  import { EdgelessLegacySlotIdentifier } from '../../affine-block-surface/index.js';
2
2
  import { NoteBlockModel, NoteDisplayMode } from '../../affine-model/index.js';
3
- import { NotificationProvider, SidebarExtensionIdentifier, TOAST_NOTE_REMOVED_FROM_PAGE, ToolbarModuleExtension, translateKey, } from '../../affine-shared/services/index.js';
3
+ import { NotificationProvider, OutlinePanelProvider, TOAST_NOTE_REMOVED_FROM_PAGE, ToolbarModuleExtension, translateKey, } from '../../affine-shared/services/index.js';
4
4
  import { Bound } from '../../global/gfx/index.js';
5
5
  import { AutoHeightIcon, CustomizedHeightIcon, InsertIntoPageIcon, ScissorsIcon, } from '@blocksuite/icons/lit';
6
6
  import { BlockFlavourIdentifier } from '../../std/index.js';
@@ -8,7 +8,7 @@ import { computed } from '@preact/signals-core';
8
8
  import { html } from 'lit';
9
9
  import { changeNoteDisplayMode } from '../commands/index.js';
10
10
  import { NoteConfigExtension } from '../config.js';
11
- import { NOTE_TOAST_ADDED_TO_PAGE_BODY, NOTE_TOAST_DISPLAYED_IN_PAGE_MODE, NOTE_TOAST_REMOVED_FROM_PAGE_BODY, NOTE_TOAST_VIEW_IN_TOC, NOTE_TOOLBAR_AUTO_HEIGHT, NOTE_TOOLBAR_CUSTOMIZED_HEIGHT, NOTE_TOOLBAR_CUTTING_MODE, NOTE_TOOLBAR_DISPLAY_IN_PAGE, NOTE_TOOLBAR_DISPLAYED_IN_PAGE, NOTE_TOOLBAR_REMOVE_FROM_PAGE_TOOLTIP, NOTE_TOOLBAR_SIZE, NOTE_TOOLBAR_SLICER, } from '../translations.js';
11
+ import { NOTE_TOAST_ADDED_TO_PAGE_BODY, NOTE_TOAST_ADDED_TO_PAGE_BODY_NO_TOC, NOTE_TOAST_DISPLAYED_IN_PAGE_MODE, NOTE_TOAST_REMOVED_FROM_PAGE_BODY, NOTE_TOAST_REMOVED_FROM_PAGE_BODY_NO_TOC, NOTE_TOAST_VIEW_IN_TOC, NOTE_TOOLBAR_AUTO_HEIGHT, NOTE_TOOLBAR_CUSTOMIZED_HEIGHT, NOTE_TOOLBAR_CUTTING_MODE, NOTE_TOOLBAR_DISPLAY_IN_PAGE, NOTE_TOOLBAR_DISPLAYED_IN_PAGE, NOTE_TOOLBAR_REMOVE_FROM_PAGE_TOOLTIP, NOTE_TOOLBAR_SIZE, NOTE_TOOLBAR_SLICER, } from '../translations.js';
12
12
  const trackBaseProps = {
13
13
  category: 'note',
14
14
  };
@@ -238,6 +238,41 @@ const builtinSurfaceToolbarConfig = {
238
238
  ],
239
239
  when: ctx => ctx.getSurfaceModelsByType(NoteBlockModel).length > 0,
240
240
  };
241
+ /**
242
+ * The toast shown after a note's display mode changes. Its "View in TOC" link
243
+ * is offered only when the host registered an outline panel
244
+ * (`OutlinePanelExtension`, ADR 0034 §1): a link that opens nothing is a lie,
245
+ * so a host with no outline, or one that passed `null`, gets a toast with no
246
+ * link, and a body without the "Find it in the TOC" sentence that points at
247
+ * it.
248
+ */
249
+ export function displayModeToast(std, newMode) {
250
+ const outline = std.getOptional(OutlinePanelProvider);
251
+ const removed = newMode === NoteDisplayMode.EdgelessOnly;
252
+ const title = removed
253
+ ? TOAST_NOTE_REMOVED_FROM_PAGE
254
+ : NOTE_TOAST_DISPLAYED_IN_PAGE_MODE;
255
+ const body = removed
256
+ ? outline
257
+ ? NOTE_TOAST_REMOVED_FROM_PAGE_BODY
258
+ : NOTE_TOAST_REMOVED_FROM_PAGE_BODY_NO_TOC
259
+ : outline
260
+ ? NOTE_TOAST_ADDED_TO_PAGE_BODY
261
+ : NOTE_TOAST_ADDED_TO_PAGE_BODY_NO_TOC;
262
+ return {
263
+ title: translateKey(std, ...title),
264
+ message: translateKey(std, ...body),
265
+ actions: outline
266
+ ? [
267
+ {
268
+ key: 'view-in-toc',
269
+ label: translateKey(std, ...NOTE_TOAST_VIEW_IN_TOC),
270
+ onClick: () => outline.open(),
271
+ },
272
+ ]
273
+ : [],
274
+ };
275
+ }
241
276
  function setDisplayMode(ctx, model, newMode) {
242
277
  const displayMode = model.props.displayMode;
243
278
  ctx.command.exec(changeNoteDisplayMode, {
@@ -249,31 +284,11 @@ function setDisplayMode(ctx, model, newMode) {
249
284
  if (newMode === NoteDisplayMode.DocOnly) {
250
285
  ctx.selection.clear();
251
286
  }
252
- const data = newMode === NoteDisplayMode.EdgelessOnly
253
- ? {
254
- title: translateKey(ctx.std, ...TOAST_NOTE_REMOVED_FROM_PAGE),
255
- message: translateKey(ctx.std, ...NOTE_TOAST_REMOVED_FROM_PAGE_BODY),
256
- }
257
- : {
258
- title: translateKey(ctx.std, ...NOTE_TOAST_DISPLAYED_IN_PAGE_MODE),
259
- message: translateKey(ctx.std, ...NOTE_TOAST_ADDED_TO_PAGE_BODY),
260
- };
261
287
  const notification = ctx.std.getOptional(NotificationProvider);
262
288
  notification?.notifyWithUndoAction({
263
- title: data.title,
264
- message: data.message,
289
+ ...displayModeToast(ctx.std, newMode),
265
290
  accent: 'success',
266
291
  duration: 5 * 1000,
267
- actions: [
268
- {
269
- key: 'view-in-toc',
270
- label: translateKey(ctx.std, ...NOTE_TOAST_VIEW_IN_TOC),
271
- onClick: () => {
272
- const sidebar = ctx.std.getOptional(SidebarExtensionIdentifier);
273
- sidebar?.open('outline');
274
- },
275
- },
276
- ],
277
292
  });
278
293
  ctx.track('NoteDisplayModeChanged', {
279
294
  ...trackBaseProps,
@@ -66,6 +66,8 @@ export declare const NOTE_DISPLAY_MODE_PAGE: ChromeWording;
66
66
  export declare const NOTE_TOAST_DISPLAYED_IN_PAGE_MODE: ChromeWording;
67
67
  export declare const NOTE_TOAST_REMOVED_FROM_PAGE_BODY: ChromeWording;
68
68
  export declare const NOTE_TOAST_ADDED_TO_PAGE_BODY: ChromeWording;
69
+ export declare const NOTE_TOAST_REMOVED_FROM_PAGE_BODY_NO_TOC: ChromeWording;
70
+ export declare const NOTE_TOAST_ADDED_TO_PAGE_BODY_NO_TOC: ChromeWording;
69
71
  export declare const NOTE_TOAST_VIEW_IN_TOC: ChromeWording;
70
72
  export declare const NOTE_TOOLBAR_DISPLAY_IN_PAGE: ChromeWording;
71
73
  export declare const NOTE_TOOLBAR_DISPLAYED_IN_PAGE: ChromeWording;
@@ -154,7 +154,10 @@ export const NOTE_DISPLAY_MODE_PAGE = DISPLAY_MODE_PAGE;
154
154
  * `TOAST_NOTE_REMOVED_FROM_PAGE` (already in `chrome.ts`); its opposite gets
155
155
  * its own key here. Each toast's BODY bakes in the trailing "Find it in the
156
156
  * TOC…" sentence rather than composing it at the call site — one full
157
- * sentence per key, never a fragment plus a suffix.
157
+ * sentence per key, never a fragment plus a suffix. That sentence promises the
158
+ * "View in TOC" link, which exists only when the host registers an outline
159
+ * panel (`OutlinePanelExtension`, ADR 0034); each body therefore has a
160
+ * `body-no-toc` twin without it, picked when no outline answers.
158
161
  */
159
162
  export const NOTE_TOAST_DISPLAYED_IN_PAGE_MODE = [
160
163
  'com.labre.note.toast.displayed-in-page-mode',
@@ -168,6 +171,14 @@ export const NOTE_TOAST_ADDED_TO_PAGE_BODY = [
168
171
  'com.labre.note.toast.added-to-page.body',
169
172
  'Content added to your document. Find it in the TOC for quick navigation.',
170
173
  ];
174
+ export const NOTE_TOAST_REMOVED_FROM_PAGE_BODY_NO_TOC = [
175
+ 'com.labre.note.toast.removed-from-page.body-no-toc',
176
+ 'Content removed from your document.',
177
+ ];
178
+ export const NOTE_TOAST_ADDED_TO_PAGE_BODY_NO_TOC = [
179
+ 'com.labre.note.toast.added-to-page.body-no-toc',
180
+ 'Content added to your document.',
181
+ ];
171
182
  export const NOTE_TOAST_VIEW_IN_TOC = [
172
183
  'com.labre.note.toast.view-in-toc',
173
184
  'View in Toc',
@@ -236,6 +247,8 @@ export const NOTE_WORDINGS = [
236
247
  NOTE_TOAST_DISPLAYED_IN_PAGE_MODE,
237
248
  NOTE_TOAST_REMOVED_FROM_PAGE_BODY,
238
249
  NOTE_TOAST_ADDED_TO_PAGE_BODY,
250
+ NOTE_TOAST_REMOVED_FROM_PAGE_BODY_NO_TOC,
251
+ NOTE_TOAST_ADDED_TO_PAGE_BODY_NO_TOC,
239
252
  NOTE_TOAST_VIEW_IN_TOC,
240
253
  NOTE_TOOLBAR_DISPLAY_IN_PAGE,
241
254
  NOTE_TOOLBAR_DISPLAYED_IN_PAGE,
@@ -271,12 +271,41 @@ export const builtinToolbarConfig = {
271
271
  blockId: block.model.id,
272
272
  })));
273
273
  }
274
- chain
274
+ // The copy lands right after the last selected block that no other
275
+ // selected block contains. Said explicitly: the command's default
276
+ // anchors on the LAST selected model, which in `flat` mode below
277
+ // is a descendant — and `highest` keeps a block-selected child of
278
+ // a block-selected parent too, so with both selected the copy went
279
+ // INSIDE the parent, after the child.
280
+ const [, { selectedModels: highest = [] }] = chain
275
281
  .pipe(getSelectedModelsCommand, {
276
282
  types: ['block', 'image'],
277
283
  mode: 'highest',
278
284
  })
279
- .pipe(duplicateSelectedModelsCommand)
285
+ .run();
286
+ const selected = new Set(highest);
287
+ const hasSelectedAncestor = (model) => {
288
+ for (let at = store.getParent(model); at; at = store.getParent(at))
289
+ if (selected.has(at))
290
+ return true;
291
+ return false;
292
+ };
293
+ const last = highest.findLast(model => !hasSelectedAncestor(model));
294
+ const parentModel = last && store.getParent(last);
295
+ if (!last || !parentModel)
296
+ return;
297
+ // `flat`, not `highest`: `draftSelectedModelsCommand` keeps a
298
+ // drafted block's children only when they are selected too, so a
299
+ // paragraph duplicated alone lost its nested children.
300
+ chain
301
+ .pipe(getSelectedModelsCommand, {
302
+ types: ['block', 'image'],
303
+ mode: 'flat',
304
+ })
305
+ .pipe(duplicateSelectedModelsCommand, {
306
+ parentModel,
307
+ index: parentModel.children.indexOf(last) + 1,
308
+ })
280
309
  .run();
281
310
  },
282
311
  },
@@ -4,6 +4,10 @@ import { type ChromeWording } from '../affine-shared/services/index.js';
4
4
  * `PACKAGE_SEED_WORDINGS` in `@labre/affine/translations` — see that file and
5
5
  * `packages/affine/shared/src/services/translation-service/README.md`.
6
6
  */
7
+ /**
8
+ * Aliases (L7 dedupe): the callout's own row says the same two words (#468),
9
+ * so they live in `chrome.ts` under the keys they always had.
10
+ */
7
11
  export declare const ROOT_TOOLBAR_CONVERSIONS_ARIA: ChromeWording;
8
12
  export declare const ROOT_TOOLBAR_TURN_INTO: ChromeWording;
9
13
  export declare const ROOT_TOOLBAR_CREATE_TABLE: ChromeWording;
@@ -51,8 +55,9 @@ export declare const ROOT_TOAST_ZOOM_TO_SELECTION: ChromeWording;
51
55
  export declare const ROOT_MINDMAP_SEED_NEW_NODE: ChromeWording;
52
56
  /**
53
57
  * This package's own CHROME wordings — L7 dedupe aliased
54
- * `ROOT_TOOLBAR_CREATE_LINKED_DOC`, `ROOT_TOOLBAR_FRAME`, `ROOT_TOOLBAR_RELOAD`
55
- * and `ROOT_TOOLBAR_REMOVE_LINK` to `chrome.ts` wordings — not listed again
58
+ * `ROOT_TOOLBAR_CONVERSIONS_ARIA`, `ROOT_TOOLBAR_CREATE_LINKED_DOC`,
59
+ * `ROOT_TOOLBAR_FRAME`, `ROOT_TOOLBAR_RELOAD`, `ROOT_TOOLBAR_REMOVE_LINK` and
60
+ * `ROOT_TOOLBAR_TURN_INTO` to `chrome.ts` wordings — not listed again
56
61
  * here, same rule the note/slash-menu packages' own chrome aliases already
57
62
  * follow.
58
63
  */
@@ -1,18 +1,16 @@
1
- import { TOOL_NAME_FRAME, TOOLBAR_CREATE_LINKED_DOC_TITLE, TOOLBAR_RELOAD, TOOLBAR_REMOVE_LINK, } from '../affine-shared/services/index.js';
1
+ import { TOOL_NAME_FRAME, TOOLBAR_CONVERSIONS_ARIA, TOOLBAR_CREATE_LINKED_DOC_TITLE, TOOLBAR_RELOAD, TOOLBAR_REMOVE_LINK, TOOLBAR_TURN_INTO, } from '../affine-shared/services/index.js';
2
2
  /**
3
3
  * This package's own wordings, joined into `PACKAGE_WORDINGS` /
4
4
  * `PACKAGE_SEED_WORDINGS` in `@labre/affine/translations` — see that file and
5
5
  * `packages/affine/shared/src/services/translation-service/README.md`.
6
6
  */
7
7
  /* ── The block contextual toolbar (`configs/toolbar.ts`) ──────────────── */
8
- export const ROOT_TOOLBAR_CONVERSIONS_ARIA = [
9
- 'com.labre.root.toolbar.conversions',
10
- 'Conversions',
11
- ];
12
- export const ROOT_TOOLBAR_TURN_INTO = [
13
- 'com.labre.root.toolbar.turn-into',
14
- 'Turn into',
15
- ];
8
+ /**
9
+ * Aliases (L7 dedupe): the callout's own row says the same two words (#468),
10
+ * so they live in `chrome.ts` under the keys they always had.
11
+ */
12
+ export const ROOT_TOOLBAR_CONVERSIONS_ARIA = TOOLBAR_CONVERSIONS_ARIA;
13
+ export const ROOT_TOOLBAR_TURN_INTO = TOOLBAR_TURN_INTO;
16
14
  export const ROOT_TOOLBAR_CREATE_TABLE = [
17
15
  'com.labre.root.toolbar.create-table',
18
16
  'Create Table',
@@ -134,14 +132,13 @@ export const ROOT_MINDMAP_SEED_NEW_NODE = [
134
132
  ];
135
133
  /**
136
134
  * This package's own CHROME wordings — L7 dedupe aliased
137
- * `ROOT_TOOLBAR_CREATE_LINKED_DOC`, `ROOT_TOOLBAR_FRAME`, `ROOT_TOOLBAR_RELOAD`
138
- * and `ROOT_TOOLBAR_REMOVE_LINK` to `chrome.ts` wordings — not listed again
135
+ * `ROOT_TOOLBAR_CONVERSIONS_ARIA`, `ROOT_TOOLBAR_CREATE_LINKED_DOC`,
136
+ * `ROOT_TOOLBAR_FRAME`, `ROOT_TOOLBAR_RELOAD`, `ROOT_TOOLBAR_REMOVE_LINK` and
137
+ * `ROOT_TOOLBAR_TURN_INTO` to `chrome.ts` wordings — not listed again
139
138
  * here, same rule the note/slash-menu packages' own chrome aliases already
140
139
  * follow.
141
140
  */
142
141
  export const ROOT_CHROME_WORDINGS = [
143
- ROOT_TOOLBAR_CONVERSIONS_ARIA,
144
- ROOT_TOOLBAR_TURN_INTO,
145
142
  ROOT_TOOLBAR_CREATE_TABLE,
146
143
  ROOT_ALIGNMENT_MENU_ARIA,
147
144
  ROOT_ALIGN_LEFT,