@formicoidea/labre-core 0.42.0 → 0.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/_pkgs/affine-block-data-view/configs/tooltips.js +1 -1
  2. package/dist/_pkgs/affine-block-database/configs/tooltips.js +1 -1
  3. package/dist/_pkgs/affine-block-edgeless-text/edgeless-text-block.js +4 -2
  4. package/dist/_pkgs/affine-block-embed/common/iframe-sandbox.d.ts +39 -0
  5. package/dist/_pkgs/affine-block-embed/common/iframe-sandbox.js +39 -0
  6. package/dist/_pkgs/affine-block-embed/embed-figma-block/embed-figma-block.js +2 -0
  7. package/dist/_pkgs/affine-block-embed/embed-iframe-block/components/embed-iframe-error-card.d.ts +14 -1
  8. package/dist/_pkgs/affine-block-embed/embed-iframe-block/components/embed-iframe-error-card.js +21 -2
  9. package/dist/_pkgs/affine-block-embed/embed-iframe-block/consts.d.ts +0 -2
  10. package/dist/_pkgs/affine-block-embed/embed-iframe-block/consts.js +2 -5
  11. package/dist/_pkgs/affine-block-embed/embed-iframe-block/embed-iframe-block.d.ts +1 -1
  12. package/dist/_pkgs/affine-block-embed/embed-iframe-block/embed-iframe-block.js +14 -6
  13. package/dist/_pkgs/affine-block-embed/embed-iframe-block/types.d.ts +16 -0
  14. package/dist/_pkgs/affine-block-embed/embed-iframe-block/types.js +17 -1
  15. package/dist/_pkgs/affine-block-embed/embed-loom-block/embed-loom-block.js +2 -0
  16. package/dist/_pkgs/affine-block-embed/embed-youtube-block/embed-youtube-block.js +2 -0
  17. package/dist/_pkgs/affine-block-embed/index.d.ts +1 -0
  18. package/dist/_pkgs/affine-block-embed/index.js +1 -0
  19. package/dist/_pkgs/affine-block-embed/translations.d.ts +8 -0
  20. package/dist/_pkgs/affine-block-embed/translations.js +28 -0
  21. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +4 -2
  22. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/embed-synced-doc-block.js +2 -2
  23. package/dist/_pkgs/affine-block-frame/frame-manager.js +5 -2
  24. package/dist/_pkgs/affine-block-list/utils/get-list-icon.js +3 -1
  25. package/dist/_pkgs/affine-block-note/configs/tooltips.js +1 -1
  26. package/dist/_pkgs/affine-block-root/adapters/html.js +2 -2
  27. package/dist/_pkgs/affine-block-root/commands/copy-link-commands.d.ts +2 -0
  28. package/dist/_pkgs/affine-block-root/commands/copy-link-commands.js +155 -0
  29. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  30. package/dist/_pkgs/affine-block-root/index.js +1 -0
  31. package/dist/_pkgs/affine-block-root/view.js +2 -0
  32. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +13 -1
  33. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +8 -0
  34. package/dist/_pkgs/affine-block-surface/extensions/legend.js +59 -3
  35. package/dist/_pkgs/affine-block-surface/extensions/morph-toolbar.js +4 -0
  36. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +3 -0
  37. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +23 -0
  38. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +1 -1
  39. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +12 -4
  40. package/dist/_pkgs/affine-block-surface/framework-background/background-view.js +1 -1
  41. package/dist/_pkgs/affine-block-surface/framework-background/fit.d.ts +36 -0
  42. package/dist/_pkgs/affine-block-surface/framework-background/fit.js +30 -0
  43. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +1 -0
  44. package/dist/_pkgs/affine-block-surface/framework-background/index.js +1 -0
  45. package/dist/_pkgs/affine-block-surface/renderer/element-highlight-overlay.js +2 -7
  46. package/dist/_pkgs/affine-block-surface-ref/configs/tooltips.js +2 -2
  47. package/dist/_pkgs/affine-components/color-picker/palette-carousel.js +5 -0
  48. package/dist/_pkgs/affine-components/color-picker/styles.js +1 -1
  49. package/dist/_pkgs/affine-components/context-menu/button.js +3 -1
  50. package/dist/_pkgs/affine-components/icons/ai.js +3 -3
  51. package/dist/_pkgs/affine-components/icons/list.js +7 -1
  52. package/dist/_pkgs/affine-components/size-dropdown-menu/dropdown-menu.js +6 -1
  53. package/dist/_pkgs/affine-components/toolbar/index.d.ts +1 -1
  54. package/dist/_pkgs/affine-components/toolbar/index.js +1 -1
  55. package/dist/_pkgs/affine-components/toolbar/menu-button.d.ts +7 -0
  56. package/dist/_pkgs/affine-components/toolbar/menu-button.js +30 -0
  57. package/dist/_pkgs/affine-components/toolbar/picker-menu.d.ts +16 -1
  58. package/dist/_pkgs/affine-components/toolbar/picker-menu.js +2 -2
  59. package/dist/_pkgs/affine-components/toolbar/toolbar.d.ts +1 -0
  60. package/dist/_pkgs/affine-components/toolbar/toolbar.js +67 -0
  61. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card-title-editor.js +1 -1
  62. package/dist/_pkgs/affine-fragment-frame-panel/card/frame-card.js +1 -1
  63. package/dist/_pkgs/affine-gfx-brush/toolbar/components/eraser/icons.js +1 -1
  64. package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.d.ts +11 -2
  65. package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.js +11 -2
  66. package/dist/_pkgs/affine-gfx-connector/text/edgeless-connector-label-editor.js +3 -2
  67. package/dist/_pkgs/affine-gfx-connector/toolbar/config.js +23 -7
  68. package/dist/_pkgs/affine-gfx-connector/translations.d.ts +3 -0
  69. package/dist/_pkgs/affine-gfx-connector/translations.js +23 -0
  70. package/dist/_pkgs/affine-gfx-mindmap/indicator-overlay/index.js +6 -2
  71. package/dist/_pkgs/affine-gfx-mindmap/toolbar/config.js +4 -2
  72. package/dist/_pkgs/affine-gfx-mindmap/translations.d.ts +6 -0
  73. package/dist/_pkgs/affine-gfx-mindmap/translations.js +10 -0
  74. package/dist/_pkgs/affine-gfx-shape/toolbar/config.js +4 -2
  75. package/dist/_pkgs/affine-gfx-shape/translations.d.ts +7 -0
  76. package/dist/_pkgs/affine-gfx-shape/translations.js +11 -0
  77. package/dist/_pkgs/affine-gfx-template/builtin/other.js +13 -7
  78. package/dist/_pkgs/affine-gfx-template/toolbar/template-loading.js +1 -1
  79. package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
  80. package/dist/_pkgs/affine-gfx-template/translations.js +38 -0
  81. package/dist/_pkgs/affine-gfx-text/edgeless-text-editor.js +3 -2
  82. package/dist/_pkgs/affine-gfx-text/toolbar/actions.js +3 -1
  83. package/dist/_pkgs/affine-gfx-text/translations.d.ts +8 -0
  84. package/dist/_pkgs/affine-gfx-text/translations.js +12 -0
  85. package/dist/_pkgs/affine-inline-latex/latex-node/latex-editor-menu.js +2 -1
  86. package/dist/_pkgs/affine-model/consts/text.d.ts +15 -2
  87. package/dist/_pkgs/affine-model/consts/text.js +25 -1
  88. package/dist/_pkgs/affine-shared/services/font-loader/config.d.ts +13 -2
  89. package/dist/_pkgs/affine-shared/services/font-loader/config.js +43 -54
  90. package/dist/_pkgs/affine-shared/services/theme-service.d.ts +19 -0
  91. package/dist/_pkgs/affine-shared/services/theme-service.js +24 -0
  92. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +20 -0
  93. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +81 -0
  94. package/dist/_pkgs/affine-widget-edgeless-dragging-area/edgeless-dragging-area-rect.js +2 -2
  95. package/dist/_pkgs/affine-widget-edgeless-selected-rect/utils.js +5 -1
  96. package/dist/_pkgs/affine-widget-edgeless-toolbar/config/types.d.ts +15 -0
  97. package/dist/_pkgs/affine-widget-edgeless-toolbar/config/utils.d.ts +1 -1
  98. package/dist/_pkgs/affine-widget-edgeless-toolbar/config/utils.js +7 -2
  99. package/dist/_pkgs/affine-widget-edgeless-toolbar/panel/font-family-panel.d.ts +18 -0
  100. package/dist/_pkgs/affine-widget-edgeless-toolbar/panel/font-family-panel.js +52 -6
  101. package/dist/_pkgs/affine-widget-edgeless-toolbar/translations.d.ts +6 -0
  102. package/dist/_pkgs/affine-widget-edgeless-toolbar/translations.js +10 -0
  103. package/dist/_pkgs/affine-widget-frame-title/edgeless-frame-title-editor.js +2 -1
  104. package/dist/_pkgs/affine-widget-slash-menu/translations.d.ts +16 -0
  105. package/dist/_pkgs/affine-widget-slash-menu/translations.js +23 -1
  106. package/dist/_pkgs/affine-widget-toolbar/utils.js +57 -14
  107. package/dist/_pkgs/data-view/core/detail/field.js +2 -1
  108. package/dist/_pkgs/data-view/core/filter/literal/define.js +3 -1
  109. package/dist/_pkgs/data-view/core/group-by/renderer/boolean-group.js +1 -1
  110. package/dist/_pkgs/data-view/property-presets/checkbox/cell-renderer.js +8 -2
  111. package/dist/_pkgs/data-view/view-presets/kanban/pc/cell.js +1 -1
  112. package/dist/_pkgs/data-view/view-presets/kanban/pc/controller/drag.js +2 -1
  113. package/dist/_pkgs/data-view/view-presets/table/pc/controller/drag.js +2 -1
  114. package/dist/_pkgs/data-view/view-presets/table/pc/controller/selection.js +1 -1
  115. package/dist/_pkgs/data-view/view-presets/table/pc/header/vertical-indicator.js +2 -1
  116. package/dist/_pkgs/data-view/view-presets/table/pc/row/row-select-checkbox.js +3 -1
  117. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/controller/drag.js +2 -1
  118. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/controller/selection.js +1 -1
  119. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/group/top/header/vertical-indicator.js +2 -1
  120. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/row/row-header.js +3 -1
  121. package/dist/_pkgs/std/gfx/model/surface/role.d.ts +10 -5
  122. package/dist/commands.js +2 -1
  123. package/dist/translations.js +5 -0
  124. package/package.json +5 -1
@@ -0,0 +1,155 @@
1
+ /**
2
+ * `doc.copyLink` — put the current document's URL on the clipboard, anchored on
3
+ * whatever is selected.
4
+ *
5
+ * The library does not know what a document's address looks like: routing is
6
+ * the host's, and the seam for it — `GenerateDocUrlProvider` — has shipped
7
+ * since AFFiNE with **no consumer at all** on either side. This command is the
8
+ * consumer, and the counterpart of the paste path that already turns a host URL
9
+ * back into an inline reference (`ParseDocUrlProvider`, read by
10
+ * `adapters/middlewares/paste.ts`). Copy here, paste there, and the round trip
11
+ * closes without the library ever spelling a URL.
12
+ *
13
+ * It lives in the registry rather than in the host chrome because ADR 0008
14
+ * makes a command the single source for its keystroke, its palette entry and
15
+ * its Settings › Shortcuts row. A host-side button would be reachable from none
16
+ * of the three, and would not fire while the focus is inside the editor.
17
+ *
18
+ * Default chord `Mod-Alt-l` (product decision, PR #401 — the gesture users
19
+ * already know from Notion). NOT `Mod-l`: `Ctrl+L` / `Cmd+L` is the browser's
20
+ * address bar and never reaches the page, so a default on it would silently do
21
+ * nothing on the web. `scope: 'global'` so it fires in page mode and on the
22
+ * canvas alike.
23
+ *
24
+ * **`navigator.clipboard` can be absent, not merely refusing.** The Clipboard
25
+ * API is a secure-context feature: over plain `http://` on anything but
26
+ * `localhost`, and in a cross-origin iframe with no `clipboard-write`
27
+ * permission, the `clipboard` property is `undefined` altogether — so reaching
28
+ * for `writeText` throws a synchronous `TypeError` out of `run` rather than
29
+ * returning a promise to reject. That is checked before anything is announced:
30
+ * no copy, no toast, no telemetry, one `console.error` saying why. Announcing a
31
+ * copy that never happened is the one failure mode worse than doing nothing.
32
+ *
33
+ * A clipboard that EXISTS and then rejects (permission denied at the moment of
34
+ * writing) is a different case and is left as it is: the promise is caught, and
35
+ * the toast has already been shown — the same optimistic behaviour the inline
36
+ * link toolbar has always had, kept deliberately so the two copy-a-link
37
+ * gestures do not answer differently.
38
+ */
39
+ import { DocModeProvider, GenerateDocUrlProvider, NotificationProvider, TOAST_LINK_COPIED, TelemetryProvider, translateKey, } from '../../affine-shared/services/index.js';
40
+ import { BlockSelection, SurfaceSelection, } from '../../std/index.js';
41
+ /**
42
+ * Where the link points INSIDE the document, read off the live selection.
43
+ *
44
+ * `mode` is not decoration: the reader of these params
45
+ * (`extractSearchParams`, `adapters/middlewares/paste.ts`) only looks at
46
+ * `blockIds` / `elementIds` once it has found a valid `mode` in the query
47
+ * string, so an anchor emitted without one is dropped on the way back in. When
48
+ * no `DocModeProvider` is registered the anchor is therefore left out entirely
49
+ * rather than written in a form nothing can read — the link still opens the
50
+ * document, which is the honest degradation.
51
+ *
52
+ * Canvas before prose: a surface selection and a block selection can coexist in
53
+ * the store (selecting a note on the canvas leaves the page's own selection
54
+ * behind), and on the canvas the element IS what the user pointed at.
55
+ *
56
+ * A caret is deliberately NOT an anchor. `TextSelection` exists the whole time
57
+ * somebody is typing, so reading it would mean every copy silently points at a
58
+ * paragraph instead of the document — the opposite of what an unsolicited
59
+ * keystroke should do.
60
+ */
61
+ function anchorOf(std) {
62
+ const mode = std.getOptional(DocModeProvider)?.getEditorMode();
63
+ if (!mode)
64
+ return undefined;
65
+ const elementIds = std.selection
66
+ .filter(SurfaceSelection)
67
+ .flatMap(selection => selection.elements);
68
+ if (elementIds.length)
69
+ return { mode, elementIds };
70
+ const blockIds = std.selection
71
+ .filter(BlockSelection)
72
+ .map(selection => selection.blockId);
73
+ if (blockIds.length)
74
+ return { mode, blockIds };
75
+ return { mode };
76
+ }
77
+ /**
78
+ * The seam, or nothing. Read through `getOptional` like every other host seam,
79
+ * and reported as UNAVAILABLE rather than as a silent no-op: a palette entry
80
+ * that copies nothing and a shortcut that does nothing are both worse than an
81
+ * entry that is not there (`docs/integrate/04-host-seams.md`, step 2 of "How to
82
+ * add a seam").
83
+ */
84
+ const canCopyDocLink = (std) => !!std.getOptional(GenerateDocUrlProvider);
85
+ const copyDocLink = {
86
+ id: 'doc.copyLink',
87
+ owner: 'core',
88
+ kind: 'action',
89
+ // `com.labre.command.*` like the other keyless-by-intent core commands, not
90
+ // `com.labre.keyboardShortcuts.*`: this one is a capability that happens to
91
+ // carry a default chord, and the shortcut namespace would file it as a
92
+ // keymap row for a translator.
93
+ labelKey: 'com.labre.command.doc.copy-link',
94
+ labelFallback: 'Copy document link',
95
+ descriptionKey: 'com.labre.command.doc.copy-link.description',
96
+ descriptionFallback: 'Copy a link to this document, anchored on the selection, so it can be ' +
97
+ 'pasted into a text as a reference.',
98
+ keywords: ['link', 'url', 'share', 'reference'],
99
+ surfaces: ['palette', 'agent'],
100
+ scope: 'global',
101
+ defaultKeys: { mac: ['Mod-Alt-l'], other: ['Mod-Alt-l'] },
102
+ // Copying an address is legitimate in a read-only document, so nothing here
103
+ // is gated on `editable`; the only precondition is the seam, which is not
104
+ // expressible in the serializable union and therefore rides on `when`.
105
+ availability: 'always',
106
+ when: canCopyDocLink,
107
+ run: (std, invocation) => {
108
+ // The load-bearing half of the gate: `runCommand` consults neither
109
+ // `availability` nor `when`, so the palette and the agent reach `run`
110
+ // directly — the same arbitration `tag.set` records.
111
+ const generator = std.getOptional(GenerateDocUrlProvider);
112
+ if (!generator)
113
+ return;
114
+ const params = anchorOf(std);
115
+ const url = generator.generateDocUrl(std.store.id, params);
116
+ // A host may decline — an unsaved document, one it does not route. Nothing
117
+ // is copied and nothing is announced.
118
+ if (!url)
119
+ return;
120
+ // Not a promise rejection: outside a secure context the property itself is
121
+ // `undefined`, so this would be a synchronous `TypeError` thrown out of
122
+ // `run` — and `runCommand` only catches what a returned promise rejects
123
+ // with, so it would surface as an unhandled error taking the keystroke
124
+ // down with it. Nothing announced, one line saying why.
125
+ if (!navigator.clipboard) {
126
+ console.error('doc.copyLink: no clipboard API — a secure context (https, or ' +
127
+ 'localhost) is required, and an iframe needs `clipboard-write`.');
128
+ return;
129
+ }
130
+ navigator.clipboard.writeText(url).catch(console.error);
131
+ std
132
+ .getOptional(NotificationProvider)
133
+ ?.toast(translateKey(std, ...TOAST_LINK_COPIED));
134
+ // Self-emitted rather than declared through `CommandDescriptor.telemetry`:
135
+ // that field routes the bottleneck's reporter onto the three framework
136
+ // CREATION events and requires a `FrameworkId`, and this command creates
137
+ // nothing and belongs to no framework. `CopiedLink` is the event the two
138
+ // other copy-a-link sites already emit (the inline link toolbar, the
139
+ // iframe embed toolbar); reusing it keeps "links copied" one series.
140
+ // Ids never cross: the document id and the anchored ids stay here.
141
+ std.getOptional(TelemetryProvider)?.track('CopiedLink', {
142
+ page: params?.mode === 'edgeless' ? 'whiteboard editor' : 'doc editor',
143
+ segment: 'editor',
144
+ module: invocation.surface,
145
+ control: invocation.source,
146
+ type: params?.elementIds?.length
147
+ ? 'element'
148
+ : params?.blockIds?.length
149
+ ? 'block'
150
+ : 'document',
151
+ result: 'success',
152
+ });
153
+ },
154
+ };
155
+ export const copyLinkCommands = [copyDocLink];
@@ -1,5 +1,6 @@
1
1
  export * from './adapters/index.js';
2
2
  export * from './clipboard/index.js';
3
+ export * from './commands/copy-link-commands.js';
3
4
  export * from './commands/pivot-commands.js';
4
5
  export * from './commands/reading-commands.js';
5
6
  export * from './commands/tag-commands.js';
@@ -1,5 +1,6 @@
1
1
  export * from './adapters/index.js';
2
2
  export * from './clipboard/index.js';
3
+ export * from './commands/copy-link-commands.js';
3
4
  export * from './commands/pivot-commands.js';
4
5
  export * from './commands/reading-commands.js';
5
6
  export * from './commands/tag-commands.js';
@@ -4,6 +4,7 @@ import { ToolbarModuleExtension, ViewportElementExtension, } from '../affine-sha
4
4
  import { BlockFlavourIdentifier, BlockViewExtension, FlavourExtension, CommandExtension, ShortcutKeymapExtension, } from '../std/index.js';
5
5
  import { literal } from 'lit/static-html.js';
6
6
  import { PageClipboard, ReadOnlyClipboard } from './clipboard/index.js';
7
+ import { copyLinkCommands } from './commands/copy-link-commands.js';
7
8
  import { pivotCommands } from './commands/pivot-commands.js';
8
9
  import { readingCommands } from './commands/reading-commands.js';
9
10
  import { tagCommands } from './commands/tag-commands.js';
@@ -70,6 +71,7 @@ export class RootViewExtension extends ViewExtensionProvider {
70
71
  ...pivotCommands,
71
72
  ...tagCommands,
72
73
  ...readingCommands,
74
+ ...copyLinkCommands,
73
75
  ]),
74
76
  ShortcutKeymapExtension('global'),
75
77
  ToolbarModuleExtension({
@@ -276,6 +276,15 @@ const IMPORT_DONE_KEY = 'com.labre.interchange.import.done';
276
276
  const IMPORT_DONE_FALLBACK = 'file imported';
277
277
  const IMPORT_FAILED_KEY = 'com.labre.interchange.import.failed';
278
278
  const IMPORT_FAILED_FALLBACK = 'This file could not be imported';
279
+ /**
280
+ * What an imported sheet is called when neither the file nor the source
281
+ * document names it — resolved HERE, at the creation site, because that is
282
+ * where `std` is (ADR 0023 §3: a seed is translated by whoever writes it, and
283
+ * a pure reader never can). The readers keep the same English text as their
284
+ * own last-resort default, so a headless call still produces a named sheet.
285
+ */
286
+ const IMPORT_DEFAULT_NAME_KEY = 'com.labre.interchange.import.default-name';
287
+ const IMPORT_DEFAULT_NAME_FALLBACK = 'Imported diagram';
279
288
  /**
280
289
  * The document went read-only between the moment the file was accepted and the
281
290
  * moment its board was about to be written — see the second guard in
@@ -531,7 +540,10 @@ export async function importInterchangeFile(std, capability, file, options = {})
531
540
  // inflated" and "this is not a BPMN document" are the same event to the
532
541
  // person who picked the file, and they take the same notification.
533
542
  const source = options.decode ? await options.decode(text, file) : text;
534
- result = capability.run(source, { name: file.name });
543
+ result = capability.run(source, {
544
+ name: file.name.trim() ||
545
+ translateKey(std, IMPORT_DEFAULT_NAME_KEY, IMPORT_DEFAULT_NAME_FALLBACK),
546
+ });
535
547
  }
536
548
  catch (error) {
537
549
  // `InterchangeImportError` checked FIRST: a reader that throws it declared
@@ -128,6 +128,14 @@ export declare function legendFromCommands(std: BlockStdScope, owner: CommandOwn
128
128
  * Build the legend of what is drawn inside `board` and drop it bottom-left of
129
129
  * it, grouped and selected. THE gesture the shared toolbar button runs.
130
130
  *
131
+ * REPLACES the board's own legend if it already has one, rather than stacking a
132
+ * second box on the first (issue #391): the placement is derived from the board
133
+ * alone, so every press landed at the same pixel and the copies were invisible
134
+ * — one box to the eye, N groups and N × ~15 elements in the document. The
135
+ * second press is therefore a REFRESH, which is what a user who has just added
136
+ * three artefacts is asking for; the cost is that hand retouching of the box is
137
+ * lost, and one undo brings it back (see {@link legendsOn} and ADR 0026).
138
+ *
131
139
  * Returns the group id, or `undefined` when there is no surface to draw on.
132
140
  */
133
141
  export declare function createBoardLegend(std: BlockStdScope, board: {
@@ -276,13 +276,42 @@ const INSET_BOTTOM = 56;
276
276
  * It is also why a legend never lists itself: nothing it draws carries a role.
277
277
  */
278
278
  export function rolesInBound(gfx, bound) {
279
+ return rolesOf(gfx.getElementsByBound(bound, { type: 'canvas' }));
280
+ }
281
+ /** {@link rolesInBound} over a perimeter already scanned. */
282
+ function rolesOf(elements) {
279
283
  const present = new Set();
280
- for (const el of gfx.getElementsByBound(bound, { type: 'canvas' })) {
284
+ for (const el of elements) {
281
285
  if (el.role !== undefined)
282
286
  present.add(el.role);
283
287
  }
284
288
  return present;
285
289
  }
290
+ /**
291
+ * The legends ALREADY drawn on this board — what turns the button's second
292
+ * press into "regenerate" rather than a second stacked box (issue #391).
293
+ *
294
+ * The scan already sees them: a generated legend's wrapper carries
295
+ * {@link LEGEND_ROLE}, so recognising one costs a filter over the elements the
296
+ * perimeter pass returned, and nothing in the document format changes.
297
+ *
298
+ * CONTAINMENT rather than intersection, which is what `getElementsByBound`
299
+ * answers with: two boards drawn edge to edge each overlap the other's corner,
300
+ * and the press on one would eat the other's box. A legend is inset well inside
301
+ * the board it documents, so asking for the whole box costs nothing here and
302
+ * keeps the neighbour's.
303
+ *
304
+ * Two boxes it deliberately does not find: one the author dragged off the board
305
+ * (it is no longer this board's legend, and a fresh one is drawn), and one
306
+ * generated before {@link LEGEND_ROLE} shipped in 0.42 — that box carries no
307
+ * role, so the old stacking survives on those documents. Nothing is backfilled,
308
+ * as ever (ADR 0026).
309
+ */
310
+ function legendsOn(elements, bound) {
311
+ return elements.filter(el => el.type === 'group' &&
312
+ el.role === LEGEND_ROLE &&
313
+ bound.contains(el.elementBound));
314
+ }
286
315
  /** Every registered vocabulary, merged — a role id is namespaced, so it is flat. */
287
316
  function vocabularyOf(std) {
288
317
  const merged = {};
@@ -410,15 +439,32 @@ function boardLegendBox(std, owner) {
410
439
  * Build the legend of what is drawn inside `board` and drop it bottom-left of
411
440
  * it, grouped and selected. THE gesture the shared toolbar button runs.
412
441
  *
442
+ * REPLACES the board's own legend if it already has one, rather than stacking a
443
+ * second box on the first (issue #391): the placement is derived from the board
444
+ * alone, so every press landed at the same pixel and the copies were invisible
445
+ * — one box to the eye, N groups and N × ~15 elements in the document. The
446
+ * second press is therefore a REFRESH, which is what a user who has just added
447
+ * three artefacts is asking for; the cost is that hand retouching of the box is
448
+ * lost, and one undo brings it back (see {@link legendsOn} and ADR 0026).
449
+ *
413
450
  * Returns the group id, or `undefined` when there is no surface to draw on.
414
451
  */
415
452
  export function createBoardLegend(std, board, owner) {
416
453
  const gfx = std.get(GfxControllerIdentifier);
417
454
  const surface = gfx.surface;
418
- if (!surface)
455
+ // The button already refuses on a readonly document; said again here because
456
+ // this gesture now DELETES before it draws, and a refused delete followed by
457
+ // a throwing draw is the one order that could lose a box.
458
+ if (!surface || std.store.readonly)
419
459
  return undefined;
420
460
  const bound = Bound.deserialize(board.xywh);
421
- const present = rolesInBound(gfx, bound);
461
+ const inside = gfx.getElementsByBound(bound, { type: 'canvas' });
462
+ const previous = legendsOn(inside, bound);
463
+ // The box about to be drawn describes the DRAWING, never the box it replaces:
464
+ // the outgoing wrapper is dropped from the scan so `core:legend` is not among
465
+ // the roles the rows are derived from. Its glyphs carry no role at all.
466
+ const outgoing = new Set(previous);
467
+ const present = rolesOf(inside.filter(el => !outgoing.has(el)));
422
468
  const sections = legendFromCommands(std, owner, present);
423
469
  const box = boardLegendBox(std, owner);
424
470
  const layout = {
@@ -432,7 +478,17 @@ export function createBoardLegend(std, board, owner) {
432
478
  const extras = box?.extras?.({ std, board, present }) ?? [];
433
479
  const title = translateKey(std, ...(box?.titleWording ?? BOARD_LEGEND_TITLE));
434
480
  const { height } = measureLegend(sections, layout, extras);
481
+ // ONE checkpoint, in front of BOTH halves of the replacement: a single
482
+ // Ctrl+Z after a regenerate puts the previous box back whole, rather than
483
+ // leaving the board with no legend at all.
435
484
  std.store.captureSync();
485
+ if (previous.length > 0) {
486
+ const crud = std.get(EdgelessCRUDIdentifier);
487
+ // Cascades into the group's children (`crud-extension.ts`), so one call
488
+ // per legend takes the whole box away.
489
+ for (const legend of previous)
490
+ crud.removeElement(legend.id);
491
+ }
436
492
  const id = addLegend(surface, std, bound.x + INSET_X, bound.y + bound.h - INSET_BOTTOM - height, { title, sections, extras, ...layout });
437
493
  gfx.selection.set({ elements: [id], editing: false });
438
494
  return id;
@@ -229,6 +229,10 @@ export function morphToolbarConfig(spec) {
229
229
  return renderPickerMenu({
230
230
  testId: 'element-morph',
231
231
  label: wording(ctx, spec.label),
232
+ // The shell's accessible name stays the stable ENGLISH
233
+ // identifier: composing it from the translated label produced
234
+ // half a sentence per language (#390).
235
+ menuAria: `${spec.label.fallback.toLowerCase()}-menu`,
232
236
  // The current kind's own icon on the closed button, exactly as the
233
237
  // shape picker shows the current shape: the dropdown reads as a
234
238
  // statement about the selection before it is opened.
@@ -39,6 +39,9 @@ export declare const SVG_SKETCH_SKIPPED: ChromeWording;
39
39
  export declare const SVG_SKETCH_REMOVED: ChromeWording;
40
40
  export declare const SVG_SKETCH_REMOVED_ATTRIBUTE: ChromeWording;
41
41
  export declare const SVG_SKETCH_EMPTY: ChromeWording;
42
+ export declare const SVG_SKETCH_ERROR_MALFORMED_XML: ChromeWording;
43
+ export declare const SVG_SKETCH_ERROR_NOT_SVG: ChromeWording;
44
+ export declare const SVG_SKETCH_ERROR_SANITIZED_AWAY: ChromeWording;
42
45
  /**
43
46
  * Every wording declared above, in declaration order — joined into
44
47
  * `PACKAGE_WORDINGS` (`packages/affine/all/src/translations.ts`) alongside
@@ -92,6 +92,26 @@ export const SVG_SKETCH_EMPTY = [
92
92
  'com.labre.interchange.svg.empty',
93
93
  'No shape or text was recognised in this SVG, so nothing was drawn.',
94
94
  ];
95
+ /* ── The reader's own refusals (#390) ─────────────────────────────────────
96
+ *
97
+ * Thrown via `InterchangeImportError` and rendered by `runInterchangeImport`
98
+ * in the failure notification — the same shape as `BPMN_IMPORT_ERRORS`
99
+ * (`gfx/bpmn/src/import.ts`), and keyed for the same reason: these three
100
+ * sentences were written for a user, not for a console, and were reaching a
101
+ * French host in English because a bare `new Error(…)` carries no key.
102
+ */
103
+ export const SVG_SKETCH_ERROR_MALFORMED_XML = [
104
+ 'com.labre.interchange.svg.error.malformed-xml',
105
+ 'This file is not well-formed XML, so no drawing can be read out of it: {{detail}}',
106
+ ];
107
+ export const SVG_SKETCH_ERROR_NOT_SVG = [
108
+ 'com.labre.interchange.svg.error.not-svg',
109
+ 'An SVG opens on <svg>; this one opens on <{{tag}}>.',
110
+ ];
111
+ export const SVG_SKETCH_ERROR_SANITIZED_AWAY = [
112
+ 'com.labre.interchange.svg.error.sanitized-away',
113
+ 'Nothing survived sanitizing this SVG, so there is no drawing to read.',
114
+ ];
95
115
  /**
96
116
  * Every wording declared above, in declaration order — joined into
97
117
  * `PACKAGE_WORDINGS` (`packages/affine/all/src/translations.ts`) alongside
@@ -117,4 +137,7 @@ export const SVG_SKETCH_WORDINGS = [
117
137
  SVG_SKETCH_REMOVED,
118
138
  SVG_SKETCH_REMOVED_ATTRIBUTE,
119
139
  SVG_SKETCH_EMPTY,
140
+ SVG_SKETCH_ERROR_MALFORMED_XML,
141
+ SVG_SKETCH_ERROR_NOT_SVG,
142
+ SVG_SKETCH_ERROR_SANITIZED_AWAY,
120
143
  ];
@@ -1,4 +1,4 @@
1
- import type { InterchangeImportContext, InterchangeImportResult } from './interchange.js';
1
+ import { type InterchangeImportContext, type InterchangeImportResult } from './interchange.js';
2
2
  /**
3
3
  * **SVG → a canvas sketch.** The platform's VISUAL-tier importer, shared by
4
4
  * every framework that declares it (`docs/adr/0012`, P2).
@@ -1,6 +1,7 @@
1
1
  import { ShapeStyle, ShapeType, StrokeStyle, TextAlign, } from '../../affine-model/index.js';
2
2
  import DOMPurify from 'dompurify';
3
- import { SVG_SKETCH_CURRENT_COLOR, SVG_SKETCH_CURVE, SVG_SKETCH_EMPTY, SVG_SKETCH_EMPTY_BOX, SVG_SKETCH_EMPTY_TEXT, SVG_SKETCH_FLAT_POLYGON, SVG_SKETCH_FONT_UNIT, SVG_SKETCH_HIDDEN_DISPLAY, SVG_SKETCH_HIDDEN_VISIBILITY, SVG_SKETCH_LONE_POINT, SVG_SKETCH_OPACITY, SVG_SKETCH_PAINT_SERVER, SVG_SKETCH_PERCENT_RADIUS, SVG_SKETCH_REMOVED, SVG_SKETCH_REMOVED_ATTRIBUTE, SVG_SKETCH_SKIPPED, SVG_SKETCH_SWITCH, SVG_SKETCH_TRANSFORM, } from './svg-sketch-translations.js';
3
+ import { InterchangeImportError, } from './interchange.js';
4
+ import { SVG_SKETCH_CURRENT_COLOR, SVG_SKETCH_CURVE, SVG_SKETCH_EMPTY, SVG_SKETCH_EMPTY_BOX, SVG_SKETCH_EMPTY_TEXT, SVG_SKETCH_ERROR_MALFORMED_XML, SVG_SKETCH_ERROR_NOT_SVG, SVG_SKETCH_ERROR_SANITIZED_AWAY, SVG_SKETCH_FLAT_POLYGON, SVG_SKETCH_FONT_UNIT, SVG_SKETCH_HIDDEN_DISPLAY, SVG_SKETCH_HIDDEN_VISIBILITY, SVG_SKETCH_LONE_POINT, SVG_SKETCH_OPACITY, SVG_SKETCH_PAINT_SERVER, SVG_SKETCH_PERCENT_RADIUS, SVG_SKETCH_REMOVED, SVG_SKETCH_REMOVED_ATTRIBUTE, SVG_SKETCH_SKIPPED, SVG_SKETCH_SWITCH, SVG_SKETCH_TRANSFORM, } from './svg-sketch-translations.js';
4
5
  /**
5
6
  * **SVG → a canvas sketch.** The platform's VISUAL-tier importer, shared by
6
7
  * every framework that declares it (`docs/adr/0012`, P2).
@@ -879,10 +880,15 @@ function parseSvgRoot(source, notes) {
879
880
  .split('\n')
880
881
  .map(line => line.trim())
881
882
  .filter(line => line.length > 0);
882
- throw new Error(`This file is not well-formed XML, so no drawing can be read out of it: ${summary.slice(0, 200)}`);
883
+ const detail = summary.slice(0, 200);
884
+ throw new InterchangeImportError(`This file is not well-formed XML, so no drawing can be read out of it: ${detail}`, {
885
+ messageKey: SVG_SKETCH_ERROR_MALFORMED_XML[0],
886
+ messageParams: { detail },
887
+ });
883
888
  }
884
889
  if (nameOf(doc.documentElement) !== 'svg') {
885
- throw new Error(`An SVG opens on <svg>; this one opens on <${doc.documentElement?.localName ?? 'nothing'}>.`);
890
+ const tag = doc.documentElement?.localName ?? 'nothing';
891
+ throw new InterchangeImportError(`An SVG opens on <svg>; this one opens on <${tag}>.`, { messageKey: SVG_SKETCH_ERROR_NOT_SVG[0], messageParams: { tag } });
886
892
  }
887
893
  const clean = DOMPurify.sanitize(source, {
888
894
  USE_PROFILES: { svg: true },
@@ -923,7 +929,9 @@ function parseSvgRoot(source, notes) {
923
929
  // overload, which answers an `HTMLElement` — the sanitized body wrapper.
924
930
  const root = childElements(clean).find(child => nameOf(child) === 'svg');
925
931
  if (!root) {
926
- throw new Error('Nothing survived sanitizing this SVG, so there is no drawing to read.');
932
+ throw new InterchangeImportError(SVG_SKETCH_ERROR_SANITIZED_AWAY[1], {
933
+ messageKey: SVG_SKETCH_ERROR_SANITIZED_AWAY[0],
934
+ });
927
935
  }
928
936
  return root;
929
937
  }
@@ -105,7 +105,7 @@ export class FrameworkBackgroundView extends GfxElementModelView {
105
105
  font: '14px Inter, sans-serif',
106
106
  color: 'var(--affine-text-primary-color, #1f2328)',
107
107
  background: 'var(--affine-background-overlay-panel-color, #ffffff)',
108
- border: '1px solid var(--affine-primary-color, #1e96eb)',
108
+ border: '1px solid var(--affine-primary-color)',
109
109
  borderRadius: '6px',
110
110
  boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
111
111
  outline: 'none',
@@ -0,0 +1,36 @@
1
+ /**
2
+ * How a framework that draws a FIXED reference artwork puts it in its element.
3
+ *
4
+ * A declared background (`FrameworkBackgroundDef`) never needs this: it is made
5
+ * of ratios of a plot and stretches with the element by construction. The
6
+ * frameworks that reproduce an authored drawing — Cynefin, Estuarine, EDGY, the
7
+ * DDD stencils — cannot stretch: circles would turn into ellipses and a
8
+ * hand-drawn boundary into a smear. They fit the artwork uniformly instead and
9
+ * centre what is left over.
10
+ *
11
+ * This was written three times over — `gfx/cynefin-estuarine/src/utils.ts`,
12
+ * `gfx/ddd-shared/src/shared/utils.ts` and `gfx/edgy/src/consts.ts` — with the
13
+ * same body each time. One source of truth (R37): the three call it, and a
14
+ * board that crops itself onto its drawing
15
+ * (`gfx/cynefin-estuarine/src/cynefin/crop.ts`) reads the very function the
16
+ * renderer painted through, so the two can never disagree about where the
17
+ * drawing is.
18
+ */
19
+ /** A uniform fit: the scale, and the offsets that centre the letterbox. */
20
+ export interface RefFit {
21
+ /** Scale from reference units to element units. */
22
+ s: number;
23
+ /** Left offset of the fitted artwork inside the element, in element units. */
24
+ ox: number;
25
+ /** Top offset of the fitted artwork inside the element, in element units. */
26
+ oy: number;
27
+ }
28
+ /**
29
+ * Uniform fit of a `refW × refH` reference design into a `w × h` box: the
30
+ * scale factor plus the centring offsets (letterboxed), undistorted.
31
+ *
32
+ * The fitted artwork is `refW * s` by `refH * s` and shares the box's CENTRE —
33
+ * `ox + refW * s / 2 === w / 2` — which is what lets a board crop itself onto
34
+ * its drawing without the drawing moving, rotation included.
35
+ */
36
+ export declare function refScale(w: number, h: number, refW: number, refH: number): RefFit;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * How a framework that draws a FIXED reference artwork puts it in its element.
3
+ *
4
+ * A declared background (`FrameworkBackgroundDef`) never needs this: it is made
5
+ * of ratios of a plot and stretches with the element by construction. The
6
+ * frameworks that reproduce an authored drawing — Cynefin, Estuarine, EDGY, the
7
+ * DDD stencils — cannot stretch: circles would turn into ellipses and a
8
+ * hand-drawn boundary into a smear. They fit the artwork uniformly instead and
9
+ * centre what is left over.
10
+ *
11
+ * This was written three times over — `gfx/cynefin-estuarine/src/utils.ts`,
12
+ * `gfx/ddd-shared/src/shared/utils.ts` and `gfx/edgy/src/consts.ts` — with the
13
+ * same body each time. One source of truth (R37): the three call it, and a
14
+ * board that crops itself onto its drawing
15
+ * (`gfx/cynefin-estuarine/src/cynefin/crop.ts`) reads the very function the
16
+ * renderer painted through, so the two can never disagree about where the
17
+ * drawing is.
18
+ */
19
+ /**
20
+ * Uniform fit of a `refW × refH` reference design into a `w × h` box: the
21
+ * scale factor plus the centring offsets (letterboxed), undistorted.
22
+ *
23
+ * The fitted artwork is `refW * s` by `refH * s` and shares the box's CENTRE —
24
+ * `ox + refW * s / 2 === w / 2` — which is what lets a board crop itself onto
25
+ * its drawing without the drawing moving, rotation included.
26
+ */
27
+ export function refScale(w, h, refW, refH) {
28
+ const s = Math.min(w / refW, h / refH);
29
+ return { s, ox: (w - refW * s) / 2, oy: (h - refH * s) / 2 };
30
+ }
@@ -1,6 +1,7 @@
1
1
  export * from './background-view.js';
2
2
  export * from './def.js';
3
3
  export * from './facts.js';
4
+ export * from './fit.js';
4
5
  export * from './interaction.js';
5
6
  export * from './labels.js';
6
7
  export * from './renderer.js';
@@ -1,6 +1,7 @@
1
1
  export * from './background-view.js';
2
2
  export * from './def.js';
3
3
  export * from './facts.js';
4
+ export * from './fit.js';
4
5
  export * from './interaction.js';
5
6
  export * from './labels.js';
6
7
  export * from './renderer.js';
@@ -1,4 +1,4 @@
1
- import { ThemeProvider } from '../../affine-shared/services/index.js';
1
+ import { getChromeAccentColor } from '../../affine-shared/services/index.js';
2
2
  import { DisposableGroup } from '../../global/disposable/index.js';
3
3
  import { effect } from '@preact/signals-core';
4
4
  import { Overlay } from './overlay.js';
@@ -8,8 +8,6 @@ const HIGHLIGHT_STROKE_WIDTH = 2;
8
8
  const HIGHLIGHT_GAP = 4;
9
9
  /** Ring corner radius, in screen pixels. */
10
10
  const HIGHLIGHT_RADIUS = 4;
11
- /** Used when no theme is provided, matches the legacy frame highlight. */
12
- const FALLBACK_HIGHLIGHT_COLOR = '#1E96EB';
13
11
  /**
14
12
  * Draws the transient emphasis ring around the elements held by
15
13
  * `gfx.highlight` (`ElementHighlightManager`).
@@ -20,10 +18,7 @@ const FALLBACK_HIGHLIGHT_COLOR = '#1E96EB';
20
18
  export class ElementHighlightOverlay extends Overlay {
21
19
  static { this.overlayName = 'element-highlight'; }
22
20
  get _strokeColor() {
23
- return (this.gfx.std
24
- .getOptional(ThemeProvider)
25
- ?.getCssVariableColor('--affine-primary-color') ??
26
- FALLBACK_HIGHLIGHT_COLOR);
21
+ return getChromeAccentColor(this.gfx.std);
27
22
  }
28
23
  constructor(gfx) {
29
24
  super(gfx);
@@ -6,8 +6,8 @@ export const EdgelessTooltip = html `<svg width="170" height="106" viewBox="0 0
6
6
  <rect width="170" height="106" rx="2" fill="white"/>
7
7
  </mask>
8
8
  <g mask="url(#mask0_16460_1252)">
9
- <rect x="100.5" y="42.6565" width="141" height="51" stroke="#1E96EB" stroke-width="3" stroke-dasharray="5 5"/>
10
- <circle cx="101.5" cy="43.5" r="6" fill="white" stroke="#1E96EB" stroke-width="3"/>
9
+ <rect x="100.5" y="42.6565" width="141" height="51" stroke="var(--affine-primary-color)" stroke-width="3" stroke-dasharray="5 5"/>
10
+ <circle cx="101.5" cy="43.5" r="6" fill="white" stroke="var(--affine-primary-color)" stroke-width="3"/>
11
11
  <rect x="105" y="8" width="59" height="26" rx="10" fill="black" fill-opacity="0.1"/>
12
12
  <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="117" y="25.3636">Group</tspan></text>
13
13
  <mask id="mask1_16460_1252" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="98" height="106">
@@ -125,6 +125,11 @@ export const paletteCarouselStyles = css `
125
125
  /* Nine pages fit without scrolling; the cap is a seatbelt for a tenth. */
126
126
  max-height: 280px;
127
127
  overflow-y: auto;
128
+ /* Same trap as the popup box that holds this list (issue #392, see
129
+ * toolbar/menu-button.ts): an overflow-y of its own makes the other axis
130
+ * scrollable too, and this list is one long page name — a translation, or
131
+ * a tenth page — away from overflowing it. */
132
+ overflow-x: hidden;
128
133
  overscroll-behavior: contain;
129
134
  animation: palette-carousel-settle ${springDuration} ${springEasing};
130
135
  }
@@ -109,7 +109,7 @@ export const COLOR_PICKER_STYLE = css `
109
109
  }
110
110
  .modes .mode button[active] {
111
111
  pointer-events: none;
112
- outline: 2px solid var(--affine-brand-color, #1e96eb);
112
+ outline: 2px solid var(--affine-brand-color);
113
113
  }
114
114
 
115
115
  .content {
@@ -264,7 +264,9 @@ export const menuButtonItems = {
264
264
  const data = {
265
265
  content: () => html `
266
266
  ${config.checked.value
267
- ? CheckBoxCheckSolidIcon({ style: `color:#1E96EB` })
267
+ ? CheckBoxCheckSolidIcon({
268
+ style: `color:var(--affine-primary-color)`,
269
+ })
268
270
  : CheckBoxUnIcon()}
269
271
  <div class="affine-menu-action-text">
270
272
  ${config.label?.() ?? config.name}
@@ -9,11 +9,11 @@ export const AIStarIcon = html `<svg
9
9
  <g clip-path="url(#clip0_7444_26654)">
10
10
  <path
11
11
  d="M9.40033 4.57585C9.36627 4.27519 9.11207 4.04792 8.80948 4.04761C8.50689 4.0473 8.25223 4.27405 8.21756 4.57465C7.99211 6.52928 7.40771 7.90373 6.46492 8.84651C5.52213 9.7893 4.14769 10.3737 2.19305 10.5991C1.89246 10.6338 1.66571 10.8885 1.66602 11.1911C1.66633 11.4937 1.8936 11.7479 2.19426 11.7819C4.11659 11.9997 5.52025 12.5838 6.48542 13.5315C7.44696 14.4757 8.04356 15.8498 8.21597 17.7908C8.24327 18.0981 8.50096 18.3337 8.80954 18.3333C9.11813 18.333 9.37528 18.0969 9.40189 17.7894C9.56714 15.8804 10.1632 14.4774 11.1295 13.5111C12.0958 12.5448 13.4988 11.9487 15.4078 11.7835C15.7153 11.7569 15.9514 11.4997 15.9517 11.1911C15.9521 10.8825 15.7165 10.6249 15.4092 10.5976C13.4683 10.4252 12.0941 9.82855 11.15 8.86701C10.2022 7.90184 9.61807 6.49818 9.40033 4.57585Z"
12
- fill="#1E96EB"
12
+ fill="var(--affine-primary-color)"
13
13
  />
14
14
  <path
15
15
  d="M15.7849 1.87208C15.7717 1.75516 15.6728 1.66678 15.5551 1.66666C15.4375 1.66654 15.3384 1.75472 15.3249 1.87162C15.2373 2.63175 15.01 3.16626 14.6434 3.5329C14.2767 3.89954 13.7422 4.1268 12.9821 4.21448C12.8652 4.22796 12.777 4.327 12.7771 4.44467C12.7772 4.56234 12.8656 4.6612 12.9826 4.67445C13.7301 4.75912 14.276 4.9863 14.6513 5.35485C15.0253 5.72203 15.2573 6.25642 15.3243 7.01121C15.335 7.13075 15.4352 7.22235 15.5552 7.22221C15.6752 7.22208 15.7752 7.13025 15.7855 7.01069C15.8498 6.26831 16.0816 5.72268 16.4574 5.3469C16.8332 4.97111 17.3788 4.73932 18.1212 4.67505C18.2407 4.6647 18.3325 4.5647 18.3327 4.44469C18.3328 4.32469 18.2412 4.22448 18.1217 4.21386C17.3669 4.14681 16.8325 3.9148 16.4653 3.54087C16.0968 3.16552 15.8696 2.61966 15.7849 1.87208Z"
16
- fill="#1E96EB"
16
+ fill="var(--affine-primary-color)"
17
17
  />
18
18
  </g>
19
19
  <defs>
@@ -46,7 +46,7 @@ export const AIStopIcon = html `<svg
46
46
  fill-rule="evenodd"
47
47
  clip-rule="evenodd"
48
48
  d="M17.0827 10C17.0827 13.912 13.9114 17.0834 9.99935 17.0834C6.08733 17.0834 2.91602 13.912 2.91602 10C2.91602 6.088 6.08733 2.91669 9.99935 2.91669C13.9114 2.91669 17.0827 6.088 17.0827 10ZM18.3327 10C18.3327 14.6024 14.6017 18.3334 9.99935 18.3334C5.39698 18.3334 1.66602 14.6024 1.66602 10C1.66602 5.39765 5.39698 1.66669 9.99935 1.66669C14.6017 1.66669 18.3327 5.39765 18.3327 10ZM8.33268 6.66669C7.41221 6.66669 6.66602 7.41288 6.66602 8.33335V11.6667C6.66602 12.5872 7.41221 13.3334 8.33268 13.3334H11.666C12.5865 13.3334 13.3327 12.5872 13.3327 11.6667V8.33335C13.3327 7.41288 12.5865 6.66669 11.666 6.66669H8.33268Z"
49
- fill="#1E96EB"
49
+ fill="var(--affine-primary-color)"
50
50
  />
51
51
  </g>
52
52
  <defs>