@formicoidea/labre-core 0.58.0 → 0.59.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 (167) hide show
  1. package/architecture/code.c4 +94 -65
  2. package/architecture/domain.c4 +11 -5
  3. package/dist/_pkgs/affine-block-attachment/edgeless-clipboard-config.d.ts +3 -2
  4. package/dist/_pkgs/affine-block-attachment/edgeless-clipboard-config.js +18 -13
  5. package/dist/_pkgs/affine-block-callout/adapters/markdown.js +4 -1
  6. package/dist/_pkgs/affine-block-code/adapters/html.js +1 -1
  7. package/dist/_pkgs/affine-block-code/adapters/markdown/preprocessor.js +13 -1
  8. package/dist/_pkgs/affine-block-code/adapters/notion-html.js +1 -1
  9. package/dist/_pkgs/affine-block-code/code-block.d.ts +17 -2
  10. package/dist/_pkgs/affine-block-code/code-block.js +28 -23
  11. package/dist/_pkgs/affine-block-code/code-toolbar/components/lang-button.js +17 -4
  12. package/dist/_pkgs/affine-block-code/code-toolbar/components/preview-button.js +1 -1
  13. package/dist/_pkgs/affine-block-data-view/data-source.js +12 -5
  14. package/dist/_pkgs/affine-block-edgeless-text/edgeless-clipboard-config.d.ts +3 -2
  15. package/dist/_pkgs/affine-block-edgeless-text/edgeless-clipboard-config.js +8 -5
  16. package/dist/_pkgs/affine-block-image/asset-picker.d.ts +23 -2
  17. package/dist/_pkgs/affine-block-image/asset-picker.js +52 -12
  18. package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +8 -5
  19. package/dist/_pkgs/affine-block-image/configs/replace-action.js +8 -8
  20. package/dist/_pkgs/affine-block-image/configs/toolbar.js +2 -1
  21. package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +15 -0
  22. package/dist/_pkgs/affine-block-image/crop/crop-controller.js +40 -2
  23. package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +22 -0
  24. package/dist/_pkgs/affine-block-image/crop/crop-math.js +55 -2
  25. package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +17 -3
  26. package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +64 -6
  27. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.d.ts +3 -2
  28. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +20 -14
  29. package/dist/_pkgs/affine-block-image/image-block.js +1 -0
  30. package/dist/_pkgs/affine-block-image/image-edgeless-block.js +3 -0
  31. package/dist/_pkgs/affine-block-image/index.d.ts +2 -0
  32. package/dist/_pkgs/affine-block-image/index.js +2 -0
  33. package/dist/_pkgs/affine-block-image/replace/commands.d.ts +68 -0
  34. package/dist/_pkgs/affine-block-image/replace/commands.js +200 -0
  35. package/dist/_pkgs/affine-block-image/replace/replace-model.d.ts +44 -0
  36. package/dist/_pkgs/affine-block-image/replace/replace-model.js +168 -0
  37. package/dist/_pkgs/affine-block-image/replace/toolbar.d.ts +21 -0
  38. package/dist/_pkgs/affine-block-image/replace/toolbar.js +159 -0
  39. package/dist/_pkgs/affine-block-image/translations.d.ts +15 -2
  40. package/dist/_pkgs/affine-block-image/translations.js +35 -2
  41. package/dist/_pkgs/affine-block-image/utils.d.ts +7 -0
  42. package/dist/_pkgs/affine-block-image/utils.js +13 -0
  43. package/dist/_pkgs/affine-block-image/view.js +7 -0
  44. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.d.ts +14 -5
  45. package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.js +18 -11
  46. package/dist/_pkgs/affine-block-note/config.d.ts +9 -2
  47. package/dist/_pkgs/affine-block-note/edgeless-clipboard-config.d.ts +3 -2
  48. package/dist/_pkgs/affine-block-note/edgeless-clipboard-config.js +8 -5
  49. package/dist/_pkgs/affine-block-paragraph/adapters/markdown.js +11 -0
  50. package/dist/_pkgs/affine-block-root/commands/invisible-characters-commands.d.ts +17 -0
  51. package/dist/_pkgs/affine-block-root/commands/invisible-characters-commands.js +123 -0
  52. package/dist/_pkgs/affine-block-root/edgeless/clipboard/command.js +82 -65
  53. package/dist/_pkgs/affine-block-root/edgeless/edgeless-keyboard.js +2 -0
  54. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  55. package/dist/_pkgs/affine-block-root/index.js +1 -0
  56. package/dist/_pkgs/affine-block-root/translations.d.ts +6 -0
  57. package/dist/_pkgs/affine-block-root/translations.js +10 -0
  58. package/dist/_pkgs/affine-block-root/view.js +6 -1
  59. package/dist/_pkgs/affine-block-surface/commands/index.d.ts +1 -1
  60. package/dist/_pkgs/affine-block-surface/commands/index.js +1 -1
  61. package/dist/_pkgs/affine-block-surface/commands/reassociate-connectors.d.ts +9 -0
  62. package/dist/_pkgs/affine-block-surface/commands/reassociate-connectors.js +21 -12
  63. package/dist/_pkgs/affine-block-surface/extensions/clipboard-config.d.ts +33 -0
  64. package/dist/_pkgs/affine-block-surface/extensions/clipboard-config.js +53 -0
  65. package/dist/_pkgs/affine-block-surface/framework-background/background-view.js +2 -1
  66. package/dist/_pkgs/affine-block-surface/framework-background/band-widths.d.ts +104 -0
  67. package/dist/_pkgs/affine-block-surface/framework-background/band-widths.js +156 -0
  68. package/dist/_pkgs/affine-block-surface/framework-background/def.d.ts +87 -4
  69. package/dist/_pkgs/affine-block-surface/framework-background/def.js +26 -7
  70. package/dist/_pkgs/affine-block-surface/framework-background/ellipsis.d.ts +58 -0
  71. package/dist/_pkgs/affine-block-surface/framework-background/ellipsis.js +110 -0
  72. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +3 -0
  73. package/dist/_pkgs/affine-block-surface/framework-background/index.js +3 -0
  74. package/dist/_pkgs/affine-block-surface/framework-background/labels.d.ts +12 -2
  75. package/dist/_pkgs/affine-block-surface/framework-background/labels.js +29 -5
  76. package/dist/_pkgs/affine-block-surface/framework-background/measure-text.d.ts +14 -0
  77. package/dist/_pkgs/affine-block-surface/framework-background/measure-text.js +31 -0
  78. package/dist/_pkgs/affine-block-surface/framework-background/renderer.d.ts +14 -1
  79. package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +84 -31
  80. package/dist/_pkgs/affine-block-table/adapters/html.js +26 -14
  81. package/dist/_pkgs/affine-block-table/adapters/utils.d.ts +1 -0
  82. package/dist/_pkgs/affine-block-table/adapters/utils.js +23 -5
  83. package/dist/_pkgs/affine-block-table/header-menu.d.ts +13 -0
  84. package/dist/_pkgs/affine-block-table/header-menu.js +25 -0
  85. package/dist/_pkgs/affine-block-table/selection-controller.d.ts +15 -0
  86. package/dist/_pkgs/affine-block-table/selection-controller.js +62 -0
  87. package/dist/_pkgs/affine-block-table/table-cell-css.d.ts +7 -0
  88. package/dist/_pkgs/affine-block-table/table-cell-css.js +10 -0
  89. package/dist/_pkgs/affine-block-table/table-cell.d.ts +8 -0
  90. package/dist/_pkgs/affine-block-table/table-cell.js +35 -3
  91. package/dist/_pkgs/affine-block-table/table-data-manager.d.ts +14 -0
  92. package/dist/_pkgs/affine-block-table/table-data-manager.js +44 -3
  93. package/dist/_pkgs/affine-block-table/translations.d.ts +14 -0
  94. package/dist/_pkgs/affine-block-table/translations.js +22 -0
  95. package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.d.ts +2 -2
  96. package/dist/_pkgs/affine-gfx-template/builtin/other.d.ts +4 -0
  97. package/dist/_pkgs/affine-gfx-template/builtin/other.js +1 -1
  98. package/dist/_pkgs/affine-gfx-template/builtin/slides/build.d.ts +81 -0
  99. package/dist/_pkgs/affine-gfx-template/builtin/slides/build.js +276 -0
  100. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.d.ts +126 -0
  101. package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.js +101 -0
  102. package/dist/_pkgs/affine-gfx-template/builtin/slides/index.d.ts +8 -0
  103. package/dist/_pkgs/affine-gfx-template/builtin/slides/index.js +27 -0
  104. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.d.ts +83 -0
  105. package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.js +674 -0
  106. package/dist/_pkgs/affine-gfx-template/contribute.js +12 -17
  107. package/dist/_pkgs/affine-gfx-template/make-snapshot.d.ts +14 -2
  108. package/dist/_pkgs/affine-gfx-template/make-snapshot.js +6 -2
  109. package/dist/_pkgs/affine-gfx-template/services/template-middlewares.d.ts +12 -0
  110. package/dist/_pkgs/affine-gfx-template/services/template-middlewares.js +48 -0
  111. package/dist/_pkgs/affine-gfx-template/services/template.js +2 -1
  112. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +15 -1
  113. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +33 -37
  114. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +6 -0
  115. package/dist/_pkgs/affine-gfx-template/translations.d.ts +94 -0
  116. package/dist/_pkgs/affine-gfx-template/translations.js +441 -2
  117. package/dist/_pkgs/affine-model/blocks/table/table-model.d.ts +4 -0
  118. package/dist/_pkgs/affine-model/blocks/table/table-model.js +4 -0
  119. package/dist/_pkgs/affine-model/elements/bpmn/node.d.ts +46 -3
  120. package/dist/_pkgs/affine-model/elements/bpmn/node.js +47 -4
  121. package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +20 -0
  122. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +33 -4
  123. package/dist/_pkgs/affine-shared/services/edit-props-store.d.ts +3 -16
  124. package/dist/_pkgs/affine-shared/services/edit-props-store.js +16 -16
  125. package/dist/_pkgs/affine-shared/services/editor-setting-service.d.ts +3 -0
  126. package/dist/_pkgs/affine-shared/services/editor-setting-service.js +4 -0
  127. package/dist/_pkgs/affine-shared/services/index.d.ts +1 -0
  128. package/dist/_pkgs/affine-shared/services/index.js +1 -0
  129. package/dist/_pkgs/affine-shared/services/invisible-characters.d.ts +33 -0
  130. package/dist/_pkgs/affine-shared/services/invisible-characters.js +63 -0
  131. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-cache.d.ts +0 -9
  132. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-cache.js +3 -9
  133. package/dist/_pkgs/affine-shared/services/telemetry-service/lifecycle.d.ts +26 -0
  134. package/dist/_pkgs/affine-shared/services/telemetry-service/telemetry-service.d.ts +4 -3
  135. package/dist/_pkgs/affine-shared/services/telemetry-service/types.d.ts +11 -0
  136. package/dist/_pkgs/affine-shared/utils/index.d.ts +1 -0
  137. package/dist/_pkgs/affine-shared/utils/index.js +1 -0
  138. package/dist/_pkgs/affine-shared/utils/invisible-characters.d.ts +42 -0
  139. package/dist/_pkgs/affine-shared/utils/invisible-characters.js +81 -0
  140. package/dist/_pkgs/affine-widget-edgeless-zoom-toolbar/zoom-toolbar.js +13 -2
  141. package/dist/_pkgs/data-view/view-presets/table/pc/header/column-header.js +6 -0
  142. package/dist/_pkgs/data-view/view-presets/table/pc/table-view-ui-logic.js +5 -2
  143. package/dist/_pkgs/std/clipboard/clipboard.d.ts +15 -1
  144. package/dist/_pkgs/std/clipboard/clipboard.js +18 -0
  145. package/dist/_pkgs/std/inline/components/embed-gap.js +1 -1
  146. package/dist/_pkgs/std/inline/components/v-line.js +1 -1
  147. package/dist/_pkgs/std/inline/components/v-text.d.ts +15 -2
  148. package/dist/_pkgs/std/inline/components/v-text.js +147 -15
  149. package/dist/_pkgs/std/inline/consts.d.ts +5 -0
  150. package/dist/_pkgs/std/inline/consts.js +5 -0
  151. package/dist/_pkgs/std/inline/utils/index.d.ts +1 -0
  152. package/dist/_pkgs/std/inline/utils/index.js +1 -0
  153. package/dist/_pkgs/std/inline/utils/invisible-characters.d.ts +57 -0
  154. package/dist/_pkgs/std/inline/utils/invisible-characters.js +162 -0
  155. package/dist/_pkgs/std/inline/utils/point-conversion.js +3 -3
  156. package/dist/_pkgs/std/inline/utils/text.d.ts +8 -0
  157. package/dist/_pkgs/std/inline/utils/text.js +11 -2
  158. package/dist/_pkgs/store/model/store/crud.d.ts +28 -0
  159. package/dist/_pkgs/store/model/store/crud.js +97 -30
  160. package/dist/_pkgs/store/model/store/query.js +3 -2
  161. package/dist/_pkgs/store/model/store/store.d.ts +21 -0
  162. package/dist/_pkgs/store/model/store/store.js +88 -21
  163. package/dist/_pkgs/store/transformer/transformer.d.ts +46 -0
  164. package/dist/_pkgs/store/transformer/transformer.js +72 -0
  165. package/dist/commands.js +6 -1
  166. package/dist/translations.js +2 -0
  167. package/package.json +3 -4
@@ -1,5 +1,16 @@
1
1
  import { MarkdownPreprocessorExtension, } from '../../../affine-shared/adapters/index.js';
2
2
  import { isValidUrl } from '../../../affine-shared/utils/index.js';
3
+ /**
4
+ * A list item's marker (`-`, `*`, `+`, `1.`, `1)`) followed by a space or the
5
+ * end of the line. Its indentation is what nests it, so it is left alone (#519).
6
+ */
7
+ const LIST_ITEM = /^(?:[*+-]|\d{1,9}[.)])(?:\s|$)/;
8
+ /**
9
+ * Plain text pasted as a slice (BlockSuite #6017, the plain-text adapter):
10
+ * the first leading space of a line becomes ` `, so the line keeps its
11
+ * indentation instead of losing it or turning into an indented code block.
12
+ * Fenced code, list items and `-` lines are left as written.
13
+ */
3
14
  const codePreprocessor = {
4
15
  name: 'code',
5
16
  levels: ['slice'],
@@ -8,7 +19,8 @@ const codePreprocessor = {
8
19
  const lines = content
9
20
  .split('\n')
10
21
  .map(line => {
11
- if (line.trimStart().startsWith('-')) {
22
+ if (line.trimStart().startsWith('-') ||
23
+ LIST_ITEM.test(line.trimStart())) {
12
24
  return line;
13
25
  }
14
26
  let trimmedLine = line.trimStart();
@@ -25,7 +25,7 @@ export const codeBlockNotionHtmlAdapterMatcher = {
25
25
  id: nanoid(),
26
26
  flavour: CodeBlockSchema.model.flavour,
27
27
  props: {
28
- language: 'Plain Text',
28
+ language: null,
29
29
  wrap,
30
30
  text: {
31
31
  '$blocksuite:internal:text$': true,
@@ -3,8 +3,21 @@ import type { CodeBlockModel } from '../affine-model/index.js';
3
3
  import type { BlockComponent } from '../std/index.js';
4
4
  import { type Signal } from '@preact/signals-core';
5
5
  import { type TemplateResult } from 'lit';
6
- import { type ThemedToken } from 'shiki';
6
+ import { type BundledLanguageInfo, type ThemedToken } from 'shiki';
7
7
  import { CodeBlockHighlighter } from './code-block-service.js';
8
+ /**
9
+ * The language a stored `language` value stands for: matched on the id, the
10
+ * display name or an alias, so a document whose block says `'TypeScript'` or
11
+ * `'ts'` highlights as `typescript`. `undefined` (unknown or `null`) means
12
+ * plain text.
13
+ *
14
+ * Derived, never written back: the render path used to normalise the stored
15
+ * value to the id and to null an unknown one, which wrote to the store on every
16
+ * mount — on a readonly store, from a remote peer's render, and as an undo
17
+ * entry nobody made. The stored value stays what the author or the importer
18
+ * wrote; only a gesture (the language picker) changes it.
19
+ */
20
+ export declare function resolveCodeLanguage(language: string | null, langs: readonly BundledLanguageInfo[]): BundledLanguageInfo | undefined;
8
21
  export declare class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel> {
9
22
  static styles: import("lit").CSSResult;
10
23
  private _inlineRangeProvider;
@@ -12,6 +25,8 @@ export declare class CodeBlockComponent extends CaptionedBlockComponent<CodeBloc
12
25
  preview$: Signal<boolean>;
13
26
  collapsed$: Signal<boolean>;
14
27
  highlightTokens$: Signal<ThemedToken[][]>;
28
+ /** The language the block highlights as, see `resolveCodeLanguage`. */
29
+ languageInfo$: Signal<BundledLanguageInfo | undefined>;
15
30
  languageName$: Signal<string>;
16
31
  get inlineEditor(): import("../std/inline/inline-editor.js").InlineEditor<{
17
32
  bold?: true | null | undefined;
@@ -24,7 +39,7 @@ export declare class CodeBlockComponent extends CaptionedBlockComponent<CodeBloc
24
39
  get inlineManager(): import("../std/inline/index.js").InlineManager<import("../affine-shared/types/index.js").AffineTextAttributes>;
25
40
  get notificationService(): import("../affine-shared/services/notification-service.js").NotificationService | null;
26
41
  get readonly(): boolean;
27
- get langs(): import("shiki").BundledLanguageInfo[];
42
+ get langs(): BundledLanguageInfo[];
28
43
  get highlighter(): CodeBlockHighlighter;
29
44
  /**
30
45
  * Whether line numbers are currently shown for this block, accounting for the
@@ -47,7 +47,7 @@ import { html, nothing } from 'lit';
47
47
  import { query } from 'lit/decorators.js';
48
48
  import { classMap } from 'lit/directives/class-map.js';
49
49
  import { styleMap } from 'lit/directives/style-map.js';
50
- import { bundledLanguagesInfo } from 'shiki';
50
+ import { bundledLanguagesInfo, } from 'shiki';
51
51
  import { CodeBlockConfigExtension } from './code-block-config.js';
52
52
  import { CodeBlockInlineManagerExtension } from './code-block-inline.js';
53
53
  import { CodeBlockHighlighter } from './code-block-service.js';
@@ -55,6 +55,25 @@ import { CodeBlockPreviewIdentifier } from './code-preview-extension.js';
55
55
  import { readGlobalLineNumbers, resolveShowLineNumbers, } from './line-numbers.js';
56
56
  import { codeBlockStyles } from './styles.js';
57
57
  import { CODE_COPY_FAILED, CODE_PLAIN_TEXT_LANGUAGE } from './translations.js';
58
+ /**
59
+ * The language a stored `language` value stands for: matched on the id, the
60
+ * display name or an alias, so a document whose block says `'TypeScript'` or
61
+ * `'ts'` highlights as `typescript`. `undefined` (unknown or `null`) means
62
+ * plain text.
63
+ *
64
+ * Derived, never written back: the render path used to normalise the stored
65
+ * value to the id and to null an unknown one, which wrote to the store on every
66
+ * mount — on a readonly store, from a remote peer's render, and as an undo
67
+ * entry nobody made. The stored value stays what the author or the importer
68
+ * wrote; only a gesture (the language picker) changes it.
69
+ */
70
+ export function resolveCodeLanguage(language, langs) {
71
+ if (language === null)
72
+ return undefined;
73
+ return langs.find(info => info.id === language ||
74
+ info.name === language ||
75
+ info.aliases?.includes(language));
76
+ }
58
77
  let CodeBlockComponent = (() => {
59
78
  let _classSuper = CaptionedBlockComponent;
60
79
  let __richTextElement_decorators;
@@ -74,16 +93,10 @@ let CodeBlockComponent = (() => {
74
93
  });
75
94
  this.collapsed$ = computed(() => !!this.model.props.collapsed$.value);
76
95
  this.highlightTokens$ = signal([]);
77
- this.languageName$ = computed(() => {
78
- const lang = this.model.props.language$.value;
79
- if (lang === null) {
80
- return translateKey(this.std, ...CODE_PLAIN_TEXT_LANGUAGE);
81
- }
82
- const matchedInfo = this.langs.find(info => info.id === lang);
83
- return matchedInfo
84
- ? matchedInfo.name
85
- : translateKey(this.std, ...CODE_PLAIN_TEXT_LANGUAGE);
86
- });
96
+ /** The language the block highlights as, see `resolveCodeLanguage`. */
97
+ this.languageInfo$ = computed(() => resolveCodeLanguage(this.model.props.language$.value, this.langs));
98
+ this.languageName$ = computed(() => this.languageInfo$.value?.name ??
99
+ translateKey(this.std, ...CODE_PLAIN_TEXT_LANGUAGE));
87
100
  /**
88
101
  * Bumped on every highlight request: a grammar load that resolves after a
89
102
  * newer request (another language, or plain text) must not repaint the
@@ -153,16 +166,10 @@ let CodeBlockComponent = (() => {
153
166
  }
154
167
  _updateHighlightTokens() {
155
168
  const requestId = ++this._highlightRequestId;
156
- const modelLang = this.model.props.language$.value;
157
- if (modelLang === null) {
158
- this.highlightTokens$.value = [];
159
- return;
160
- }
161
- const matchedInfo = this.langs.find(info => info.id === modelLang ||
162
- info.name === modelLang ||
163
- info.aliases?.includes(modelLang));
169
+ // Resolved here rather than read from `languageInfo$`: the race spec
170
+ // drives this method on a stub that has no component field initializers.
171
+ const matchedInfo = resolveCodeLanguage(this.model.props.language$.value, this.langs);
164
172
  if (matchedInfo) {
165
- this.model.props.language$.value = matchedInfo.id;
166
173
  const langImport = matchedInfo.import;
167
174
  const lang = matchedInfo.id;
168
175
  const highlighter = this.highlighter.highlighter$.value;
@@ -196,8 +203,6 @@ let CodeBlockComponent = (() => {
196
203
  }
197
204
  else {
198
205
  this.highlightTokens$.value = [];
199
- // clear language if not found
200
- this.model.props.language$.value = null;
201
206
  }
202
207
  }
203
208
  connectedCallback() {
@@ -418,7 +423,7 @@ let CodeBlockComponent = (() => {
418
423
  renderBlock() {
419
424
  const showLineNumbers = this.showLineNumbers;
420
425
  const preview = this.preview$.value;
421
- const previewContext = this.std.getOptional(CodeBlockPreviewIdentifier(this.model.props.language ?? ''));
426
+ const previewContext = this.std.getOptional(CodeBlockPreviewIdentifier(this.languageInfo$.value?.id ?? ''));
422
427
  const shouldRenderPreview = preview && previewContext;
423
428
  const collapsed = this.collapsed$.value;
424
429
  return html `
@@ -42,6 +42,7 @@ import { css, LitElement, nothing } from 'lit';
42
42
  import { property, query } from 'lit/decorators.js';
43
43
  import { styleMap } from 'lit/directives/style-map.js';
44
44
  import { html } from 'lit/static-html.js';
45
+ import { resolveCodeLanguage } from '../../code-block.js';
45
46
  import { CODE_PLAIN_TEXT_LANGUAGE, CODE_SEARCH_FOR_LANGUAGE, } from '../../translations.js';
46
47
  /**
47
48
  * The list name of the Plain Text entry, which stands for `language: null`
@@ -191,9 +192,17 @@ let LanguageListButton = (() => {
191
192
  sortedBundledLanguages.splice(index, 1);
192
193
  sortedBundledLanguages.unshift(item);
193
194
  }
194
- this.blockComponent.store.transact(() => {
195
- this.blockComponent.model.props.language$.value = languageOf(item);
196
- });
195
+ const { store, model } = this.blockComponent;
196
+ // The button is disabled on a readonly store, but a list opened
197
+ // before the store turned readonly can still fire.
198
+ if (store.readonly)
199
+ return;
200
+ const language = languageOf(item);
201
+ // An unchanged write would push an empty undo entry.
202
+ if (model.props.language === language)
203
+ return;
204
+ store.captureSync();
205
+ store.updateBlock(model, { language });
197
206
  const std = this.blockComponent.std;
198
207
  const mode = std.getOptional(DocModeProvider)?.getEditorMode() ?? 'page';
199
208
  const telemetryService = std.getOptional(TelemetryProvider);
@@ -206,7 +215,11 @@ let LanguageListButton = (() => {
206
215
  control: item.name,
207
216
  });
208
217
  },
209
- active: item => languageOf(item) === this.blockComponent.model.props.language,
218
+ // Against the resolved id, so a block stored as `'TypeScript'` or
219
+ // with an unknown language still marks its entry (Plain Text for the
220
+ // latter).
221
+ active: item => languageOf(item) ===
222
+ (resolveCodeLanguage(this.blockComponent.model.props.language, this.blockComponent.langs)?.id ?? null),
210
223
  items: this._sortedBundledLanguages,
211
224
  };
212
225
  showPopFilterableList({
@@ -100,7 +100,7 @@ let PreviewButton = (() => {
100
100
  return this.blockComponent.preview$.value;
101
101
  }
102
102
  render() {
103
- const lang = this.blockComponent.model.props.language$.value ?? '';
103
+ const lang = this.blockComponent.languageInfo$.value?.id ?? '';
104
104
  const previewContext = this.blockComponent.std.getOptional(CodeBlockPreviewIdentifier(lang));
105
105
  if (!previewContext)
106
106
  return nothing;
@@ -81,10 +81,14 @@ export class BlockQueryDataSource extends DataSourceBase {
81
81
  cellValueChange(rowId, propertyId, value) {
82
82
  const viewColumn = this.getViewColumn(propertyId);
83
83
  if (viewColumn) {
84
- this.block.props.cells[rowId] = {
85
- ...this.block.props.cells[rowId],
86
- [propertyId]: value,
87
- };
84
+ // A nested prop write goes through the props proxy with its own origin,
85
+ // which the undo manager does not track: transact makes it undoable.
86
+ this.block.store.transact(() => {
87
+ this.block.props.cells[rowId] = {
88
+ ...this.block.props.cells[rowId],
89
+ [propertyId]: value,
90
+ };
91
+ });
88
92
  return;
89
93
  }
90
94
  const block = this.blockMap.get(rowId);
@@ -158,7 +162,10 @@ export class BlockQueryDataSource extends DataSourceBase {
158
162
  propertyDelete(_id) {
159
163
  const index = this.block.props.columns.findIndex(v => v.id === _id);
160
164
  if (index >= 0) {
161
- this.block.props.columns.splice(index, 1);
165
+ // Same origin trap as `cellValueChange`.
166
+ this.block.store.transact(() => {
167
+ this.block.props.columns.splice(index, 1);
168
+ });
162
169
  }
163
170
  }
164
171
  propertyDuplicate(_columnId) {
@@ -1,6 +1,7 @@
1
- import { EdgelessClipboardConfig } from '../affine-block-surface/index.js';
1
+ import { type ClipboardConfigCreationContext, EdgelessClipboardConfig, type PreparedClipboardBlock } from '../affine-block-surface/index.js';
2
2
  import { type BlockSnapshot } from '../store/index.js';
3
3
  export declare class EdgelessClipboardEdgelessTextConfig extends EdgelessClipboardConfig {
4
4
  static readonly key = "affine:edgeless-text";
5
- createBlock(edgelessText: BlockSnapshot): Promise<string | null>;
5
+ prepare(edgelessText: BlockSnapshot): Promise<PreparedClipboardBlock | null>;
6
+ createBlock(edgelessText: BlockSnapshot, context: ClipboardConfigCreationContext): Promise<string | null>;
6
7
  }
@@ -1,8 +1,8 @@
1
- import { EdgelessClipboardConfig } from '../affine-block-surface/index.js';
1
+ import { EdgelessClipboardConfig, } from '../affine-block-surface/index.js';
2
2
  import {} from '../store/index.js';
3
3
  export class EdgelessClipboardEdgelessTextConfig extends EdgelessClipboardConfig {
4
4
  static { this.key = 'affine:edgeless-text'; }
5
- async createBlock(edgelessText) {
5
+ async prepare(edgelessText) {
6
6
  const oldId = edgelessText.id;
7
7
  delete edgelessText.props.index;
8
8
  if (!edgelessText.props.xywh) {
@@ -12,11 +12,14 @@ export class EdgelessClipboardEdgelessTextConfig extends EdgelessClipboardConfig
12
12
  if (!this.surface) {
13
13
  return null;
14
14
  }
15
- const newId = await this.onBlockSnapshotPaste(edgelessText, this.std.store, this.surface.model.id);
16
- if (!newId) {
15
+ const prepared = await this.prepareBlockSnapshotPaste(edgelessText, this.std.store, this.surface.model.id);
16
+ if (!prepared) {
17
17
  console.error(`Failed to paste EdgelessText block(id: ${oldId})`);
18
18
  return null;
19
19
  }
20
- return newId;
20
+ return prepared;
21
+ }
22
+ createBlock(edgelessText, context) {
23
+ return this.createPreparedBlock(edgelessText, context);
21
24
  }
22
25
  }
@@ -3,6 +3,7 @@ import { type AssetRef } from '../affine-shared/services/index.js';
3
3
  import { type IVec } from '../global/gfx/index.js';
4
4
  import type { BlockStdScope } from '../std/index.js';
5
5
  import type { BlockModel } from '../store/index.js';
6
+ import { type ImageBlockSource } from './utils.js';
6
7
  /**
7
8
  * Images from the host's asset library (`AssetPickerProvider`, labre P4): the
8
9
  * block references a blob the host already stores, by its `sourceId`. Nothing
@@ -16,6 +17,7 @@ type Size = {
16
17
  width: number;
17
18
  height: number;
18
19
  };
20
+ export declare function imagePropsFrom(std: BlockStdScope, assets: readonly AssetRef[]): Promise<ImageBlockSource[]>;
19
21
  /**
20
22
  * Adds one image block per image ref next to `targetModel`, as one undo
21
23
  * step. Refs that are not `image/*`, or whose size cannot be read, are
@@ -45,6 +47,12 @@ export declare function replacementProps(model: ImageBlockModel, asset: Pick<Ass
45
47
  * that cannot be read, or a block deleted meanwhile.
46
48
  */
47
49
  export declare function replaceImage(std: BlockStdScope, model: ImageBlockModel, asset: AssetRef): Promise<boolean>;
50
+ /**
51
+ * The write half of {@link replaceImage}, for a picture already measured
52
+ * ({@link pickImageSource}): same refusals, same one undo step, synchronous,
53
+ * so a caller re-reads its target after a dialog and writes at once.
54
+ */
55
+ export declare function replaceImageSource(model: ImageBlockModel, source: ImageBlockSource): boolean;
48
56
  /**
49
57
  * The image entries of the slash menu and of the mobile keyboard toolbar:
50
58
  * the host's asset dialog when there is one, the system file chooser
@@ -56,8 +64,21 @@ export declare function pickAndAddImages(std: BlockStdScope, options: {
56
64
  maxWidth?: number;
57
65
  }): Promise<string[]>;
58
66
  /**
59
- * The image toolbar's "Replace": only with a host (`false` otherwise, and
60
- * the toolbar does not offer it), one ref, `mode: 'replace'`.
67
+ * One picture to put in place of another, measured: the host's asset dialog
68
+ * when there is one (`replace` mode, one asset), the system file chooser
69
+ * otherwise (the file goes up through the blob source, like every
70
+ * file-chooser path). `null` when cancelled, when the ref is not an image or
71
+ * when its size cannot be read.
72
+ *
73
+ * Shared by the page toolbar's "Replace", the canvas image's « Replace with
74
+ * image » (`canvas.image.replaceWithImage`) and the shape's (ADR 0044,
75
+ * amendment of 2026-10-27): there is one way to choose a picture.
76
+ */
77
+ export declare function pickImageSource(std: BlockStdScope): Promise<ImageBlockSource | null>;
78
+ /**
79
+ * The page toolbar's "Replace": {@link pickImageSource}, then
80
+ * {@link replaceImageSource} on the block as it is AFTER the dialog (it may
81
+ * be gone, or the document read-only, by then).
61
82
  */
62
83
  export declare function pickAndReplaceImage(std: BlockStdScope, model: ImageBlockModel): Promise<boolean>;
63
84
  export {};
@@ -1,10 +1,10 @@
1
1
  import { toast } from '../affine-components/toast/index.js';
2
2
  import { ImageBlockSchema, } from '../affine-model/index.js';
3
3
  import { AssetPickerProvider, translateKey, } from '../affine-shared/services/index.js';
4
- import { getImageFilesFromLocal, readImageSize, } from '../affine-shared/utils/index.js';
4
+ import { getImageFilesFromLocal, openFilesWith, readImageSize, } from '../affine-shared/utils/index.js';
5
5
  import { Bound } from '../global/gfx/index.js';
6
6
  import { IMAGE_TOAST_READ_SIZE_FAILED } from './translations.js';
7
- import { addImages, addSiblingImageBlocks, placeImagesOnCanvas, } from './utils.js';
7
+ import { addImages, addSiblingImageBlocks, imageSourceFromFile, placeImagesOnCanvas, } from './utils.js';
8
8
  function isSize(width = 0, height = 0) {
9
9
  return width > 0 && height > 0;
10
10
  }
@@ -20,7 +20,7 @@ async function naturalSize(store, asset) {
20
20
  const size = await readImageSize(typed);
21
21
  return isSize(size.width, size.height) ? size : null;
22
22
  }
23
- async function imagePropsFrom(std, assets) {
23
+ export async function imagePropsFrom(std, assets) {
24
24
  const images = assets.filter(asset => asset.type.startsWith('image/'));
25
25
  const sized = await Promise.all(images.map(async (asset) => {
26
26
  const size = await naturalSize(std.store, asset);
@@ -109,8 +109,27 @@ export async function replaceImage(std, model, asset) {
109
109
  toast(std.host, translateKey(std, ...IMAGE_TOAST_READ_SIZE_FAILED));
110
110
  return false;
111
111
  }
112
+ return replaceImageSource(model, {
113
+ sourceId: asset.sourceId,
114
+ size: asset.size,
115
+ ...natural,
116
+ });
117
+ }
118
+ /**
119
+ * The write half of {@link replaceImage}, for a picture already measured
120
+ * ({@link pickImageSource}): same refusals, same one undo step, synchronous,
121
+ * so a caller re-reads its target after a dialog and writes at once.
122
+ */
123
+ export function replaceImageSource(model, source) {
124
+ const { store } = model;
112
125
  if (store.readonly || !store.getBlock(model.id))
113
126
  return false;
127
+ if (!source.sourceId || !isSize(source.width, source.height))
128
+ return false;
129
+ if (source.sourceId === model.props.sourceId$.peek())
130
+ return false;
131
+ const natural = { width: source.width, height: source.height };
132
+ const asset = { sourceId: source.sourceId, size: source.size };
114
133
  store.captureSync();
115
134
  store.transact(() => {
116
135
  store.updateBlock(model, replacementProps(model, asset, natural));
@@ -163,21 +182,42 @@ export async function pickAndAddImages(std, options) {
163
182
  return addImagesFromAssets(std, assets, options);
164
183
  }
165
184
  /**
166
- * The image toolbar's "Replace": only with a host (`false` otherwise, and
167
- * the toolbar does not offer it), one ref, `mode: 'replace'`.
185
+ * One picture to put in place of another, measured: the host's asset dialog
186
+ * when there is one (`replace` mode, one asset), the system file chooser
187
+ * otherwise (the file goes up through the blob source, like every
188
+ * file-chooser path). `null` when cancelled, when the ref is not an image or
189
+ * when its size cannot be read.
190
+ *
191
+ * Shared by the page toolbar's "Replace", the canvas image's « Replace with
192
+ * image » (`canvas.image.replaceWithImage`) and the shape's (ADR 0044,
193
+ * amendment of 2026-10-27): there is one way to choose a picture.
168
194
  */
169
- export async function pickAndReplaceImage(std, model) {
170
- if (model.store.readonly)
171
- return false;
195
+ export async function pickImageSource(std) {
172
196
  const picked = pickRequest(std, {
173
197
  kind: 'image',
174
198
  mode: 'replace',
175
199
  multiple: false,
176
200
  });
177
- if (!picked)
201
+ if (picked) {
202
+ const assets = await picked;
203
+ if (!assets?.length)
204
+ return null;
205
+ const [props] = await imagePropsFrom(std, assets.slice(0, 1));
206
+ return props ?? null;
207
+ }
208
+ const [file] = (await openFilesWith('Images', false)) ?? [];
209
+ return file ? imageSourceFromFile(std, file) : null;
210
+ }
211
+ /**
212
+ * The page toolbar's "Replace": {@link pickImageSource}, then
213
+ * {@link replaceImageSource} on the block as it is AFTER the dialog (it may
214
+ * be gone, or the document read-only, by then).
215
+ */
216
+ export async function pickAndReplaceImage(std, model) {
217
+ if (model.store.readonly)
178
218
  return false;
179
- const [asset] = (await picked) ?? [];
180
- if (!asset)
219
+ const source = await pickImageSource(std);
220
+ if (!source)
181
221
  return false;
182
- return replaceImage(std, model, asset);
222
+ return replaceImageSource(model, source);
183
223
  }
@@ -1,9 +1,12 @@
1
1
  import type { ImageBlockModel } from '../../affine-model/index.js';
2
- import { type ToolbarAction, type ToolbarContext } from '../../affine-shared/services/index.js';
2
+ import type { ToolbarAction, ToolbarContext } from '../../affine-shared/services/index.js';
3
3
  /**
4
- * "Replace" on the image toolbar, page and canvas alike: opens the host's
5
- * asset dialog in `replace` mode and swaps the picture in place
6
- * (`replaceImage`). It exists only with an `AssetPickerProvider` and on a
7
- * writable document: without a host there is no library to pick from.
4
+ * "Replace" on the PAGE image toolbar: swaps the picture in place
5
+ * (`pickAndReplaceImage`), from the host's asset dialog in `replace` mode when
6
+ * there is one, from the system file chooser otherwise — so it is offered
7
+ * without a host too (ADR 0044, amendment of 2026-10-27). Hidden on a
8
+ * read-only document. The canvas toolbar has a « Replace » MENU instead
9
+ * (`replace/toolbar.ts`), which reaches the same picking helper through
10
+ * `canvas.image.replaceWithImage`.
8
11
  */
9
12
  export declare function imageReplaceToolbarAction(getModel: (ctx: ToolbarContext) => ImageBlockModel | null): ToolbarAction;
@@ -1,12 +1,14 @@
1
- import { AssetPickerProvider, } from '../../affine-shared/services/index.js';
2
1
  import { ReplaceIcon } from '@blocksuite/icons/lit';
3
2
  import { pickAndReplaceImage } from '../asset-picker.js';
4
3
  import { IMAGE_TOOLBAR_REPLACE } from '../translations.js';
5
4
  /**
6
- * "Replace" on the image toolbar, page and canvas alike: opens the host's
7
- * asset dialog in `replace` mode and swaps the picture in place
8
- * (`replaceImage`). It exists only with an `AssetPickerProvider` and on a
9
- * writable document: without a host there is no library to pick from.
5
+ * "Replace" on the PAGE image toolbar: swaps the picture in place
6
+ * (`pickAndReplaceImage`), from the host's asset dialog in `replace` mode when
7
+ * there is one, from the system file chooser otherwise — so it is offered
8
+ * without a host too (ADR 0044, amendment of 2026-10-27). Hidden on a
9
+ * read-only document. The canvas toolbar has a « Replace » MENU instead
10
+ * (`replace/toolbar.ts`), which reaches the same picking helper through
11
+ * `canvas.image.replaceWithImage`.
10
12
  */
11
13
  export function imageReplaceToolbarAction(getModel) {
12
14
  return {
@@ -14,9 +16,7 @@ export function imageReplaceToolbarAction(getModel) {
14
16
  tooltip: 'Replace',
15
17
  tooltipWording: IMAGE_TOOLBAR_REPLACE,
16
18
  icon: ReplaceIcon(),
17
- when: ctx => !ctx.readonly &&
18
- !!ctx.std.getOptional(AssetPickerProvider) &&
19
- getModel(ctx) !== null,
19
+ when: ctx => !ctx.readonly && getModel(ctx) !== null,
20
20
  run(ctx) {
21
21
  const model = getModel(ctx);
22
22
  if (!model)
@@ -7,6 +7,7 @@ import { ImageBlockComponent } from '../image-block.js';
7
7
  import { ImageEdgelessBlockComponent } from '../image-edgeless-block.js';
8
8
  import { IMAGE_TOOLBAR_TURN_INTO_CARD_VIEW } from '../translations.js';
9
9
  import { duplicate } from '../utils.js';
10
+ import { imageReplaceMenuToolbarAction } from '../replace/toolbar.js';
10
11
  import { imageReplaceToolbarAction } from './replace-action.js';
11
12
  const trackBaseProps = {
12
13
  category: 'image',
@@ -121,7 +122,7 @@ const builtinSurfaceToolbarConfig = {
121
122
  block?.download();
122
123
  },
123
124
  },
124
- imageReplaceToolbarAction(ctx => ctx.getCurrentBlockByType(ImageEdgelessBlockComponent)?.model ?? null),
125
+ imageReplaceMenuToolbarAction,
125
126
  {
126
127
  id: 'b.caption',
127
128
  tooltip: 'Caption',
@@ -1,15 +1,26 @@
1
1
  import { type ImageBlockModel, type ImageCrop, type ImageCropMask } from '../../affine-model/index.js';
2
+ import type { BlockStdScope } from '../../std/index.js';
2
3
  import { nothing } from 'lit';
3
4
  import { type CropRect } from './crop-math.js';
4
5
  /** What the controller needs from the block it serves, page or canvas. */
5
6
  export interface CroppableImageBlock {
6
7
  readonly model: ImageBlockModel;
8
+ /** Read on the canvas only, to take the selection and the viewport. */
9
+ readonly std: Pick<BlockStdScope, 'get'>;
7
10
  querySelector<E extends Element = Element>(selectors: string): E | null;
8
11
  }
9
12
  /**
10
13
  * The « Rogner » state of one image block (ADR 0037), shared by the page and
11
14
  * the canvas components: which preset the crop frame is open on, if any, and
12
15
  * the gestures that write. The frame is local UI and never stored.
16
+ *
17
+ * On the canvas the open frame owns the pointer and the wheel: the image is
18
+ * selected in EDITING state, which hides the selected-rect handles that
19
+ * would otherwise sit on the frame's corners, and the viewport is locked,
20
+ * so the wheel zooms the picture inside the frame rather than the canvas.
21
+ * Both are taken by one open path and given back by one `stop`, whatever
22
+ * closes the frame (Escape, Enter, a press elsewhere, the toolbar, the block
23
+ * leaving the DOM, the selection moving elsewhere), so they cannot drift.
13
24
  */
14
25
  export declare class ImageCropController {
15
26
  private readonly _block;
@@ -17,6 +28,8 @@ export declare class ImageCropController {
17
28
  /** The mask the crop frame is open on, `null` when it is closed. */
18
29
  readonly editing$: import("@preact/signals-core").Signal<"free" | "16:9" | "4:3" | "1:1" | null>;
19
30
  readonly crop$: import("@preact/signals-core").ReadonlySignal<ImageCrop | undefined>;
31
+ /** Gives the canvas back; set while the frame is open on the canvas. */
32
+ private _release;
20
33
  constructor(_block: CroppableImageBlock, _onCanvas: boolean);
21
34
  /**
22
35
  * The source's proportions: the loaded `<img>`'s pixel size, else what the
@@ -35,9 +48,11 @@ export declare class ImageCropController {
35
48
  * nothing until a drag.
36
49
  */
37
50
  choose(mask: ImageCropMask): void;
51
+ private _open;
38
52
  commit(rect: CropRect, mask?: "free" | "16:9" | "4:3" | "1:1"): boolean;
39
53
  /** « Image d'origine »: removes the crop; the bytes never changed. */
40
54
  restoreOriginal(): boolean;
55
+ /** Closes the frame and gives the canvas back; harmless when closed. */
41
56
  stop(): void;
42
57
  /** The frame, or nothing when it is closed or the page is readonly. */
43
58
  renderOverlay(src: string | null | undefined, rotate?: number): import("lit-html").TemplateResult<1> | typeof nothing;