@formicoidea/labre-core 0.39.1 → 0.39.3

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.
@@ -6,6 +6,14 @@ import { type TemplateResult } from 'lit';
6
6
  export declare class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel> {
7
7
  static styles: import("lit").CSSResult;
8
8
  private _inlineRangeProvider;
9
+ /**
10
+ * The collapse state a READER holds locally. Same contract as
11
+ * `affine-paragraph`: a readonly document is never written, so the toggle
12
+ * carries the reader's own expansion here instead of in the model. A signal
13
+ * rather than a Lit `@state()`, so BlockSuite effects can depend on it.
14
+ */
15
+ private readonly _readonlyCollapsed;
16
+ private _setReadonlyCollapsed;
9
17
  private readonly _onClickIcon;
10
18
  get attributeRenderer(): import("../std/inline/types.js").AttributeRenderer<import("../affine-shared/types/index.js").AffineTextAttributes>;
11
19
  get attributesSchema(): import("zod").ZodType<any, import("zod").ZodTypeDef, any>;
@@ -16,7 +24,6 @@ export declare class ListBlockComponent extends CaptionedBlockComponent<ListBloc
16
24
  connectedCallback(): void;
17
25
  getUpdateComplete(): Promise<boolean>;
18
26
  renderBlock(): TemplateResult<1>;
19
- private accessor _readonlyCollapsed;
20
27
  private accessor _richTextElement;
21
28
  accessor blockContainerStyles: {
22
29
  margin: string;
@@ -42,9 +42,9 @@ import { DocModeProvider } from '../affine-shared/services/index.js';
42
42
  import { getViewportElement } from '../affine-shared/utils/index.js';
43
43
  import { BlockSelection, TextSelection } from '../std/index.js';
44
44
  import { getInlineRangeProvider, } from '../std/inline/index.js';
45
- import { effect } from '@preact/signals-core';
45
+ import { effect, signal } from '@preact/signals-core';
46
46
  import { html, nothing } from 'lit';
47
- import { query, state } from 'lit/decorators.js';
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
50
  import { correctNumberedListsOrderToPrev } from './commands/utils.js';
@@ -52,9 +52,6 @@ import { listBlockStyles } from './styles.js';
52
52
  import { getListIcon } from './utils/get-list-icon.js';
53
53
  let ListBlockComponent = (() => {
54
54
  let _classSuper = CaptionedBlockComponent;
55
- let __readonlyCollapsed_decorators;
56
- let __readonlyCollapsed_initializers = [];
57
- let __readonlyCollapsed_extraInitializers = [];
58
55
  let __richTextElement_decorators;
59
56
  let __richTextElement_initializers = [];
60
57
  let __richTextElement_extraInitializers = [];
@@ -62,12 +59,19 @@ let ListBlockComponent = (() => {
62
59
  constructor() {
63
60
  super(...arguments);
64
61
  this._inlineRangeProvider = null;
62
+ /**
63
+ * The collapse state a READER holds locally. Same contract as
64
+ * `affine-paragraph`: a readonly document is never written, so the toggle
65
+ * carries the reader's own expansion here instead of in the model. A signal
66
+ * rather than a Lit `@state()`, so BlockSuite effects can depend on it.
67
+ */
68
+ this._readonlyCollapsed = signal(false);
65
69
  this._onClickIcon = (e) => {
66
70
  e.stopPropagation();
67
71
  e.preventDefault();
68
72
  if (this.model.props.type === 'toggle') {
69
73
  if (this.store.readonly) {
70
- this._readonlyCollapsed = !this._readonlyCollapsed;
74
+ this._setReadonlyCollapsed(!this._readonlyCollapsed.value);
71
75
  }
72
76
  else {
73
77
  this.store.captureSync();
@@ -93,21 +97,22 @@ let ListBlockComponent = (() => {
93
97
  }
94
98
  this._select();
95
99
  };
96
- this.#_readonlyCollapsed_accessor_storage = __runInitializers(this, __readonlyCollapsed_initializers, false);
97
- this.#_richTextElement_accessor_storage = (__runInitializers(this, __readonlyCollapsed_extraInitializers), __runInitializers(this, __richTextElement_initializers, null));
100
+ this.#_richTextElement_accessor_storage = __runInitializers(this, __richTextElement_initializers, null);
98
101
  this.#blockContainerStyles_accessor_storage = (__runInitializers(this, __richTextElement_extraInitializers), {
99
102
  margin: 'var(--affine-list-margin, 10px 0)',
100
103
  });
101
104
  }
102
105
  static {
103
106
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
104
- __readonlyCollapsed_decorators = [state()];
105
107
  __richTextElement_decorators = [query('rich-text')];
106
- __esDecorate(this, null, __readonlyCollapsed_decorators, { kind: "accessor", name: "_readonlyCollapsed", static: false, private: false, access: { has: obj => "_readonlyCollapsed" in obj, get: obj => obj._readonlyCollapsed, set: (obj, value) => { obj._readonlyCollapsed = value; } }, metadata: _metadata }, __readonlyCollapsed_initializers, __readonlyCollapsed_extraInitializers);
107
108
  __esDecorate(this, null, __richTextElement_decorators, { kind: "accessor", name: "_richTextElement", static: false, private: false, access: { has: obj => "_richTextElement" in obj, get: obj => obj._richTextElement, set: (obj, value) => { obj._richTextElement = value; } }, metadata: _metadata }, __richTextElement_initializers, __richTextElement_extraInitializers);
108
109
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
109
110
  }
110
111
  static { this.styles = listBlockStyles; }
112
+ _setReadonlyCollapsed(collapsed) {
113
+ this._readonlyCollapsed.value = collapsed;
114
+ this.requestUpdate();
115
+ }
111
116
  get attributeRenderer() {
112
117
  return this.inlineManager.getRenderer();
113
118
  }
@@ -137,9 +142,14 @@ let ListBlockComponent = (() => {
137
142
  connectedCallback() {
138
143
  super.connectedCallback();
139
144
  this._inlineRangeProvider = getInlineRangeProvider(this);
145
+ // Only ENTERING readonly mode (or a later change of the persisted value)
146
+ // seeds the reader's local state; while readonly holds, the reader's own
147
+ // expansion is never overwritten. Aligned on `affine-paragraph`, where the
148
+ // unguarded version was a real defect (#262).
140
149
  this.disposables.add(effect(() => {
141
- const collapsed = this.model.props.collapsed$.value;
142
- this._readonlyCollapsed = collapsed;
150
+ if (this.store.readonly$.value) {
151
+ this._setReadonlyCollapsed(this.model.props.collapsed$.value);
152
+ }
143
153
  }));
144
154
  this.disposables.add(effect(() => {
145
155
  const type = this.model.props.type$.value;
@@ -162,10 +172,12 @@ let ListBlockComponent = (() => {
162
172
  renderBlock() {
163
173
  const { model, _onClickIcon } = this;
164
174
  const collapsed = this.store.readonly
165
- ? this._readonlyCollapsed
175
+ ? this._readonlyCollapsed.value
166
176
  : model.props.collapsed;
167
177
  const listIcon = getListIcon(model, !collapsed, _onClickIcon);
178
+ const childrenId = `list-children-${this.model.id}`;
168
179
  const children = html `<div
180
+ id=${childrenId}
169
181
  class="affine-block-children-container"
170
182
  style=${styleMap({
171
183
  paddingLeft: `${BLOCK_CHILDREN_CONTAINER_PADDING_LEFT}px`,
@@ -187,9 +199,10 @@ let ListBlockComponent = (() => {
187
199
  ? html `
188
200
  <blocksuite-toggle-button
189
201
  .collapsed=${collapsed}
202
+ .controls=${childrenId}
190
203
  .updateCollapsed=${(value) => {
191
204
  if (this.store.readonly) {
192
- this._readonlyCollapsed = value;
205
+ this._setReadonlyCollapsed(value);
193
206
  }
194
207
  else {
195
208
  this.store.captureSync();
@@ -222,9 +235,6 @@ let ListBlockComponent = (() => {
222
235
  </div>
223
236
  `;
224
237
  }
225
- #_readonlyCollapsed_accessor_storage;
226
- get _readonlyCollapsed() { return this.#_readonlyCollapsed_accessor_storage; }
227
- set _readonlyCollapsed(value) { this.#_readonlyCollapsed_accessor_storage = value; }
228
238
  #_richTextElement_accessor_storage;
229
239
  get _richTextElement() { return this.#_richTextElement_accessor_storage; }
230
240
  set _richTextElement(value) { this.#_richTextElement_accessor_storage = value; }
@@ -7,9 +7,17 @@ export declare class ParagraphBlockComponent extends CaptionedBlockComponent<Par
7
7
  focused$: import("@preact/signals-core").ReadonlySignal<boolean>;
8
8
  private readonly _composing;
9
9
  private readonly _displayPlaceholder;
10
+ /**
11
+ * The collapse state a READER holds locally. A readonly document must never
12
+ * be written, so the chevron cannot touch `model.props.collapsed`; this
13
+ * signal carries the reader's own expansion instead. It is a signal rather
14
+ * than a Lit `@state()` so BlockSuite effects can depend on it as well.
15
+ */
16
+ private readonly _readonlyCollapsed;
10
17
  private _inlineRangeProvider;
11
18
  private readonly _isInDatabase;
12
19
  private get _placeholder();
20
+ private _setReadonlyCollapsed;
13
21
  get citationService(): import("../affine-shared/services/index.js").CitationViewService;
14
22
  get attributeRenderer(): import("../std/inline/types.js").AttributeRenderer<import("../affine-shared/types/index.js").AffineTextAttributes>;
15
23
  get attributesSchema(): import("zod").ZodType<any, import("zod").ZodTypeDef, any>;
@@ -24,7 +32,6 @@ export declare class ParagraphBlockComponent extends CaptionedBlockComponent<Par
24
32
  connectedCallback(): void;
25
33
  getUpdateComplete(): Promise<boolean>;
26
34
  renderBlock(): TemplateResult<1>;
27
- private accessor _readonlyCollapsed;
28
35
  private accessor _richTextElement;
29
36
  accessor blockContainerStyles: {
30
37
  margin: string;
@@ -42,7 +42,7 @@ import { TextSelection } from '../std/index.js';
42
42
  import { getInlineRangeProvider, } from '../std/inline/index.js';
43
43
  import { computed, effect, signal } from '@preact/signals-core';
44
44
  import { html, nothing } from 'lit';
45
- import { query, state } from 'lit/decorators.js';
45
+ import { query } from 'lit/decorators.js';
46
46
  import { classMap } from 'lit/directives/class-map.js';
47
47
  import { styleMap } from 'lit/directives/style-map.js';
48
48
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
@@ -50,9 +50,6 @@ import { ParagraphBlockConfigExtension } from './paragraph-block-config.js';
50
50
  import { paragraphBlockStyles } from './styles.js';
51
51
  let ParagraphBlockComponent = (() => {
52
52
  let _classSuper = CaptionedBlockComponent;
53
- let __readonlyCollapsed_decorators;
54
- let __readonlyCollapsed_initializers = [];
55
- let __readonlyCollapsed_extraInitializers = [];
56
53
  let __richTextElement_decorators;
57
54
  let __richTextElement_initializers = [];
58
55
  let __richTextElement_extraInitializers = [];
@@ -67,6 +64,13 @@ let ParagraphBlockComponent = (() => {
67
64
  });
68
65
  this._composing = signal(false);
69
66
  this._displayPlaceholder = signal(false);
67
+ /**
68
+ * The collapse state a READER holds locally. A readonly document must never
69
+ * be written, so the chevron cannot touch `model.props.collapsed`; this
70
+ * signal carries the reader's own expansion instead. It is a signal rather
71
+ * than a Lit `@state()` so BlockSuite effects can depend on it as well.
72
+ */
73
+ this._readonlyCollapsed = signal(false);
70
74
  this._inlineRangeProvider = null;
71
75
  this._isInDatabase = () => {
72
76
  let parent = this.parentElement;
@@ -78,17 +82,14 @@ let ParagraphBlockComponent = (() => {
78
82
  }
79
83
  return false;
80
84
  };
81
- this.#_readonlyCollapsed_accessor_storage = __runInitializers(this, __readonlyCollapsed_initializers, false);
82
- this.#_richTextElement_accessor_storage = (__runInitializers(this, __readonlyCollapsed_extraInitializers), __runInitializers(this, __richTextElement_initializers, null));
85
+ this.#_richTextElement_accessor_storage = __runInitializers(this, __richTextElement_initializers, null);
83
86
  this.#blockContainerStyles_accessor_storage = (__runInitializers(this, __richTextElement_extraInitializers), {
84
87
  margin: 'var(--affine-paragraph-margin, 10px 0)',
85
88
  });
86
89
  }
87
90
  static {
88
91
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
89
- __readonlyCollapsed_decorators = [state()];
90
92
  __richTextElement_decorators = [query('rich-text')];
91
- __esDecorate(this, null, __readonlyCollapsed_decorators, { kind: "accessor", name: "_readonlyCollapsed", static: false, private: false, access: { has: obj => "_readonlyCollapsed" in obj, get: obj => obj._readonlyCollapsed, set: (obj, value) => { obj._readonlyCollapsed = value; } }, metadata: _metadata }, __readonlyCollapsed_initializers, __readonlyCollapsed_extraInitializers);
92
93
  __esDecorate(this, null, __richTextElement_decorators, { kind: "accessor", name: "_richTextElement", static: false, private: false, access: { has: obj => "_richTextElement" in obj, get: obj => obj._richTextElement, set: (obj, value) => { obj._richTextElement = value; } }, metadata: _metadata }, __richTextElement_initializers, __richTextElement_extraInitializers);
93
94
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
94
95
  }
@@ -98,6 +99,10 @@ let ParagraphBlockComponent = (() => {
98
99
  .get(ParagraphBlockConfigExtension.identifier)
99
100
  ?.getPlaceholder(this.model);
100
101
  }
102
+ _setReadonlyCollapsed(collapsed) {
103
+ this._readonlyCollapsed.value = collapsed;
104
+ this.requestUpdate();
105
+ }
101
106
  get citationService() {
102
107
  return this.std.get(CitationProvider);
103
108
  }
@@ -176,9 +181,19 @@ let ParagraphBlockComponent = (() => {
176
181
  this.model.props.collapsed = false;
177
182
  }
178
183
  }));
184
+ // Entering readonly mode (and any later change of the persisted value)
185
+ // seeds the reader's local state. Deliberately NOT re-run on every
186
+ // selection change: this effect must not depend on the selection, or the
187
+ // reader's own expansion would be overwritten mid-drag.
188
+ this.disposables.add(effect(() => {
189
+ if (this.store.readonly$.value) {
190
+ this._setReadonlyCollapsed(this.model.props.collapsed$.value);
191
+ }
192
+ }));
179
193
  this.disposables.add(effect(() => {
180
- const collapsed = this.model.props.collapsed$.value;
181
- this._readonlyCollapsed = collapsed;
194
+ const collapsed = this.store.readonly
195
+ ? this._readonlyCollapsed.value
196
+ : this.model.props.collapsed$.value;
182
197
  // reset text selection when selected block is collapsed
183
198
  if (this.model.props.type$.value.startsWith('h') && collapsed) {
184
199
  const collapsedSiblings = this.collapsedSiblings;
@@ -216,7 +231,7 @@ let ParagraphBlockComponent = (() => {
216
231
  renderBlock() {
217
232
  const { type$ } = this.model.props;
218
233
  const collapsed = this.store.readonly
219
- ? this._readonlyCollapsed
234
+ ? this._readonlyCollapsed.value
220
235
  : this.model.props.collapsed;
221
236
  const collapsedSiblings = this.collapsedSiblings;
222
237
  let style = html ``;
@@ -231,7 +246,9 @@ let ParagraphBlockComponent = (() => {
231
246
  </style>
232
247
  `;
233
248
  }
249
+ const childrenId = `heading-children-${this.model.id}`;
234
250
  const children = html `<div
251
+ id=${childrenId}
235
252
  class="affine-block-children-container"
236
253
  style=${styleMap({
237
254
  paddingLeft: `${BLOCK_CHILDREN_CONTAINER_PADDING_LEFT}px`,
@@ -275,9 +292,10 @@ let ParagraphBlockComponent = (() => {
275
292
  ? html `
276
293
  <blocksuite-toggle-button
277
294
  .collapsed=${collapsed}
295
+ .controls=${childrenId}
278
296
  .updateCollapsed=${(value) => {
279
297
  if (this.store.readonly) {
280
- this._readonlyCollapsed = value;
298
+ this._setReadonlyCollapsed(value);
281
299
  }
282
300
  else {
283
301
  this.store.captureSync();
@@ -328,9 +346,6 @@ let ParagraphBlockComponent = (() => {
328
346
  </div>
329
347
  `;
330
348
  }
331
- #_readonlyCollapsed_accessor_storage;
332
- get _readonlyCollapsed() { return this.#_readonlyCollapsed_accessor_storage; }
333
- set _readonlyCollapsed(value) { this.#_readonlyCollapsed_accessor_storage = value; }
334
349
  #_richTextElement_accessor_storage;
335
350
  get _richTextElement() { return this.#_richTextElement_accessor_storage; }
336
351
  set _richTextElement(value) { this.#_richTextElement_accessor_storage = value; }
@@ -4,7 +4,7 @@ import { addImages } from '../../../affine-block-image/index.js';
4
4
  import { CanvasRenderer, DefaultTool, EdgelessCRUDIdentifier, ExportManager, getSurfaceComponent, } from '../../../affine-block-surface/index.js';
5
5
  import { splitIntoLines } from '../../../affine-gfx-text/index.js';
6
6
  import { BookmarkStyles, DEFAULT_NOTE_HEIGHT, DEFAULT_NOTE_WIDTH, FrameBlockModel, MAX_IMAGE_WIDTH, } from '../../../affine-model/index.js';
7
- import { ClipboardAdapter, decodeClipboardBlobs, } from '../../../affine-shared/adapters/index.js';
7
+ import { ClipboardAdapter, decodeClipboardBlobs, ImageProxyService, } from '../../../affine-shared/adapters/index.js';
8
8
  import { CANVAS_EXPORT_IGNORE_TAGS, EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, } from '../../../affine-shared/consts/index.js';
9
9
  import { EmbedOptionProvider, ParseDocUrlProvider, TelemetryProvider, } from '../../../affine-shared/services/index.js';
10
10
  import { convertToPng, isInsidePageEditor, isTopLevelBlock, isUrlInClipboard, matchModels, referenceToNode, } from '../../../affine-shared/utils/index.js';
@@ -324,7 +324,7 @@ export class EdgelessClipboardController extends PageClipboard {
324
324
  }
325
325
  ctx.scale(dpr, dpr);
326
326
  const replaceImgSrcWithSvg = this._exportManager?.replaceImgSrcWithSvg;
327
- const imageProxy = host.std.clipboard.configs.get('imageProxy');
327
+ const imageProxy = host.std.get(ImageProxyService).imageProxyURL;
328
328
  const html2canvasOption = {
329
329
  ignoreElements: function (element) {
330
330
  if (CANVAS_EXPORT_IGNORE_TAGS.includes(element.tagName) ||
@@ -351,7 +351,7 @@ export class EdgelessClipboardController extends PageClipboard {
351
351
  await replaceImgSrcWithSvg?.(element);
352
352
  },
353
353
  backgroundColor: 'transparent',
354
- useCORS: imageProxy ? false : true,
354
+ useCORS: !imageProxy,
355
355
  proxy: imageProxy,
356
356
  };
357
357
  const _drawTopLevelBlock = async (block, isInFrame = false) => {
@@ -1,13 +1,33 @@
1
1
  import { KeymapExtension } from '../../std/index.js';
2
+ const NATIVE_FOCUSABLE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
3
+ /**
4
+ * The editor host swallows Tab and Shift-Tab so that indentation stays under
5
+ * the control of the block-scoped handlers. That would however trap the focus
6
+ * on the native controls the editor renders (the collapse button of headings
7
+ * and lists, for instance), which then could neither be reached nor left with
8
+ * the keyboard. When the event originates from such a control — and not from
9
+ * editable text — the native tabbing behaviour is let through.
10
+ */
11
+ const isNativeFocusableTarget = (target) => {
12
+ if (!(target instanceof Element))
13
+ return false;
14
+ if (target instanceof HTMLElement && target.isContentEditable)
15
+ return false;
16
+ return !!target.closest(NATIVE_FOCUSABLE_SELECTOR);
17
+ };
2
18
  export const fallbackKeymap = KeymapExtension(() => {
3
19
  return {
4
20
  Tab: ctx => {
5
21
  const event = ctx.get('defaultState').event;
22
+ if (isNativeFocusableTarget(event.target))
23
+ return;
6
24
  event.stopPropagation();
7
25
  event.preventDefault();
8
26
  },
9
27
  'Shift-Tab': ctx => {
10
28
  const event = ctx.get('defaultState').event;
29
+ if (isNativeFocusableTarget(event.target))
30
+ return;
11
31
  event.stopPropagation();
12
32
  event.preventDefault();
13
33
  },
@@ -3,12 +3,14 @@ import { type BlockStdScope, type EditorHost } from '../../../std/index.js';
3
3
  import { GfxBlockElementModel, type GfxController, GfxPrimitiveElementModel } from '../../../std/gfx/index.js';
4
4
  import type { ExtensionType, Store } from '../../../store/index.js';
5
5
  import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
6
- export type ExportOptions = {
7
- imageProxyEndpoint: string;
8
- };
9
6
  export declare class ExportManager {
10
7
  readonly std: BlockStdScope;
11
- private readonly _exportOptions;
8
+ /**
9
+ * The image proxy every remote-image fetch of the export goes through —
10
+ * resolved from {@link ImageProxyService}, the one host seam for it, never
11
+ * from a local constant. Empty means « fetch direct » (issue #258).
12
+ */
13
+ private get _imageProxyURL();
12
14
  replaceImgSrcWithSvg: (element: HTMLElement) => Promise<void>;
13
15
  get doc(): Store;
14
16
  get editorHost(): EditorHost;
@@ -1,6 +1,6 @@
1
1
  import { ImageBlockModel } from '../../../affine-model/index.js';
2
- import { FetchUtils } from '../../../affine-shared/adapters/index.js';
3
- import { CANVAS_EXPORT_IGNORE_TAGS, DEFAULT_IMAGE_PROXY_ENDPOINT, } from '../../../affine-shared/consts/index.js';
2
+ import { FetchUtils, ImageProxyService } from '../../../affine-shared/adapters/index.js';
3
+ import { CANVAS_EXPORT_IGNORE_TAGS } from '../../../affine-shared/consts/index.js';
4
4
  import { isInsidePageEditor, matchModels } from '../../../affine-shared/utils/index.js';
5
5
  import { BlockSuiteError, ErrorCode } from '../../../global/exceptions/index.js';
6
6
  import { deserializeXYWH } from '../../../global/gfx/index.js';
@@ -10,6 +10,14 @@ import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
10
10
  import { getBgGridGap } from '../../utils/get-bg-grip-gap.js';
11
11
  import { FileExporter } from './file-exporter.js';
12
12
  export class ExportManager {
13
+ /**
14
+ * The image proxy every remote-image fetch of the export goes through —
15
+ * resolved from {@link ImageProxyService}, the one host seam for it, never
16
+ * from a local constant. Empty means « fetch direct » (issue #258).
17
+ */
18
+ get _imageProxyURL() {
19
+ return this.std.get(ImageProxyService).imageProxyURL;
20
+ }
13
21
  get doc() {
14
22
  return this.std.store;
15
23
  }
@@ -18,14 +26,11 @@ export class ExportManager {
18
26
  }
19
27
  constructor(std) {
20
28
  this.std = std;
21
- this._exportOptions = {
22
- imageProxyEndpoint: DEFAULT_IMAGE_PROXY_ENDPOINT,
23
- };
24
29
  this.replaceImgSrcWithSvg = async (element) => {
25
30
  const imgList = Array.from(element.querySelectorAll('img'));
26
31
  // Create an array of promises
27
32
  const promises = imgList.map(img => {
28
- return FetchUtils.fetchImage(img.src, undefined, this._exportOptions.imageProxyEndpoint)
33
+ return FetchUtils.fetchImage(img.src, undefined, this._imageProxyURL)
29
34
  .then(response => response && response.blob())
30
35
  .then(async (blob) => {
31
36
  if (!blob)
@@ -159,8 +164,8 @@ export class ExportManager {
159
164
  x: pageLeft - viewport.left,
160
165
  width: pageWidth,
161
166
  height: viewportHeight,
162
- useCORS: this._exportOptions.imageProxyEndpoint ? false : true,
163
- proxy: this._exportOptions.imageProxyEndpoint,
167
+ useCORS: !this._imageProxyURL,
168
+ proxy: this._imageProxyURL,
164
169
  };
165
170
  let data;
166
171
  try {
@@ -228,8 +233,8 @@ export class ExportManager {
228
233
  });
229
234
  await this.replaceImgSrcWithSvg(element);
230
235
  },
231
- useCORS: this._exportOptions.imageProxyEndpoint ? false : true,
232
- proxy: this._exportOptions.imageProxyEndpoint,
236
+ useCORS: !this._imageProxyURL,
237
+ proxy: this._imageProxyURL,
233
238
  };
234
239
  let data;
235
240
  try {
@@ -21,9 +21,9 @@ export class PriorityQueue {
21
21
  if (rightChildIndex < length) {
22
22
  leftChild = this.heap[leftChildIndex];
23
23
  rightChild = this.heap[rightChildIndex];
24
- if ((swap === null &&
24
+ if ((swap === -1 &&
25
25
  this._compare(rightChild.priority, element.priority) < 0) ||
26
- (swap !== null &&
26
+ (swap !== -1 &&
27
27
  this._compare(rightChild.priority, leftChild.priority) < 0)) {
28
28
  swap = rightChildIndex;
29
29
  }
@@ -6,6 +6,11 @@ export declare class ToggleButton extends ToggleButton_base {
6
6
  render(): import("lit-html").TemplateResult<1>;
7
7
  accessor collapsed: boolean;
8
8
  accessor updateCollapsed: (collapsed: boolean) => void;
9
+ /**
10
+ * Id of the children container this button expands and collapses,
11
+ * exposed to assistive technologies through `aria-controls`.
12
+ */
13
+ accessor controls: string;
9
14
  }
10
15
  declare global {
11
16
  interface HTMLElementTagNameMap {
@@ -48,13 +48,18 @@ let ToggleButton = (() => {
48
48
  let _updateCollapsed_decorators;
49
49
  let _updateCollapsed_initializers = [];
50
50
  let _updateCollapsed_extraInitializers = [];
51
+ let _controls_decorators;
52
+ let _controls_initializers = [];
53
+ let _controls_extraInitializers = [];
51
54
  return class ToggleButton extends _classSuper {
52
55
  static {
53
56
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
54
57
  _collapsed_decorators = [property({ attribute: false })];
55
58
  _updateCollapsed_decorators = [property({ attribute: false })];
59
+ _controls_decorators = [property({ attribute: false })];
56
60
  __esDecorate(this, null, _collapsed_decorators, { kind: "accessor", name: "collapsed", static: false, private: false, access: { has: obj => "collapsed" in obj, get: obj => obj.collapsed, set: (obj, value) => { obj.collapsed = value; } }, metadata: _metadata }, _collapsed_initializers, _collapsed_extraInitializers);
57
61
  __esDecorate(this, null, _updateCollapsed_decorators, { kind: "accessor", name: "updateCollapsed", static: false, private: false, access: { has: obj => "updateCollapsed" in obj, get: obj => obj.updateCollapsed, set: (obj, value) => { obj.updateCollapsed = value; } }, metadata: _metadata }, _updateCollapsed_initializers, _updateCollapsed_extraInitializers);
62
+ __esDecorate(this, null, _controls_decorators, { kind: "accessor", name: "controls", static: false, private: false, access: { has: obj => "controls" in obj, get: obj => obj.controls, set: (obj, value) => { obj.controls = value; } }, metadata: _metadata }, _controls_initializers, _controls_extraInitializers);
58
63
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
59
64
  }
60
65
  static { this.styles = css `
@@ -63,6 +68,11 @@ let ToggleButton = (() => {
63
68
  align-items: start;
64
69
  justify-content: start;
65
70
  position: absolute;
71
+ padding: 0;
72
+ border: 0;
73
+ color: inherit;
74
+ background: transparent;
75
+ font: inherit;
66
76
  width: 16px;
67
77
  height: 16px;
68
78
  top: calc((1em - 16px) / 2 + 5px);
@@ -78,6 +88,11 @@ let ToggleButton = (() => {
78
88
  background: var(--affine-hover-color);
79
89
  }
80
90
 
91
+ .toggle-icon:focus-visible {
92
+ opacity: 1;
93
+ outline: 1px solid var(--affine-primary-color);
94
+ }
95
+
81
96
  .toggle-icon[data-collapsed='true'] {
82
97
  opacity: 1;
83
98
  }
@@ -100,32 +115,23 @@ let ToggleButton = (() => {
100
115
  }
101
116
  `; }
102
117
  render() {
103
- const toggleDownTemplate = html `
104
- <div
105
- contenteditable="false"
106
- class="toggle-icon"
107
- @click=${() => this.updateCollapsed(!this.collapsed)}
108
- >
109
- ${ToggleDownIcon({
110
- width: '16px',
111
- height: '16px',
112
- })}
113
- </div>
114
- `;
115
- const toggleRightTemplate = html `
116
- <div
118
+ return html `
119
+ <button
120
+ type="button"
117
121
  contenteditable="false"
118
122
  class="toggle-icon"
119
123
  data-collapsed=${this.collapsed}
124
+ aria-label=${this.collapsed ? 'Expand content' : 'Collapse content'}
125
+ aria-expanded=${!this.collapsed}
126
+ aria-controls=${this.controls}
120
127
  @click=${() => this.updateCollapsed(!this.collapsed)}
121
128
  >
122
- ${ToggleRightIcon({
129
+ ${(this.collapsed ? ToggleRightIcon : ToggleDownIcon)({
123
130
  width: '16px',
124
131
  height: '16px',
125
132
  })}
126
- </div>
133
+ </button>
127
134
  `;
128
- return this.collapsed ? toggleRightTemplate : toggleDownTemplate;
129
135
  }
130
136
  #collapsed_accessor_storage = __runInitializers(this, _collapsed_initializers, void 0);
131
137
  get collapsed() { return this.#collapsed_accessor_storage; }
@@ -133,9 +139,16 @@ let ToggleButton = (() => {
133
139
  #updateCollapsed_accessor_storage = (__runInitializers(this, _collapsed_extraInitializers), __runInitializers(this, _updateCollapsed_initializers, void 0));
134
140
  get updateCollapsed() { return this.#updateCollapsed_accessor_storage; }
135
141
  set updateCollapsed(value) { this.#updateCollapsed_accessor_storage = value; }
142
+ #controls_accessor_storage = (__runInitializers(this, _updateCollapsed_extraInitializers), __runInitializers(this, _controls_initializers, void 0));
143
+ /**
144
+ * Id of the children container this button expands and collapses,
145
+ * exposed to assistive technologies through `aria-controls`.
146
+ */
147
+ get controls() { return this.#controls_accessor_storage; }
148
+ set controls(value) { this.#controls_accessor_storage = value; }
136
149
  constructor() {
137
150
  super(...arguments);
138
- __runInitializers(this, _updateCollapsed_extraInitializers);
151
+ __runInitializers(this, _controls_extraInitializers);
139
152
  }
140
153
  };
141
154
  })();
@@ -2,7 +2,17 @@ import type { TransformerMiddleware } from '../../../store/index.js';
2
2
  import { StoreExtension } from '../../../store/index.js';
3
3
  export declare const customImageProxyMiddleware: (imageProxyURL: string) => TransformerMiddleware;
4
4
  export declare const setImageProxyMiddlewareURL: (url: string) => void;
5
+ /**
6
+ * Reads the URL at run time: a `setImageProxyURL` made after module load
7
+ * reaches every job using this middleware.
8
+ */
5
9
  export declare const defaultImageProxyMiddleware: TransformerMiddleware;
10
+ /**
11
+ * The ONE seam governing every remote-image fetch the library performs on the
12
+ * host's behalf: link-card favicons and og:images, image import, canvas
13
+ * PNG/PDF export, copy-as-image. An empty URL means « fetch direct ».
14
+ * See `docs/integrate/04-host-seams.md`.
15
+ */
6
16
  export declare class ImageProxyService extends StoreExtension {
7
17
  static key: string;
8
18
  private _imageProxyURL;
@@ -5,18 +5,25 @@ export const customImageProxyMiddleware = (imageProxyURL) => {
5
5
  adapterConfigs.set('imageProxy', imageProxyURL);
6
6
  };
7
7
  };
8
- const imageProxyMiddlewareBuilder = () => {
9
- let middleware = customImageProxyMiddleware(DEFAULT_IMAGE_PROXY_ENDPOINT);
10
- return {
11
- get: () => middleware,
12
- set: (url) => {
13
- middleware = customImageProxyMiddleware(url);
14
- },
15
- };
8
+ // Module-wide so that transformer jobs built outside a store (playground
9
+ // panels, host-side importers) follow `setImageProxyURL` too.
10
+ let imageProxyMiddlewareURL = DEFAULT_IMAGE_PROXY_ENDPOINT;
11
+ export const setImageProxyMiddlewareURL = (url) => {
12
+ imageProxyMiddlewareURL = url;
13
+ };
14
+ /**
15
+ * Reads the URL at run time: a `setImageProxyURL` made after module load
16
+ * reaches every job using this middleware.
17
+ */
18
+ export const defaultImageProxyMiddleware = ({ adapterConfigs, }) => {
19
+ adapterConfigs.set('imageProxy', imageProxyMiddlewareURL);
16
20
  };
17
- const defaultImageProxyMiddlewarBuilder = imageProxyMiddlewareBuilder();
18
- export const setImageProxyMiddlewareURL = defaultImageProxyMiddlewarBuilder.set;
19
- export const defaultImageProxyMiddleware = defaultImageProxyMiddlewarBuilder.get();
21
+ /**
22
+ * The ONE seam governing every remote-image fetch the library performs on the
23
+ * host's behalf: link-card favicons and og:images, image import, canvas
24
+ * PNG/PDF export, copy-as-image. An empty URL means « fetch direct ».
25
+ * See `docs/integrate/04-host-seams.md`.
26
+ */
20
27
  // TODO(@mirone): this should be configured when setup instead of runtime
21
28
  export class ImageProxyService extends StoreExtension {
22
29
  constructor() {
@@ -29,7 +36,7 @@ export class ImageProxyService extends StoreExtension {
29
36
  setImageProxyMiddlewareURL(url);
30
37
  }
31
38
  buildUrl(imageUrl) {
32
- if (imageUrl.startsWith(this.imageProxyURL)) {
39
+ if (!this.imageProxyURL || imageUrl.startsWith(this.imageProxyURL)) {
33
40
  return imageUrl;
34
41
  }
35
42
  return `${this.imageProxyURL}?url=${encodeURIComponent(imageUrl)}`;
@@ -12,6 +12,10 @@ const fetchImage = async (url, init, proxy) => {
12
12
  if (url.startsWith(window.location.origin)) {
13
13
  return await fetch(url, init);
14
14
  }
15
+ // Already proxied (a link card renders `buildUrl(...)`): do not wrap twice.
16
+ if (url.startsWith(proxy)) {
17
+ return await fetch(url, init);
18
+ }
15
19
  return await fetch(proxy + '?url=' + encodeURIComponent(url), init)
16
20
  .then(res => {
17
21
  if (!res.ok) {
@@ -227,7 +227,7 @@ export declare abstract class GfxPrimitiveElementModel<Props extends BaseElement
227
227
  * This is a direct write path into the element's Y.Map, on the class that
228
228
  * carries the document format, exported by `@labre/std` and therefore
229
229
  * callable by a host. `_assignElementProp` learned the same lesson in the
230
- * unknown-props change (see `docs/spikes/us-1-8-unknown-props-preservation.md`,
230
+ * unknown-props change (see the unknown-props spike (PR #73),
231
231
  * whose deny-list is explicitly security rather than hygiene); an unguarded
232
232
  * delete re-opens the door from the other side. So:
233
233
  *
@@ -467,7 +467,7 @@ export declare abstract class GfxPrimitiveElementModel<Props extends BaseElement
467
467
  * An element re-created from props (paste, duplicate, alt-drag clone,
468
468
  * template insertion) only reaches the Y.Map through keys that have a
469
469
  * declared accessor, so a payload declared per subclass would be silently
470
- * dropped on copy — the exact loss `docs/spikes/us-1-8-unknown-props-preservation.md`
470
+ * dropped on copy — the exact loss the unknown-props spike (PR #73)
471
471
  * exists to prevent. Declaring it, rather than leaning on that spike's
472
472
  * verbatim fallback for undeclared keys, is what makes it a contract: a field
473
473
  * nothing declares is a field nothing can test, strip or find.
@@ -407,7 +407,7 @@ let GfxPrimitiveElementModel = (() => {
407
407
  * This is a direct write path into the element's Y.Map, on the class that
408
408
  * carries the document format, exported by `@labre/std` and therefore
409
409
  * callable by a host. `_assignElementProp` learned the same lesson in the
410
- * unknown-props change (see `docs/spikes/us-1-8-unknown-props-preservation.md`,
410
+ * unknown-props change (see the unknown-props spike (PR #73),
411
411
  * whose deny-list is explicitly security rather than hygiene); an unguarded
412
412
  * delete re-opens the door from the other side. So:
413
413
  *
@@ -692,7 +692,7 @@ let GfxPrimitiveElementModel = (() => {
692
692
  * An element re-created from props (paste, duplicate, alt-drag clone,
693
693
  * template insertion) only reaches the Y.Map through keys that have a
694
694
  * declared accessor, so a payload declared per subclass would be silently
695
- * dropped on copy — the exact loss `docs/spikes/us-1-8-unknown-props-preservation.md`
695
+ * dropped on copy — the exact loss the unknown-props spike (PR #73)
696
696
  * exists to prevent. Declaring it, rather than leaning on that spike's
697
697
  * verbatim fallback for undeclared keys, is what makes it a contract: a field
698
698
  * nothing declares is a field nothing can test, strip or find.
@@ -134,7 +134,7 @@ export declare class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
134
134
  * The value is already Y-converted at this point: both call sites run the
135
135
  * whole props object through {@link _propsToY} first, which is key-agnostic.
136
136
  *
137
- * See `docs/spikes/us-1-8-unknown-props-preservation.md`.
137
+ * See the unknown-props spike (PR #73).
138
138
  */
139
139
  private _assignElementProp;
140
140
  private _createElementFromYMap;
@@ -294,7 +294,7 @@ export class SurfaceBlockModel extends BlockModel {
294
294
  * The value is already Y-converted at this point: both call sites run the
295
295
  * whole props object through {@link _propsToY} first, which is key-agnostic.
296
296
  *
297
- * See `docs/spikes/us-1-8-unknown-props-preservation.md`.
297
+ * See the unknown-props spike (PR #73).
298
298
  */
299
299
  _assignElementProp(element, key, value) {
300
300
  if (UNSAFE_ELEMENT_PROP_KEYS.has(key)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-core",
3
3
  "description": "Labre editor — the full BlockSuite-derived editor as one package.",
4
- "version": "0.39.1",
4
+ "version": "0.39.3",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",