@formicoidea/labre-core 0.53.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 (85) 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 +47 -0
  66. package/dist/_pkgs/data-view/translations.js +124 -0
  67. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.d.ts +3 -1
  68. package/dist/_pkgs/data-view/view-presets/table/mobile/column-header.js +102 -124
  69. package/dist/_pkgs/data-view/view-presets/table/mobile/group.js +4 -2
  70. package/dist/_pkgs/data-view/view-presets/table/mobile/header.js +17 -9
  71. package/dist/_pkgs/data-view/view-presets/table/mobile/row.js +20 -11
  72. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.d.ts +17 -0
  73. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-style.js +31 -6
  74. package/dist/_pkgs/data-view/view-presets/table/mobile/table-view-ui-logic.js +2 -2
  75. package/dist/_pkgs/data-view/view-presets/table/pc/group.js +4 -2
  76. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.d.ts +3 -1
  77. package/dist/_pkgs/data-view/widget-presets/quick-setting-bar/filter/list-view.js +23 -3
  78. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.d.ts +3 -0
  79. package/dist/_pkgs/data-view/widget-presets/tools/presets/search/search.js +37 -5
  80. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.d.ts +3 -0
  81. package/dist/_pkgs/data-view/widget-presets/tools/presets/table-add-row/add-row.js +99 -35
  82. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-options.js +30 -22
  83. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-settings.js +8 -6
  84. package/dist/_pkgs/data-view/widget-presets/views-bar/views-view.js +15 -0
  85. 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
+ }
@@ -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,
@@ -26,7 +26,9 @@ export declare function hitsThreadRange(rects: readonly SelectionRect[], x: numb
26
26
  * - a target `locate` cannot find (deleted, of the other view, hidden for this
27
27
  * viewer) is skipped, never an error: an orphan anchor has no mark;
28
28
  * - a mark at `count <= 0` is not drawn;
29
- * - two marks on one target are one mark: counts add up, unread wins.
29
+ * - two marks on one target are one mark: counts add up, unread wins, and
30
+ * the run of text it opens is kept only when every merged mark names the
31
+ * same one (a mark that stands for several discussions opens the block).
30
32
  */
31
33
  export declare function placeThreadMarks(marks: readonly ThreadMark[], locate: (mark: ThreadMark) => ThreadMarkPlace | null): PlacedThreadMark[];
32
34
  /**
@@ -21,6 +21,7 @@ export function hitsThreadRange(rects, x, y) {
21
21
  y >= rect.top &&
22
22
  y <= rect.top + rect.height);
23
23
  }
24
+ const sameRange = (a, b) => a?.start === b?.start && a?.end === b?.end;
24
25
  /**
25
26
  * The marks to draw, from what the host says and where the editor finds each
26
27
  * target. Pure, so the "ignore what is absent" rule is held by a test:
@@ -28,7 +29,9 @@ export function hitsThreadRange(rects, x, y) {
28
29
  * - a target `locate` cannot find (deleted, of the other view, hidden for this
29
30
  * viewer) is skipped, never an error: an orphan anchor has no mark;
30
31
  * - a mark at `count <= 0` is not drawn;
31
- * - two marks on one target are one mark: counts add up, unread wins.
32
+ * - two marks on one target are one mark: counts add up, unread wins, and
33
+ * the run of text it opens is kept only when every merged mark names the
34
+ * same one (a mark that stands for several discussions opens the block).
32
35
  */
33
36
  export function placeThreadMarks(marks, locate) {
34
37
  const merged = new Map();
@@ -42,6 +45,7 @@ export function placeThreadMarks(marks, locate) {
42
45
  ...seen,
43
46
  count: seen.count + mark.count,
44
47
  unread: seen.unread || mark.unread,
48
+ range: sameRange(seen.range, mark.range) ? seen.range : undefined,
45
49
  }
46
50
  : mark);
47
51
  }
@@ -317,6 +321,7 @@ export class ThreadMarksWidget extends WidgetComponent {
317
321
  openThreadOn(this.std, {
318
322
  targetType: mark.targetType,
319
323
  targetId: mark.targetId,
324
+ range: mark.range,
320
325
  });
321
326
  }}
322
327
  >${CommentIcon({ width: '16px', height: '16px' })}</icon-button
@@ -1,5 +1,5 @@
1
1
  import { type EditorToolbar } from '../affine-components/toolbar/index.js';
2
- import { ActionPlacement, type ToolbarActions, type ToolbarContext, type ToolbarLayoutStep, type ToolbarPlacement } from '../affine-shared/services/index.js';
2
+ import { ActionPlacement, type ToolbarAction, type ToolbarActions, type ToolbarContext, type ToolbarLayoutStep, type ToolbarPlacement } from '../affine-shared/services/index.js';
3
3
  import type { AutoUpdateOptions, ReferenceElement, SideObject } from '@floating-ui/dom';
4
4
  export declare const sideMap: Map<string, {
5
5
  top: number;
@@ -33,6 +33,7 @@ export declare function combine(actions: ToolbarActions, context: ToolbarContext
33
33
  content?: ((cx: ToolbarContext) => import("lit-html").TemplateResult | null) | (import("lit-html").TemplateResult | null);
34
34
  run?: (cx: ToolbarContext) => void;
35
35
  }[];
36
+ export declare function group(actions: ToolbarAction[]): ToolbarAction[];
36
37
  /**
37
38
  * How much of the row an entry has given up, decided by {@link ToolbarFitter}
38
39
  * and re-applied by every render until the room changes again.
@@ -210,7 +210,7 @@ export function combine(actions, context) {
210
210
  });
211
211
  return filtered;
212
212
  }
213
- function group(actions) {
213
+ export function group(actions) {
214
214
  const grouped = groupBy(actions, a => a.id);
215
215
  const paired = toPairs(grouped).map(([_, items]) => {
216
216
  if (items.length === 1)
@@ -222,7 +222,10 @@ function group(actions) {
222
222
  });
223
223
  return paired;
224
224
  }
225
- const merge = (a, b) => mergeWith(a, b, (obj, src) => Array.isArray(obj) ? group(obj.concat(src)) : src);
225
+ // Only a group's `actions` is a list of entries to merge by id; any other
226
+ // array (a `labelWording` tuple) is a value, and grouping it turned the
227
+ // wording into a keyless object the host's i18next choked on.
228
+ const merge = (a, b) => mergeWith(a, b, (obj, src, key) => key === 'actions' && Array.isArray(obj) ? group(obj.concat(src)) : src);
226
229
  export function emptyToolbarLayout() {
227
230
  return { shrunk: new Set(), collapsed: new Set() };
228
231
  }
@@ -147,11 +147,11 @@ let DataViewPropertiesSettingView = (() => {
147
147
  width: 4px;
148
148
  height: 12px;
149
149
  border-radius: 1px;
150
- background-color: #efeff0;
150
+ background-color: var(--affine-border-color);
151
151
  }
152
152
 
153
153
  .property-item:hover .property-item-drag-bar {
154
- background-color: #c0bfc1;
154
+ background-color: var(--affine-placeholder-color);
155
155
  }
156
156
 
157
157
  .property-item-icon {