@formicoidea/labre-core 0.52.0 → 0.54.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 (96) 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/services/thread-marks-service.d.ts +32 -1
  36. package/dist/_pkgs/affine-shared/styles/scrollbar-style.js +1 -1
  37. package/dist/_pkgs/affine-widget-edgeless-auto-connect/index.js +5 -5
  38. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-auto-complete.js +2 -2
  39. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +6 -6
  40. package/dist/_pkgs/affine-widget-keyboard-toolbar/styles.js +1 -1
  41. package/dist/_pkgs/affine-widget-linked-doc/config.d.ts +8 -0
  42. package/dist/_pkgs/affine-widget-linked-doc/config.js +8 -1
  43. package/dist/_pkgs/affine-widget-linked-doc/linked-doc-popover.js +40 -32
  44. package/dist/_pkgs/affine-widget-linked-doc/styles.js +1 -1
  45. package/dist/_pkgs/affine-widget-linked-doc/translations.d.ts +2 -0
  46. package/dist/_pkgs/affine-widget-linked-doc/translations.js +6 -0
  47. package/dist/_pkgs/affine-widget-thread-marks/excerpt.d.ts +6 -2
  48. package/dist/_pkgs/affine-widget-thread-marks/excerpt.js +8 -0
  49. package/dist/_pkgs/affine-widget-thread-marks/index.d.ts +1 -0
  50. package/dist/_pkgs/affine-widget-thread-marks/index.js +1 -0
  51. package/dist/_pkgs/affine-widget-thread-marks/text-range.d.ts +26 -0
  52. package/dist/_pkgs/affine-widget-thread-marks/text-range.js +59 -0
  53. package/dist/_pkgs/affine-widget-thread-marks/toolbar.d.ts +4 -3
  54. package/dist/_pkgs/affine-widget-thread-marks/toolbar.js +24 -8
  55. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +33 -2
  56. package/dist/_pkgs/affine-widget-thread-marks/widget.js +121 -5
  57. package/dist/_pkgs/affine-widget-toolbar/utils.d.ts +2 -1
  58. package/dist/_pkgs/affine-widget-toolbar/utils.js +5 -2
  59. package/dist/_pkgs/data-view/core/common/properties.js +2 -2
  60. package/dist/_pkgs/data-view/core/common/property-menu.d.ts +3 -3
  61. package/dist/_pkgs/data-view/core/common/property-menu.js +7 -7
  62. package/dist/_pkgs/data-view/core/component/overflow/overflow.js +4 -1
  63. package/dist/_pkgs/data-view/core/group-by/renderer/base.d.ts +4 -1
  64. package/dist/_pkgs/data-view/core/group-by/renderer/base.js +17 -2
  65. package/dist/_pkgs/data-view/core/group-by/renderer/number-group.js +1 -1
  66. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.d.ts +0 -3
  67. package/dist/_pkgs/data-view/core/group-by/renderer/select-group.js +63 -118
  68. package/dist/_pkgs/data-view/core/group-by/renderer/string-group.js +1 -1
  69. package/dist/_pkgs/data-view/core/group-by/setting.js +2 -2
  70. package/dist/_pkgs/data-view/core/utils/wording.d.ts +6 -2
  71. package/dist/_pkgs/data-view/core/utils/wording.js +5 -3
  72. package/dist/_pkgs/data-view/core/view/default-name.d.ts +6 -0
  73. package/dist/_pkgs/data-view/core/view/default-name.js +8 -0
  74. package/dist/_pkgs/data-view/translations.d.ts +47 -0
  75. package/dist/_pkgs/data-view/translations.js +124 -0
  76. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.d.ts +3 -1
  77. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.js +102 -124
  78. package/dist/_pkgs/data-view/view-presets/table/mobile/group.js +4 -2
  79. package/dist/_pkgs/data-view/view-presets/table/mobile/header.js +17 -9
  80. package/dist/_pkgs/data-view/view-presets/table/mobile/row.js +20 -11
  81. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.d.ts +17 -0
  82. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.js +31 -6
  83. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.js +2 -2
  84. package/dist/_pkgs/data-view/view-presets/table/pc/group.js +4 -2
  85. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.d.ts +3 -1
  86. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.js +23 -3
  87. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.d.ts +3 -0
  88. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.js +37 -5
  89. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.d.ts +3 -0
  90. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.js +99 -35
  91. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-options.js +30 -22
  92. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-settings.js +8 -6
  93. package/dist/_pkgs/data-view/widget-presets/views-bar/views-view.js +15 -0
  94. package/dist/host-panels.d.ts +1 -1
  95. package/dist/host-panels.js +2 -1
  96. package/package.json +1 -1
@@ -137,7 +137,7 @@ let ReferencePopup = (() => {
137
137
  box-sizing: border-box;
138
138
  }
139
139
 
140
- .overlay-mask {
140
+ reference-popup .overlay-mask {
141
141
  position: fixed;
142
142
  z-index: var(--affine-z-index-popover);
143
143
  top: 0;
@@ -146,8 +146,8 @@ let ReferencePopup = (() => {
146
146
  height: 100vh;
147
147
  }
148
148
 
149
- ${panelBaseStyle('.popover-container')}
150
- .popover-container {
149
+ ${panelBaseStyle('reference-popup .popover-container')}
150
+ reference-popup .popover-container {
151
151
  position: absolute;
152
152
  display: flex;
153
153
  gap: 8px;
@@ -169,7 +169,7 @@ let ReferencePopup = (() => {
169
169
  }
170
170
  }
171
171
 
172
- input {
172
+ reference-popup input {
173
173
  display: flex;
174
174
  flex: 1;
175
175
  padding: 0;
@@ -177,16 +177,16 @@ let ReferencePopup = (() => {
177
177
  background: transparent;
178
178
  color: var(--affine-text-primary-color);
179
179
  }
180
- ${fontXSStyle('input')}
181
- input::placeholder {
180
+ ${fontXSStyle('reference-popup input')}
181
+ reference-popup input::placeholder {
182
182
  color: var(--affine-placeholder-color);
183
183
  }
184
- input:focus {
184
+ reference-popup input:focus {
185
185
  outline: none;
186
186
  }
187
187
 
188
- ${fontXSStyle('editor-icon-button.save .label')}
189
- editor-icon-button.save .label {
188
+ ${fontXSStyle('reference-popup editor-icon-button.save .label')}
189
+ reference-popup editor-icon-button.save .label {
190
190
  color: inherit;
191
191
  text-transform: none;
192
192
  }
@@ -64,4 +64,5 @@ declare const SCALE: {
64
64
  */
65
65
  export declare const NOTATION_NEUTRALS: Readonly<typeof SCALE>;
66
66
  export type NotationNeutral = keyof typeof NOTATION_NEUTRALS;
67
+ export declare const NOTATION_NEUTRALS_DARK: Readonly<Record<NotationNeutral, string>>;
67
68
  export {};
@@ -63,3 +63,32 @@ const SCALE = {
63
63
  * colour is (a helper whose parameter type is inferred from a default).
64
64
  */
65
65
  export const NOTATION_NEUTRALS = Object.freeze(SCALE);
66
+ /**
67
+ * The same scale on the dark canvas, key for key (ADR 0039). A canvas in the
68
+ * dark scheme does not show a white card with a near-black ink: the PAPER
69
+ * inverts and the hues stay. These values are what a notation element is
70
+ * painted with at render time when the canvas is dark; nothing is written to a
71
+ * document, and the SVG export keeps the light scale.
72
+ *
73
+ * Read off the dark chrome the editor already paints (`@toeverything/theme`,
74
+ * AFFiNE's dark tables): the card is the dark page itself (`rgb(20, 20, 20)`,
75
+ * `--affine-background-primary-color`), exactly as the light card is the white
76
+ * page, so a board is told apart by its border in both schemes; the ink is the
77
+ * dark primary text (`rgb(234, 234, 234)`) and the label the dark secondary
78
+ * text (`rgb(156, 156, 160)`). Every other step keeps the contrast its light
79
+ * twin has on white, within a hair (`notation-dark-scale.unit.spec.ts`).
80
+ *
81
+ * The card being the page is also what keeps a dark accent around 3:1 under a
82
+ * selection (labre ADR-0049, question 2): anything lighter than the page
83
+ * beneath a selection frame lowers it.
84
+ */
85
+ const DARK_SCALE = {
86
+ ink: '#eaeaea',
87
+ frameInk: '#c4c5c9',
88
+ label: '#9c9ca0',
89
+ divider: '#5f6166',
90
+ cardFill: '#141414',
91
+ cardBorder: '#3a3a3c',
92
+ legendBorder: '#4b4d52',
93
+ };
94
+ export const NOTATION_NEUTRALS_DARK = Object.freeze(DARK_SCALE);
@@ -61,6 +61,10 @@ export interface FrameTemplateFrameRef {
61
61
  * your id for the template: link what the copy references (its images) to
62
62
  * that document. Not called when the insertion failed.
63
63
  *
64
+ * - `showTemplates` (since 0.54) is called by the « View » action of the
65
+ * notification that says a save succeeded: open the place where the viewer
66
+ * finds the saved template. Absent, that notification has no action.
67
+ *
64
68
  * Both notifications may come twice for one gesture (two editors mounted on
65
69
  * one document): make them idempotent.
66
70
  */
@@ -70,6 +74,7 @@ export interface FrameTemplateService {
70
74
  frameDeleted?(frame: FrameTemplateFrameRef): void;
71
75
  frameRestored?(frame: FrameTemplateFrameRef): void;
72
76
  frameInserted?(insertion: FrameTemplateInsertion): void;
77
+ showTemplates?(): void;
73
78
  }
74
79
  export declare const FrameTemplateProvider: import("../../global/di/types.js").ServiceIdentifier<FrameTemplateService> & (<U extends FrameTemplateService = FrameTemplateService>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
75
80
  /**
@@ -29,6 +29,7 @@ export * from './native-clipboard-service.js';
29
29
  export * from './notification-service.js';
30
30
  export * from './open-doc-config.js';
31
31
  export * from './outline-panel-service.js';
32
+ export * from './page-drop-service.js';
32
33
  export * from './page-viewport-service.js';
33
34
  export * from './parse-url-service.js';
34
35
  export * from './pivot-materiality-publisher.js';
@@ -29,6 +29,7 @@ export * from './native-clipboard-service.js';
29
29
  export * from './notification-service.js';
30
30
  export * from './open-doc-config.js';
31
31
  export * from './outline-panel-service.js';
32
+ export * from './page-drop-service.js';
32
33
  export * from './page-viewport-service.js';
33
34
  export * from './parse-url-service.js';
34
35
  export * from './pivot-materiality-publisher.js';
@@ -0,0 +1,61 @@
1
+ import type { BlockStdScope } from '../../std/index.js';
2
+ import type { ExtensionType } from '../../store/index.js';
3
+ /**
4
+ * What the host says about a native drag over an editor (dragover).
5
+ *
6
+ * - `'ignore'`: not one of the host's pages; the editor treats it as before.
7
+ * - `'refuse'`: one of the host's pages that may not land in this document;
8
+ * the browser shows its no-drop cursor and no drop line is drawn.
9
+ * - `'accept'`: the drop line is drawn and the drop calls `drop`.
10
+ */
11
+ export type PageDropVerdict = 'ignore' | 'refuse' | 'accept';
12
+ /** The page a drop placed, and how to undo what the host did for it. */
13
+ export interface PageDrop {
14
+ /** The page whose `affine:embed-linked-doc` card goes where it was dropped. */
15
+ pageId: string;
16
+ /**
17
+ * Called when the undo step that holds the card is undone (Mod+Z, the
18
+ * history), after the card is gone: put back what `drop` changed.
19
+ */
20
+ undo?: () => void;
21
+ /** Called when that step is redone, after the card is back. */
22
+ redo?: () => void;
23
+ }
24
+ /**
25
+ * Pages dragged from the host's own chrome (a sidebar tree) into the editor
26
+ * (labre P7, ADR-0045 §7 amended 2026-10-16): the host recognises its drag
27
+ * and decides what the drop means; the editor draws the drop line and places
28
+ * a card of the page where it was dropped (the drop line in a note, the drop
29
+ * point on the canvas), in one undo step that also carries the host's
30
+ * `undo` / `redo`.
31
+ *
32
+ * - `verdict` is called on every dragover. The browser exposes only
33
+ * `dataTransfer.types` then, never the data: the host knows which page it is
34
+ * dragging from its own state. A read-only document refuses whatever the
35
+ * host says.
36
+ * - `drop` is called once, synchronously, for a drop the last `verdict`
37
+ * accepted. Return `null` to place nothing (the host changed its mind).
38
+ * The editor never copies or creates a page: the card references `pageId`.
39
+ */
40
+ export interface PageDropService {
41
+ verdict(drag: {
42
+ types: readonly string[];
43
+ docId: string;
44
+ }): PageDropVerdict;
45
+ drop(drag: {
46
+ dataTransfer: DataTransfer;
47
+ docId: string;
48
+ }): PageDrop | null;
49
+ }
50
+ export declare const PageDropProvider: import("../../global/di/types.js").ServiceIdentifier<PageDropService> & (<U extends PageDropService = PageDropService>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
51
+ /**
52
+ * Host seam: accept pages dragged from the host into the editor. Absent, such
53
+ * a drag is any other foreign drag: the page view draws its drop line and the
54
+ * drop does nothing.
55
+ *
56
+ * `di.override`, like `FrameTemplateExtension`, so a later registration wins
57
+ * and registering twice never throws at boot.
58
+ */
59
+ export declare function PageDropExtension(service: PageDropService): ExtensionType;
60
+ /** The verdict for a native drag over `std`'s editor, read-only included. */
61
+ export declare function pageDropVerdict(std: Pick<BlockStdScope, 'getOptional' | 'store'>, dataTransfer: DataTransfer | null): PageDropVerdict;
@@ -0,0 +1,30 @@
1
+ import { createIdentifier } from '../../global/di/index.js';
2
+ export const PageDropProvider = createIdentifier('AffinePageDrop');
3
+ /**
4
+ * Host seam: accept pages dragged from the host into the editor. Absent, such
5
+ * a drag is any other foreign drag: the page view draws its drop line and the
6
+ * drop does nothing.
7
+ *
8
+ * `di.override`, like `FrameTemplateExtension`, so a later registration wins
9
+ * and registering twice never throws at boot.
10
+ */
11
+ export function PageDropExtension(service) {
12
+ return {
13
+ setup: di => {
14
+ di.override(PageDropProvider, () => service);
15
+ },
16
+ };
17
+ }
18
+ /** The verdict for a native drag over `std`'s editor, read-only included. */
19
+ export function pageDropVerdict(std, dataTransfer) {
20
+ const service = std.getOptional(PageDropProvider);
21
+ if (!service || !dataTransfer)
22
+ return 'ignore';
23
+ const verdict = service.verdict({
24
+ types: [...dataTransfer.types],
25
+ docId: std.store.id,
26
+ });
27
+ if (verdict === 'ignore')
28
+ return 'ignore';
29
+ return std.store.readonly ? 'refuse' : verdict;
30
+ }
@@ -6,6 +6,21 @@ import type { ReadonlySignal } from '@preact/signals-core';
6
6
  * the one the library already gives it: a block id, an element id.
7
7
  */
8
8
  export type ThreadMarkTargetType = 'block' | 'element';
9
+ /**
10
+ * A run of text inside one block (ADR 0038, amendment of 2026-10-15): two
11
+ * Yjs relative positions on the block's `Y.Text`, each encoded with
12
+ * `Y.encodeRelativePosition` then base64. Opaque to the host, which stores
13
+ * and returns the two strings as they are; only the library reads them.
14
+ *
15
+ * A relative position follows the text through local and remote edits and is
16
+ * computed by reading, never by writing: nothing lands in the document. When
17
+ * the text it points at is gone, the range no longer resolves and the library
18
+ * falls back to the block.
19
+ */
20
+ export interface ThreadTextRange {
21
+ start: string;
22
+ end: string;
23
+ }
9
24
  /**
10
25
  * One marked target of the mounted document, as the host computes it. Ids and
11
26
  * counters only: the library never learns what the host attaches to a target,
@@ -19,6 +34,14 @@ export interface ThreadMark {
19
34
  targetId: string;
20
35
  count: number;
21
36
  unread: boolean;
37
+ /**
38
+ * The text the discussion is anchored to, for a `block` target (ignored on
39
+ * an `element`). Optional (ADR 0038 amendment): without it only the
40
+ * block's mark is drawn; with it the run is also highlighted in the page
41
+ * view, and a click on it opens the discussion. A range that no longer
42
+ * resolves is not drawn, and the block's mark stays.
43
+ */
44
+ range?: ThreadTextRange;
22
45
  }
23
46
  /**
24
47
  * What the library hands the host when someone clicks a mark or "Discuss"
@@ -26,7 +49,8 @@ export interface ThreadMark {
26
49
  *
27
50
  * `excerpt` is computed here because the library knows the content: the
28
51
  * block's text (its descendants' for a container such as a note), a canvas
29
- * element's label as the selection pane reads it, else the kind of target.
52
+ * element's label as the selection pane reads it, else the kind of target;
53
+ * for a text range, the text it covers.
30
54
  * Whitespace is collapsed and it is cut to at most 280 characters, an
31
55
  * ellipsis included (`THREAD_EXCERPT_MAX`). It is a snapshot for the host to
32
56
  * store beside its anchor; the library keeps no copy.
@@ -36,6 +60,13 @@ export interface ThreadOpenRequest {
36
60
  targetType: ThreadMarkTargetType;
37
61
  targetId: string;
38
62
  excerpt: string;
63
+ /**
64
+ * Set when the discussion starts on a text selection, or on a highlighted
65
+ * range (ADR 0038 amendment): the block is still `targetId` and
66
+ * `excerpt` is the selected text itself. Absent for a whole block or an
67
+ * element.
68
+ */
69
+ range?: ThreadTextRange;
39
70
  }
40
71
  /** The longest excerpt `openThread` is ever handed, in code points. */
41
72
  export declare const THREAD_EXCERPT_MAX = 280;
@@ -23,7 +23,7 @@ export const scrollbarStyle = (container) => {
23
23
  }
24
24
  ${unsafeCSS(container)}::-webkit-scrollbar-thumb {
25
25
  border-radius: 2px;
26
- background-color: #b1b1b1;
26
+ background-color: var(--affine-text-disable-color);
27
27
  }
28
28
  ${unsafeCSS(container)}::-webkit-scrollbar-corner {
29
29
  display: none;
@@ -157,7 +157,7 @@ let EdgelessAutoConnectWidget = (() => {
157
157
  .page-visible-index-label {
158
158
  box-sizing: border-box;
159
159
  padding: 0px 6px;
160
- border: 1px solid #0000001a;
160
+ border: 1px solid var(--affine-black-10);
161
161
 
162
162
  width: fit-content;
163
163
  height: 24px;
@@ -179,9 +179,9 @@ let EdgelessAutoConnectWidget = (() => {
179
179
  width: 48px;
180
180
  padding: 4px;
181
181
  border-radius: 58px;
182
- border: 1px solid rgba(227, 226, 228, 1);
182
+ border: 1px solid var(--affine-border-color);
183
183
  transition: opacity 0.5s ease-in-out;
184
- background: rgba(251, 251, 252, 1);
184
+ background: var(--affine-background-overlay-panel-color);
185
185
  display: flex;
186
186
  align-items: center;
187
187
  justify-content: space-between;
@@ -197,7 +197,7 @@ let EdgelessAutoConnectWidget = (() => {
197
197
  .navigator span {
198
198
  display: inline-block;
199
199
  height: 8px;
200
- border: 1px solid rgba(227, 226, 228, 1);
200
+ border: 1px solid var(--affine-border-color);
201
201
  }
202
202
 
203
203
  .navigator div:hover {
@@ -357,7 +357,7 @@ let EdgelessAutoConnectWidget = (() => {
357
357
  const iconStyle = {
358
358
  width: '16px',
359
359
  height: '16px',
360
- style: 'color:#77757D;',
360
+ style: 'color:var(--affine-icon-color);',
361
361
  };
362
362
  return html `<div class=${className} style=${navigatorStyle}>
363
363
  <div
@@ -180,8 +180,8 @@ let EdgelessAutoComplete = (() => {
180
180
  }
181
181
 
182
182
  .edgeless-auto-complete-arrow svg {
183
- fill: #77757d;
184
- color: #77757d;
183
+ fill: var(--affine-icon-color);
184
+ color: var(--affine-icon-color);
185
185
  }
186
186
  .edgeless-auto-complete-arrow:hover svg {
187
187
  fill: #ffffff;
@@ -563,7 +563,7 @@ const textStyleToolItems = [
563
563
  icon: BoldIcon(),
564
564
  background: ({ std }) => {
565
565
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
566
- return textAttributes?.bold ? '#00000012' : '';
566
+ return textAttributes?.bold ? 'var(--affine-hover-color)' : '';
567
567
  },
568
568
  action: ({ std }) => {
569
569
  std.command.exec(toggleBold);
@@ -574,7 +574,7 @@ const textStyleToolItems = [
574
574
  icon: ItalicIcon(),
575
575
  background: ({ std }) => {
576
576
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
577
- return textAttributes?.italic ? '#00000012' : '';
577
+ return textAttributes?.italic ? 'var(--affine-hover-color)' : '';
578
578
  },
579
579
  action: ({ std }) => {
580
580
  std.command.exec(toggleItalic);
@@ -585,7 +585,7 @@ const textStyleToolItems = [
585
585
  icon: UnderLineIcon(),
586
586
  background: ({ std }) => {
587
587
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
588
- return textAttributes?.underline ? '#00000012' : '';
588
+ return textAttributes?.underline ? 'var(--affine-hover-color)' : '';
589
589
  },
590
590
  action: ({ std }) => {
591
591
  std.command.exec(toggleUnderline);
@@ -596,7 +596,7 @@ const textStyleToolItems = [
596
596
  icon: StrikeThroughIcon(),
597
597
  background: ({ std }) => {
598
598
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
599
- return textAttributes?.strike ? '#00000012' : '';
599
+ return textAttributes?.strike ? 'var(--affine-hover-color)' : '';
600
600
  },
601
601
  action: ({ std }) => {
602
602
  std.command.exec(toggleStrike);
@@ -607,7 +607,7 @@ const textStyleToolItems = [
607
607
  icon: CodeIcon(),
608
608
  background: ({ std }) => {
609
609
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
610
- return textAttributes?.code ? '#00000012' : '';
610
+ return textAttributes?.code ? 'var(--affine-hover-color)' : '';
611
611
  },
612
612
  action: ({ std }) => {
613
613
  std.command.exec(toggleCode);
@@ -618,7 +618,7 @@ const textStyleToolItems = [
618
618
  icon: LinkIcon(),
619
619
  background: ({ std }) => {
620
620
  const [_, { textAttributes }] = std.command.exec(getTextAttributes);
621
- return textAttributes?.link ? '#00000012' : '';
621
+ return textAttributes?.link ? 'var(--affine-hover-color)' : '';
622
622
  },
623
623
  action: ({ std }) => {
624
624
  std.command.exec(toggleLink);
@@ -119,7 +119,7 @@ export const keyboardToolPanelStyles = css `
119
119
  }
120
120
 
121
121
  button:active {
122
- background: #00000012;
122
+ background: var(--affine-hover-color);
123
123
  }
124
124
 
125
125
  button svg {
@@ -44,6 +44,14 @@ export declare function createLinkedDocMenuGroup(query: string, abort: () => voi
44
44
  overflowText: string;
45
45
  };
46
46
  export declare function createNewDocMenuGroup(query: string, abort: () => void, editorHost: EditorHost, inlineEditor: AffineInlineEditor): LinkedMenuGroup;
47
+ /**
48
+ * The default `@` menu REFERENCES a page that exists; it creates nothing.
49
+ * Creating a page is the slash menu's job ("New Page", which goes through
50
+ * `LinkedDocCreationProvider`), so the two gestures never overlap: `@` points,
51
+ * `/` makes (PO, 2026-10-14). The creation group is still exported through
52
+ * {@link LinkedWidgetUtils} for a host that wants it back in its own
53
+ * `getMenus`.
54
+ */
47
55
  export declare function getMenus(query: string, abort: () => void, editorHost: EditorHost, inlineEditor: AffineInlineEditor): Promise<LinkedMenuGroup[]>;
48
56
  export declare const LinkedWidgetUtils: {
49
57
  createNewDocMenuGroup: typeof createNewDocMenuGroup;
@@ -120,10 +120,17 @@ export function createNewDocMenuGroup(query, abort, editorHost, inlineEditor) {
120
120
  items,
121
121
  };
122
122
  }
123
+ /**
124
+ * The default `@` menu REFERENCES a page that exists; it creates nothing.
125
+ * Creating a page is the slash menu's job ("New Page", which goes through
126
+ * `LinkedDocCreationProvider`), so the two gestures never overlap: `@` points,
127
+ * `/` makes (PO, 2026-10-14). The creation group is still exported through
128
+ * {@link LinkedWidgetUtils} for a host that wants it back in its own
129
+ * `getMenus`.
130
+ */
123
131
  export function getMenus(query, abort, editorHost, inlineEditor) {
124
132
  return Promise.resolve([
125
133
  createLinkedDocMenuGroup(query, abort, editorHost, inlineEditor),
126
- createNewDocMenuGroup(query, abort, editorHost, inlineEditor),
127
134
  ]);
128
135
  }
129
136
  export const LinkedWidgetUtils = {
@@ -47,7 +47,7 @@ import { property, query, queryAll, state } from 'lit/decorators.js';
47
47
  import { styleMap } from 'lit/directives/style-map.js';
48
48
  import throttle from 'lodash-es/throttle.js';
49
49
  import { linkedDocPopoverStyles } from './styles.js';
50
- import { LINKED_DOC_OVERFLOW_MORE } from './translations.js';
50
+ import { LINKED_DOC_NO_RESULT, LINKED_DOC_OVERFLOW_MORE, } from './translations.js';
51
51
  import { createLinkedDocKeydownInterceptor, resolveSignal } from './utils.js';
52
52
  let LinkedDocPopover = (() => {
53
53
  let _classDecorators = [requiredProperties({
@@ -239,46 +239,54 @@ let LinkedDocPopover = (() => {
239
239
  isHidden,
240
240
  };
241
241
  });
242
+ const shown = actionGroups.filter(group => (group.items.length > 0 || group.isLoading) && !group.isHidden);
243
+ // An empty box reads as a broken menu: since "@" only references
244
+ // existing pages, "no match" is an ordinary outcome and says so.
242
245
  return html `<div class="linked-doc-popover" style="${style}">
243
- ${actionGroups
244
- .filter(group => (group.items.length > 0 || group.isLoading) && !group.isHidden)
245
- .map((group, idx) => {
246
+ ${shown.length === 0
247
+ ? html `<div class="group-title" data-testid="linked-doc-no-result">
248
+ <div class="group-title-text">
249
+ ${translateKey(this.context.std, ...LINKED_DOC_NO_RESULT)}
250
+ </div>
251
+ </div>`
252
+ : nothing}
253
+ ${shown.map((group, idx) => {
246
254
  return html `
247
- <div class="divider" ?hidden=${idx === 0}></div>
248
- <div class="group-title">
249
- <div class="group-title-text">${group.name}</div>
250
- ${group.isLoading
255
+ <div class="divider" ?hidden=${idx === 0}></div>
256
+ <div class="group-title">
257
+ <div class="group-title-text">${group.name}</div>
258
+ ${group.isLoading
251
259
  ? html `<span class="loading-icon">${LoadingIcon()}</span>`
252
260
  : nothing}
253
- </div>
254
- <div class="group" style=${group.styles ?? ''}>
255
- ${group.items.map(({ key, name, icon, action }) => {
261
+ </div>
262
+ <div class="group" style=${group.styles ?? ''}>
263
+ ${group.items.map(({ key, name, icon, action }) => {
256
264
  const tooltip = this._showTooltip
257
265
  ? html `<affine-tooltip
258
- tip-position=${'right'}
259
- .tooltipStyle=${css `
260
- * {
261
- color: ${unsafeCSSVar('white')} !important;
262
- }
263
- `}
264
- >${name}</affine-tooltip
265
- >`
266
+ tip-position=${'right'}
267
+ .tooltipStyle=${css `
268
+ * {
269
+ color: ${unsafeCSSVar('white')} !important;
270
+ }
271
+ `}
272
+ >${name}</affine-tooltip
273
+ >`
266
274
  : nothing;
267
275
  return html `<icon-button
268
- width="260px"
269
- height="30px"
270
- data-id=${key}
271
- .text=${name}
272
- hover=${this._activatedItemKey === key}
273
- @pointerdown=${(e) => {
276
+ width="260px"
277
+ height="30px"
278
+ data-id=${key}
279
+ .text=${name}
280
+ hover=${this._activatedItemKey === key}
281
+ @pointerdown=${(e) => {
274
282
  // Prevent event listeners being registered on the root document
275
283
  // eg., radix-ui dialogs usePointerDownOutside hooks
276
284
  e.stopPropagation();
277
285
  }}
278
- @click=${() => {
286
+ @click=${() => {
279
287
  action()?.catch(console.error);
280
288
  }}
281
- @mousemove=${() => {
289
+ @mousemove=${() => {
282
290
  // Use `mousemove` instead of `mouseover` to avoid navigate conflict with keyboard
283
291
  this._activatedItemKey = key;
284
292
  // show tooltip whether text length overflows
@@ -290,12 +298,12 @@ let LinkedDocPopover = (() => {
290
298
  }
291
299
  }
292
300
  }}
293
- >
294
- ${icon} ${tooltip}
295
- </icon-button>`;
301
+ >
302
+ ${icon} ${tooltip}
303
+ </icon-button>`;
296
304
  })}
297
- </div>
298
- `;
305
+ </div>
306
+ `;
299
307
  })}
300
308
  </div>`;
301
309
  }
@@ -6,7 +6,7 @@ export const linkedDocWidgetStyles = css `
6
6
  .input-mask {
7
7
  position: absolute;
8
8
  pointer-events: none;
9
- background: rgba(0, 0, 0, 0.1);
9
+ background: var(--affine-black-10);
10
10
  border-radius: 4px;
11
11
  }
12
12
  `;
@@ -18,6 +18,8 @@ export declare const LINKED_DOC_NEW_DOC: ChromeWording;
18
18
  * word, in the same package.
19
19
  */
20
20
  export declare const LINKED_DOC_IMPORT: ChromeWording;
21
+ /** Said when no page matches the query, instead of an empty popover. */
22
+ export declare const LINKED_DOC_NO_RESULT: ChromeWording;
21
23
  /** The "Create doc" item's own wording, interpolated with the truncated name. */
22
24
  export declare const LINKED_DOC_CREATE_DOC: ChromeWording;
23
25
  /**
@@ -25,6 +25,11 @@ export const LINKED_DOC_IMPORT = [
25
25
  'com.labre.linked-doc.import',
26
26
  'Import',
27
27
  ];
28
+ /** Said when no page matches the query, instead of an empty popover. */
29
+ export const LINKED_DOC_NO_RESULT = [
30
+ 'com.labre.linked-doc.no-result',
31
+ 'No page found',
32
+ ];
28
33
  /** The "Create doc" item's own wording, interpolated with the truncated name. */
29
34
  export const LINKED_DOC_CREATE_DOC = [
30
35
  'com.labre.linked-doc.create-doc',
@@ -92,6 +97,7 @@ export const LINKED_DOC_WORDINGS = [
92
97
  LINKED_DOC_LINK_TO_DOC,
93
98
  LINKED_DOC_IMPORT,
94
99
  LINKED_DOC_CREATE_DOC,
100
+ LINKED_DOC_NO_RESULT,
95
101
  LINKED_DOC_IMPORT_SUCCESS_TOAST,
96
102
  LINKED_DOC_OVERFLOW_MORE,
97
103
  LINKED_DOC_IMPORT_NOTION_MARKDOWN_DEPRECATED,
@@ -1,9 +1,13 @@
1
- import { type ThreadMarkTargetType } from '../affine-shared/services/index.js';
1
+ import { type ThreadMarkTargetType, type ThreadTextRange } from '../affine-shared/services/index.js';
2
2
  import type { BlockStdScope } from '../std/index.js';
3
- /** A block or a canvas element, by the id the library gave it. */
3
+ /**
4
+ * A block or a canvas element, by the id the library gave it, and for a
5
+ * block optionally a run of its text (ADR 0038, amendment of 2026-10-15).
6
+ */
4
7
  export interface ThreadTarget {
5
8
  targetType: ThreadMarkTargetType;
6
9
  targetId: string;
10
+ range?: ThreadTextRange;
7
11
  }
8
12
  /**
9
13
  * Collapse whitespace and cut to {@link THREAD_EXCERPT_MAX} code points, the
@@ -2,6 +2,7 @@ import { getSurfaceBlock } from '../affine-block-surface/index.js';
2
2
  import { THREAD_EXCERPT_MAX, } from '../affine-shared/services/index.js';
3
3
  import { selectionPaneRowLabel, selectionPaneTypeLabel, } from '../affine-widget-edgeless-toolbar/index.js';
4
4
  import { GfxBlockElementModel } from '../std/gfx/index.js';
5
+ import { resolveThreadRange } from './text-range.js';
5
6
  const ELLIPSIS = '…';
6
7
  /**
7
8
  * Collapse whitespace and cut to {@link THREAD_EXCERPT_MAX} code points, the
@@ -45,6 +46,13 @@ function rawExcerpt(std, target) {
45
46
  const model = std.store.getModelById(target.targetId);
46
47
  if (!model)
47
48
  return '';
49
+ // A run of text reads as itself: the quote a reader selected. A range
50
+ // that no longer resolves falls back to the block, like its mark.
51
+ const yText = model.text?.yText;
52
+ const run = target.range && yText ? resolveThreadRange(yText, target.range) : null;
53
+ if (yText && run) {
54
+ return yText.toString().slice(run.index, run.index + run.length);
55
+ }
48
56
  const text = blockText(model);
49
57
  if (text.trim())
50
58
  return text;
@@ -1,4 +1,5 @@
1
1
  export * from './excerpt.js';
2
+ export * from './text-range.js';
2
3
  export * from './toolbar.js';
3
4
  export * from './translations.js';
4
5
  export * from './widget.js';
@@ -2,6 +2,7 @@ import { WidgetViewExtension } from '../std/index.js';
2
2
  import { literal, unsafeStatic } from 'lit/static-html.js';
3
3
  import { AFFINE_THREAD_MARKS_WIDGET } from './widget.js';
4
4
  export * from './excerpt.js';
5
+ export * from './text-range.js';
5
6
  export * from './toolbar.js';
6
7
  export * from './translations.js';
7
8
  export * from './widget.js';