@formicoidea/labre-core 0.46.0 → 0.46.2

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 (32) hide show
  1. package/dist/_pkgs/affine-block-bookmark/bookmark-block.js +5 -2
  2. package/dist/_pkgs/affine-block-callout/commands/turn-into-callout.d.ts +1 -1
  3. package/dist/_pkgs/affine-block-callout/commands/turn-into-callout.js +1 -1
  4. package/dist/_pkgs/affine-block-callout/configs/slash-menu.js +4 -2
  5. package/dist/_pkgs/affine-block-callout/view.d.ts +30 -1
  6. package/dist/_pkgs/affine-block-callout/view.js +40 -4
  7. package/dist/_pkgs/affine-block-code/code-toolbar/components/lang-button.js +1 -1
  8. package/dist/_pkgs/affine-block-database/properties/link/cell-renderer.js +2 -2
  9. package/dist/_pkgs/affine-block-embed/embed-figma-block/embed-figma-block.js +3 -1
  10. package/dist/_pkgs/affine-block-embed/embed-github-block/embed-github-block.js +3 -1
  11. package/dist/_pkgs/affine-block-embed/embed-loom-block/embed-loom-block.js +3 -1
  12. package/dist/_pkgs/affine-block-embed/embed-youtube-block/embed-youtube-block.js +3 -1
  13. package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.js +12 -12
  14. package/dist/_pkgs/affine-components/highlight-dropdown-menu/highlight-duotone-icon.js +8 -8
  15. package/dist/_pkgs/affine-components/highlight-dropdown-menu/text-duotone-icon.js +11 -11
  16. package/dist/_pkgs/affine-components/link-preview/link.js +3 -3
  17. package/dist/_pkgs/affine-inline-footnote/footnote-node/footnote-node.js +3 -0
  18. package/dist/_pkgs/affine-inline-link/adapters/markdown/markdown-inline.js +5 -0
  19. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +6 -3
  20. package/dist/_pkgs/affine-shared/utils/url.d.ts +15 -0
  21. package/dist/_pkgs/affine-shared/utils/url.js +30 -0
  22. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.js +2 -2
  23. package/dist/_pkgs/affine-widget-edgeless-zoom-toolbar/index.js +26 -26
  24. package/dist/_pkgs/affine-widget-keyboard-toolbar/icons.js +98 -98
  25. package/dist/_pkgs/affine-widget-page-dragging-area/index.js +10 -10
  26. package/dist/_pkgs/affine-widget-viewport-overlay/index.js +13 -13
  27. package/dist/_pkgs/data-view/core/utils/menu-title.js +17 -17
  28. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/group/top/group-title.js +18 -18
  29. package/dist/extensions/view.js +5 -1
  30. package/dist/flags.d.ts +3 -0
  31. package/dist/flags.js +3 -0
  32. package/package.json +1 -1
@@ -35,7 +35,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
35
35
  import { CaptionedBlockComponent, SelectedStyle, } from '../affine-components/caption/index.js';
36
36
  import { ImageProxyService } from '../affine-shared/adapters/index.js';
37
37
  import { BlockCommentManager, CitationProvider, DocModeProvider, LinkPreviewServiceIdentifier, } from '../affine-shared/services/index.js';
38
- import { normalizeUrl, shouldRefreshOnPropsUpdate, } from '../affine-shared/utils/index.js';
38
+ import { isSafeLinkUrl, normalizeUrl, shouldRefreshOnPropsUpdate, } from '../affine-shared/utils/index.js';
39
39
  import { BlockSelection } from '../std/index.js';
40
40
  import { computed, signal } from '@preact/signals-core';
41
41
  import { html } from 'lit';
@@ -122,7 +122,10 @@ let BookmarkBlockComponent = (() => {
122
122
  selectionManager.setGroup('note', [blockSelection]);
123
123
  };
124
124
  this.open = () => {
125
- window.open(this.link, '_blank');
125
+ const { link } = this;
126
+ if (!isSafeLinkUrl(link))
127
+ return;
128
+ window.open(link, '_blank');
126
129
  };
127
130
  this.refreshData = () => {
128
131
  refreshBookmarkUrlData(this, this._fetchAbortController?.signal).catch(console.error);
@@ -14,7 +14,7 @@ import type { BlockModel, Store } from '../../store/index.js';
14
14
  */
15
15
  export declare function calloutConversionTargets(store: Store, models: BlockModel[]): BlockModel[];
16
16
  /**
17
- * Wraps each selected paragraph, list item or code block in a callout (#418).
17
+ * Turns each selected paragraph, list item or code block into a callout (#418).
18
18
  *
19
19
  * A callout is a hub with no text of its own, so this cannot be
20
20
  * `transformModel`: the callout takes the block's place, a paragraph inside it
@@ -37,7 +37,7 @@ export function calloutConversionTargets(store, models) {
37
37
  return convertible.filter(model => !convertible.some(other => isAncestor(store, other, model)));
38
38
  }
39
39
  /**
40
- * Wraps each selected paragraph, list item or code block in a callout (#418).
40
+ * Turns each selected paragraph, list item or code block into a callout (#418).
41
41
  *
42
42
  * A callout is a hub with no text of its own, so this cannot be
43
43
  * `transformModel`: the callout takes the block's place, a paragraph inside it
@@ -42,10 +42,12 @@ export const calloutSlashMenuConfig = {
42
42
  if (index === -1)
43
43
  return;
44
44
  const calloutId = store.addBlock('affine:callout', {}, parent, index + 1);
45
- if (!calloutId)
45
+ // `addBlock` returns an id even when the schema refused the block
46
+ // (`transact` swallows the throw, #418): only the model proves it.
47
+ if (!store.getModelById(calloutId))
46
48
  return;
47
49
  const paragraphId = store.addBlock('affine:paragraph', {}, calloutId);
48
- if (!paragraphId)
50
+ if (!store.getModelById(paragraphId))
49
51
  return;
50
52
  std.host.updateComplete
51
53
  .then(() => {
@@ -1,6 +1,35 @@
1
1
  import { type ViewExtensionContext, ViewExtensionProvider } from '../affine-ext-loader/index.js';
2
- export declare class CalloutViewExtension extends ViewExtensionProvider {
2
+ /**
3
+ * Callout rendering — ALWAYS registered, independent of the `callout` flag
4
+ * (ADR 0009, #418). A document that already holds callouts must paint them,
5
+ * let them be edited and let them be turned back into text whatever the flag
6
+ * says: until 2026-10-11 the whole view sat behind the flag, and
7
+ * `{ callout: false }` left every stored callout rendered as nothing.
8
+ *
9
+ * What lives here is what an EXISTING callout needs, the mirror of
10
+ * `WardleyRenderViewExtension`:
11
+ * - `effects()` whole: `affine-callout` paints the block, and
12
+ * `affine-emoji-menu` opens from a rendered callout's icon — both are
13
+ * render-side, so unlike the frameworks' this `effects()` is not tooling;
14
+ * - the flavour and the block view, the lookup `lit-host` performs;
15
+ * - the keymap: Enter and Backspace inside a callout edit stored content, they
16
+ * create nothing;
17
+ * - the block toolbar (#468): Turn into text, copy, duplicate, delete — the
18
+ * way OUT of a stored callout, as the Wardley contextual toolbar stays with
19
+ * a stored map.
20
+ */
21
+ export declare class CalloutRenderViewExtension extends ViewExtensionProvider {
3
22
  name: string;
4
23
  effect(): void;
5
24
  setup(context: ViewExtensionContext): void;
6
25
  }
26
+ /**
27
+ * Callout creation tooling — flag-gated (`callout`, ADR 0009): the slash-menu
28
+ * item and the "Turn into → Callout" entry, the two ways to CREATE a callout.
29
+ * With the flag off no new callout can be made; the ones already written keep
30
+ * rendering through {@link CalloutRenderViewExtension}.
31
+ */
32
+ export declare class CalloutViewExtension extends ViewExtensionProvider {
33
+ name: string;
34
+ setup(context: ViewExtensionContext): void;
35
+ }
@@ -9,10 +9,29 @@ import { calloutSlashMenuConfig } from './configs/slash-menu.js';
9
9
  import { calloutToolbarConfig } from './configs/toolbar.js';
10
10
  import { calloutTurnIntoEntry } from './configs/turn-into.js';
11
11
  import { effects } from './effects.js';
12
- export class CalloutViewExtension extends ViewExtensionProvider {
12
+ /**
13
+ * Callout rendering — ALWAYS registered, independent of the `callout` flag
14
+ * (ADR 0009, #418). A document that already holds callouts must paint them,
15
+ * let them be edited and let them be turned back into text whatever the flag
16
+ * says: until 2026-10-11 the whole view sat behind the flag, and
17
+ * `{ callout: false }` left every stored callout rendered as nothing.
18
+ *
19
+ * What lives here is what an EXISTING callout needs, the mirror of
20
+ * `WardleyRenderViewExtension`:
21
+ * - `effects()` whole: `affine-callout` paints the block, and
22
+ * `affine-emoji-menu` opens from a rendered callout's icon — both are
23
+ * render-side, so unlike the frameworks' this `effects()` is not tooling;
24
+ * - the flavour and the block view, the lookup `lit-host` performs;
25
+ * - the keymap: Enter and Backspace inside a callout edit stored content, they
26
+ * create nothing;
27
+ * - the block toolbar (#468): Turn into text, copy, duplicate, delete — the
28
+ * way OUT of a stored callout, as the Wardley contextual toolbar stays with
29
+ * a stored map.
30
+ */
31
+ export class CalloutRenderViewExtension extends ViewExtensionProvider {
13
32
  constructor() {
14
33
  super(...arguments);
15
- this.name = 'affine-callout-block';
34
+ this.name = 'affine-callout-render-block';
16
35
  }
17
36
  effect() {
18
37
  super.effect();
@@ -24,8 +43,6 @@ export class CalloutViewExtension extends ViewExtensionProvider {
24
43
  FlavourExtension('affine:callout'),
25
44
  BlockViewExtension('affine:callout', literal `affine-callout`),
26
45
  CalloutKeymapExtension,
27
- SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig),
28
- TextConversionEntryExtension(calloutTurnIntoEntry),
29
46
  ToolbarModuleExtension({
30
47
  id: BlockFlavourIdentifier('affine:callout'),
31
48
  config: calloutToolbarConfig,
@@ -33,3 +50,22 @@ export class CalloutViewExtension extends ViewExtensionProvider {
33
50
  ]);
34
51
  }
35
52
  }
53
+ /**
54
+ * Callout creation tooling — flag-gated (`callout`, ADR 0009): the slash-menu
55
+ * item and the "Turn into → Callout" entry, the two ways to CREATE a callout.
56
+ * With the flag off no new callout can be made; the ones already written keep
57
+ * rendering through {@link CalloutRenderViewExtension}.
58
+ */
59
+ export class CalloutViewExtension extends ViewExtensionProvider {
60
+ constructor() {
61
+ super(...arguments);
62
+ this.name = 'affine-callout-block';
63
+ }
64
+ setup(context) {
65
+ super.setup(context);
66
+ context.register([
67
+ SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig),
68
+ TextConversionEntryExtension(calloutTurnIntoEntry),
69
+ ]);
70
+ }
71
+ }
@@ -118,7 +118,7 @@ let LanguageListButton = (() => {
118
118
  const plainText = {
119
119
  label: translateKey(this.blockComponent.std, ...CODE_PLAIN_TEXT_LANGUAGE),
120
120
  name: PLAIN_TEXT_ITEM_NAME,
121
- aliases: ['plain', 'text', 'none'],
121
+ aliases: ['plain', 'text', 'plaintext', 'txt', 'none'],
122
122
  };
123
123
  const index = languages.findIndex(item => item.name === PLAIN_TEXT_ITEM_NAME);
124
124
  if (index === -1) {
@@ -1,6 +1,6 @@
1
1
  import { RefNodeSlotsProvider } from '../../../affine-inline-reference/index.js';
2
2
  import { ParseDocUrlProvider } from '../../../affine-shared/services/index.js';
3
- import { isValidUrl, normalizeUrl, stopPropagation, } from '../../../affine-shared/utils/index.js';
3
+ import { isSafeLinkUrl, isValidUrl, normalizeUrl, stopPropagation, } from '../../../affine-shared/utils/index.js';
4
4
  import { BaseCellRenderer, createFromBaseCellRenderer, createIcon, } from '../../../data-view/index.js';
5
5
  import { EditIcon } from '@blocksuite/icons/lit';
6
6
  import { computed } from '@preact/signals-core';
@@ -101,7 +101,7 @@ export class LinkCell extends BaseCellRenderer {
101
101
  ? html `<span class="${linkedDocStyle}" @click="${this.openDoc}"
102
102
  >${docName}</span
103
103
  >`
104
- : isValidUrl(linkText)
104
+ : isValidUrl(linkText) && isSafeLinkUrl(linkText)
105
105
  ? html `<a
106
106
  data-testid="property-link-a"
107
107
  class="${inlineLinkNodeStyle}"
@@ -1,5 +1,5 @@
1
1
  import { OpenIcon } from '../../affine-components/icons/index.js';
2
- import { shouldRefreshOnPropsUpdate } from '../../affine-shared/utils/index.js';
2
+ import { isSafeLinkUrl, shouldRefreshOnPropsUpdate, } from '../../affine-shared/utils/index.js';
3
3
  import { BlockSelection } from '../../std/index.js';
4
4
  import { html, nothing } from 'lit';
5
5
  import { classMap } from 'lit/directives/class-map.js';
@@ -15,6 +15,8 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent {
15
15
  if (!link.match(/^[a-zA-Z]+:\/\//)) {
16
16
  link = 'https://' + link;
17
17
  }
18
+ if (!isSafeLinkUrl(link))
19
+ return;
18
20
  window.open(link, '_blank');
19
21
  };
20
22
  this.refreshData = () => { };
@@ -35,7 +35,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
35
35
  import { LoadingIcon, OpenIcon } from '../../affine-components/icons/index.js';
36
36
  import { ImageProxyService } from '../../affine-shared/adapters/index.js';
37
37
  import { ThemeProvider } from '../../affine-shared/services/index.js';
38
- import { shouldRefreshOnPropsUpdate } from '../../affine-shared/utils/index.js';
38
+ import { isSafeLinkUrl, shouldRefreshOnPropsUpdate, } from '../../affine-shared/utils/index.js';
39
39
  import { BlockSelection, isGfxBlockComponent } from '../../std/index.js';
40
40
  import { html, nothing } from 'lit';
41
41
  import { property } from 'lit/decorators.js';
@@ -249,6 +249,8 @@ let EmbedGithubBlockComponent = (() => {
249
249
  if (!link.match(/^[a-zA-Z]+:\/\//)) {
250
250
  link = 'https://' + link;
251
251
  }
252
+ if (!isSafeLinkUrl(link))
253
+ return;
252
254
  window.open(link, '_blank');
253
255
  };
254
256
  this.refreshData = () => {
@@ -35,7 +35,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
35
35
  import { LoadingIcon, OpenIcon } from '../../affine-components/icons/index.js';
36
36
  import { ImageProxyService } from '../../affine-shared/adapters/index.js';
37
37
  import { ThemeProvider } from '../../affine-shared/services/index.js';
38
- import { shouldRefreshOnPropsUpdate } from '../../affine-shared/utils/index.js';
38
+ import { isSafeLinkUrl, shouldRefreshOnPropsUpdate, } from '../../affine-shared/utils/index.js';
39
39
  import { BlockSelection } from '../../std/index.js';
40
40
  import { html } from 'lit';
41
41
  import { property } from 'lit/decorators.js';
@@ -186,6 +186,8 @@ let EmbedLoomBlockComponent = (() => {
186
186
  if (!link.match(/^[a-zA-Z]+:\/\//)) {
187
187
  link = 'https://' + link;
188
188
  }
189
+ if (!isSafeLinkUrl(link))
190
+ return;
189
191
  window.open(link, '_blank');
190
192
  };
191
193
  this.refreshData = () => {
@@ -35,7 +35,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
35
35
  import { LoadingIcon, OpenIcon } from '../../affine-components/icons/index.js';
36
36
  import { ImageProxyService } from '../../affine-shared/adapters/index.js';
37
37
  import { ThemeProvider } from '../../affine-shared/services/index.js';
38
- import { shouldRefreshOnPropsUpdate } from '../../affine-shared/utils/index.js';
38
+ import { isSafeLinkUrl, shouldRefreshOnPropsUpdate, } from '../../affine-shared/utils/index.js';
39
39
  import { BlockSelection } from '../../std/index.js';
40
40
  import { html, nothing } from 'lit';
41
41
  import { property, state } from 'lit/decorators.js';
@@ -232,6 +232,8 @@ let EmbedYoutubeBlockComponent = (() => {
232
232
  if (!link.match(/^[a-zA-Z]+:\/\//)) {
233
233
  link = 'https://' + link;
234
234
  }
235
+ if (!isSafeLinkUrl(link))
236
+ return;
235
237
  window.open(link, '_blank');
236
238
  };
237
239
  this.refreshData = () => {
@@ -58,18 +58,18 @@ const READ_LABEL_FALLBACK = 'Read this component';
58
58
  * Drawn inline like its neighbours: this package does not depend on
59
59
  * `@blocksuite/icons` and one glyph is not worth a new dependency.
60
60
  */
61
- const ReadIcon = html `<svg
62
- width="20"
63
- height="20"
64
- viewBox="0 0 24 24"
65
- fill="none"
66
- stroke="currentColor"
67
- stroke-width="2"
68
- stroke-linecap="round"
69
- stroke-linejoin="round"
70
- >
71
- <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
72
- <circle cx="12" cy="12" r="3" />
61
+ const ReadIcon = html `<svg
62
+ width="20"
63
+ height="20"
64
+ viewBox="0 0 24 24"
65
+ fill="none"
66
+ stroke="currentColor"
67
+ stroke-width="2"
68
+ stroke-linecap="round"
69
+ stroke-linejoin="round"
70
+ >
71
+ <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
72
+ <circle cx="12" cy="12" r="3" />
73
73
  </svg>`;
74
74
  /** The command this module drives. Spelled once. */
75
75
  const ELEMENT_READ_ID = 'element.read';
@@ -1,14 +1,14 @@
1
1
  import { HighLightDuotoneIcon } from '@blocksuite/icons/lit';
2
2
  import { css, LitElement } from 'lit';
3
3
  export class HighlightDuotoneIcon extends LitElement {
4
- static { this.styles = css `
5
- svg {
6
- display: flex;
7
- font-size: 20px;
8
- }
9
- svg > path:nth-child(1) {
10
- fill: var(--color, unset);
11
- }
4
+ static { this.styles = css `
5
+ svg {
6
+ display: flex;
7
+ font-size: 20px;
8
+ }
9
+ svg > path:nth-child(1) {
10
+ fill: var(--color, unset);
11
+ }
12
12
  `; }
13
13
  render() {
14
14
  return HighLightDuotoneIcon();
@@ -1,17 +1,17 @@
1
1
  import { TextBackgroundDuotoneIcon } from '@blocksuite/icons/lit';
2
2
  import { css, LitElement } from 'lit';
3
3
  export class TextDuotoneIcon extends LitElement {
4
- static { this.styles = css `
5
- svg {
6
- display: flex;
7
- font-size: 20px;
8
- }
9
- svg > path:nth-child(1) {
10
- fill: var(--background, unset);
11
- }
12
- svg > path:nth-child(3) {
13
- fill: var(--color, unset);
14
- }
4
+ static { this.styles = css `
5
+ svg {
6
+ display: flex;
7
+ font-size: 20px;
8
+ }
9
+ svg > path:nth-child(1) {
10
+ fill: var(--background, unset);
11
+ }
12
+ svg > path:nth-child(3) {
13
+ fill: var(--color, unset);
14
+ }
15
15
  `; }
16
16
  render() {
17
17
  return TextBackgroundDuotoneIcon();
@@ -32,11 +32,11 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { getHostName } from '../../affine-shared/utils/index.js';
35
+ import { getHostName, isSafeLinkUrl } from '../../affine-shared/utils/index.js';
36
36
  import { PropTypes, requiredProperties } from '../../std/index.js';
37
37
  import { css, LitElement } from 'lit';
38
38
  import { property } from 'lit/decorators.js';
39
- import { html } from 'lit-html';
39
+ import { html, nothing } from 'lit-html';
40
40
  let LinkPreview = (() => {
41
41
  let _classDecorators = [requiredProperties({
42
42
  url: PropTypes.string,
@@ -98,7 +98,7 @@ let LinkPreview = (() => {
98
98
  class="affine-link-preview"
99
99
  rel="noopener noreferrer"
100
100
  target="_blank"
101
- href=${url}
101
+ href=${isSafeLinkUrl(url) ? url : nothing}
102
102
  >
103
103
  <span>${getHostName(url)}</span>
104
104
  </a>
@@ -36,6 +36,7 @@ import { HoverController } from '../../affine-components/hover/index.js';
36
36
  import { PeekViewProvider } from '../../affine-components/peek/index.js';
37
37
  import { CitationProvider } from '../../affine-shared/services/index.js';
38
38
  import { unsafeCSSVarV2 } from '../../affine-shared/theme/index.js';
39
+ import { isSafeLinkUrl } from '../../affine-shared/utils/index.js';
39
40
  import { WithDisposable } from '../../global/lit/index.js';
40
41
  import { BlockSelection, ShadowlessElement, TextSelection, } from '../../std/index.js';
41
42
  import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMBED_NODE, ZERO_WIDTH_FOR_EMPTY_LINE, } from '../../std/inline/index.js';
@@ -203,6 +204,8 @@ let AffineFootnoteNode = (() => {
203
204
  .catch(console.error);
204
205
  };
205
206
  this._handleUrlReference = (url) => {
207
+ if (!isSafeLinkUrl(url))
208
+ return;
206
209
  window.open(url, '_blank');
207
210
  };
208
211
  this._updateFootnoteAttributes = (footnote) => {
@@ -1,4 +1,5 @@
1
1
  import { MarkdownASTToDeltaExtension } from '../../../affine-shared/adapters/index.js';
2
+ import { isSafeLinkUrl } from '../../../affine-shared/utils/index.js';
2
3
  export const markdownLinkToDeltaMatcher = MarkdownASTToDeltaExtension({
3
4
  name: 'link',
4
5
  match: ast => ast.type === 'link',
@@ -39,6 +40,10 @@ export const markdownLinkToDeltaMatcher = MarkdownASTToDeltaExtension({
39
40
  ];
40
41
  }
41
42
  }
43
+ // A refused scheme (`javascript:`, `data:`…) keeps its text, loses the mark.
44
+ if (!isSafeLinkUrl(ast.url)) {
45
+ return ast.children.flatMap(child => context.toDelta(child));
46
+ }
42
47
  return ast.children.flatMap(child => context.toDelta(child).map(delta => {
43
48
  delta.attributes = { ...delta.attributes, link: ast.url };
44
49
  return delta;
@@ -36,11 +36,11 @@ import { whenHover } from '../../affine-components/hover/index.js';
36
36
  import { RefNodeSlotsProvider } from '../../affine-inline-reference/index.js';
37
37
  import { ParseDocUrlProvider, ToolbarRegistryIdentifier, } from '../../affine-shared/services/index.js';
38
38
  import { affineTextStyles } from '../../affine-shared/styles/index.js';
39
- import { normalizeUrl } from '../../affine-shared/utils/index.js';
39
+ import { isSafeLinkUrl, normalizeUrl } from '../../affine-shared/utils/index.js';
40
40
  import { WithDisposable } from '../../global/lit/index.js';
41
41
  import { BLOCK_ID_ATTR, ShadowlessElement } from '../../std/index.js';
42
42
  import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMPTY_LINE, } from '../../std/inline/index.js';
43
- import { css, html } from 'lit';
43
+ import { css, html, nothing } from 'lit';
44
44
  import { property } from 'lit/decorators.js';
45
45
  import { styleMap } from 'lit/directives/style-map.js';
46
46
  let AffineLink = (() => {
@@ -112,8 +112,11 @@ let AffineLink = (() => {
112
112
  this._referenceInfo = { pageId, params };
113
113
  }
114
114
  _renderLink(style) {
115
+ const { link } = this;
116
+ // A stored link may come from an imported or shared document: a refused
117
+ // scheme keeps its text but gets no href, so a click navigates nowhere.
115
118
  return html `<a
116
- href=${this.link}
119
+ href=${isSafeLinkUrl(link) ? link : nothing}
117
120
  rel="noopener noreferrer"
118
121
  target="_blank"
119
122
  style=${styleMap(style)}
@@ -1,4 +1,19 @@
1
1
  export declare function normalizeUrl(str: string): string;
2
+ /**
3
+ * Whether a link stored in a document may become an `href` or be opened.
4
+ *
5
+ * Only `http:`, `https:` and `mailto:` pass, plus scheme-less values that
6
+ * {@link normalizeUrl} turns into https (`example.com`). Everything else is
7
+ * refused — `javascript:`, `data:`, `vbscript:`, `file:`, relative paths —
8
+ * because the value comes from an imported, pasted or shared document and a
9
+ * click on it must never run in the editor's origin. The scheme is read by
10
+ * `new URL` after the same cleanup the browser does, never by a prefix test,
11
+ * so ` JaVaScRiPt:` and `java\tscript:` are recognised for what they are.
12
+ *
13
+ * Render and open sites call this on the stored value; nothing is rewritten
14
+ * on load, so a refused link keeps its text and simply is not clickable.
15
+ */
16
+ export declare function isSafeLinkUrl(str: string | null | undefined): str is string;
2
17
  /**
3
18
  * Assume user will input a url, we just need to check if it is valid.
4
19
  *
@@ -57,6 +57,36 @@ export function normalizeUrl(str) {
57
57
  }
58
58
  return str;
59
59
  }
60
+ const LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:']);
61
+ // What the WHATWG URL parser drops before reading a scheme: leading and
62
+ // trailing C0 controls and spaces, and every tab or newline anywhere.
63
+ const URL_EDGE_JUNK = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g;
64
+ const URL_TAB_OR_NEWLINE = /[\t\n\r]/g;
65
+ /**
66
+ * Whether a link stored in a document may become an `href` or be opened.
67
+ *
68
+ * Only `http:`, `https:` and `mailto:` pass, plus scheme-less values that
69
+ * {@link normalizeUrl} turns into https (`example.com`). Everything else is
70
+ * refused — `javascript:`, `data:`, `vbscript:`, `file:`, relative paths —
71
+ * because the value comes from an imported, pasted or shared document and a
72
+ * click on it must never run in the editor's origin. The scheme is read by
73
+ * `new URL` after the same cleanup the browser does, never by a prefix test,
74
+ * so ` JaVaScRiPt:` and `java\tscript:` are recognised for what they are.
75
+ *
76
+ * Render and open sites call this on the stored value; nothing is rewritten
77
+ * on load, so a refused link keeps its text and simply is not clickable.
78
+ */
79
+ export function isSafeLinkUrl(str) {
80
+ if (!str)
81
+ return false;
82
+ const cleaned = str
83
+ .replace(URL_TAB_OR_NEWLINE, '')
84
+ .replace(URL_EDGE_JUNK, '');
85
+ if (!cleaned)
86
+ return false;
87
+ const url = toURL(normalizeUrl(cleaned));
88
+ return url !== null && LINK_SCHEMES.has(url.protocol);
89
+ }
60
90
  /**
61
91
  * Assume user will input a url, we just need to check if it is valid.
62
92
  *
@@ -34,7 +34,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
34
34
  };
35
35
  import { RefNodeSlotsProvider } from '../affine-inline-reference/index.js';
36
36
  import { translateKey } from '../affine-shared/services/index.js';
37
- import { overlayScale, requestThrottledConnectedFrame, toOverlayCoord, } from '../affine-shared/utils/index.js';
37
+ import { isSafeLinkUrl, overlayScale, requestThrottledConnectedFrame, toOverlayCoord, } from '../affine-shared/utils/index.js';
38
38
  import { WidgetComponent } from '../std/index.js';
39
39
  import { GfxControllerIdentifier, GfxPrimitiveElementModel, } from '../std/gfx/index.js';
40
40
  import { OpenInNewIcon, RightSidebarIcon } from '@blocksuite/icons/lit';
@@ -94,7 +94,7 @@ let EdgelessElementLinkWidget = (() => {
94
94
  host: this.std.host,
95
95
  });
96
96
  }
97
- else if (el.externalLink) {
97
+ else if (isSafeLinkUrl(el.externalLink)) {
98
98
  window.open(el.externalLink, '_blank', 'noopener,noreferrer');
99
99
  }
100
100
  };
@@ -52,29 +52,29 @@ let AffineEdgelessZoomToolbarWidget = (() => {
52
52
  __esDecorate(this, null, __hide_decorators, { kind: "accessor", name: "_hide", static: false, private: false, access: { has: obj => "_hide" in obj, get: obj => obj._hide, set: (obj, value) => { obj._hide = value; } }, metadata: _metadata }, __hide_initializers, __hide_extraInitializers);
53
53
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
54
54
  }
55
- static { this.styles = css `
56
- :host {
57
- position: absolute;
58
- bottom: 20px;
59
- left: 12px;
60
- z-index: var(--affine-z-index-popover);
61
- display: flex;
62
- justify-content: center;
63
- -webkit-user-select: none;
64
- user-select: none;
65
- }
66
-
67
- @container viewport (width <= 1200px) {
68
- edgeless-zoom-toolbar {
69
- display: none;
70
- }
71
- }
72
-
73
- @container viewport (width > 1200px) {
74
- zoom-bar-toggle-button {
75
- display: none;
76
- }
77
- }
55
+ static { this.styles = css `
56
+ :host {
57
+ position: absolute;
58
+ bottom: 20px;
59
+ left: 12px;
60
+ z-index: var(--affine-z-index-popover);
61
+ display: flex;
62
+ justify-content: center;
63
+ -webkit-user-select: none;
64
+ user-select: none;
65
+ }
66
+
67
+ @container viewport (width <= 1200px) {
68
+ edgeless-zoom-toolbar {
69
+ display: none;
70
+ }
71
+ }
72
+
73
+ @container viewport (width > 1200px) {
74
+ zoom-bar-toggle-button {
75
+ display: none;
76
+ }
77
+ }
78
78
  `; }
79
79
  get edgeless() {
80
80
  return this.block;
@@ -105,9 +105,9 @@ let AffineEdgelessZoomToolbarWidget = (() => {
105
105
  if (this._hide || !this.edgeless) {
106
106
  return nothing;
107
107
  }
108
- return html `
109
- <edgeless-zoom-toolbar .std=${this.std}></edgeless-zoom-toolbar>
110
- <zoom-bar-toggle-button .std=${this.std}></zoom-bar-toggle-button>
108
+ return html `
109
+ <edgeless-zoom-toolbar .std=${this.std}></edgeless-zoom-toolbar>
110
+ <zoom-bar-toggle-button .std=${this.std}></zoom-bar-toggle-button>
111
111
  `;
112
112
  }
113
113
  #_hide_accessor_storage = __runInitializers(this, __hide_initializers, false);
@@ -19,105 +19,105 @@ export function HeadingIcon(i) {
19
19
  return Heading1Icon();
20
20
  }
21
21
  }
22
- export const HighLightDuotoneIcon = (color) => html `<svg
23
- xmlns="http://www.w3.org/2000/svg"
24
- width="24"
25
- height="24"
26
- viewBox="0 0 24 24"
27
- fill="none"
28
- >
29
- <path
30
- d="M5.8291 16.441L7.91757 18.5295L6.57811 19.8689C6.53119 19.9158 6.46406 19.9364 6.3989 19.9239L3.37036 19.3412C3.21285 19.3109 3.15331 19.1168 3.26673 19.0034L5.8291 16.441Z"
31
- fill="${color}"
32
- />
33
- <path
34
- fill-rule="evenodd"
35
- clip-rule="evenodd"
36
- d="M19.0095 3.63759C17.9526 2.58067 16.26 2.516 15.1255 3.48919L7.32135 10.1837C6.35438 11.0132 6.05275 12.3823 6.58163 13.5414L6.73501 13.8775L5.67697 14.9356C5.30169 15.3108 5.30169 15.9193 5.67697 16.2946L8.06379 18.6814C8.43907 19.0567 9.04752 19.0567 9.4228 18.6814L10.4808 17.6234L10.8171 17.7768C11.9761 18.3057 13.3452 18.0041 14.1747 17.0371L20.8692 9.23294C21.8424 8.09846 21.7778 6.40588 20.7208 5.34896L19.0095 3.63759ZM16.1021 4.62769C16.6415 4.16498 17.4463 4.19572 17.9488 4.69825L19.6602 6.40962C20.1627 6.91215 20.1935 7.7169 19.7307 8.25631L14.6424 14.188L10.1704 9.71604L16.1021 4.62769ZM9.02857 10.6955L8.29798 11.3222C7.83822 11.7166 7.6948 12.3676 7.94627 12.9187L8.29785 13.6892C8.4348 13.9893 8.37947 14.3544 8.13372 14.6001L7.11878 15.6151L8.74329 17.2396L9.75812 16.2247C10.004 15.9789 10.3691 15.9236 10.6693 16.0606L11.4398 16.4122C11.9908 16.6636 12.6418 16.5202 13.0362 16.0605L13.6629 15.3299L9.02857 10.6955Z"
37
- fill="${cssVarV2('icon/primary')}"
38
- />
22
+ export const HighLightDuotoneIcon = (color) => html `<svg
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ width="24"
25
+ height="24"
26
+ viewBox="0 0 24 24"
27
+ fill="none"
28
+ >
29
+ <path
30
+ d="M5.8291 16.441L7.91757 18.5295L6.57811 19.8689C6.53119 19.9158 6.46406 19.9364 6.3989 19.9239L3.37036 19.3412C3.21285 19.3109 3.15331 19.1168 3.26673 19.0034L5.8291 16.441Z"
31
+ fill="${color}"
32
+ />
33
+ <path
34
+ fill-rule="evenodd"
35
+ clip-rule="evenodd"
36
+ d="M19.0095 3.63759C17.9526 2.58067 16.26 2.516 15.1255 3.48919L7.32135 10.1837C6.35438 11.0132 6.05275 12.3823 6.58163 13.5414L6.73501 13.8775L5.67697 14.9356C5.30169 15.3108 5.30169 15.9193 5.67697 16.2946L8.06379 18.6814C8.43907 19.0567 9.04752 19.0567 9.4228 18.6814L10.4808 17.6234L10.8171 17.7768C11.9761 18.3057 13.3452 18.0041 14.1747 17.0371L20.8692 9.23294C21.8424 8.09846 21.7778 6.40588 20.7208 5.34896L19.0095 3.63759ZM16.1021 4.62769C16.6415 4.16498 17.4463 4.19572 17.9488 4.69825L19.6602 6.40962C20.1627 6.91215 20.1935 7.7169 19.7307 8.25631L14.6424 14.188L10.1704 9.71604L16.1021 4.62769ZM9.02857 10.6955L8.29798 11.3222C7.83822 11.7166 7.6948 12.3676 7.94627 12.9187L8.29785 13.6892C8.4348 13.9893 8.37947 14.3544 8.13372 14.6001L7.11878 15.6151L8.74329 17.2396L9.75812 16.2247C10.004 15.9789 10.3691 15.9236 10.6693 16.0606L11.4398 16.4122C11.9908 16.6636 12.6418 16.5202 13.0362 16.0605L13.6629 15.3299L9.02857 10.6955Z"
37
+ fill="${cssVarV2('icon/primary')}"
38
+ />
39
39
  </svg>`;
40
- export const TextColorIcon = (color) => html `<svg
41
- xmlns="http://www.w3.org/2000/svg"
42
- width="32"
43
- height="32"
44
- viewBox="0 0 32 32"
45
- fill="none"
46
- >
47
- <path
48
- fill-rule="evenodd"
49
- clip-rule="evenodd"
50
- d="M14.0627 6.16255C14.385 5.30291 15.2068 4.7334 16.1249 4.7334C17.043 4.7334 17.8648 5.30291 18.1872 6.16255L23.7279 20.9378C23.9219 21.455 23.6599 22.0314 23.1427 22.2253C22.6256 22.4192 22.0492 22.1572 21.8553 21.6401L20.2289 17.3031H12.021L10.3946 21.6401C10.2007 22.1572 9.62428 22.4192 9.10716 22.2253C8.59004 22.0314 8.32803 21.455 8.52195 20.9378L14.0627 6.16255ZM12.771 15.3031H19.4789L16.3146 6.8648C16.2849 6.78576 16.2094 6.7334 16.1249 6.7334C16.0405 6.7334 15.965 6.78576 15.9353 6.8648L12.771 15.3031Z"
51
- fill="${cssVarV2('icon/primary')}"
52
- />
53
- <rect
54
- x="5.45837"
55
- y="24"
56
- width="21.3333"
57
- height="3.33333"
58
- rx="1"
59
- fill=${color}
60
- />
40
+ export const TextColorIcon = (color) => html `<svg
41
+ xmlns="http://www.w3.org/2000/svg"
42
+ width="32"
43
+ height="32"
44
+ viewBox="0 0 32 32"
45
+ fill="none"
46
+ >
47
+ <path
48
+ fill-rule="evenodd"
49
+ clip-rule="evenodd"
50
+ d="M14.0627 6.16255C14.385 5.30291 15.2068 4.7334 16.1249 4.7334C17.043 4.7334 17.8648 5.30291 18.1872 6.16255L23.7279 20.9378C23.9219 21.455 23.6599 22.0314 23.1427 22.2253C22.6256 22.4192 22.0492 22.1572 21.8553 21.6401L20.2289 17.3031H12.021L10.3946 21.6401C10.2007 22.1572 9.62428 22.4192 9.10716 22.2253C8.59004 22.0314 8.32803 21.455 8.52195 20.9378L14.0627 6.16255ZM12.771 15.3031H19.4789L16.3146 6.8648C16.2849 6.78576 16.2094 6.7334 16.1249 6.7334C16.0405 6.7334 15.965 6.78576 15.9353 6.8648L12.771 15.3031Z"
51
+ fill="${cssVarV2('icon/primary')}"
52
+ />
53
+ <rect
54
+ x="5.45837"
55
+ y="24"
56
+ width="21.3333"
57
+ height="3.33333"
58
+ rx="1"
59
+ fill=${color}
60
+ />
61
61
  </svg>`;
62
- export const TextBackgroundDuotoneIcon = (color) => html `<svg
63
- xmlns="http://www.w3.org/2000/svg"
64
- width="32"
65
- height="32"
66
- viewBox="0 0 32 32"
67
- fill="none"
68
- >
69
- <path
70
- fill-rule="evenodd"
71
- clip-rule="evenodd"
72
- d="M4.57507 7.33336C4.57507 5.60287 5.97791 4.20003 7.70841 4.20003H25.0417C26.7722 4.20003 28.1751 5.60287 28.1751 7.33336V24.6667C28.1751 26.3972 26.7722 27.8 25.0417 27.8H7.70841C5.97791 27.8 4.57507 26.3972 4.57507 24.6667V7.33336Z"
73
- fill="${color}"
74
- />
75
- <path
76
- fill-rule="evenodd"
77
- clip-rule="evenodd"
78
- d="M4.57495 7.33333C4.57495 5.60284 5.97779 4.2 7.70828 4.2H25.0416C26.7721 4.2 28.175 5.60284 28.175 7.33333V24.6667C28.175 26.3972 26.7721 27.8 25.0416 27.8H7.70828C5.97779 27.8 4.57495 26.3972 4.57495 24.6667V7.33333ZM7.70828 5.13333C6.49326 5.13333 5.50828 6.1183 5.50828 7.33333V24.6667C5.50828 25.8817 6.49326 26.8667 7.70828 26.8667H25.0416C26.2566 26.8667 27.2416 25.8817 27.2416 24.6667V7.33333C27.2416 6.1183 26.2566 5.13333 25.0416 5.13333H7.70828Z"
79
- fill="black"
80
- fill-opacity="0.22"
81
- />
82
- <path
83
- fill-rule="evenodd"
84
- clip-rule="evenodd"
85
- d="M14.5379 10.0064C14.8251 9.24064 15.5571 8.73332 16.375 8.73332C17.1928 8.73332 17.9249 9.24064 18.2121 10.0064L22.6446 21.8266C22.8386 22.3438 22.5766 22.9202 22.0594 23.1141C21.5423 23.308 20.9659 23.046 20.772 22.5289L19.5196 19.1891H13.2304L11.978 22.5289C11.7841 23.046 11.2076 23.308 10.6905 23.1141C10.1734 22.9202 9.9114 22.3438 10.1053 21.8266L14.5379 10.0064ZM13.9804 17.1891H18.7696L16.375 10.8035L13.9804 17.1891Z"
86
- fill="${cssVarV2('text/primary')}"
87
- />
62
+ export const TextBackgroundDuotoneIcon = (color) => html `<svg
63
+ xmlns="http://www.w3.org/2000/svg"
64
+ width="32"
65
+ height="32"
66
+ viewBox="0 0 32 32"
67
+ fill="none"
68
+ >
69
+ <path
70
+ fill-rule="evenodd"
71
+ clip-rule="evenodd"
72
+ d="M4.57507 7.33336C4.57507 5.60287 5.97791 4.20003 7.70841 4.20003H25.0417C26.7722 4.20003 28.1751 5.60287 28.1751 7.33336V24.6667C28.1751 26.3972 26.7722 27.8 25.0417 27.8H7.70841C5.97791 27.8 4.57507 26.3972 4.57507 24.6667V7.33336Z"
73
+ fill="${color}"
74
+ />
75
+ <path
76
+ fill-rule="evenodd"
77
+ clip-rule="evenodd"
78
+ d="M4.57495 7.33333C4.57495 5.60284 5.97779 4.2 7.70828 4.2H25.0416C26.7721 4.2 28.175 5.60284 28.175 7.33333V24.6667C28.175 26.3972 26.7721 27.8 25.0416 27.8H7.70828C5.97779 27.8 4.57495 26.3972 4.57495 24.6667V7.33333ZM7.70828 5.13333C6.49326 5.13333 5.50828 6.1183 5.50828 7.33333V24.6667C5.50828 25.8817 6.49326 26.8667 7.70828 26.8667H25.0416C26.2566 26.8667 27.2416 25.8817 27.2416 24.6667V7.33333C27.2416 6.1183 26.2566 5.13333 25.0416 5.13333H7.70828Z"
79
+ fill="black"
80
+ fill-opacity="0.22"
81
+ />
82
+ <path
83
+ fill-rule="evenodd"
84
+ clip-rule="evenodd"
85
+ d="M14.5379 10.0064C14.8251 9.24064 15.5571 8.73332 16.375 8.73332C17.1928 8.73332 17.9249 9.24064 18.2121 10.0064L22.6446 21.8266C22.8386 22.3438 22.5766 22.9202 22.0594 23.1141C21.5423 23.308 20.9659 23.046 20.772 22.5289L19.5196 19.1891H13.2304L11.978 22.5289C11.7841 23.046 11.2076 23.308 10.6905 23.1141C10.1734 22.9202 9.9114 22.3438 10.1053 21.8266L14.5379 10.0064ZM13.9804 17.1891H18.7696L16.375 10.8035L13.9804 17.1891Z"
86
+ fill="${cssVarV2('text/primary')}"
87
+ />
88
88
  </svg>`;
89
- export const FigmaDuotoneIcon = html `<svg
90
- width="32"
91
- height="32"
92
- viewBox="0 0 24 24"
93
- fill="none"
94
- xmlns="http://www.w3.org/2000/svg"
95
- >
96
- <g id="Figma_Duotone">
97
- <path
98
- id="Vector"
99
- d="M8.41842 22.5027C10.3047 22.5027 11.8356 20.9719 11.8356 19.0856V15.6685H8.41842C6.53216 15.6685 5.00128 17.1993 5.00128 19.0856C5.00128 20.9719 6.53216 22.5027 8.41842 22.5027Z"
100
- fill="#0ACF83"
101
- />
102
- <path
103
- id="Vector_2"
104
- d="M5.00128 12.2514C5.00128 10.3651 6.53216 8.83423 8.41842 8.83423H11.8356V15.6685H8.41842C6.53216 15.6685 5.00128 14.1376 5.00128 12.2514Z"
105
- fill="#A259FF"
106
- />
107
- <path
108
- id="Vector_3"
109
- d="M5.00146 5.41714C5.00146 3.53088 6.53234 2 8.4186 2H11.8357V8.83428H8.4186C6.53234 8.83428 5.00146 7.3034 5.00146 5.41714Z"
110
- fill="#F24E1E"
111
- />
112
- <path
113
- id="Vector_4"
114
- d="M11.8356 2H15.2527C17.139 2 18.6699 3.53088 18.6699 5.41714C18.6699 7.3034 17.139 8.83428 15.2527 8.83428H11.8356V2Z"
115
- fill="#FF7262"
116
- />
117
- <path
118
- id="Vector_5"
119
- d="M18.6699 12.2514C18.6699 14.1376 17.139 15.6685 15.2527 15.6685C13.3665 15.6685 11.8356 14.1376 11.8356 12.2514C11.8356 10.3651 13.3665 8.83423 15.2527 8.83423C17.139 8.83423 18.6699 10.3651 18.6699 12.2514Z"
120
- fill="#1ABCFE"
121
- />
122
- </g>
89
+ export const FigmaDuotoneIcon = html `<svg
90
+ width="32"
91
+ height="32"
92
+ viewBox="0 0 24 24"
93
+ fill="none"
94
+ xmlns="http://www.w3.org/2000/svg"
95
+ >
96
+ <g id="Figma_Duotone">
97
+ <path
98
+ id="Vector"
99
+ d="M8.41842 22.5027C10.3047 22.5027 11.8356 20.9719 11.8356 19.0856V15.6685H8.41842C6.53216 15.6685 5.00128 17.1993 5.00128 19.0856C5.00128 20.9719 6.53216 22.5027 8.41842 22.5027Z"
100
+ fill="#0ACF83"
101
+ />
102
+ <path
103
+ id="Vector_2"
104
+ d="M5.00128 12.2514C5.00128 10.3651 6.53216 8.83423 8.41842 8.83423H11.8356V15.6685H8.41842C6.53216 15.6685 5.00128 14.1376 5.00128 12.2514Z"
105
+ fill="#A259FF"
106
+ />
107
+ <path
108
+ id="Vector_3"
109
+ d="M5.00146 5.41714C5.00146 3.53088 6.53234 2 8.4186 2H11.8357V8.83428H8.4186C6.53234 8.83428 5.00146 7.3034 5.00146 5.41714Z"
110
+ fill="#F24E1E"
111
+ />
112
+ <path
113
+ id="Vector_4"
114
+ d="M11.8356 2H15.2527C17.139 2 18.6699 3.53088 18.6699 5.41714C18.6699 7.3034 17.139 8.83428 15.2527 8.83428H11.8356V2Z"
115
+ fill="#FF7262"
116
+ />
117
+ <path
118
+ id="Vector_5"
119
+ d="M18.6699 12.2514C18.6699 14.1376 17.139 15.6685 15.2527 15.6685C13.3665 15.6685 11.8356 14.1376 11.8356 12.2514C11.8356 10.3651 13.3665 8.83423 15.2527 8.83423C17.139 8.83423 18.6699 10.3651 18.6699 12.2514Z"
120
+ fill="#1ABCFE"
121
+ />
122
+ </g>
123
123
  </svg> `;
@@ -199,16 +199,16 @@ let AffinePageDraggingAreaWidget = (() => {
199
199
  width: rect.width + 'px',
200
200
  height: rect.height + 'px',
201
201
  };
202
- return html `
203
- <style>
204
- .affine-page-dragging-area {
205
- position: absolute;
206
- background: var(--affine-hover-color);
207
- z-index: 1;
208
- pointer-events: none;
209
- }
210
- </style>
211
- <div class="affine-page-dragging-area" style=${styleMap(style)}></div>
202
+ return html `
203
+ <style>
204
+ .affine-page-dragging-area {
205
+ position: absolute;
206
+ background: var(--affine-hover-color);
207
+ z-index: 1;
208
+ pointer-events: none;
209
+ }
210
+ </style>
211
+ <div class="affine-page-dragging-area" style=${styleMap(style)}></div>
212
212
  `;
213
213
  }
214
214
  #rect_accessor_storage;
@@ -51,19 +51,19 @@ let AffineViewportOverlayWidget = (() => {
51
51
  __esDecorate(this, null, __lockViewport_decorators, { kind: "accessor", name: "_lockViewport", static: false, private: false, access: { has: obj => "_lockViewport" in obj, get: obj => obj._lockViewport, set: (obj, value) => { obj._lockViewport = value; } }, metadata: _metadata }, __lockViewport_initializers, __lockViewport_extraInitializers);
52
52
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
53
53
  }
54
- static { this.styles = css `
55
- .affine-viewport-overlay-widget {
56
- position: absolute;
57
- top: 0;
58
- left: 0;
59
- background: transparent;
60
- pointer-events: none;
61
- z-index: calc(var(--affine-z-index-popover) - 1);
62
- }
63
-
64
- .affine-viewport-overlay-widget.lock {
65
- pointer-events: auto;
66
- }
54
+ static { this.styles = css `
55
+ .affine-viewport-overlay-widget {
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ background: transparent;
60
+ pointer-events: none;
61
+ z-index: calc(var(--affine-z-index-popover) - 1);
62
+ }
63
+
64
+ .affine-viewport-overlay-widget.lock {
65
+ pointer-events: auto;
66
+ }
67
67
  `; }
68
68
  connectedCallback() {
69
69
  super.connectedCallback();
@@ -1,22 +1,22 @@
1
1
  import { ArrowLeftBigIcon } from '@blocksuite/icons/lit';
2
2
  import { html } from 'lit';
3
3
  export const menuTitle = (name, onBack) => {
4
- return html `
5
- <div
6
- style="display:flex;align-items:center;gap: 4px;padding: 3px 3px 3px 2px"
7
- >
8
- <div
9
- @click=${onBack}
10
- class="dv-icon-20 dv-hover dv-pd-2 dv-round-4"
11
- style="display:flex;"
12
- >
13
- ${ArrowLeftBigIcon()}
14
- </div>
15
- <div
16
- style="font-weight:500;font-size: 14px;line-height: 22px;color: var(--affine-text-primary-color)"
17
- >
18
- ${name}
19
- </div>
20
- </div>
4
+ return html `
5
+ <div
6
+ style="display:flex;align-items:center;gap: 4px;padding: 3px 3px 3px 2px"
7
+ >
8
+ <div
9
+ @click=${onBack}
10
+ class="dv-icon-20 dv-hover dv-pd-2 dv-round-4"
11
+ style="display:flex;"
12
+ >
13
+ ${ArrowLeftBigIcon()}
14
+ </div>
15
+ <div
16
+ style="font-weight:500;font-size: 14px;line-height: 22px;color: var(--affine-text-primary-color)"
17
+ >
18
+ ${name}
19
+ </div>
20
+ </div>
21
21
  `;
22
22
  };
@@ -19,9 +19,9 @@ export const GroupTitle = (groupData, ops) => {
19
19
  }
20
20
  const icon = groupData.value == null
21
21
  ? ''
22
- : html ` <uni-lit
23
- class="${groupHeaderIcon}"
24
- .uni="${groupData.property.icon}"
22
+ : html ` <uni-lit
23
+ class="${groupHeaderIcon}"
24
+ .uni="${groupData.property.icon}"
25
25
  ></uni-lit>`;
26
26
  const props = {
27
27
  group: groupData,
@@ -29,25 +29,25 @@ export const GroupTitle = (groupData, ops) => {
29
29
  };
30
30
  const showColumnName = groupData.property.type$.value === 'checkbox';
31
31
  const columnName = showColumnName
32
- ? html `<span class="${groupHeaderTitle}"
33
- >${groupData.property.name$.value}</span
32
+ ? html `<span class="${groupHeaderTitle}"
33
+ >${groupData.property.name$.value}</span
34
34
  >`
35
35
  : nothing;
36
36
  const opsClass = clsx(ops.groupHover && show, groupHeaderOps);
37
- return html `
38
- <div class="${groupTitleRow}">
39
- ${icon} ${renderUniLit(view, props)} ${columnName}
40
- ${GroupHeaderCount(groupData)}
41
- </div>
37
+ return html `
38
+ <div class="${groupTitleRow}">
39
+ ${icon} ${renderUniLit(view, props)} ${columnName}
40
+ ${GroupHeaderCount(groupData)}
41
+ </div>
42
42
  ${!ops.readonly
43
- ? html ` <div class="${opsClass}">
44
- <div @click="${ops.clickAdd}" class="${groupHeaderOp}">
45
- ${PlusIcon()}
46
- </div>
47
- <div @click="${ops.clickOps}" class="${groupHeaderOp}">
48
- ${MoreHorizontalIcon()}
49
- </div>
43
+ ? html ` <div class="${opsClass}">
44
+ <div @click="${ops.clickAdd}" class="${groupHeaderOp}">
45
+ ${PlusIcon()}
46
+ </div>
47
+ <div @click="${ops.clickOps}" class="${groupHeaderOp}">
48
+ ${MoreHorizontalIcon()}
49
+ </div>
50
50
  </div>`
51
- : nothing}
51
+ : nothing}
52
52
  `;
53
53
  };
@@ -1,6 +1,6 @@
1
1
  import { AttachmentViewExtension } from '../_pkgs/affine-block-attachment/view.js';
2
2
  import { BookmarkViewExtension } from '../_pkgs/affine-block-bookmark/view.js';
3
- import { CalloutViewExtension } from '../_pkgs/affine-block-callout/view.js';
3
+ import { CalloutRenderViewExtension, CalloutViewExtension, } from '../_pkgs/affine-block-callout/view.js';
4
4
  import { CodeBlockViewExtension } from '../_pkgs/affine-block-code/view.js';
5
5
  import { DataViewViewExtension } from '../_pkgs/affine-block-data-view/view.js';
6
6
  import { DatabaseViewExtension } from '../_pkgs/affine-block-database/view.js';
@@ -108,6 +108,10 @@ export function getInternalViewExtensions(flags) {
108
108
  // Block
109
109
  ...(on('attachment') ? [AttachmentViewExtension] : []),
110
110
  ...(on('bookmark') ? [BookmarkViewExtension] : []),
111
+ // Split like the frameworks (ADR 0009, #418): a stored callout paints,
112
+ // edits and keeps its block toolbar; the flag removes only the creation
113
+ // entries (slash item, Turn into). The other blocks are not split yet.
114
+ CalloutRenderViewExtension,
111
115
  ...(on('callout') ? [CalloutViewExtension] : []),
112
116
  ...(on('code') ? [CodeBlockViewExtension] : []),
113
117
  ...(on('data-view') ? [DataViewViewExtension] : []),
package/dist/flags.d.ts CHANGED
@@ -47,6 +47,9 @@
47
47
  * re-renders untouched once the flag is back on — but a disabled block
48
48
  * currently renders as nothing until its view extension is split the way the
49
49
  * frameworks' were. See the "Consequences" section of `docs/adr/0009`.
50
+ * `callout` is the one block split so far (2026-10-11 amendment of that
51
+ * ADR): with `{ callout: false }` a stored callout still renders, stays
52
+ * editable and keeps its block toolbar; only its creation entries go.
50
53
  * - `latex` covers both the latex block and inline latex.
51
54
  * - `frame` also covers the frame panel fragment.
52
55
  * - `ddd-templates` covers all four DDD Templates-panel categories, so the
package/dist/flags.js CHANGED
@@ -47,6 +47,9 @@
47
47
  * re-renders untouched once the flag is back on — but a disabled block
48
48
  * currently renders as nothing until its view extension is split the way the
49
49
  * frameworks' were. See the "Consequences" section of `docs/adr/0009`.
50
+ * `callout` is the one block split so far (2026-10-11 amendment of that
51
+ * ADR): with `{ callout: false }` a stored callout still renders, stays
52
+ * editable and keeps its block toolbar; only its creation entries go.
50
53
  * - `latex` covers both the latex block and inline latex.
51
54
  * - `frame` also covers the frame panel fragment.
52
55
  * - `ddd-templates` covers all four DDD Templates-panel categories, so the
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.46.0",
4
+ "version": "0.46.2",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",