diff --git a/packages/affine/block-embed/package.json b/packages/affine/block-embed/package.json index 9d7745d51e11..b11df7e911b4 100644 --- a/packages/affine/block-embed/package.json +++ b/packages/affine/block-embed/package.json @@ -25,7 +25,7 @@ "@blocksuite/affine-shared": "workspace:*", "@blocksuite/block-std": "workspace:*", "@blocksuite/global": "workspace:*", - "@blocksuite/icons": "^2.1.70", + "@blocksuite/icons": "^2.1.75", "@blocksuite/inline": "workspace:*", "@blocksuite/store": "workspace:*", "@floating-ui/dom": "^1.6.10", diff --git a/packages/affine/block-embed/src/common/render-linked-doc.ts b/packages/affine/block-embed/src/common/render-linked-doc.ts index b2f8ff79cd69..f3f433de08e5 100644 --- a/packages/affine/block-embed/src/common/render-linked-doc.ts +++ b/packages/affine/block-embed/src/common/render-linked-doc.ts @@ -145,6 +145,11 @@ async function renderNoteContent( card.isNoteContentEmpty = false; const noteContainer = await card.noteContainer; + + if (!noteContainer) { + return; + } + while (noteContainer.firstChild) { noteContainer.firstChild.remove(); } @@ -199,13 +204,10 @@ async function renderNoteContent( } function filterTextModel(model: BlockModel) { - if (matchFlavours(model, ['affine:divider'])) { - return true; - } - if (!matchFlavours(model, ['affine:paragraph', 'affine:list'])) { - return false; + if (matchFlavours(model, ['affine:paragraph', 'affine:list'])) { + return !!model.text?.toString().length; } - return !!model.text?.toString().length; + return false; } export function getNotesFromDoc(doc: Doc) { @@ -255,3 +257,41 @@ function getSurfaceBlock(doc: Doc) { const blocks = doc.getBlocksByFlavour('affine:surface'); return blocks.length !== 0 ? (blocks[0].model as SurfaceBlockModel) : null; } + +/** + * Gets the document content with a max length. + */ +export function getDocContentWithMaxLength(doc: Doc, maxlength = 500) { + const notes = getNotesFromDoc(doc); + if (!notes) return; + + const noteChildren = notes.flatMap(note => + note.children.filter(model => filterTextModel(model)) + ); + if (!noteChildren.length) return; + + let count = 0; + let reached = false; + const texts = []; + + for (const model of noteChildren) { + let t = model.text?.toString(); + if (t?.length) { + const c: number = count + Math.max(0, texts.length - 1); + + if (t.length + c > maxlength) { + t = t.substring(0, maxlength - c); + reached = true; + } + + texts.push(t); + count += t.length; + + if (reached) { + break; + } + } + } + + return texts.join('\n'); +} diff --git a/packages/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts b/packages/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts index 56edc7e2d19f..92419094a211 100644 --- a/packages/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts +++ b/packages/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts @@ -23,11 +23,13 @@ import { } from '@blocksuite/affine-shared/services'; import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { Bound } from '@blocksuite/global/utils'; +import { AliasIcon } from '@blocksuite/icons/lit'; import { DocCollection } from '@blocksuite/store'; import { html, nothing } from 'lit'; import { property, queryAsync, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; +import { when } from 'lit/directives/when.js'; import { EmbedBlockComponent } from '../common/embed-block-element.js'; import { renderLinkedDocInCard } from '../common/render-linked-doc.js'; @@ -198,9 +200,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent 0); + return this.renderEmbed( () => html`
-
- ${showDefaultNoteContent - ? html`
- ${defaultNoteContent} -
` - : nothing} + ${when( + hasDescriptionAlias, + () => + html`
+ ${description} +
`, + () => + when( + showDefaultNoteContent, + () => html` +
+ ${defaultNoteContent} +
+ `, + () => html` +
+ ` + ) + )} ${isError ? html`
@@ -518,5 +536,5 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent; + accessor noteContainer!: Promise; } diff --git a/packages/affine/block-embed/src/embed-linked-doc-block/styles.ts b/packages/affine/block-embed/src/embed-linked-doc-block/styles.ts index 2f41a61d1fca..69ca7b65c127 100644 --- a/packages/affine/block-embed/src/embed-linked-doc-block/styles.ts +++ b/packages/affine/block-embed/src/embed-linked-doc-block/styles.ts @@ -87,6 +87,7 @@ export const styles = css` ${embedNoteContentStyles} + .affine-embed-linked-doc-content-note.alias, .affine-embed-linked-doc-content-note.default { flex: 1; display: -webkit-box; @@ -105,6 +106,10 @@ export const styles = css` line-height: 20px; } + .affine-embed-linked-doc-content-note.alias { + color: var(--affine-text-primary-color); + } + .affine-embed-linked-doc-card-content-reload, .affine-embed-linked-doc-content-date { display: flex; diff --git a/packages/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts b/packages/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts index 652857884ac2..f8c4d78852b0 100644 --- a/packages/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts +++ b/packages/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts @@ -27,7 +27,12 @@ import { } from '@blocksuite/block-std'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils'; -import { BlockViewType, DocCollection, type Query } from '@blocksuite/store'; +import { + BlockViewType, + DocCollection, + type GetDocOptions, + type Query, +} from '@blocksuite/store'; import { html, type PropertyValues } from 'lit'; import { query, state } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; @@ -327,9 +332,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent { + doc.history.off('stack-item-added', addHandler); + doc.history.off('stack-item-popped', popHandler); + disposable.dispose(); + }; + const closeNotify = () => { + abortController.abort(); + clear(); + }; + + // edit or undo or switch doc, close notify toast + const addHandler = doc.history.on('stack-item-added', closeNotify); + const popHandler = doc.history.on('stack-item-popped', closeNotify); + const disposable = host.slots.unmounted.on(closeNotify); + + notification.notify({ + title, + message, + accent: 'info', + duration: 10 * 1000, + action: { + label: 'Undo', + onClick: () => { + doc.undo(); + clear(); + }, + }, + abort: abortController.signal, + onClose: clear, + }); +} + +export function notifyLinkedDocSwitchedToCard(std: BlockStdScope) { + notify( + std, + 'View Updated', + 'The alias modification has disabled sync. The embed has been updated to a card view.' + ); +} + +export function notifyLinkedDocSwitchedToEmbed(std: BlockStdScope) { + notify( + std, + 'Embed View Restored', + 'Custom alias removed. The linked doc now displays the original title and description.' + ); +} + +export function notifyLinkedDocClearedAliases(std: BlockStdScope) { + notify( + std, + 'Reset successful', + `Card view has been restored to original doc title and description. All custom aliases have been removed.` + ); +} diff --git a/packages/affine/components/src/rich-text/effects.ts b/packages/affine/components/src/rich-text/effects.ts index 49426d079bc6..cb4908b9dedd 100644 --- a/packages/affine/components/src/rich-text/effects.ts +++ b/packages/affine/components/src/rich-text/effects.ts @@ -23,6 +23,7 @@ import { LatexEditorMenu } from './inline/presets/nodes/latex-node/latex-editor- import { LatexEditorUnit } from './inline/presets/nodes/latex-node/latex-editor-unit.js'; import { AffineLatexNode } from './inline/presets/nodes/latex-node/latex-node.js'; import { LinkPopup } from './inline/presets/nodes/link-node/link-popup/link-popup.js'; +import { ReferenceAliasPopup } from './inline/presets/nodes/reference-node/reference-alias-popup.js'; import { ReferencePopup } from './inline/presets/nodes/reference-node/reference-popup.js'; import { RichText } from './rich-text.js'; @@ -35,6 +36,7 @@ export function effects() { customElements.define('link-popup', LinkPopup); customElements.define('affine-link', AffineLink); customElements.define('reference-popup', ReferencePopup); + customElements.define('reference-alias-popup', ReferenceAliasPopup); customElements.define('affine-reference', AffineReference); } @@ -46,6 +48,7 @@ declare global { 'affine-text': AffineText; 'rich-text': RichText; 'reference-popup': ReferencePopup; + 'reference-alias-popup': ReferenceAliasPopup; 'latex-editor-unit': LatexEditorUnit; 'latex-editor-menu': LatexEditorMenu; 'link-popup': LinkPopup; diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts index 031c2cc6e66e..8dff371909fa 100644 --- a/packages/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/link-node/link-popup/link-popup.ts @@ -6,6 +6,7 @@ import { getHostName, isValidUrl, normalizeUrl, + stopPropagation, } from '@blocksuite/affine-shared/utils'; import { BLOCK_ID_ATTR, type BlockComponent } from '@blocksuite/block-std'; import { WithDisposable } from '@blocksuite/global/utils'; @@ -174,7 +175,7 @@ export class LinkPopup extends WithDisposable(LitElement) { ${CopyIcon} @@ -528,15 +529,9 @@ export class LinkPopup extends WithDisposable(LitElement) { protected override firstUpdated() { if (!this.linkInput) return; - this._disposables.addFromEvent(this.linkInput, 'copy', e => { - e.stopPropagation(); - }); - this._disposables.addFromEvent(this.linkInput, 'cut', e => { - e.stopPropagation(); - }); - this._disposables.addFromEvent(this.linkInput, 'paste', e => { - e.stopPropagation(); - }); + this._disposables.addFromEvent(this.linkInput, 'copy', stopPropagation); + this._disposables.addFromEvent(this.linkInput, 'cut', stopPropagation); + this._disposables.addFromEvent(this.linkInput, 'paste', stopPropagation); } override render() { diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts new file mode 100644 index 000000000000..d301780534b1 --- /dev/null +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-alias-popup.ts @@ -0,0 +1,259 @@ +import type { ReferenceInfo } from '@blocksuite/affine-model'; +import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; +import type { DeltaInsert, InlineRange } from '@blocksuite/inline'; + +import { FONT_XS, PANEL_BASE } from '@blocksuite/affine-shared/styles'; +import { ShadowlessElement } from '@blocksuite/block-std'; +import { + assertExists, + SignalWatcher, + WithDisposable, +} from '@blocksuite/global/utils'; +import { DoneIcon, ResetIcon } from '@blocksuite/icons/lit'; +import { computePosition, inline, offset, shift } from '@floating-ui/dom'; +import { signal } from '@preact/signals-core'; +import { css, html } from 'lit'; +import { property, query } from 'lit/decorators.js'; +import { live } from 'lit/directives/live.js'; + +import type { EditorIconButton } from '../../../../../toolbar/index.js'; +import type { AffineInlineEditor } from '../../affine-inline-specs.js'; + +import { REFERENCE_NODE } from '../consts.js'; + +export class ReferenceAliasPopup extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + :host { + box-sizing: border-box; + } + + .overlay-mask { + position: fixed; + z-index: var(--affine-z-index-popover); + top: 0; + left: 0; + width: 100vw; + height: 100vh; + } + + .alias-form-popup { + ${PANEL_BASE}; + position: absolute; + display: flex; + width: 321px; + height: 37px; + gap: 8px; + box-sizing: content-box; + justify-content: space-between; + align-items: center; + animation: affine-popover-fade-in 0.2s ease; + z-index: var(--affine-z-index-popover); + } + + @keyframes affine-popover-fade-in { + from { + opacity: 0; + transform: translateY(-3px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + input { + display: flex; + flex: 1; + padding: 0; + border: none; + background: transparent; + color: var(--affine-text-primary-color); + ${FONT_XS}; + } + input::placeholder { + color: var(--affine-placeholder-color); + } + input:focus { + outline: none; + } + + editor-icon-button.save .label { + ${FONT_XS}; + color: inherit; + text-transform: none; + } + `; + + private _onSave = () => { + const title = this.title$.value.trim(); + if (!title) { + this.remove(); + return; + } + + this._setTitle(title); + + this.remove(); + }; + + private _updateTitle = (e: InputEvent) => { + const target = e.target as HTMLInputElement; + const value = target.value; + this.title$.value = value; + }; + + private _onKeydown(e: KeyboardEvent) { + e.stopPropagation(); + if (!e.isComposing) { + if (e.key === 'Escape') { + e.preventDefault(); + this.remove(); + return; + } + if (e.key === 'Enter') { + e.preventDefault(); + this._onSave(); + } + } + } + + private _onReset() { + this.title$.value = this.docTitle; + + this._setTitle(); + + this.remove(); + } + + private _setTitle(title?: string) { + const reference: AffineTextAttributes['reference'] = { + type: 'LinkedPage', + ...this.referenceInfo, + }; + + if (title) { + reference.title = title; + } else { + delete reference.title; + delete reference.description; + } + + this.inlineEditor.insertText(this.inlineRange, REFERENCE_NODE, { + reference, + }); + this.inlineEditor.setInlineRange({ + index: this.inlineRange.index + REFERENCE_NODE.length, + length: 0, + }); + } + + override connectedCallback() { + super.connectedCallback(); + + this.title$.value = this.referenceInfo.title ?? this.docTitle; + } + + override firstUpdated() { + this.disposables.addFromEvent(this.overlayMask, 'click', e => { + e.stopPropagation(); + this.remove(); + }); + this.disposables.addFromEvent(this, 'keydown', this._onKeydown); + + this.inputElement.focus(); + this.inputElement.select(); + } + + override render() { + return html` +
+
+
+ + + ${ResetIcon({ width: '16px', height: '16px' })} + + + + ${DoneIcon({ width: '16px', height: '16px' })} + Save + +
+
+ `; + } + + override updated() { + const range = this.inlineEditor.toDomRange(this.inlineRange); + assertExists(range); + + const visualElement = { + getBoundingClientRect: () => range.getBoundingClientRect(), + getClientRects: () => range.getClientRects(), + }; + computePosition(visualElement, this.popupContainer, { + middleware: [ + offset(10), + inline(), + shift({ + padding: 6, + }), + ], + }) + .then(({ x, y }) => { + const popupContainer = this.popupContainer; + if (!popupContainer) return; + popupContainer.style.left = `${x}px`; + popupContainer.style.top = `${y}px`; + }) + .catch(console.error); + } + + @property({ type: Object }) + accessor delta!: DeltaInsert; + + @property({ attribute: false }) + accessor docTitle!: string; + + @property({ attribute: false }) + accessor inlineEditor!: AffineInlineEditor; + + @property({ attribute: false }) + accessor inlineRange!: InlineRange; + + @query('input#alias-title') + accessor inputElement!: HTMLInputElement; + + @query('.overlay-mask') + accessor overlayMask!: HTMLDivElement; + + @query('.alias-form-popup') + accessor popupContainer!: HTMLDivElement; + + @property({ type: Object }) + accessor referenceInfo!: ReferenceInfo; + + @query('editor-icon-button.save') + accessor saveButton!: EditorIconButton; + + accessor title$ = signal(''); +} diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts index 4cb13b1f4c4b..b5f892de3140 100644 --- a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts @@ -235,8 +235,9 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { const isDeleted = !refMeta; const attributes = this.delta.attributes; - const type = attributes?.reference?.type; - if (!type) { + const reference = attributes?.reference; + const type = reference?.type; + if (!attributes || !type) { return nothing; } @@ -244,9 +245,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { ? this.customTitle(this) : isDeleted ? 'Deleted doc' - : refMeta.title.length > 0 - ? refMeta.title - : DEFAULT_DOC_NAME; + : reference?.title || refMeta.title || DEFAULT_DOC_NAME; const icon = choose( type, diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts index d9813e34ec62..6423a79fbdd9 100644 --- a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts @@ -1,6 +1,7 @@ import type { ReferenceInfo } from '@blocksuite/affine-model'; import type { InlineRange } from '@blocksuite/inline'; +import { GenerateDocUrlProvider } from '@blocksuite/affine-shared/services'; import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils'; import { BLOCK_ID_ATTR, type BlockComponent } from '@blocksuite/block-std'; import { assertExists, WithDisposable } from '@blocksuite/global/utils'; @@ -16,19 +17,24 @@ import type { AffineInlineEditor } from '../../affine-inline-specs.js'; import { CenterPeekIcon, + CopyIcon, DeleteIcon, + EditIcon, ExpandFullSmallIcon, MoreVerticalIcon, OpenIcon, SmallArrowDownIcon, } from '../../../../../icons/index.js'; +import { notifyLinkedDocSwitchedToEmbed } from '../../../../../notification/index.js'; import { isPeekable, peek } from '../../../../../peek/index.js'; +import { toast } from '../../../../../toast/toast.js'; import { type MenuItem, renderActions, renderToolbarSeparator, } from '../../../../../toolbar/index.js'; import { RefNodeSlotsProvider } from '../../../../extension/index.js'; +import { ReferenceAliasPopup } from './reference-alias-popup.js'; import { styles } from './styles.js'; export class ReferencePopup extends WithDisposable(LitElement) { @@ -110,18 +116,15 @@ export class ReferencePopup extends WithDisposable(LitElement) { private _convertToEmbedView() { const block = this.block; + const std = block.std; const doc = block.host.doc; const parent = doc.getParent(block.model); assertExists(parent); const index = parent.children.indexOf(block.model); + const referenceInfo = this.referenceInfo; - doc.addBlock( - 'affine:embed-synced-doc', - this.referenceInfo, - parent, - index + 1 - ); + doc.addBlock('affine:embed-synced-doc', referenceInfo, parent, index + 1); const totalTextLength = this.inlineEditor.yTextLength; const inlineTextLength = this.targetInlineRange.length; @@ -131,6 +134,21 @@ export class ReferencePopup extends WithDisposable(LitElement) { this.inlineEditor.insertText(this.targetInlineRange, this.docTitle); } + if (referenceInfo.title) notifyLinkedDocSwitchedToEmbed(std); + + this.abortController.abort(); + } + + private _copyLink() { + const url = this.std + .getOptional(GenerateDocUrlProvider) + ?.generateDocUrl(this.referenceInfo.pageId, this.referenceInfo.params); + + if (url) { + navigator.clipboard.writeText(url).catch(console.error); + toast(this.std.host, 'Copied link to clipboard'); + } + this.abortController.abort(); } @@ -221,6 +239,19 @@ export class ReferencePopup extends WithDisposable(LitElement) { `; } + private _showAliasPopup() { + const aliasPopup = new ReferenceAliasPopup(); + + aliasPopup.docTitle = this.docTitle; + aliasPopup.referenceInfo = this.referenceInfo; + aliasPopup.inlineEditor = this.inlineEditor; + aliasPopup.inlineRange = this.targetInlineRange; + + document.body.append(aliasPopup); + + this.abortController.abort(); + } + private _viewToggleMenu() { // synced doc entry controlled by awareness flag const isSyncedDocEnabled = this.doc.awarenessStore.getFlag( @@ -308,9 +339,47 @@ export class ReferencePopup extends WithDisposable(LitElement) { } override render() { + const titleButton = this.referenceInfo.title + ? html` + this._openDoc()} + > + ${this.docTitle} + + ` + : nothing; + const buttons = [ this._openMenuButton(), + html` + ${titleButton} + + this._copyLink()} + > + ${CopyIcon} + + + this._showAliasPopup()} + > + ${EditIcon} + + `, + this._viewToggleMenu(), html` diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/styles.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/styles.ts index c08a9e6fff7c..0079ee412f9d 100644 --- a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/styles.ts +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/styles.ts @@ -21,4 +21,24 @@ export const styles = css` transform: translateY(0); } } + + editor-icon-button.doc-title .label { + max-width: 110px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; + cursor: pointer; + color: var(--affine-link-color); + font-feature-settings: + 'clig' off, + 'liga' off; + font-family: var(--affine-font-family); + font-size: var(--affine-font-sm); + font-style: normal; + font-weight: 400; + text-decoration: none; + text-wrap: nowrap; + } `; diff --git a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/utils.ts b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/utils.ts index c282f7c739c5..d5fea5447a24 100644 --- a/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/utils.ts +++ b/packages/affine/components/src/rich-text/inline/presets/nodes/reference-node/utils.ts @@ -5,10 +5,16 @@ import cloneDeep from 'lodash.clonedeep'; /** * Clones reference info. */ -export function cloneReferenceInfo({ pageId, params }: ReferenceInfo) { +export function cloneReferenceInfo({ + pageId, + params, + title, + description, +}: ReferenceInfo) { const info: ReferenceInfo = { pageId }; - if (!params) return info; - info.params = cloneDeep(params); + if (params) info.params = cloneDeep(params); + if (title) info.title = title; + if (description) info.description = description; return info; } diff --git a/packages/affine/data-view/package.json b/packages/affine/data-view/package.json index 2e08547b24e3..43af4aca8a8c 100644 --- a/packages/affine/data-view/package.json +++ b/packages/affine/data-view/package.json @@ -23,7 +23,7 @@ "@blocksuite/affine-shared": "workspace:*", "@blocksuite/block-std": "workspace:*", "@blocksuite/global": "workspace:*", - "@blocksuite/icons": "^2.1.70", + "@blocksuite/icons": "^2.1.75", "@blocksuite/store": "workspace:*", "@emotion/hash": "^0.9.2", "@floating-ui/dom": "^1.6.10", diff --git a/packages/affine/model/src/blocks/embed/linked-doc/linked-doc-schema.ts b/packages/affine/model/src/blocks/embed/linked-doc/linked-doc-schema.ts index 1287ad0196e7..f50002338202 100644 --- a/packages/affine/model/src/blocks/embed/linked-doc/linked-doc-schema.ts +++ b/packages/affine/model/src/blocks/embed/linked-doc/linked-doc-schema.ts @@ -9,6 +9,9 @@ const defaultEmbedLinkedDocBlockProps: EmbedLinkedDocBlockProps = { pageId: '', style: EmbedLinkedDocStyles[1], caption: null, + // title & description aliases + title: undefined, + description: undefined, }; export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({ diff --git a/packages/affine/model/src/blocks/embed/synced-doc/synced-doc-schema.ts b/packages/affine/model/src/blocks/embed/synced-doc/synced-doc-schema.ts index f6463bd80315..135603e1555d 100644 --- a/packages/affine/model/src/blocks/embed/synced-doc/synced-doc-schema.ts +++ b/packages/affine/model/src/blocks/embed/synced-doc/synced-doc-schema.ts @@ -10,6 +10,9 @@ export const defaultEmbedSyncedDocBlockProps: EmbedSyncedDocBlockProps = { style: EmbedSyncedDocStyles[0], caption: undefined, scale: undefined, + // title & description aliases + title: undefined, + description: undefined, }; export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({ diff --git a/packages/affine/model/src/consts/doc.ts b/packages/affine/model/src/consts/doc.ts index 8c5c10a8afda..d16cdee7316b 100644 --- a/packages/affine/model/src/consts/doc.ts +++ b/packages/affine/model/src/consts/doc.ts @@ -4,6 +4,24 @@ export type DocMode = 'edgeless' | 'page'; export const DocModes = ['edgeless', 'page'] as const; +/** + * Custom title and description information. + * + * Supports the following blocks: + * + * 1. Inline View: `AffineReference` - title + * 2. Card View: `EmbedLinkedDocBlock` - title & description + * 3. Embed View: `EmbedSyncedDocBlock` - title + */ +export const AliasInfoSchema = z + .object({ + title: z.string().nullable(), + description: z.string().nullable(), + }) + .partial(); + +export type AliasInfo = z.infer; + export const ReferenceParamsSchema = z .object({ mode: z.enum(DocModes), @@ -16,9 +34,11 @@ export const ReferenceParamsSchema = z export type ReferenceParams = z.infer; -export const ReferenceInfoSchema = z.object({ - pageId: z.string(), - params: ReferenceParamsSchema.optional(), -}); +export const ReferenceInfoSchema = z + .object({ + pageId: z.string(), + params: ReferenceParamsSchema.optional(), + }) + .merge(AliasInfoSchema); export type ReferenceInfo = z.infer; diff --git a/packages/affine/shared/src/styles/panel.ts b/packages/affine/shared/src/styles/panel.ts index 768c620c399e..9c176276321f 100644 --- a/packages/affine/shared/src/styles/panel.ts +++ b/packages/affine/shared/src/styles/panel.ts @@ -1,11 +1,12 @@ +import { cssVarV2 } from '@toeverything/theme/v2'; import { unsafeCSS } from 'lit'; import { FONT_SM } from './font.js'; export const PANEL_BASE_COLORS = unsafeCSS(` color: var(--affine-icon-color); - background: var(--affine-background-overlay-panel-color); box-shadow: var(--affine-overlay-shadow); + background: ${cssVarV2('layer/background/overlayPanel')}; `); export const PANEL_BASE = unsafeCSS(` @@ -15,7 +16,7 @@ export const PANEL_BASE = unsafeCSS(` width: max-content; padding: 0 6px; border-radius: 4px; - border: 0.5px solid var(--affine-border-color); + border: 0.5px solid ${cssVarV2('layer/insideBorder/border')}; ${PANEL_BASE_COLORS}; ${FONT_SM}; diff --git a/packages/blocks/package.json b/packages/blocks/package.json index fadcdfe64844..f7c38913cffc 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -31,7 +31,7 @@ "@blocksuite/block-std": "workspace:*", "@blocksuite/data-view": "workspace:*", "@blocksuite/global": "workspace:*", - "@blocksuite/icons": "^2.1.70", + "@blocksuite/icons": "^2.1.75", "@blocksuite/inline": "workspace:*", "@blocksuite/store": "workspace:*", "@floating-ui/dom": "^1.6.10", diff --git a/packages/blocks/src/_common/components/embed-card/embed-card-more-menu-popper.ts b/packages/blocks/src/_common/components/embed-card/embed-card-more-menu-popper.ts index a9971f2c9b11..4158815444a8 100644 --- a/packages/blocks/src/_common/components/embed-card/embed-card-more-menu-popper.ts +++ b/packages/blocks/src/_common/components/embed-card/embed-card-more-menu-popper.ts @@ -13,7 +13,7 @@ import { Slice } from '@blocksuite/store'; import { css, html, LitElement, nothing } from 'lit'; import { property } from 'lit/decorators.js'; -import type { EmbedToolbarBlockComponent } from './type.js'; +import type { EmbedBlockComponent } from './type.js'; import { isEmbedLinkedDocBlock, @@ -213,7 +213,7 @@ export class EmbedCardMoreMenu extends WithDisposable(LitElement) { accessor abortController!: AbortController; @property({ attribute: false }) - accessor block!: EmbedToolbarBlockComponent; + accessor block!: EmbedBlockComponent; } declare global { diff --git a/packages/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts b/packages/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts index 306faacd6b4b..b49e7b64d1f4 100644 --- a/packages/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts +++ b/packages/blocks/src/_common/components/embed-card/modal/embed-card-edit-modal.ts @@ -1,141 +1,400 @@ -import type { - BookmarkBlockModel, - EmbedFigmaModel, - EmbedGithubModel, - EmbedLoomModel, - EmbedYoutubeModel, -} from '@blocksuite/affine-model'; -import type { EditorHost } from '@blocksuite/block-std'; +import type { AliasInfo } from '@blocksuite/affine-model'; +import type { BlockComponent, EditorHost } from '@blocksuite/block-std'; +import type { BlockProps } from '@blocksuite/store'; +import { + EmbedLinkedDocBlockComponent, + EmbedSyncedDocBlockComponent, +} from '@blocksuite/affine-block-embed'; +import { + notifyLinkedDocClearedAliases, + notifyLinkedDocSwitchedToCard, +} from '@blocksuite/affine-components/notification'; import { toast } from '@blocksuite/affine-components/toast'; -import { ShadowlessElement } from '@blocksuite/block-std'; -import { WithDisposable } from '@blocksuite/global/utils'; -import { html } from 'lit'; -import { property, query, state } from 'lit/decorators.js'; +import { + EmbedLinkedDocModel, + EmbedSyncedDocModel, +} from '@blocksuite/affine-model'; +import { FONT_SM, FONT_XS } from '@blocksuite/affine-shared/styles'; +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; +import { + listenClickAway, + stopPropagation, +} from '@blocksuite/affine-shared/utils'; +import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils'; +import { autoUpdate, computePosition, flip, offset } from '@floating-ui/dom'; +import { computed, signal } from '@preact/signals-core'; +import { css, html, LitElement } from 'lit'; +import { property, query } from 'lit/decorators.js'; +import { choose } from 'lit/directives/choose.js'; import { classMap } from 'lit/directives/class-map.js'; +import { live } from 'lit/directives/live.js'; -import { embedCardModalStyles } from './styles.js'; +import type { LinkableEmbedModel } from '../type.js'; -type EmbedCardModel = - | BookmarkBlockModel - | EmbedGithubModel - | EmbedYoutubeModel - | EmbedFigmaModel - | EmbedLoomModel; +import { isInternalEmbedModel } from '../type.js'; -export class EmbedCardEditModal extends WithDisposable(ShadowlessElement) { - static override styles = embedCardModalStyles; +export class EmbedCardEditModal extends SignalWatcher( + WithDisposable(LitElement) +) { + static override styles = css` + :host { + position: absolute; + top: 0; + left: 0; + z-index: var(--affine-z-index-popover); + animation: affine-popover-fade-in 0.2s ease; + } - private _handleInput(e: InputEvent) { - const target = e.target as HTMLInputElement; - this._titleInputValue = target.value; - } + @keyframes affine-popover-fade-in { + from { + opacity: 0; + transform: translateY(-3px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .embed-card-modal-wrapper { + display: flex; + padding: 12px; + flex-direction: column; + justify-content: flex-end; + align-items: flex-start; + gap: 12px; + width: 421px; + + color: var(--affine-icon-color); + box-shadow: var(--affine-overlay-shadow); + background: ${unsafeCSSVarV2('layer/background/overlayPanel')}; + border-radius: 4px; + border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; + } - private _onDocumentKeydown(e: KeyboardEvent) { + .row { + width: 100%; + display: flex; + align-items: center; + gap: 12px; + } + + .row .input { + display: flex; + padding: 4px 10px; + width: 100%; + min-width: 100%; + box-sizing: border-box; + border-radius: 4px; + user-select: none; + background: transparent; + border: 1px solid ${unsafeCSSVarV2('input/border/default')}; + color: var(--affine-text-primary-color); + ${FONT_SM}; + } + .input::placeholder { + color: var(--affine-placeholder-color); + } + .input:focus { + border-color: ${unsafeCSSVarV2('input/border/active')}; + outline: none; + } + + textarea.input { + min-height: 80px; + resize: none; + } + + .row.actions { + justify-content: flex-end; + } + + .row.actions .button { + display: flex; + padding: 4px 12px; + align-items: center; + gap: 4px; + border-radius: 4px; + border: 1px solid ${unsafeCSSVarV2('button/innerBlackBorder')}; + background: ${unsafeCSSVarV2('button/secondary')}; + ${FONT_XS}; + color: ${unsafeCSSVarV2('text/primary')}; + } + .row.actions .button[disabled], + .row.actions .button:disabled { + pointer-events: none; + color: ${unsafeCSSVarV2('text/disable')}; + background: ${unsafeCSSVarV2('button/disable')}; + } + .row.actions .button.save { + color: ${unsafeCSSVarV2('button/pureWhiteText')}; + background: ${unsafeCSSVarV2('button/primary')}; + } + `; + + private _blockComponent: BlockComponent | null = null; + + private _hide = () => { + this.remove(); + }; + + private _onKeydown = (e: KeyboardEvent) => { e.stopPropagation(); - if (e.key === 'Enter' && !e.isComposing) { + if (e.key === 'Enter' && !(e.isComposing || e.shiftKey)) { this._onSave(); } if (e.key === 'Escape') { + e.preventDefault(); this.remove(); } - } + }; - private _onSave() { - const title = this.titleInput.value; + private _onReset = () => { + this.model.doc.updateBlock(this.model, { title: null, description: null }); + + const blockComponent = this._blockComponent; + if ( + this.isEmbedLinkedDocModel && + blockComponent instanceof EmbedLinkedDocBlockComponent + ) { + const std = blockComponent.std; + + blockComponent.refreshData(); + + notifyLinkedDocClearedAliases(std); + } + blockComponent?.requestUpdate(); + + this.remove(); + }; + + private _onSave = () => { + const title = this.title$.value.trim(); if (title.length === 0) { - toast(this.host, 'Link title can not be empty'); + toast(this.host, 'Title can not be empty'); return; } - this.model.doc.updateBlock(this.model, { - title, - description: this.descInput.value, - }); + const description = this.description$.value.trim(); + + const props: Partial = { title }; + if (description) props.description = description; + + this.model.doc.updateBlock(this.model, props); + + const blockComponent = this._blockComponent; + + if ( + this.isEmbedSyncedDocModel && + blockComponent instanceof EmbedSyncedDocBlockComponent + ) { + const std = blockComponent.std; + + blockComponent.convertToCard(); + + notifyLinkedDocSwitchedToCard(std); + } else { + blockComponent?.requestUpdate(); + } + this.remove(); + }; + + private _updateDescription = (e: InputEvent) => { + const target = e.target as HTMLTextAreaElement; + this.description$.value = target.value; + }; + + private _updateTitle = (e: InputEvent) => { + const target = e.target as HTMLInputElement; + this.title$.value = target.value; + }; + + get isEmbedLinkedDocModel() { + return this.model instanceof EmbedLinkedDocModel; + } + + get isEmbedSyncedDocModel() { + return this.model instanceof EmbedSyncedDocModel; + } + + get isInternalEmbedModel() { + return isInternalEmbedModel(this.model); + } + + get modelType(): 'linked' | 'synced' | null { + if (this.isEmbedLinkedDocModel) return 'linked'; + if (this.isEmbedSyncedDocModel) return 'synced'; + return null; + } + + get placeholders() { + if (this.isInternalEmbedModel) { + return { + title: 'Add title alias', + description: + 'Add description alias (empty to inherit document content)', + }; + } + + return { + title: 'Write a title', + description: 'Write a description...', + }; + } + + private _updateInfo() { + const title = this.model.title || this.originalDocInfo?.title || ''; + const description = + this.model.description || this.originalDocInfo?.description || ''; + + this.title$.value = title; + this.description$.value = description; } override connectedCallback() { super.connectedCallback(); - this.updateComplete - .then(() => { - this.titleInput.focus(); - this.titleInput.setSelectionRange(0, this.titleInput.value.length); + this._updateInfo(); + } + + override firstUpdated() { + const blockComponent = this.host.std.view.getBlock(this.model.id); + if (!blockComponent) return; + + this._blockComponent = blockComponent; + + this.disposables.add( + autoUpdate(blockComponent, this, () => { + computePosition(blockComponent, this, { + placement: 'top-start', + middleware: [flip(), offset(8)], + }) + .then(({ x, y }) => { + this.style.left = `${x}px`; + this.style.top = `${y}px`; + }) + .catch(console.error); }) - .catch(console.error); + ); - this.disposables.addFromEvent(this, 'keydown', this._onDocumentKeydown); + this.disposables.add(listenClickAway(this, this._hide)); + this.disposables.addFromEvent(this, 'keydown', this._onKeydown); + this.disposables.addFromEvent(this, 'pointerdown', stopPropagation); - this._titleInputValue = this.model.title ?? ''; + this.titleInput.focus(); + this.titleInput.select(); } override render() { return html` -
-
this.remove()}>
-
-
- - -
-
- - -
-
- -
+
+
+ +
+
+ +
+
+ ${choose(this.modelType, [ + [ + 'linked', + () => html` + + `, + ], + [ + 'synced', + () => html` + + `, + ], + ])} +
`; } - @state() - private accessor _titleInputValue = ''; - - @query('.embed-card-modal-input.description') - accessor descInput!: HTMLTextAreaElement; + accessor description$ = signal(''); @property({ attribute: false }) accessor host!: EditorHost; @property({ attribute: false }) - accessor model!: EmbedCardModel; + accessor model!: LinkableEmbedModel; + + @property({ attribute: false }) + accessor originalDocInfo: AliasInfo | undefined = undefined; - @query('.embed-card-modal-input.title') + accessor resetButtonDisabled$ = computed( + () => + !( + Boolean(this.model.title$.value?.length) || + Boolean(this.model.description$.value?.length) + ) + ); + + accessor saveButtonDisabled$ = computed( + () => this.title$.value.trim().length === 0 + ); + + accessor title$ = signal(''); + + @query('.input.title') accessor titleInput!: HTMLInputElement; } export function toggleEmbedCardEditModal( host: EditorHost, - embedCardModel: EmbedCardModel + embedCardModel: LinkableEmbedModel, + originalDocInfo?: AliasInfo ) { - host.selection.clear(); + document.body.querySelector('embed-card-edit-modal')?.remove(); + const embedCardEditModal = new EmbedCardEditModal(); embedCardEditModal.model = embedCardModel; embedCardEditModal.host = host; + embedCardEditModal.originalDocInfo = originalDocInfo; document.body.append(embedCardEditModal); } diff --git a/packages/blocks/src/_common/components/embed-card/modal/styles.ts b/packages/blocks/src/_common/components/embed-card/modal/styles.ts index 77085241496b..98b17e52f361 100644 --- a/packages/blocks/src/_common/components/embed-card/modal/styles.ts +++ b/packages/blocks/src/_common/components/embed-card/modal/styles.ts @@ -74,13 +74,15 @@ export const embedCardModalStyles = css` .embed-card-modal-row:has(.embed-card-modal-button) { flex-direction: row; gap: 4px; - align-self: flex-end; + justify-content: flex-end; + } + .embed-card-modal-row:has(.embed-card-modal-button.reset) { + justify-content: space-between; } .embed-card-modal-button { padding: 4px 18px; border-radius: 8px; - align-self: self-end; box-sizing: border-box; } .embed-card-modal-button.save { @@ -94,6 +96,14 @@ export const embedCardModalStyles = css` color: var(--affine-text-disable-color); background: transparent; } + .embed-card-modal-button.reset { + padding: 4px 0; + border: none; + background: transparent; + text-decoration: underline; + color: var(--affine-secondary-color); + user-select: none; + } .embed-card-modal-title { font-size: 18px; diff --git a/packages/blocks/src/_common/components/embed-card/type.ts b/packages/blocks/src/_common/components/embed-card/type.ts index 85a15aefaa16..782629705c93 100644 --- a/packages/blocks/src/_common/components/embed-card/type.ts +++ b/packages/blocks/src/_common/components/embed-card/type.ts @@ -2,9 +2,8 @@ import type { BookmarkBlockModel, EmbedFigmaModel, EmbedGithubModel, - EmbedLinkedDocModel, + EmbedHtmlModel, EmbedLoomModel, - EmbedSyncedDocModel, EmbedYoutubeModel, } from '@blocksuite/affine-model'; import type { BlockComponent } from '@blocksuite/block-std'; @@ -12,42 +11,70 @@ import type { BlockComponent } from '@blocksuite/block-std'; import { EmbedFigmaBlockComponent, EmbedGithubBlockComponent, + EmbedHtmlBlockComponent, EmbedLinkedDocBlockComponent, EmbedLoomBlockComponent, EmbedSyncedDocBlockComponent, EmbedYoutubeBlockComponent, } from '@blocksuite/affine-block-embed'; +import { + EmbedLinkedDocModel, + EmbedSyncedDocModel, +} from '@blocksuite/affine-model'; import { BookmarkBlockComponent } from '../../../bookmark-block/bookmark-block.js'; -export type EmbedToolbarBlockComponent = +export type ExternalEmbedBlockComponent = | BookmarkBlockComponent - | EmbedGithubBlockComponent - | EmbedYoutubeBlockComponent | EmbedFigmaBlockComponent + | EmbedGithubBlockComponent + | EmbedLoomBlockComponent + | EmbedYoutubeBlockComponent; + +export type InternalEmbedBlockComponent = | EmbedLinkedDocBlockComponent - | EmbedSyncedDocBlockComponent - | EmbedLoomBlockComponent; + | EmbedSyncedDocBlockComponent; + +export type LinkableEmbedBlockComponent = + | ExternalEmbedBlockComponent + | InternalEmbedBlockComponent; + +export type EmbedBlockComponent = + | LinkableEmbedBlockComponent + | EmbedHtmlBlockComponent; -export type EmbedToolbarModel = +export type ExternalEmbedModel = | BookmarkBlockModel - | EmbedGithubModel - | EmbedYoutubeModel | EmbedFigmaModel - | EmbedLinkedDocModel - | EmbedSyncedDocModel - | EmbedLoomModel; + | EmbedGithubModel + | EmbedLoomModel + | EmbedYoutubeModel; + +export type InternalEmbedModel = EmbedLinkedDocModel | EmbedSyncedDocModel; + +export type LinkableEmbedModel = ExternalEmbedModel | InternalEmbedModel; + +export type EmbedModel = LinkableEmbedModel | EmbedHtmlModel; export function isEmbedCardBlockComponent( block: BlockComponent -): block is EmbedToolbarBlockComponent { +): block is EmbedBlockComponent { return ( block instanceof BookmarkBlockComponent || + block instanceof EmbedFigmaBlockComponent || block instanceof EmbedGithubBlockComponent || + block instanceof EmbedHtmlBlockComponent || + block instanceof EmbedLoomBlockComponent || block instanceof EmbedYoutubeBlockComponent || - block instanceof EmbedFigmaBlockComponent || block instanceof EmbedLinkedDocBlockComponent || - block instanceof EmbedSyncedDocBlockComponent || - block instanceof EmbedLoomBlockComponent + block instanceof EmbedSyncedDocBlockComponent + ); +} + +export function isInternalEmbedModel( + model: EmbedModel +): model is InternalEmbedModel { + return ( + model instanceof EmbedLinkedDocModel || model instanceof EmbedSyncedDocModel ); } diff --git a/packages/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts b/packages/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts index 8978038fc1cc..4a40627c0daa 100644 --- a/packages/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts +++ b/packages/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts @@ -1,22 +1,4 @@ -import type { - EmbedFigmaBlockComponent, - EmbedGithubBlockComponent, - EmbedLinkedDocBlockComponent, - EmbedLoomBlockComponent, - EmbedSyncedDocBlockComponent, - EmbedYoutubeBlockComponent, -} from '@blocksuite/affine-block-embed'; -import type { - BookmarkBlockModel, - EmbedFigmaModel, - EmbedGithubModel, - EmbedHtmlModel, - EmbedLinkedDocModel, - EmbedLoomModel, - EmbedSyncedDocModel, - EmbedYoutubeModel, -} from '@blocksuite/affine-model'; - +import { getDocContentWithMaxLength } from '@blocksuite/affine-block-embed'; import { CaptionIcon, CenterPeekIcon, @@ -27,6 +9,7 @@ import { PaletteIcon, SmallArrowDownIcon, } from '@blocksuite/affine-components/icons'; +import { notifyLinkedDocSwitchedToEmbed } from '@blocksuite/affine-components/notification'; import { isPeekable, peek } from '@blocksuite/affine-components/peek'; import { isLinkToNode } from '@blocksuite/affine-components/rich-text'; import { toast } from '@blocksuite/affine-components/toast'; @@ -34,10 +17,12 @@ import { type MenuItem, renderToolbarSeparator, } from '@blocksuite/affine-components/toolbar'; -import { BookmarkStyles } from '@blocksuite/affine-model'; +import { type AliasInfo, BookmarkStyles } from '@blocksuite/affine-model'; import { EmbedOptionProvider, type EmbedOptions, + GenerateDocUrlProvider, + type GenerateDocUrlService, ThemeProvider, } from '@blocksuite/affine-shared/services'; import { getHostName } from '@blocksuite/affine-shared/utils'; @@ -48,11 +33,15 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { join } from 'lit/directives/join.js'; import { repeat } from 'lit/directives/repeat.js'; +import type { + EmbedBlockComponent, + EmbedModel, +} from '../../../_common/components/embed-card/type.js'; import type { EmbedCardStyle } from '../../../_common/types.js'; -import type { BookmarkBlockComponent } from '../../../bookmark-block/index.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import { toggleEmbedCardEditModal } from '../../../_common/components/embed-card/modal/embed-card-edit-modal.js'; +import { isInternalEmbedModel } from '../../../_common/components/embed-card/type.js'; import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, @@ -99,6 +88,26 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { overflow: hidden; opacity: var(--add, 1); } + + editor-icon-button.doc-title .label { + max-width: 110px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; + cursor: pointer; + color: var(--affine-link-color); + font-feature-settings: + 'clig' off, + 'liga' off; + font-family: var(--affine-font-family); + font-size: var(--affine-font-sm); + font-style: normal; + font-weight: 400; + text-decoration: none; + text-wrap: nowrap; + } `; private _convertToCardView = () => { @@ -158,7 +167,13 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { const block = this._blockComponent; if (block && 'convertToEmbed' in block) { + const referenceInfo = block.referenceInfo; + block.convertToEmbed(); + + if (referenceInfo.title || referenceInfo.description) + notifyLinkedDocSwitchedToEmbed(this.std); + return; } @@ -201,11 +216,19 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }; private _copyUrl = () => { - if (!('url' in this.model)) { - return; + let url!: ReturnType; + + if ('url' in this.model) { + url = this.model.url; + } else if (isInternalEmbedModel(this.model)) { + url = this.std + .getOptional(GenerateDocUrlProvider) + ?.generateDocUrl(this.model.pageId, this.model.params); } - navigator.clipboard.writeText(this.model.url).catch(console.error); + if (!url) return; + + navigator.clipboard.writeText(url).catch(console.error); toast(this.std.host, 'Copied link to clipboard'); this.edgeless.service.selection.clear(); }; @@ -241,15 +264,9 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { return; } - const blockComponent = this.std.view.getBlock(blockSelection[0].blockId) as - | BookmarkBlockComponent - | EmbedGithubBlockComponent - | EmbedYoutubeBlockComponent - | EmbedFigmaBlockComponent - | EmbedLinkedDocBlockComponent - | EmbedSyncedDocBlockComponent - | EmbedLoomBlockComponent - | null; + const blockComponent = this.std.view.getBlock( + blockSelection[0].blockId + ) as EmbedBlockComponent | null; if (!blockComponent) return; @@ -369,6 +386,21 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { ); } + get _originalDocInfo(): AliasInfo | undefined { + const model = this.model; + const doc = isInternalEmbedModel(model) + ? this.std.collection.getDoc(model.pageId) + : null; + + if (doc) { + const title = doc.meta?.title; + const description = getDocContentWithMaxLength(doc); + return { title, description }; + } + + return undefined; + } + private get _viewType(): 'inline' | 'embed' | 'card' { if (this._isCardView) { return 'card'; @@ -558,6 +590,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { override render() { const model = this.model; + const isHtmlBlockModel = isEmbedHtmlBlock(model); + if ('url' in this.model) { this._embedOptions = this.std .get(EmbedOptionProvider) @@ -565,6 +599,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { } const buttons = [ + this._openMenuButton(), + this._canShowUrlOptions && 'url' in model ? html` ${getHostName(model.url)} + ` + : nothing, + // internal embed model + isEmbedLinkedDocBlock(model) && model.title + ? html` + ${this.std.collection.getDoc(model.pageId)?.meta?.title || + 'Untitled'} + + ` + : nothing, + + isHtmlBlockModel + ? nothing + : html` + toggleEmbedCardEditModal(this.std.host, model)} + @click=${(e: MouseEvent) => { + e.stopPropagation(); + + this.std.selection.clear(); + + const originalDocInfo = this._originalDocInfo; + + toggleEmbedCardEditModal(this.std.host, model, originalDocInfo); + }} > ${EditIcon} - ` - : nothing, - - this._openMenuButton(), + `, this._viewToggleMenu(), @@ -642,7 +707,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { this.quickConnectButton, - isEmbedHtmlBlock(model) + isHtmlBlockModel ? nothing : html` | typeof nothing; diff --git a/packages/blocks/src/root-block/widgets/element-toolbar/index.ts b/packages/blocks/src/root-block/widgets/element-toolbar/index.ts index bf4f36f231ea..e616c602c7dd 100644 --- a/packages/blocks/src/root-block/widgets/element-toolbar/index.ts +++ b/packages/blocks/src/root-block/widgets/element-toolbar/index.ts @@ -1,16 +1,8 @@ import type { AttachmentBlockModel, - BookmarkBlockModel, BrushElementModel, ConnectorElementModel, EdgelessTextBlockModel, - EmbedFigmaModel, - EmbedGithubModel, - EmbedHtmlModel, - EmbedLinkedDocModel, - EmbedLoomModel, - EmbedSyncedDocModel, - EmbedYoutubeModel, FrameBlockModel, ImageBlockModel, MindmapElementModel, @@ -48,6 +40,7 @@ import { css, html, nothing, type TemplateResult, unsafeCSS } from 'lit'; import { property, state } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; +import type { EmbedModel } from '../../../_common/components/embed-card/type.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import type { ElementToolbarMoreMenuContext } from './more-menu/context.js'; @@ -90,14 +83,7 @@ type CategorizedElements = { image?: ImageBlockModel[]; attachment?: AttachmentBlockModel[]; mindmap?: MindmapElementModel[]; - embedCard?: BookmarkBlockModel[] & - EmbedGithubModel[] & - EmbedYoutubeModel[] & - EmbedFigmaModel[] & - EmbedLinkedDocModel[] & - EmbedSyncedDocModel[] & - EmbedHtmlModel[] & - EmbedLoomModel[]; + embedCard?: EmbedModel[]; edgelessText?: EdgelessTextBlockModel[]; }; diff --git a/packages/blocks/src/root-block/widgets/embed-card-toolbar/context.ts b/packages/blocks/src/root-block/widgets/embed-card-toolbar/context.ts index 6457140c9a26..a924fb5e4d4c 100644 --- a/packages/blocks/src/root-block/widgets/embed-card-toolbar/context.ts +++ b/packages/blocks/src/root-block/widgets/embed-card-toolbar/context.ts @@ -1,4 +1,4 @@ -import type { EmbedToolbarBlockComponent } from '../../../_common/components/embed-card/type.js'; +import type { EmbedBlockComponent } from '../../../_common/components/embed-card/type.js'; import { MenuContext } from '../../configs/toolbar.js'; @@ -25,7 +25,7 @@ export class EmbedCardToolbarContext extends MenuContext { } constructor( - public blockComponent: EmbedToolbarBlockComponent, + public blockComponent: EmbedBlockComponent, public abortController: AbortController ) { super(); diff --git a/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts b/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts index c1b25f3e92b6..a24eb097c757 100644 --- a/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts +++ b/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts @@ -1,3 +1,4 @@ +import { getDocContentWithMaxLength } from '@blocksuite/affine-block-embed'; import { CaptionIcon, CenterPeekIcon, @@ -9,6 +10,7 @@ import { PaletteIcon, SmallArrowDownIcon, } from '@blocksuite/affine-components/icons'; +import { notifyLinkedDocSwitchedToEmbed } from '@blocksuite/affine-components/notification'; import { isPeekable, peek } from '@blocksuite/affine-components/peek'; import { isLinkToNode } from '@blocksuite/affine-components/rich-text'; import { toast } from '@blocksuite/affine-components/toast'; @@ -20,6 +22,7 @@ import { renderToolbarSeparator, } from '@blocksuite/affine-components/toolbar'; import { + type AliasInfo, type BookmarkBlockModel, BookmarkStyles, type EmbedGithubModel, @@ -29,6 +32,8 @@ import { import { EmbedOptionProvider, type EmbedOptions, + GenerateDocUrlProvider, + type GenerateDocUrlService, ThemeProvider, } from '@blocksuite/affine-shared/services'; import { getHostName } from '@blocksuite/affine-shared/utils'; @@ -48,15 +53,17 @@ import type { RootBlockComponent } from '../../types.js'; import { toggleEmbedCardCaptionEditModal } from '../../../_common/components/embed-card/modal/embed-card-caption-edit-modal.js'; import { toggleEmbedCardEditModal } from '../../../_common/components/embed-card/modal/embed-card-edit-modal.js'; import { - type EmbedToolbarBlockComponent, - type EmbedToolbarModel, + type EmbedBlockComponent, + type EmbedModel, isEmbedCardBlockComponent, + isInternalEmbedModel, } from '../../../_common/components/embed-card/type.js'; import { getEmbedCardIcons } from '../../../_common/utils/url.js'; import { getMoreMenuConfig } from '../../configs/toolbar.js'; import { isBookmarkBlock, isEmbedGithubBlock, + isEmbedHtmlBlock, isEmbedLinkedDocBlock, isEmbedSyncedDocBlock, } from '../../edgeless/utils/query.js'; @@ -151,6 +158,23 @@ export class EmbedCardToolbar extends WidgetComponent< ); } + get _originalDocInfo(): AliasInfo | undefined { + const model = this.focusModel; + if (!model) return undefined; + + const doc = isInternalEmbedModel(model) + ? this.std.collection.getDoc(model.pageId) + : null; + + if (doc) { + const title = doc.meta?.title; + const description = getDocContentWithMaxLength(doc); + return { title, description }; + } + + return undefined; + } + private get _selection() { return this.host.selection; } @@ -167,7 +191,7 @@ export class EmbedCardToolbar extends WidgetComponent< return 'inline'; } - get focusModel(): EmbedToolbarModel | undefined { + get focusModel(): EmbedModel | undefined { return this.focusBlock?.model; } @@ -303,7 +327,13 @@ export class EmbedCardToolbar extends WidgetComponent< } if ('convertToEmbed' in this.focusBlock) { + const referenceInfo = this.focusBlock.referenceInfo; + this.focusBlock.convertToEmbed(); + + if (referenceInfo.title || referenceInfo.description) + notifyLinkedDocSwitchedToEmbed(this.std); + return; } @@ -339,12 +369,22 @@ export class EmbedCardToolbar extends WidgetComponent< } private _copyUrl() { - if (!this.focusModel || !('url' in this.focusModel)) { - return; + if (!this.focusModel) return; + + let url!: ReturnType; + + if ('url' in this.focusModel) { + url = this.focusModel.url; + } else if (isInternalEmbedModel(this.focusModel)) { + url = this.std + .getOptional(GenerateDocUrlProvider) + ?.generateDocUrl(this.focusModel.pageId, this.focusModel.params); } - navigator.clipboard.writeText(this.focusModel.url).catch(console.error); - toast(this.host, 'Copied link to clipboard'); + if (!url) return; + + navigator.clipboard.writeText(url).catch(console.error); + toast(this.std.host, 'Copied link to clipboard'); } private _hide() { @@ -596,7 +636,7 @@ export class EmbedCardToolbar extends WidgetComponent< return; } - this.focusBlock = block as EmbedToolbarBlockComponent; + this.focusBlock = block as EmbedBlockComponent; this._show(); }) ); @@ -606,13 +646,19 @@ export class EmbedCardToolbar extends WidgetComponent< if (this.hide) return nothing; const model = this.focusModel; + if (!model) return nothing; + this._embedOptions = - model && 'url' in model + 'url' in model ? this.std.get(EmbedOptionProvider).getEmbedBlockOptions(model.url) : null; + const hasUrl = this._canShowUrlOptions && 'url' in model; + const buttons = [ - this._canShowUrlOptions && model && 'url' in model + this._openMenuButton(), + + hasUrl ? html` ${getHostName(model.url)} + ` + : nothing, + // internal embed model + isEmbedLinkedDocBlock(model) && model.title + ? html` + ${this.std.collection.getDoc(model.pageId)?.meta?.title || + 'Untitled'} + + ` + : nothing, + + isEmbedHtmlBlock(model) + ? nothing + : html` + this._copyUrl()} > ${CopyIcon} @@ -638,14 +707,18 @@ export class EmbedCardToolbar extends WidgetComponent< data-testid="edit" .tooltip=${'Edit'} ?disabled=${this.doc.readonly} - @click=${() => toggleEmbedCardEditModal(this.host, model)} + @click=${(e: MouseEvent) => { + e.stopPropagation(); + const originalDocInfo = this._originalDocInfo; + + this._hide(); + + toggleEmbedCardEditModal(this.host, model, originalDocInfo); + }} > ${EditIcon} - ` - : nothing, - - this._openMenuButton(), + `, this._viewToggleMenu(), @@ -695,7 +768,7 @@ export class EmbedCardToolbar extends WidgetComponent< accessor embedCardToolbarElement!: HTMLElement; @state() - accessor focusBlock: EmbedToolbarBlockComponent | null = null; + accessor focusBlock: EmbedBlockComponent | null = null; @state() accessor hide: boolean = true; diff --git a/packages/blocks/src/root-block/widgets/embed-card-toolbar/styles.ts b/packages/blocks/src/root-block/widgets/embed-card-toolbar/styles.ts index 758f7c21380e..c042746bcf45 100644 --- a/packages/blocks/src/root-block/widgets/embed-card-toolbar/styles.ts +++ b/packages/blocks/src/root-block/widgets/embed-card-toolbar/styles.ts @@ -44,4 +44,24 @@ export const embedCardToolbarStyle = css` .card-style-select icon-button.selected { border: 1px solid var(--affine-brand-color); } + + editor-icon-button.doc-title .label { + max-width: 110px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; + cursor: pointer; + color: var(--affine-link-color); + font-feature-settings: + 'clig' off, + 'liga' off; + font-family: var(--affine-font-family); + font-size: var(--affine-font-sm); + font-style: normal; + font-weight: 400; + text-decoration: none; + text-wrap: nowrap; + } `; diff --git a/packages/framework/block-std/src/range/range-binding.ts b/packages/framework/block-std/src/range/range-binding.ts index 7aacba204fcf..898a39168c4c 100644 --- a/packages/framework/block-std/src/range/range-binding.ts +++ b/packages/framework/block-std/src/range/range-binding.ts @@ -170,6 +170,7 @@ export class RangeBinding { private _onNativeSelectionChanged = async () => { if (this.isComposing) return; + if (!this.host) return; // Unstable when switching views, card <-> embed await this.host.updateComplete; diff --git a/packages/playground/apps/_common/components/docs-panel.ts b/packages/playground/apps/_common/components/docs-panel.ts index 0989591327ef..73019471291b 100644 --- a/packages/playground/apps/_common/components/docs-panel.ts +++ b/packages/playground/apps/_common/components/docs-panel.ts @@ -65,13 +65,10 @@ export class DocsPanel extends WithDisposable(ShadowlessElement) { }; gotoDoc = (doc: BlockCollection) => { - const generateDocUrlProvider = this.editor.std.getOptional( - GenerateDocUrlProvider - ); - if (generateDocUrlProvider) { - const url = generateDocUrlProvider.generateDocUrl(doc.id); - if (url) history.pushState({}, '', url); - } + const url = this.editor.std + .getOptional(GenerateDocUrlProvider) + ?.generateDocUrl(doc.id); + if (url) history.pushState({}, '', url); this.editor.doc = doc.getDoc(); this.editor.doc.load(); diff --git a/tests/linked-page.spec.ts b/tests/linked-page.spec.ts index 2a3fbbf5c5db..cd2a941e3779 100644 --- a/tests/linked-page.spec.ts +++ b/tests/linked-page.spec.ts @@ -830,3 +830,321 @@ test('linked doc can be dragged from note to surface top level block', async ({ await waitNextFrame(page); await assertParentBlockFlavour(page, '10', 'affine:surface'); }); + +// Aliases +test.describe('Customize linked doc title and description', () => { + // Inline View + test('should set a custom title for inline link', async ({ page }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + await focusRichText(page); + await type(page, 'page0'); + await assertRichTexts(page, ['page0']); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + await focusRichText(page); + await type(page, 'page1'); + await assertRichTexts(page, ['page1']); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + await expect(referencePopup).toBeVisible(); + + const editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const popup = page.locator('.alias-form-popup'); + await expect(popup).toBeVisible(); + + const input = popup.locator('input'); + await expect(input).toBeFocused(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await page.mouse.click(0, 0); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + await expect(referencePopup).toBeVisible(); + + // original title button + const docTitle = referencePopup.getByRole('button', { name: 'Doc title' }); + await expect(docTitle).toHaveText('title0', { useInnerText: true }); + + // reedit + await editButton.click(); + + await waitNextFrame(page, 200); + + // reset + await popup.getByRole('button', { name: 'Reset' }).click(); + + await waitNextFrame(page, 200); + + const resetedPage0 = await findRefNode('title0'); + await resetedPage0.hover(); + + await waitNextFrame(page, 200); + await expect(referencePopup).toBeVisible(); + await expect(docTitle).not.toBeVisible(); + }); + + // Card View + test('should set a custom title and description for card link', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + + let editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + const switchButton = referencePopup.getByRole('button', { + name: 'Switch view', + }); + await switchButton.click(); + + // switches to card view + const toCardButton = referencePopup.getByRole('button', { + name: 'Card view', + }); + await toCardButton.click(); + + await waitNextFrame(page, 200); + const linkedDocBlock = page.locator('affine-embed-linked-doc-block'); + await expect(linkedDocBlock).toBeVisible(); + + const linkedDocBlockTitle = linkedDocBlock.locator( + '.affine-embed-linked-doc-content-title-text' + ); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + const cardToolbar = page.locator('affine-embed-card-toolbar'); + const docTitleButton = cardToolbar.getByRole('button', { + name: 'Doc title', + }); + await expect(docTitleButton).toBeVisible(); + + editButton = cardToolbar.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const editModal = page.locator('embed-card-edit-modal'); + const resetButton = editModal.getByRole('button', { name: 'Reset' }); + const saveButton = editModal.getByRole('button', { name: 'Save' }); + + // clears aliases + await resetButton.click(); + + await waitNextFrame(page, 200); + await expect(linkedDocBlockTitle).toHaveText('title0'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + await editButton.click(); + + await waitNextFrame(page, 200); + + // title alias + await type(page, 'page0-title0'); + await page.keyboard.press('Tab'); + // description alias + await type(page, 'This is a new description'); + + // saves aliases + await saveButton.click(); + + await waitNextFrame(page, 200); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + await expect( + page.locator('.affine-embed-linked-doc-content-note.alias') + ).toHaveText('This is a new description'); + }); + + // Embed View + test('should automatically switch to card view and set a custom title and description', async ({ + page, + }) => { + await enterPlaygroundRoom(page); + await initEmptyParagraphState(page); + await focusTitle(page); + await type(page, 'title0'); + + const { id } = await addNewPage(page); + await switchToPage(page, id); + await focusTitle(page); + await type(page, 'title1'); + + await getDebugMenu(page).pagesBtn.click(); + + await focusRichText(page); + await type(page, '@title0'); + await pressEnter(page); + + const { findRefNode } = getLinkedDocPopover(page); + const page0 = await findRefNode('title0'); + await page0.hover(); + + await waitNextFrame(page, 200); + const referencePopup = page.locator('.affine-reference-popover-container'); + + let editButton = referencePopup.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + // title alias + await type(page, 'page0-title0'); + await pressEnter(page); + + await focusRichText(page); + await waitNextFrame(page, 200); + + const page0Alias = await findRefNode('page0-title0'); + await page0Alias.hover(); + + await waitNextFrame(page, 200); + let switchButton = referencePopup.getByRole('button', { + name: 'Switch view', + }); + await switchButton.click(); + + // switches to card view + const toCardButton = referencePopup.getByRole('button', { + name: 'Card view', + }); + await toCardButton.click(); + + await waitNextFrame(page, 200); + const linkedDocBlock = page.locator('affine-embed-linked-doc-block'); + + await linkedDocBlock.click(); + + await waitNextFrame(page, 200); + const cardToolbar = page.locator('affine-embed-card-toolbar'); + switchButton = cardToolbar.getByRole('button', { name: 'Switch view' }); + await switchButton.click(); + + await waitNextFrame(page, 200); + + // switches to embed view + const toEmbedButton = cardToolbar.getByRole('button', { + name: 'Embed view', + }); + await toEmbedButton.click(); + + await waitNextFrame(page, 200); + const syncedDocBlock = page.locator('affine-embed-synced-doc-block'); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + const syncedDocBlockTitle = syncedDocBlock.locator( + '.affine-embed-synced-doc-title' + ); + await expect(syncedDocBlockTitle).toHaveText('title0'); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + editButton = cardToolbar.getByRole('button', { name: 'Edit' }); + await editButton.click(); + + await waitNextFrame(page, 200); + const editModal = page.locator('embed-card-edit-modal'); + const cancelButton = editModal.getByRole('button', { name: 'Cancel' }); + const saveButton = editModal.getByRole('button', { name: 'Save' }); + + // closes edit-model + await cancelButton.click(); + + await waitNextFrame(page, 200); + await expect(editModal).not.toBeVisible(); + + await syncedDocBlock.click(); + + await waitNextFrame(page, 200); + await editButton.click(); + + await waitNextFrame(page, 200); + + // title alias + await type(page, 'page0-title0'); + await page.keyboard.press('Tab'); + // description alias + await type(page, 'This is a new description'); + + // saves aliases + await saveButton.click(); + + await waitNextFrame(page, 200); + + // automatically switch to card view + await expect(syncedDocBlock).not.toBeVisible(); + + await expect(linkedDocBlock).toBeVisible(); + const linkedDocBlockTitle = linkedDocBlock.locator( + '.affine-embed-linked-doc-content-title-text' + ); + await expect(linkedDocBlockTitle).toHaveText('page0-title0'); + await expect( + linkedDocBlock.locator('.affine-embed-linked-doc-content-note.alias') + ).toHaveText('This is a new description'); + }); +}); diff --git a/yarn.lock b/yarn.lock index ca955e4d3030..30332f2eb14d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1482,7 +1482,7 @@ __metadata: "@blocksuite/affine-shared": "workspace:*" "@blocksuite/block-std": "workspace:*" "@blocksuite/global": "workspace:*" - "@blocksuite/icons": "npm:^2.1.70" + "@blocksuite/icons": "npm:^2.1.75" "@blocksuite/inline": "workspace:*" "@blocksuite/store": "workspace:*" "@floating-ui/dom": "npm:^1.6.10" @@ -1571,7 +1571,7 @@ __metadata: "@blocksuite/affine-shared": "workspace:*" "@blocksuite/block-std": "workspace:*" "@blocksuite/global": "workspace:*" - "@blocksuite/icons": "npm:^2.1.70" + "@blocksuite/icons": "npm:^2.1.75" "@blocksuite/inline": "workspace:*" "@blocksuite/store": "workspace:*" "@floating-ui/dom": "npm:^1.6.10" @@ -1692,7 +1692,7 @@ __metadata: "@blocksuite/block-std": "workspace:*" "@blocksuite/data-view": "workspace:*" "@blocksuite/global": "workspace:*" - "@blocksuite/icons": "npm:^2.1.70" + "@blocksuite/icons": "npm:^2.1.75" "@blocksuite/inline": "workspace:*" "@blocksuite/store": "workspace:*" "@floating-ui/dom": "npm:^1.6.10" @@ -1745,7 +1745,7 @@ __metadata: "@blocksuite/affine-shared": "workspace:*" "@blocksuite/block-std": "workspace:*" "@blocksuite/global": "workspace:*" - "@blocksuite/icons": "npm:^2.1.70" + "@blocksuite/icons": "npm:^2.1.75" "@blocksuite/store": "workspace:*" "@emotion/hash": "npm:^0.9.2" "@floating-ui/dom": "npm:^1.6.10" @@ -1798,9 +1798,9 @@ __metadata: languageName: unknown linkType: soft -"@blocksuite/icons@npm:^2.1.70": - version: 2.1.74 - resolution: "@blocksuite/icons@npm:2.1.74" +"@blocksuite/icons@npm:^2.1.75": + version: 2.1.75 + resolution: "@blocksuite/icons@npm:2.1.75" peerDependencies: "@types/react": ^18.0.25 lit: ^3.1.1 @@ -1810,7 +1810,7 @@ __metadata: optional: true react: optional: true - checksum: 10/84bdcab64c09d5a15fdbe97d753a9b994bdf00b1606c7672417e199b031a8d2f3f549b6f913ee786259739b11e9f61a44f997ac41950afa4a327151ab140c5ba + checksum: 10/25e8c25630690b7b1b13761593d9f776c3e78a7be6db4bd30f2c3a25134acd22a41a3258e324ac843ea04cfc9a2c29456563a254a6e7e627043b5248a82cb778 languageName: node linkType: hard