@formicoidea/labre-core 0.39.1 → 0.40.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 (37) hide show
  1. package/dist/_pkgs/affine-block-callout/emoji-margin.js +19 -11
  2. package/dist/_pkgs/affine-block-list/list-block.d.ts +8 -1
  3. package/dist/_pkgs/affine-block-list/list-block.js +27 -17
  4. package/dist/_pkgs/affine-block-note/configs/tooltips.d.ts +16 -0
  5. package/dist/_pkgs/affine-block-note/configs/tooltips.js +99 -66
  6. package/dist/_pkgs/affine-block-paragraph/paragraph-block.d.ts +8 -1
  7. package/dist/_pkgs/affine-block-paragraph/paragraph-block.js +30 -15
  8. package/dist/_pkgs/affine-block-paragraph/styles.js +30 -83
  9. package/dist/_pkgs/affine-block-paragraph/turbo/paragraph-layout-handler.js +1 -0
  10. package/dist/_pkgs/affine-block-paragraph/turbo/paragraph-painter.worker.d.ts +2 -0
  11. package/dist/_pkgs/affine-block-paragraph/turbo/paragraph-painter.worker.js +6 -1
  12. package/dist/_pkgs/affine-block-root/edgeless/clipboard/clipboard.js +3 -3
  13. package/dist/_pkgs/affine-block-root/keyboard/keymap.js +20 -0
  14. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.d.ts +6 -4
  15. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +15 -10
  16. package/dist/_pkgs/affine-block-surface/framework-background/def.d.ts +4 -1
  17. package/dist/_pkgs/affine-block-surface/framework-background/def.js +6 -2
  18. package/dist/_pkgs/affine-block-surface/utils/priority-queue.js +2 -2
  19. package/dist/_pkgs/affine-components/toggle-button/toggle-button.d.ts +5 -0
  20. package/dist/_pkgs/affine-components/toggle-button/toggle-button.js +31 -18
  21. package/dist/_pkgs/affine-gfx-template/builtin/other.js +31 -14
  22. package/dist/_pkgs/affine-inline-preset/markdown.d.ts +3 -0
  23. package/dist/_pkgs/affine-inline-preset/markdown.js +19 -0
  24. package/dist/_pkgs/affine-shared/adapters/middlewares/proxy.d.ts +10 -0
  25. package/dist/_pkgs/affine-shared/adapters/middlewares/proxy.js +19 -12
  26. package/dist/_pkgs/affine-shared/adapters/utils/fetch.js +4 -0
  27. package/dist/_pkgs/affine-shared/consts/heading.d.ts +29 -0
  28. package/dist/_pkgs/affine-shared/consts/heading.js +70 -0
  29. package/dist/_pkgs/affine-shared/consts/index.d.ts +2 -0
  30. package/dist/_pkgs/affine-shared/consts/index.js +2 -0
  31. package/dist/_pkgs/affine-shared/consts/notation.d.ts +67 -0
  32. package/dist/_pkgs/affine-shared/consts/notation.js +65 -0
  33. package/dist/_pkgs/std/gfx/model/surface/element-model.d.ts +2 -2
  34. package/dist/_pkgs/std/gfx/model/surface/element-model.js +2 -2
  35. package/dist/_pkgs/std/gfx/model/surface/surface-model.d.ts +1 -1
  36. package/dist/_pkgs/std/gfx/model/surface/surface-model.js +1 -1
  37. package/package.json +1 -1
@@ -1,22 +1,30 @@
1
1
  import { ParagraphBlockModel, } from '../affine-model/index.js';
2
+ import { HEADING_LEVELS, HEADING_SCALE, headingLineBox, } from '../affine-shared/consts/index.js';
2
3
  import { matchModels } from '../affine-shared/utils/index.js';
4
+ /** The emoji's box, px (`.affine-callout-emoji-container` in callout-block.ts). */
5
+ const EMOJI_BOX_PX = 24;
3
6
  /**
4
- * How far the emoji has to drop to sit on the first line's baseline.
7
+ * H1's bold caps read as sitting low when the emoji is centred on the line
8
+ * box mathematically; lifting the emoji by 1px levels it optically.
9
+ */
10
+ const H1_OPTICAL_LIFT_PX = 1;
11
+ /**
12
+ * How far the emoji has to drop to sit level with the first line.
5
13
  *
6
14
  * The emoji is a fixed 24px box next to a column whose first line can be
7
15
  * anything from an H1 to an ordinary paragraph. A single margin therefore
8
16
  * cannot be right twice: level with a paragraph it floats above an H1, level
9
- * with an H1 it sinks below a paragraph. These are the drops measured per
10
- * heading level.
17
+ * with an H1 it sinks below a paragraph. Per heading level, the drop centres
18
+ * the emoji on the heading's first line box, below its margin-top:
19
+ * `marginTop + lineBox / 2 - emojiBox / 2`.
11
20
  */
12
- const EMOJI_MARGIN_TOP_BY_TYPE = {
13
- h1: '23px',
14
- h2: '20px',
15
- h3: '16px',
16
- h4: '15px',
17
- h5: '14px',
18
- h6: '13px',
19
- };
21
+ const EMOJI_MARGIN_TOP_BY_TYPE = Object.fromEntries(HEADING_LEVELS.map(level => {
22
+ const centred = HEADING_SCALE[level].marginTop +
23
+ headingLineBox(level) / 2 -
24
+ EMOJI_BOX_PX / 2;
25
+ const drop = level === 'h1' ? centred - H1_OPTICAL_LIFT_PX : centred;
26
+ return [level, `${drop}px`];
27
+ }));
20
28
  /** What a paragraph, a list, or an empty callout gets. */
21
29
  const DEFAULT_EMOJI_MARGIN_TOP = '10px';
22
30
  /**
@@ -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; }
@@ -1,4 +1,20 @@
1
+ import { type HeadingLevel } from '../../affine-shared/consts/index.js';
1
2
  import type { SlashMenuTooltip } from '../../affine-widget-slash-menu/index.js';
3
+ /**
4
+ * The body lines of a heading preview that fit ENTIRELY in the frame, with the
5
+ * baseline each is set on.
6
+ *
7
+ * The body starts right under the heading's line box, so how many of its lines
8
+ * the card can show depends on the level: derived here from the geometry (line
9
+ * boxes, frame height, bottom padding) rather than tuned per level. At 10px on
10
+ * a 12px line, Inter's descenders end within a twentieth of a pixel of the line
11
+ * box's bottom edge, which the bottom padding covers: a line whose box fits is
12
+ * never clipped.
13
+ */
14
+ export declare function headingPreviewBodyLines(level: HeadingLevel): {
15
+ y: number;
16
+ text: string;
17
+ }[];
2
18
  export declare const BoldTextTooltip: import("lit-html").TemplateResult<1>;
3
19
  export declare const ItalicTooltip: import("lit-html").TemplateResult<1>;
4
20
  export declare const StrikethroughTooltip: import("lit-html").TemplateResult<1>;
@@ -1,4 +1,5 @@
1
- import { html } from 'lit';
1
+ import { HEADING_SCALE, headingLineBox, } from '../../affine-shared/consts/index.js';
2
+ import { html, svg } from 'lit';
2
3
  // prettier-ignore
3
4
  const TextTooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
4
5
  <rect width="170" height="68" rx="2" fill="white"/>
@@ -10,78 +11,110 @@ const TextTooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill
10
11
  </g>
11
12
  </svg>
12
13
  `;
14
+ /** The preview frame: every slash-menu figure is a 170×68 card. */
15
+ const PREVIEW_HEIGHT = 68;
16
+ /** Space above the first line of a preview, px. */
17
+ const PREVIEW_PADDING_TOP = 6;
18
+ /**
19
+ * Space a preview keeps free under its last line, px. A body line is drawn only
20
+ * if its whole line box ends above it, so no line is ever cut by the frame.
21
+ */
22
+ const PREVIEW_PADDING_BOTTOM = 2;
23
+ /** The body text under a heading preview, shrunk to 10px on a 12px line. */
24
+ const BODY_FONT_SIZE = 10;
25
+ const BODY_LINE_HEIGHT = 12;
26
+ /** Extra space between two paragraphs of the body, px. */
27
+ const BODY_PARAGRAPH_GAP = 4;
28
+ /**
29
+ * The body, as the lines it is set in (the `text` preview's own wording). A
30
+ * paragraph ends with a newline, as the Figma export wrote it.
31
+ */
32
+ const BODY_PARAGRAPHS = [
33
+ [
34
+ 'In a decentralized system, we can have a kaleidoscopic ',
35
+ 'complexity to our data.\n',
36
+ ],
37
+ [
38
+ 'Any user may have a different perspective on what data they ',
39
+ 'either have, choose to share, or accept.\n',
40
+ ],
41
+ [
42
+ 'For example, one user\u2019s edits to a document might be on ',
43
+ 'their laptop on an airplane; when the plane lands and the ',
44
+ 'computer reconnects, those changes are distributed to ',
45
+ 'other users.\n',
46
+ ],
47
+ [
48
+ 'Other users might choose to accept all, some, or none of ',
49
+ 'those changes to their version of the document.',
50
+ ],
51
+ ];
52
+ /**
53
+ * Where Figma sets an Inter baseline in a line box: 4/11 em below the box's
54
+ * centre (half of Inter's ascent minus descent).
55
+ */
56
+ function interBaseline(top, lineHeight, fontSize) {
57
+ return Number((top + lineHeight / 2 + (fontSize * 4) / 11).toFixed(4));
58
+ }
59
+ /**
60
+ * The body lines of a heading preview that fit ENTIRELY in the frame, with the
61
+ * baseline each is set on.
62
+ *
63
+ * The body starts right under the heading's line box, so how many of its lines
64
+ * the card can show depends on the level: derived here from the geometry (line
65
+ * boxes, frame height, bottom padding) rather than tuned per level. At 10px on
66
+ * a 12px line, Inter's descenders end within a twentieth of a pixel of the line
67
+ * box's bottom edge, which the bottom padding covers: a line whose box fits is
68
+ * never clipped.
69
+ */
70
+ export function headingPreviewBodyLines(level) {
71
+ const limit = PREVIEW_HEIGHT - PREVIEW_PADDING_BOTTOM;
72
+ const lines = [];
73
+ let top = PREVIEW_PADDING_TOP + headingLineBox(level);
74
+ for (const [index, paragraph] of BODY_PARAGRAPHS.entries()) {
75
+ if (index > 0)
76
+ top += BODY_PARAGRAPH_GAP;
77
+ for (const text of paragraph) {
78
+ if (top + BODY_LINE_HEIGHT > limit)
79
+ return lines;
80
+ lines.push({
81
+ y: interBaseline(top, BODY_LINE_HEIGHT, BODY_FONT_SIZE),
82
+ text,
83
+ });
84
+ top += BODY_LINE_HEIGHT;
85
+ }
86
+ }
87
+ return lines;
88
+ }
89
+ /**
90
+ * A heading preview drawn at the heading's real size, weight and line box
91
+ * (from `HEADING_SCALE`), above as many lines of body text as the card shows
92
+ * whole (`headingPreviewBodyLines`).
93
+ */
13
94
  // prettier-ignore
14
- const Heading1Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
95
+ function headingTooltip(level) {
96
+ const { fontSize, fontWeight, letterSpacing } = HEADING_SCALE[level];
97
+ const lineBox = headingLineBox(level);
98
+ const maskId = `mask_heading_tooltip_${level}`;
99
+ const body = headingPreviewBodyLines(level).map(line => svg `<tspan x="8" y=${line.y}>${line.text}</tspan>`);
100
+ return html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
15
101
  <rect width="170" height="68" rx="2" fill="white"/>
16
- <mask id="mask0_16460_873" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
102
+ <mask id=${maskId} style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
17
103
  <rect width="170" height="68" rx="2" fill="white"/>
18
104
  </mask>
19
- <g mask="url(#mask0_16460_873)">
20
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="28" font-weight="bold" letter-spacing="-0.24px"><tspan x="8" y="34.1818">Heading 1</tspan></text>
21
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="51.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="63.6364">complexity to our data.&#10;</tspan><tspan x="8" y="79.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="91.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="107.636">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="119.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="131.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="143.636">other users.&#10;</tspan><tspan x="8" y="159.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="171.636">those changes to their version of the document.</tspan></text>
22
- </g>
23
- </svg>
24
- `;
25
- // prettier-ignore
26
- const Heading2Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
27
- <rect width="170" height="68" rx="2" fill="white"/>
28
- <mask id="mask0_16460_880" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
29
- <rect width="170" height="68" rx="2" fill="white"/>
30
- </mask>
31
- <g mask="url(#mask0_16460_880)">
32
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="26" font-weight="600" letter-spacing="-0.24px"><tspan x="8" y="33.4545">Heading 2</tspan></text>
33
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="51.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="63.6364">complexity to our data.&#10;</tspan><tspan x="8" y="79.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="91.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="107.636">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="119.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="131.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="143.636">other users.&#10;</tspan><tspan x="8" y="159.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="171.636">those changes to their version of the document.</tspan></text>
34
- </g>
35
- </svg>
36
- `;
37
- // prettier-ignore
38
- const Heading3Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
39
- <rect width="170" height="68" rx="2" fill="white"/>
40
- <mask id="mask0_16460_887" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
41
- <rect width="170" height="68" rx="2" fill="white"/>
42
- </mask>
43
- <g mask="url(#mask0_16460_887)">
44
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="24" font-weight="600" letter-spacing="-0.24px"><tspan x="8" y="30.7273">Heading 3</tspan></text>
45
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="47.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="59.6364">complexity to our data.&#10;</tspan><tspan x="8" y="75.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="87.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="103.636">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="115.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="127.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="139.636">other users.&#10;</tspan><tspan x="8" y="155.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="167.636">those changes to their version of the document.</tspan></text>
46
- </g>
47
- </svg>
48
- `;
49
- // prettier-ignore
50
- const Heading4Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
51
- <rect width="170" height="68" rx="2" fill="white"/>
52
- <mask id="mask0_16460_894" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
53
- <rect width="170" height="68" rx="2" fill="white"/>
54
- </mask>
55
- <g mask="url(#mask0_16460_894)">
56
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="22" font-weight="600" letter-spacing="0.24px"><tspan x="8" y="29">Heading 4</tspan></text>
57
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="45.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="57.6364">complexity to our data.&#10;</tspan><tspan x="8" y="73.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="85.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="101.636">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="113.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="125.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="137.636">other users.&#10;</tspan><tspan x="8" y="153.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="165.636">those changes to their version of the document.</tspan></text>
58
- </g>
59
- </svg>
60
- `;
61
- // prettier-ignore
62
- const Heading5Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
63
- <rect width="170" height="68" rx="2" fill="white"/>
64
- <mask id="mask0_16460_901" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
65
- <rect width="170" height="68" rx="2" fill="white"/>
66
- </mask>
67
- <g mask="url(#mask0_16460_901)">
68
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="20" font-weight="600" letter-spacing="0.24px"><tspan x="8" y="27.2727">Heading 5</tspan></text>
69
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="43.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="55.6364">complexity to our data.&#10;</tspan><tspan x="8" y="71.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="83.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="99.6364">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="111.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="123.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="135.636">other users.&#10;</tspan><tspan x="8" y="151.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="163.636">those changes to their version of the document.</tspan></text>
70
- </g>
71
- </svg>
72
- `;
73
- // prettier-ignore
74
- const Heading6Tooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
75
- <rect width="170" height="68" rx="2" fill="white"/>
76
- <mask id="mask0_16460_908" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="170" height="68">
77
- <rect width="170" height="68" rx="2" fill="white"/>
78
- </mask>
79
- <g mask="url(#mask0_16460_908)">
80
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="18" font-weight="600" letter-spacing="0.24px"><tspan x="8" y="25.5455">Heading 6</tspan></text>
81
- <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="8" y="41.6364">In a decentralized system, we can have a kaleidoscopic </tspan><tspan x="8" y="53.6364">complexity to our data.&#10;</tspan><tspan x="8" y="69.6364">Any user may have a different perspective on what data they </tspan><tspan x="8" y="81.6364">either have, choose to share, or accept.&#10;</tspan><tspan x="8" y="97.6364">For example, one user&#x2019;s edits to a document might be on </tspan><tspan x="8" y="109.636">their laptop on an airplane; when the plane lands and the </tspan><tspan x="8" y="121.636">computer reconnects, those changes are distributed to </tspan><tspan x="8" y="133.636">other users.&#10;</tspan><tspan x="8" y="149.636">Other users might choose to accept all, some, or none of </tspan><tspan x="8" y="161.636">those changes to their version of the document.</tspan></text>
105
+ <g mask="url(#${maskId})">
106
+ <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size=${fontSize} font-weight=${fontWeight} letter-spacing="${letterSpacing}em"><tspan x="8" y=${interBaseline(PREVIEW_PADDING_TOP, lineBox, fontSize)}>Heading ${level.slice(1)}</tspan></text>
107
+ <text fill="#121212" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size=${BODY_FONT_SIZE} letter-spacing="0px">${body}</text>
82
108
  </g>
83
109
  </svg>
84
110
  `;
111
+ }
112
+ const Heading1Tooltip = headingTooltip('h1');
113
+ const Heading2Tooltip = headingTooltip('h2');
114
+ const Heading3Tooltip = headingTooltip('h3');
115
+ const Heading4Tooltip = headingTooltip('h4');
116
+ const Heading5Tooltip = headingTooltip('h5');
117
+ const Heading6Tooltip = headingTooltip('h6');
85
118
  // prettier-ignore
86
119
  const CodeBlockTooltip = html `<svg width="170" height="68" viewBox="0 0 170 68" fill="none" xmlns="http://www.w3.org/2000/svg">
87
120
  <rect width="170" height="68" rx="2" fill="white"/>
@@ -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; }