@formicoidea/labre-core 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/_pkgs/affine-block-data-view/data-view-block.js +1 -1
  2. package/dist/_pkgs/affine-block-database/components/title/index.js +7 -2
  3. package/dist/_pkgs/affine-block-database/database-block-styles.js +9 -1
  4. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.d.ts +70 -0
  5. package/dist/_pkgs/affine-block-embed-doc/page-drop/page-drop.js +196 -0
  6. package/dist/_pkgs/affine-block-embed-doc/view.js +3 -0
  7. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +11 -0
  8. package/dist/_pkgs/affine-block-frame/frame-template.js +24 -4
  9. package/dist/_pkgs/affine-block-frame/translations.d.ts +3 -0
  10. package/dist/_pkgs/affine-block-frame/translations.js +7 -0
  11. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +9 -3
  12. package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +9 -5
  13. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  14. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  15. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.d.ts +55 -3
  16. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.js +139 -6
  17. package/dist/_pkgs/affine-block-surface/renderer/notation-tint.d.ts +92 -0
  18. package/dist/_pkgs/affine-block-surface/renderer/notation-tint.js +113 -0
  19. package/dist/_pkgs/affine-block-surface/surface-block.js +2 -2
  20. package/dist/_pkgs/affine-components/color-picker/styles.js +1 -1
  21. package/dist/_pkgs/affine-components/context-menu/input.js +2 -1
  22. package/dist/_pkgs/affine-components/context-menu/menu-renderer.d.ts +7 -0
  23. package/dist/_pkgs/affine-components/context-menu/menu-renderer.js +53 -13
  24. package/dist/_pkgs/affine-components/drop-indicator/file-drop-manager.js +15 -0
  25. package/dist/_pkgs/affine-gfx-shape/element-renderer/shape/index.js +4 -2
  26. package/dist/_pkgs/affine-inline-link/link-node/link-popup/styles.js +1 -1
  27. package/dist/_pkgs/affine-inline-reference/reference-node/reference-popup/reference-popup.js +9 -9
  28. package/dist/_pkgs/affine-shared/consts/notation.d.ts +1 -0
  29. package/dist/_pkgs/affine-shared/consts/notation.js +29 -0
  30. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +5 -0
  31. package/dist/_pkgs/affine-shared/services/index.d.ts +1 -0
  32. package/dist/_pkgs/affine-shared/services/index.js +1 -0
  33. package/dist/_pkgs/affine-shared/services/page-drop-service.d.ts +61 -0
  34. package/dist/_pkgs/affine-shared/services/page-drop-service.js +30 -0
  35. package/dist/_pkgs/affine-shared/styles/scrollbar-style.js +1 -1
  36. package/dist/_pkgs/affine-widget-edgeless-auto-connect/index.js +5 -5
  37. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-auto-complete.js +2 -2
  38. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +6 -6
  39. package/dist/_pkgs/affine-widget-keyboard-toolbar/styles.js +1 -1
  40. package/dist/_pkgs/affine-widget-linked-doc/config.d.ts +8 -0
  41. package/dist/_pkgs/affine-widget-linked-doc/config.js +8 -1
  42. package/dist/_pkgs/affine-widget-linked-doc/linked-doc-popover.js +40 -32
  43. package/dist/_pkgs/affine-widget-linked-doc/styles.js +1 -1
  44. package/dist/_pkgs/affine-widget-linked-doc/translations.d.ts +2 -0
  45. package/dist/_pkgs/affine-widget-linked-doc/translations.js +6 -0
  46. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +3 -1
  47. package/dist/_pkgs/affine-widget-thread-marks/widget.js +6 -1
  48. package/dist/_pkgs/affine-widget-toolbar/utils.d.ts +2 -1
  49. package/dist/_pkgs/affine-widget-toolbar/utils.js +5 -2
  50. package/dist/_pkgs/data-view/core/common/properties.js +2 -2
  51. package/dist/_pkgs/data-view/core/common/property-menu.d.ts +3 -3
  52. package/dist/_pkgs/data-view/core/common/property-menu.js +7 -7
  53. package/dist/_pkgs/data-view/core/component/overflow/overflow.js +4 -1
  54. package/dist/_pkgs/data-view/core/group-by/renderer/base.d.ts +4 -1
  55. package/dist/_pkgs/data-view/core/group-by/renderer/base.js +17 -2
  56. package/dist/_pkgs/data-view/core/group-by/renderer/number-group.js +1 -1
  57. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.d.ts +0 -3
  58. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.js +63 -118
  59. package/dist/_pkgs/data-view/core/group-by/renderer/string-group.js +1 -1
  60. package/dist/_pkgs/data-view/core/group-by/setting.js +2 -2
  61. package/dist/_pkgs/data-view/core/utils/wording.d.ts +6 -2
  62. package/dist/_pkgs/data-view/core/utils/wording.js +5 -3
  63. package/dist/_pkgs/data-view/core/view/default-name.d.ts +6 -0
  64. package/dist/_pkgs/data-view/core/view/default-name.js +8 -0
  65. package/dist/_pkgs/data-view/translations.d.ts +45 -0
  66. package/dist/_pkgs/data-view/translations.js +118 -0
  67. package/dist/_pkgs/data-view/view-presets/flat/flat-view-ui.d.ts +3 -1
  68. package/dist/_pkgs/data-view/view-presets/flat/flat-view-ui.js +5 -1
  69. package/dist/_pkgs/data-view/view-presets/kanban/mobile/card.d.ts +6 -0
  70. package/dist/_pkgs/data-view/view-presets/kanban/mobile/card.js +11 -0
  71. package/dist/_pkgs/data-view/view-presets/kanban/mobile/cell.js +9 -10
  72. package/dist/_pkgs/data-view/view-presets/kanban/mobile/kanban-view-ui-logic.js +3 -0
  73. package/dist/_pkgs/data-view/view-presets/table/mobile/cell.js +7 -4
  74. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.d.ts +3 -1
  75. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.js +102 -124
  76. package/dist/_pkgs/data-view/view-presets/table/mobile/group.js +12 -7
  77. package/dist/_pkgs/data-view/view-presets/table/mobile/header.d.ts +8 -2
  78. package/dist/_pkgs/data-view/view-presets/table/mobile/header.js +64 -24
  79. package/dist/_pkgs/data-view/view-presets/table/mobile/menu.d.ts +3 -1
  80. package/dist/_pkgs/data-view/view-presets/table/mobile/menu.js +7 -3
  81. package/dist/_pkgs/data-view/view-presets/table/mobile/row.d.ts +23 -1
  82. package/dist/_pkgs/data-view/view-presets/table/mobile/row.js +138 -55
  83. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.d.ts +17 -0
  84. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.js +31 -6
  85. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.d.ts +5 -1
  86. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.js +11 -4
  87. package/dist/_pkgs/data-view/view-presets/table/pc/group.js +4 -2
  88. package/dist/_pkgs/data-view/view-presets/table/pc/row/row.js +5 -1
  89. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/index.js +1 -2
  90. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.d.ts +3 -1
  91. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.js +23 -3
  92. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/index.js +8 -8
  93. package/dist/_pkgs/data-view/widget-presets/tools/presets/filter/filter.d.ts +3 -0
  94. package/dist/_pkgs/data-view/widget-presets/tools/presets/filter/filter.js +125 -60
  95. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.d.ts +3 -0
  96. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.js +37 -5
  97. package/dist/_pkgs/data-view/widget-presets/tools/presets/sort/sort.d.ts +3 -0
  98. package/dist/_pkgs/data-view/widget-presets/tools/presets/sort/sort.js +131 -66
  99. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.d.ts +3 -0
  100. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.js +143 -40
  101. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-options.js +30 -22
  102. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-settings.js +8 -6
  103. package/dist/_pkgs/data-view/widget-presets/views-bar/views-view.js +15 -0
  104. package/package.json +1 -1
@@ -85,7 +85,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent {
85
85
  style="display:flex;align-items:center;justify-content: space-between;gap: 12px"
86
86
  class="database-header-bar"
87
87
  >
88
- <div style="flex:1">
88
+ <div style="flex:1;min-width:0">
89
89
  ${renderUniLit(widgetPresets.viewBar, props)}
90
90
  </div>
91
91
  ${renderUniLit(this.toolsWidget, props)}
@@ -32,6 +32,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
+ import { DOC_UNTITLED, translateKey } from '../../../affine-shared/services/index.js';
35
36
  import { unsafeCSSVarV2 } from '../../../affine-shared/theme/index.js';
36
37
  import { stopPropagation } from '../../../affine-shared/utils/index.js';
37
38
  import { SignalWatcher, WithDisposable } from '../../../global/lit/index.js';
@@ -112,7 +113,7 @@ let DatabaseTitle = (() => {
112
113
  }
113
114
 
114
115
  .affine-database-title [data-title-empty='true']::before {
115
- content: 'Untitled';
116
+ content: attr(data-untitled);
116
117
  position: absolute;
117
118
  pointer-events: none;
118
119
  color: var(--affine-text-primary-color);
@@ -152,12 +153,16 @@ let DatabaseTitle = (() => {
152
153
  height: isEmpty ? 'auto' : 0,
153
154
  opacity: isEmpty && !this.isFocus$.value ? 1 : 0,
154
155
  });
156
+ const std = this.database?.std;
157
+ // A base is a page: its empty title is the page's "Untitled" (P7).
158
+ const untitled = std ? translateKey(std, ...DOC_UNTITLED) : DOC_UNTITLED[1];
155
159
  return html ` <div
156
160
  class="${classList}"
157
161
  data-title-empty="${isEmpty}"
158
162
  data-title-focus="${this.isFocus$.value}"
163
+ data-untitled="${untitled}"
159
164
  >
160
- <div class="text" style="${untitledStyle}">Untitled</div>
165
+ <div class="text" style="${untitledStyle}">${untitled}</div>
161
166
  <div class="text">${this.text$.value}</div>
162
167
  <textarea
163
168
  .disabled="${this.readonly$.value}"
@@ -46,14 +46,22 @@ export const databaseTitleRowStyles = css({
46
46
  marginBottom: '8px',
47
47
  alignItems: 'center',
48
48
  });
49
+ // Too narrow for the tabs and the tools side by side (a phone, P7), the tools
50
+ // drop to a line of their own, on the right, instead of squeezing the
51
+ // current view's tab to its icon.
49
52
  export const databaseToolbarRowStyles = css({
50
53
  display: 'flex',
54
+ flexWrap: 'wrap',
51
55
  alignItems: 'center',
52
56
  justifyContent: 'space-between',
53
57
  gap: '12px',
58
+ '& data-view-header-tools': {
59
+ marginLeft: 'auto',
60
+ },
54
61
  });
55
62
  export const databaseViewBarContainerStyles = css({
56
- flex: 1,
63
+ flex: '1 1 200px',
64
+ minWidth: 0,
57
65
  });
58
66
  export const databaseContentStyles = css({
59
67
  position: 'relative',
@@ -0,0 +1,70 @@
1
+ import { type PageDrop } from '../../affine-shared/services/index.js';
2
+ import { type IVec } from '../../global/gfx/index.js';
3
+ import { type BlockStdScope, LifeCycleWatcher } from '../../std/index.js';
4
+ import type { BlockModel, Store } from '../../store/index.js';
5
+ /** The drop line in a note: a block and which side of it. */
6
+ export interface PageDropLine {
7
+ model: BlockModel;
8
+ placement: 'before' | 'after';
9
+ }
10
+ /**
11
+ * Where a dropped page's card goes (labre P7, ADR-0045 §7 amended
12
+ * 2026-10-16), mirroring "Show here" (`fragments/doc-title`, `show-here.ts`):
13
+ *
14
+ * - on a drop line in a note (page view, or a note on the canvas): a block
15
+ * beside the line's block, climbing out of whatever cannot hold a card (a
16
+ * list item, a callout) to the first parent that can;
17
+ * - on the canvas, off any note: a canvas card centred on the drop point,
18
+ * through the CRUD `addBlock`, which stamps the viewer's active layer
19
+ * (ADR 0031 §6), then selected;
20
+ * - in the page view with no line (dropped under the content): the end of
21
+ * the last note.
22
+ *
23
+ * The caller owns the undo step (`captureSync` around it). Returns the new
24
+ * card's id, or `null` when nothing was written (read-only, no root).
25
+ */
26
+ export declare function placePageCard(std: BlockStdScope, pageId: string, at: {
27
+ canvas: boolean;
28
+ line: PageDropLine | null;
29
+ point: IVec;
30
+ }): string | null;
31
+ /**
32
+ * The drop line under a client point, as the editor's own file drop draws it
33
+ * (`FileDropExtension.dropTarget$`): none on the canvas itself or on a block
34
+ * that sits on it, only inside a note.
35
+ */
36
+ export declare function dropLineAt(std: BlockStdScope, clientX: number, clientY: number): PageDropLine | null;
37
+ /**
38
+ * Hangs the host's `undo` / `redo` on the undo step that holds the card (the
39
+ * last one, the caller having opened it with `captureSync`), so one Mod+Z
40
+ * removes the card AND puts back what the host did. Yjs gives a popped step's
41
+ * twin (the redo step an undo creates, and back) a fresh `meta`: the hooks are
42
+ * carried over at each pop. A step whose card was already gone (removed by
43
+ * another peer) changes nothing, so Yjs skips it and the host's hooks are not
44
+ * called: the page stays where the drop put it, nothing is lost.
45
+ */
46
+ export declare function rideUndoStep(store: Store, drop: PageDrop): void;
47
+ /** The `stack-item-popped` half of {@link rideUndoStep}. */
48
+ export declare function runUndoHooks(store: Store, event: {
49
+ stackItem: {
50
+ meta: Map<unknown, unknown>;
51
+ };
52
+ type: 'undo' | 'redo';
53
+ }): void;
54
+ /**
55
+ * The whole drop: ask the host, place the card in one undo step, hang the
56
+ * host's hooks on it. A drop the host turns down, or a card that cannot be
57
+ * placed, writes nothing (the host's own change is undone at once).
58
+ */
59
+ export declare function dropPage(std: BlockStdScope, event: Pick<DragEvent, 'dataTransfer' | 'clientX' | 'clientY'>, canvas: boolean): string | null;
60
+ /**
61
+ * Mounts the page drop on the editor: a native drop the host accepts places
62
+ * the page's card, and the history runs the host's hooks with the card's
63
+ * undo step. Without a `PageDropProvider` nothing changes: `dropPage` returns
64
+ * at the verdict.
65
+ */
66
+ export declare class PageDropWatcher extends LifeCycleWatcher {
67
+ static readonly key = "labre-page-drop-watcher";
68
+ private readonly _onPopped;
69
+ mounted(): void;
70
+ }
@@ -0,0 +1,196 @@
1
+ import { EdgelessCRUDIdentifier } from '../../affine-block-surface/index.js';
2
+ import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, } from '../../affine-shared/consts/index.js';
3
+ import { PageDropProvider, pageDropVerdict, TelemetryProvider, } from '../../affine-shared/services/index.js';
4
+ import { calcDropTarget, getClosestBlockComponentByPoint, getLastNoteBlock, isInsideEdgelessEditor, matchModels, } from '../../affine-shared/utils/index.js';
5
+ import { Bound, Point } from '../../global/gfx/index.js';
6
+ import { LifeCycleWatcher } from '../../std/index.js';
7
+ import { GfxControllerIdentifier, SurfaceBlockModel } from '../../std/gfx/index.js';
8
+ const LINKED_DOC = 'affine:embed-linked-doc';
9
+ /** The canvas card's style, the one "Show here" and "Linked doc" insert. */
10
+ const CANVAS_STYLE = 'vertical';
11
+ /** Where the host's `undo` / `redo` ride on the card's undo step. */
12
+ const UNDO_HOOKS = 'labre:page-drop';
13
+ /**
14
+ * Where a dropped page's card goes (labre P7, ADR-0045 §7 amended
15
+ * 2026-10-16), mirroring "Show here" (`fragments/doc-title`, `show-here.ts`):
16
+ *
17
+ * - on a drop line in a note (page view, or a note on the canvas): a block
18
+ * beside the line's block, climbing out of whatever cannot hold a card (a
19
+ * list item, a callout) to the first parent that can;
20
+ * - on the canvas, off any note: a canvas card centred on the drop point,
21
+ * through the CRUD `addBlock`, which stamps the viewer's active layer
22
+ * (ADR 0031 §6), then selected;
23
+ * - in the page view with no line (dropped under the content): the end of
24
+ * the last note.
25
+ *
26
+ * The caller owns the undo step (`captureSync` around it). Returns the new
27
+ * card's id, or `null` when nothing was written (read-only, no root).
28
+ */
29
+ export function placePageCard(std, pageId, at) {
30
+ const { store } = std;
31
+ if (store.readonly || !store.root || !pageId)
32
+ return null;
33
+ let id = null;
34
+ if (at.line)
35
+ id = insertBeside(store, pageId, at.line);
36
+ else if (at.canvas)
37
+ id = insertOnCanvas(std, pageId, at.point);
38
+ else
39
+ id = insertAtEnd(store, pageId);
40
+ if (id) {
41
+ std.getOptional(TelemetryProvider)?.track('BlockCreated', {
42
+ blockType: LINKED_DOC,
43
+ });
44
+ }
45
+ return id;
46
+ }
47
+ function insertBeside(store, pageId, line) {
48
+ let model = line.model;
49
+ let placement = line.placement;
50
+ while (model) {
51
+ const parent = store.getParent(model);
52
+ if (!parent)
53
+ return null;
54
+ if (store.schema.safeValidate(LINKED_DOC, parent.flavour)) {
55
+ return (store.addSiblingBlocks(model, [{ flavour: LINKED_DOC, pageId }], placement)[0] ?? null);
56
+ }
57
+ // Out of a list item or a callout: the card goes after the container.
58
+ model = parent;
59
+ placement = 'after';
60
+ }
61
+ return null;
62
+ }
63
+ function insertAtEnd(store, pageId) {
64
+ const root = store.root;
65
+ if (!root)
66
+ return null;
67
+ const noteId = getLastNoteBlock(store)?.id ?? store.addBlock('affine:note', {}, root.id);
68
+ return store.addBlock(LINKED_DOC, { pageId }, noteId);
69
+ }
70
+ function insertOnCanvas(std, pageId, point) {
71
+ const gfx = std.get(GfxControllerIdentifier);
72
+ const surface = gfx.surface;
73
+ if (!surface)
74
+ return null;
75
+ const xywh = Bound.fromCenter(gfx.viewport.toModelCoordFromClientCoord(point), EMBED_CARD_WIDTH[CANVAS_STYLE], EMBED_CARD_HEIGHT[CANVAS_STYLE]).serialize();
76
+ const id = std
77
+ .get(EdgelessCRUDIdentifier)
78
+ .addBlock(LINKED_DOC, { pageId, xywh, style: CANVAS_STYLE }, surface);
79
+ gfx.selection.set({ elements: [id], editing: false });
80
+ return id;
81
+ }
82
+ /**
83
+ * The drop line under a client point, as the editor's own file drop draws it
84
+ * (`FileDropExtension.dropTarget$`): none on the canvas itself or on a block
85
+ * that sits on it, only inside a note.
86
+ */
87
+ export function dropLineAt(std, clientX, clientY) {
88
+ const point = new Point(clientX, clientY);
89
+ const element = getClosestBlockComponentByPoint(point);
90
+ if (!element)
91
+ return null;
92
+ const model = element.model;
93
+ if (matchModels(std.store.getParent(model), [SurfaceBlockModel]))
94
+ return null;
95
+ const target = calcDropTarget(point, model, element);
96
+ if (!target)
97
+ return null;
98
+ const placement = target.placement === 'before' ? 'before' : 'after';
99
+ return { model: target.modelState.model, placement };
100
+ }
101
+ /**
102
+ * Hangs the host's `undo` / `redo` on the undo step that holds the card (the
103
+ * last one, the caller having opened it with `captureSync`), so one Mod+Z
104
+ * removes the card AND puts back what the host did. Yjs gives a popped step's
105
+ * twin (the redo step an undo creates, and back) a fresh `meta`: the hooks are
106
+ * carried over at each pop. A step whose card was already gone (removed by
107
+ * another peer) changes nothing, so Yjs skips it and the host's hooks are not
108
+ * called: the page stays where the drop put it, nothing is lost.
109
+ */
110
+ export function rideUndoStep(store, drop) {
111
+ if (!drop.undo && !drop.redo)
112
+ return;
113
+ store.history.undoManager.undoStack.at(-1)?.meta.set(UNDO_HOOKS, drop);
114
+ }
115
+ /** The `stack-item-popped` half of {@link rideUndoStep}. */
116
+ export function runUndoHooks(store, event) {
117
+ const drop = event.stackItem.meta.get(UNDO_HOOKS);
118
+ if (!drop)
119
+ return;
120
+ const manager = store.history.undoManager;
121
+ const twin = event.type === 'undo' ? manager.redoStack.at(-1) : manager.undoStack.at(-1);
122
+ twin?.meta.set(UNDO_HOOKS, drop);
123
+ if (event.type === 'undo')
124
+ drop.undo?.();
125
+ else
126
+ drop.redo?.();
127
+ }
128
+ /**
129
+ * Drops already placed: an editor nested in another (a synced page's
130
+ * content) sees the same bubbling event as its host, and one drop is one card.
131
+ */
132
+ const handled = new WeakSet();
133
+ /**
134
+ * The whole drop: ask the host, place the card in one undo step, hang the
135
+ * host's hooks on it. A drop the host turns down, or a card that cannot be
136
+ * placed, writes nothing (the host's own change is undone at once).
137
+ */
138
+ export function dropPage(std, event, canvas) {
139
+ const { dataTransfer } = event;
140
+ if (!dataTransfer || dataTransfer.files.length > 0)
141
+ return null;
142
+ if (handled.has(event))
143
+ return null;
144
+ if (pageDropVerdict(std, dataTransfer) !== 'accept')
145
+ return null;
146
+ const service = std.getOptional(PageDropProvider);
147
+ if (!service)
148
+ return null;
149
+ const { store } = std;
150
+ const line = dropLineAt(std, event.clientX, event.clientY);
151
+ handled.add(event);
152
+ const drop = service.drop({ dataTransfer, docId: store.id });
153
+ if (!drop)
154
+ return null;
155
+ store.captureSync();
156
+ const id = placePageCard(std, drop.pageId, {
157
+ canvas,
158
+ line,
159
+ point: [event.clientX, event.clientY],
160
+ });
161
+ if (!id) {
162
+ drop.undo?.();
163
+ return null;
164
+ }
165
+ rideUndoStep(store, drop);
166
+ store.captureSync();
167
+ return id;
168
+ }
169
+ /**
170
+ * Mounts the page drop on the editor: a native drop the host accepts places
171
+ * the page's card, and the history runs the host's hooks with the card's
172
+ * undo step. Without a `PageDropProvider` nothing changes: `dropPage` returns
173
+ * at the verdict.
174
+ */
175
+ export class PageDropWatcher extends LifeCycleWatcher {
176
+ constructor() {
177
+ super(...arguments);
178
+ this._onPopped = (event) => runUndoHooks(this.std.store, event);
179
+ }
180
+ static { this.key = 'labre-page-drop-watcher'; }
181
+ mounted() {
182
+ super.mounted();
183
+ const { std } = this;
184
+ std.event.disposables.add(std.event.add('nativeDrop', context => {
185
+ const event = context.get('dndState').raw;
186
+ // Never `return true`: that would stop the dispatch before the file
187
+ // drop's own handler, which clears the drop line it drew.
188
+ if (dropPage(std, event, isInsideEdgelessEditor(std.host))) {
189
+ event.preventDefault();
190
+ }
191
+ }));
192
+ const manager = std.store.history.undoManager;
193
+ manager.on('stack-item-popped', this._onPopped);
194
+ std.event.disposables.add(() => manager.off('stack-item-popped', this._onPopped));
195
+ }
196
+ }
@@ -3,6 +3,7 @@ import { effects } from './effects.js';
3
3
  import { EdgelessClipboardEmbedLinkedDocConfig, EmbedLinkedDocInteraction, EmbedLinkedDocViewExtensions, } from './embed-linked-doc-block/index.js';
4
4
  import { EdgelessClipboardEmbedSyncedDocConfig, EmbedSyncedDocViewExtensions, } from './embed-synced-doc-block/index.js';
5
5
  import { EmbedSyncedDocInteraction } from './embed-synced-doc-block/configs/edgeless-interaction.js';
6
+ import { PageDropWatcher } from './page-drop/page-drop.js';
6
7
  export class EmbedDocViewExtension extends ViewExtensionProvider {
7
8
  constructor() {
8
9
  super(...arguments);
@@ -16,6 +17,8 @@ export class EmbedDocViewExtension extends ViewExtensionProvider {
16
17
  super.setup(context);
17
18
  context.register(EmbedLinkedDocViewExtensions);
18
19
  context.register(EmbedSyncedDocViewExtensions);
20
+ // Pages dragged in from the host (PageDropProvider), both views.
21
+ context.register(PageDropWatcher);
19
22
  const isEdgeless = this.isEdgeless(context.scope);
20
23
  if (isEdgeless) {
21
24
  context.register([
@@ -48,6 +48,17 @@ export declare class FrameTemplateWatcher extends LifeCycleWatcher {
48
48
  mounted(): void;
49
49
  unmounted(): void;
50
50
  }
51
+ interface NoticeAction {
52
+ key: string;
53
+ label: string;
54
+ onClick: () => void;
55
+ }
56
+ /**
57
+ * The success notification's « View », when the host can show its templates
58
+ * (labre P7): it calls `showTemplates` and nothing else. Absent, the
59
+ * notification has no action, as before 0.54.
60
+ */
61
+ export declare function templateViewAction(std: BlockStdScope, service: FrameTemplateService): NoticeAction | undefined;
51
62
  /**
52
63
  * "Save as template": serialize the frame and hand it to the host. Reads the
53
64
  * document and writes nothing to it, so a read-only viewer may save too: the
@@ -4,7 +4,7 @@ import { ConnectorElementModel, } from '../affine-model/index.js';
4
4
  import { FrameTemplateProvider, NotificationProvider, translateKey, } from '../affine-shared/services/index.js';
5
5
  import { LifeCycleWatcher } from '../std/index.js';
6
6
  import { GfxBlockElementModel } from '../std/gfx/index.js';
7
- import { FRAME_TOAST_TEMPLATE_SAVE_FAILED, FRAME_TOAST_TEMPLATE_SAVED, } from './translations.js';
7
+ import { FRAME_TOAST_TEMPLATE_SAVE_FAILED, FRAME_TOAST_TEMPLATE_SAVED, FRAME_TOAST_TEMPLATE_VIEW, } from './translations.js';
8
8
  /**
9
9
  * Narrow a transformer to one frame's members: the surface keeps only the
10
10
  * elements and child blocks in `ids` — the surface transformer's own
@@ -160,15 +160,35 @@ export class FrameTemplateWatcher extends LifeCycleWatcher {
160
160
  }
161
161
  /** Frames whose save is in flight, per document: a double click saves once. */
162
162
  const saving = new Set();
163
- function tell(std, wording, success) {
163
+ function tell(std, wording, success, action) {
164
164
  const text = translateKey(std, ...wording);
165
165
  const notification = std.getOptional(NotificationProvider);
166
166
  if (notification) {
167
- notification.notify({ title: text, accent: success ? 'success' : 'error' });
167
+ notification.notify({
168
+ title: text,
169
+ accent: success ? 'success' : 'error',
170
+ ...(action ? { actions: [action] } : {}),
171
+ });
168
172
  return;
169
173
  }
174
+ // The plain toast has no button: without a notification service the
175
+ // action is dropped, and the template is still where the host keeps it.
170
176
  toast(std.host, text);
171
177
  }
178
+ /**
179
+ * The success notification's « View », when the host can show its templates
180
+ * (labre P7): it calls `showTemplates` and nothing else. Absent, the
181
+ * notification has no action, as before 0.54.
182
+ */
183
+ export function templateViewAction(std, service) {
184
+ if (!service.showTemplates)
185
+ return undefined;
186
+ return {
187
+ key: 'frame-template-view',
188
+ label: translateKey(std, ...FRAME_TOAST_TEMPLATE_VIEW),
189
+ onClick: () => service.showTemplates?.(),
190
+ };
191
+ }
172
192
  /**
173
193
  * "Save as template": serialize the frame and hand it to the host. Reads the
174
194
  * document and writes nothing to it, so a read-only viewer may save too: the
@@ -195,7 +215,7 @@ export async function saveFrameAsTemplate(std, frame) {
195
215
  title: snapshot.meta.title,
196
216
  snapshot,
197
217
  });
198
- tell(std, FRAME_TOAST_TEMPLATE_SAVED, true);
218
+ tell(std, FRAME_TOAST_TEMPLATE_SAVED, true, templateViewAction(std, service));
199
219
  }
200
220
  catch (error) {
201
221
  console.error(error);
@@ -41,6 +41,9 @@ export declare const FRAME_TOOLBAR_UNGROUP: ChromeWording;
41
41
  export declare const FRAME_TOOLBAR_SAVE_AS_TEMPLATE: ChromeWording;
42
42
  export declare const FRAME_TOAST_TEMPLATE_SAVED: ChromeWording;
43
43
  export declare const FRAME_TOAST_TEMPLATE_SAVE_FAILED: ChromeWording;
44
+ /** The success notification's action, when the host can show its templates
45
+ * (`FrameTemplateService.showTemplates`). */
46
+ export declare const FRAME_TOAST_TEMPLATE_VIEW: ChromeWording;
44
47
  export declare const FRAME_TOOLBAR_BACKGROUND: ChromeWording;
45
48
  export declare const FRAME_PRESENT_TOOL_TOOLTIP: ChromeWording;
46
49
  export declare const FRAME_PRESENT_PREVIOUS: ChromeWording;
@@ -61,6 +61,12 @@ export const FRAME_TOAST_TEMPLATE_SAVE_FAILED = [
61
61
  'com.labre.frame.toast.template-save-failed',
62
62
  'The frame could not be saved as a template',
63
63
  ];
64
+ /** The success notification's action, when the host can show its templates
65
+ * (`FrameTemplateService.showTemplates`). */
66
+ export const FRAME_TOAST_TEMPLATE_VIEW = [
67
+ 'com.labre.frame.toast.template-view',
68
+ 'View',
69
+ ];
64
70
  export const FRAME_TOOLBAR_BACKGROUND = [
65
71
  'com.labre.frame.toolbar.background',
66
72
  'Background',
@@ -132,6 +138,7 @@ export const FRAME_CHROME_WORDINGS = [
132
138
  FRAME_TOOLBAR_BACKGROUND,
133
139
  FRAME_TOAST_TEMPLATE_SAVED,
134
140
  FRAME_TOAST_TEMPLATE_SAVE_FAILED,
141
+ FRAME_TOAST_TEMPLATE_VIEW,
135
142
  FRAME_PRESENT_TOOL_TOOLTIP,
136
143
  FRAME_PRESENT_PREVIOUS,
137
144
  FRAME_PRESENT_NEXT,
@@ -1,4 +1,4 @@
1
- import { FrameworkBackgroundElementModel } from '../../../affine-model/index.js';
1
+ import { ColorScheme, FrameworkBackgroundElementModel, } from '../../../affine-model/index.js';
2
2
  import { createIdentifier } from '../../../global/di/index.js';
3
3
  import { Bound } from '../../../global/gfx/index.js';
4
4
  import { GfxBlockElementModel, GfxControllerIdentifier, GfxPrimitiveElementModel, } from '../../../std/gfx/index.js';
@@ -149,10 +149,16 @@ export function renderCanvasSvg(std) {
149
149
  * `bound`, one model per pass, each inside a group carrying its markers (ADR
150
150
  * 0032 §6), in the canvas' own z-order: a text block is painted at its layer
151
151
  * index, between the elements it sits between on screen.
152
+ *
153
+ * Always in the LIGHT scheme, whatever the canvas shows (ADR 0039): the file
154
+ * is printed on white, read back by the SVG importer by its stored colours
155
+ * (rule R33), and a `{ light, dark }` pair painted on its dark side would turn
156
+ * a black stroke white on a white page. The PNG export, a picture of the
157
+ * screen, keeps the scheme in force.
152
158
  */
153
159
  function paintSvg(std, renderer, elements, texts, bound) {
154
160
  const gfx = std.get(GfxControllerIdentifier);
155
- return runWithRecordingPath2D(() => {
161
+ return renderer.withColorScheme(ColorScheme.Light, () => runWithRecordingPath2D(() => {
156
162
  const { ctx, canvas, serialize, markRoot, markedGroup } = createSvgContext(bound.w, bound.h);
157
163
  const rc = new RoughCanvas(canvas);
158
164
  markRoot();
@@ -174,7 +180,7 @@ function paintSvg(std, renderer, elements, texts, bound) {
174
180
  });
175
181
  }
176
182
  return { svg: serialize(), bound };
177
- });
183
+ }));
178
184
  }
179
185
  /**
180
186
  * What an element's group says about it in the file (ADR 0032 §6): its id, its
@@ -1,4 +1,5 @@
1
1
  import { TranslationProvider } from '../../affine-shared/services/index.js';
2
+ import { notationColor, } from '../renderer/notation-tint.js';
2
3
  import { backgroundColor, backgroundInstanceZoneBand, backgroundInstanceZones, backgroundPlot, backgroundPoint, BROKEN_BACKGROUND_COLOR, DEFAULT_BACKGROUND_SURFACE, warnBrokenBackgroundColor, } from './def.js';
3
4
  import { backgroundInVariant, backgroundLabelText, backgroundTexts, backgroundVisible, } from './labels.js';
4
5
  /**
@@ -106,12 +107,15 @@ export function createFrameworkBackgroundRenderer(def) {
106
107
  const palette = def.chrome?.palette;
107
108
  const family = def.chrome?.fontFamily ?? DEFAULT_FONT_FAMILY;
108
109
  const surface = def.chrome?.surface ?? DEFAULT_BACKGROUND_SURFACE;
109
- const color = (c) => backgroundColor(c, palette);
110
+ const declared = (c) => backgroundColor(c, palette);
110
111
  const texts = backgroundTexts(def);
111
112
  // The DECLARATION of the instance partition — how it stacks, how it is drawn.
112
113
  // What it contains is the model's business and is read on every paint.
113
114
  const instanceZonesDef = def.instanceZones;
114
115
  return (model, ctx, matrix, renderer) => {
116
+ // A board is chrome painted at render time, so on a dark canvas every
117
+ // declared colour goes through the dark notation table (ADR 0039).
118
+ const color = (c, use = 'ink') => notationColor(renderer, declared(c), use);
115
119
  const props = model;
116
120
  const visible = (prop) => backgroundVisible(prop, props);
117
121
  // Which READING of the frame this instance is showing. Washes, zones and
@@ -175,7 +179,7 @@ export function createFrameworkBackgroundRenderer(def) {
175
179
  // neither its fill nor its frame is painted.
176
180
  const carded = cardPath();
177
181
  if (carded && surface.fill) {
178
- ctx.fillStyle = color(surface.fill);
182
+ ctx.fillStyle = color(surface.fill, 'fill');
179
183
  ctx.fill();
180
184
  }
181
185
  // The side bands sit BETWEEN the fill and the border: a band is part of the
@@ -194,7 +198,7 @@ export function createFrameworkBackgroundRenderer(def) {
194
198
  if (thickness <= 0)
195
199
  continue;
196
200
  if (band.fill) {
197
- ctx.fillStyle = color(band.fill);
201
+ ctx.fillStyle = color(band.fill, 'fill');
198
202
  if (top)
199
203
  ctx.fillRect(0, 0, w, thickness);
200
204
  else
@@ -236,7 +240,7 @@ export function createFrameworkBackgroundRenderer(def) {
236
240
  if (!visible(wash.visibleProp))
237
241
  continue;
238
242
  const gradient = ctx.createLinearGradient(x0, 0, x1, 0);
239
- const hex = color(wash.color);
243
+ const hex = color(wash.color, 'fill');
240
244
  for (const [offset, alpha] of wash.stops) {
241
245
  gradient.addColorStop(offset, rgba(hex, alpha));
242
246
  }
@@ -252,7 +256,7 @@ export function createFrameworkBackgroundRenderer(def) {
252
256
  continue;
253
257
  if (!visible(zone.fillVisibleProp))
254
258
  continue;
255
- ctx.fillStyle = color(zone.fill);
259
+ ctx.fillStyle = color(zone.fill, 'fill');
256
260
  ctx.fillRect(x0 + zone.rect.x * pw, y0 + zone.rect.y * ph, zone.rect.w * pw, zone.rect.h * ph);
257
261
  }
258
262
  // The partition THIS element carries, resolved once per paint: alone on
@@ -4,6 +4,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
4
4
  export { CanvasElementType } from './element-model/index.js';
5
5
  export { CanvasRenderer } from './renderer/canvas-renderer.js';
6
6
  export { DomRenderer } from './renderer/dom-renderer.js';
7
+ export * from './renderer/notation-tint.js';
7
8
  export { isPainted } from './renderer/paint-predicate.js';
8
9
  export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
9
10
  export type { ElementRenderer } from './renderer/elements/index.js';
@@ -6,6 +6,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
6
6
  export { CanvasElementType } from './element-model/index.js';
7
7
  export { CanvasRenderer } from './renderer/canvas-renderer.js';
8
8
  export { DomRenderer } from './renderer/dom-renderer.js';
9
+ export * from './renderer/notation-tint.js';
9
10
  export { isPainted } from './renderer/paint-predicate.js';
10
11
  export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
11
12
  export * from './renderer/elements/type.js';
@@ -5,12 +5,14 @@ import type { GfxLocalElementModel, GridManager, LayerManager, SurfaceBlockModel
5
5
  import { Subject } from 'rxjs';
6
6
  import type { SurfaceElementModel } from '../element-model/base.js';
7
7
  import { RoughCanvas } from '../utils/rough/canvas.js';
8
+ import { type NotationColorUse, NotationTint } from './notation-tint.js';
8
9
  import type { Overlay } from './overlay.js';
9
10
  type EnvProvider = {
10
11
  generateColorProperty: (color: Color, fallback?: Color) => string;
11
12
  getColorScheme: () => ColorScheme;
12
- getColorValue: (color: Color, fallback?: Color, real?: boolean) => string;
13
- getPropertyValue: (property: string) => string;
13
+ /** `theme` absent: the scheme in force. */
14
+ getColorValue: (color: Color, fallback?: Color, real?: boolean, theme?: ColorScheme) => string;
15
+ getPropertyValue: (property: string, theme?: ColorScheme) => string;
14
16
  selectedElements?: () => string[];
15
17
  };
16
18
  type RendererOptions = {
@@ -96,6 +98,19 @@ export declare class CanvasRenderer {
96
98
  }>;
97
99
  usePlaceholder: boolean;
98
100
  viewport: Viewport;
101
+ private readonly _surface;
102
+ /** Set by {@link withColorScheme} for the length of one paint. */
103
+ private _schemeOverride;
104
+ /** Built on the first dark paint, from the registered framework entries. */
105
+ private _darkTint;
106
+ private _notationTint;
107
+ /**
108
+ * The dark notation table while a NOTATION element is being painted on a
109
+ * dark canvas, `null` otherwise (light scheme, a neutral element, outside a
110
+ * paint). Painters read it through `notationColor` and
111
+ * {@link getColorValue}; see `notation-tint.ts` and ADR 0039.
112
+ */
113
+ get notationTint(): NotationTint | null;
99
114
  get stackingCanvas(): HTMLCanvasElement[];
100
115
  constructor(options: RendererOptions);
101
116
  /**
@@ -144,8 +159,45 @@ export declare class CanvasRenderer {
144
159
  */
145
160
  renderBoundTo(ctx: CanvasRenderingContext2D, rc: RoughCanvas, bound: IBound, surfaceElements?: SurfaceElementModel[]): void;
146
161
  getColorScheme(): ColorScheme;
147
- getColorValue(color: Color, fallback?: Color, real?: boolean): string;
162
+ /**
163
+ * A stored colour, resolved for the scheme in force.
164
+ *
165
+ * While a notation element is painted on a dark canvas, a PLAIN stored value
166
+ * (a string: what every framework writes) goes through the dark notation
167
+ * table as `use`; a `{ light, dark }` pair already knows its dark side and
168
+ * is never tinted, nor is a fallback (ADR 0039).
169
+ */
170
+ getColorValue(color: Color, fallback?: Color, real?: boolean, use?: NotationColorUse): string;
171
+ /**
172
+ * The colour of words drawn on a fill of the same element: tinted like any
173
+ * ink, unless the fill is painted as stored, in which case the words keep
174
+ * the colour they were chosen for on it ("ink follows its paper", ADR 0039).
175
+ * `fill` is `null` when the element draws no fill.
176
+ */
177
+ getInkColorValue(color: Color, fallback: Color, fill: Color | null): string;
148
178
  getPropertyValue(property: string): string;
179
+ /**
180
+ * Paint with `scheme` whatever the canvas shows. The SVG export paints
181
+ * light: a file is printed on white and read back by colour (ADR 0039).
182
+ */
183
+ withColorScheme<T>(scheme: ColorScheme, paint: () => T): T;
184
+ /**
185
+ * "Ink follows its paper" for words that are elements of their own (a C4
186
+ * title, an Event Storming label): text with no fill of its own, drawn on
187
+ * the topmost filled element under it that keeps its stored fill, keeps its
188
+ * stored colour too (ADR 0039). `papers` are the filled elements painted
189
+ * before it in this pass, in z-order.
190
+ */
191
+ private _sitsOnKeptPaper;
192
+ /** The dark table when this pass paints dark, else `null`. */
193
+ private _tintForPass;
194
+ /**
195
+ * Whether an element is part of a notation, read off the document as the
196
+ * colour carousel reads it (ADR 0027): its own role or its being a board,
197
+ * else (a connector) a role at either end, else a framework board it sits
198
+ * inside. A neutral element anywhere else is painted exactly as stored.
199
+ */
200
+ private _isNotation;
149
201
  getDebugMetrics(): CanvasRendererDebugMetrics;
150
202
  resetDebugMetrics(): void;
151
203
  refresh(target?: RefreshTarget): void;