diff --git a/blocksuite/affine/blocks/block-surface-ref/src/components/index.ts b/blocksuite/affine/blocks/block-surface-ref/src/components/index.ts index 284bdca8b4..824d3080aa 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/components/index.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/components/index.ts @@ -1 +1,2 @@ +export { SurfaceRefPlaceHolder } from './placeholder'; export { SurfaceRefToolbarTitle } from './surface-ref-toolbar-title'; diff --git a/blocksuite/affine/blocks/block-surface-ref/src/components/placeholder.ts b/blocksuite/affine/blocks/block-surface-ref/src/components/placeholder.ts new file mode 100644 index 0000000000..9ebea549d1 --- /dev/null +++ b/blocksuite/affine/blocks/block-surface-ref/src/components/placeholder.ts @@ -0,0 +1,122 @@ +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; +import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; +import { DeleteIcon } from '@blocksuite/icons/lit'; +import { ShadowlessElement } from '@blocksuite/std'; +import { type GfxModel } from '@blocksuite/std/gfx'; +import { css, html, nothing } from 'lit'; +import { property } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { SurfaceRefNotFoundBackground } from '../icons'; +import { getReferenceModelTitle, TYPE_ICON_MAP } from '../utils'; + +export class SurfaceRefPlaceHolder extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + .surface-ref-placeholder { + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px; + } + + .surface-ref-placeholder.not-found { + background: ${unsafeCSSVarV2('layer/background/secondary', '#F5F5F5')}; + } + + .surface-ref-placeholder-heading { + position: relative; + display: flex; + align-items: center; + gap: 8px; + align-self: stretch; + + font-size: 14px; + font-weight: 500; + line-height: 22px; + + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + color: ${unsafeCSSVarV2('text/primary', '#141414')}; + } + + .surface-ref-placeholder-body { + position: relative; + font-size: 12px; + font-weight: 400; + line-height: 20px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + color: ${unsafeCSSVarV2('text/disable', '#7a7a7a')}; + } + + .surface-ref-not-found-background { + position: absolute; + right: 12px; + bottom: -5px; + } + `; + + @property({ attribute: false }) + accessor referenceModel: GfxModel | null = null; + + @property({ attribute: false }) + accessor refFlavour = ''; + + @property({ attribute: false }) + accessor inEdgeless = false; + + override render() { + const { referenceModel, refFlavour, inEdgeless } = this; + + // When surface ref is in page mode and reference exists, don't render placeholder + if (referenceModel && !inEdgeless) return nothing; + + const modelNotFound = !referenceModel; + const matchedType = TYPE_ICON_MAP[refFlavour] ?? TYPE_ICON_MAP['edgeless']; + + const title = + (referenceModel && getReferenceModelTitle(referenceModel)) ?? + matchedType.name; + + return html` +
+ ${modelNotFound + ? html`
+ ${SurfaceRefNotFoundBackground} +
` + : nothing} +
+ ${modelNotFound ? DeleteIcon() : matchedType.icon} + + ${modelNotFound + ? `This ${matchedType.name} not available` + : `${title}`} + +
+
+ + ${modelNotFound + ? `The ${matchedType.name.toLowerCase()} is deleted or not in this doc.` + : `The ${matchedType.name.toLowerCase()} is inserted but cannot display in edgeless mode. Switch to page mode to view the block.`} + +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'surface-ref-placeholder': SurfaceRefPlaceHolder; + } +} diff --git a/blocksuite/affine/blocks/block-surface-ref/src/components/surface-ref-toolbar-title.ts b/blocksuite/affine/blocks/block-surface-ref/src/components/surface-ref-toolbar-title.ts index 2364c5dde6..cfca3549c1 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/components/surface-ref-toolbar-title.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/components/surface-ref-toolbar-title.ts @@ -66,3 +66,9 @@ export class SurfaceRefToolbarTitle extends ShadowlessElement { return html`${icon}${title}`; } } + +declare global { + interface HTMLElementTagNameMap { + 'surface-ref-toolbar-title': SurfaceRefToolbarTitle; + } +} diff --git a/blocksuite/affine/blocks/block-surface-ref/src/configs/toolbar.ts b/blocksuite/affine/blocks/block-surface-ref/src/configs/toolbar.ts index b890208af0..33fe225217 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/configs/toolbar.ts @@ -17,6 +17,8 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = { actions: [ { id: 'a.surface-ref-title', + when: ctx => + !!ctx.getCurrentBlockByType(SurfaceRefBlockComponent)?.referenceModel, content: ctx => { const surfaceRefBlock = ctx.getCurrentBlockByType( SurfaceRefBlockComponent diff --git a/blocksuite/affine/blocks/block-surface-ref/src/effects.ts b/blocksuite/affine/blocks/block-surface-ref/src/effects.ts index 27bf90ea87..787de1a2b4 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/effects.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/effects.ts @@ -1,4 +1,4 @@ -import { SurfaceRefToolbarTitle } from './components'; +import { SurfaceRefPlaceHolder, SurfaceRefToolbarTitle } from './components'; import { SurfaceRefGenericBlockPortal } from './portal/generic-block'; import { SurfaceRefNotePortal } from './portal/note'; import { SurfaceRefBlockComponent } from './surface-ref-block'; @@ -16,10 +16,5 @@ export function effects() { ); customElements.define('surface-ref-note-portal', SurfaceRefNotePortal); customElements.define('surface-ref-toolbar-title', SurfaceRefToolbarTitle); -} - -declare global { - interface HTMLElementTagNameMap { - 'surface-ref-toolbar-title': SurfaceRefToolbarTitle; - } + customElements.define('surface-ref-placeholder', SurfaceRefPlaceHolder); } diff --git a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block-edgeless.ts b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block-edgeless.ts index 177ee33d8c..0762ff3162 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block-edgeless.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block-edgeless.ts @@ -1,52 +1,11 @@ -import { isFrameBlock } from '@blocksuite/affine-block-frame'; -import { - GroupElementModel, - type SurfaceRefBlockModel, -} from '@blocksuite/affine-model'; +import { type SurfaceRefBlockModel } from '@blocksuite/affine-model'; import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; -import { - DeleteIcon, - EdgelessIcon, - FrameIcon, - GroupIcon, - MindmapIcon, -} from '@blocksuite/icons/lit'; -import { BlockComponent } from '@blocksuite/std'; -import { - GfxControllerIdentifier, - type GfxModel, - isPrimitiveModel, -} from '@blocksuite/std/gfx'; -import { css, html, nothing } from 'lit'; +import { BlockComponent, BlockSelection } from '@blocksuite/std'; +import { GfxControllerIdentifier, type GfxModel } from '@blocksuite/std/gfx'; +import { css, html } from 'lit'; import { state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { SurfaceRefNotFoundBackground } from './icons'; - -const TYPE_ICON_MAP: { - [key: string]: { - name: string; - icon: typeof DeleteIcon; - }; -} = { - 'affine:frame': { - name: 'Frame', - icon: FrameIcon, - }, - group: { - name: 'Group', - icon: GroupIcon, - }, - mindmap: { - name: 'Mind map', - icon: MindmapIcon, - }, - edgeless: { - name: 'Edgeless content', - icon: EdgelessIcon, - }, -}; - export class EdgelessSurfaceRefBlockComponent extends BlockComponent { static override styles = css` affine-edgeless-surface-ref { @@ -58,50 +17,11 @@ export class EdgelessSurfaceRefBlockComponent extends BlockComponent .surface-ref-heading { - display: flex; - align-items: center; - gap: 8px; - align-self: stretch; - - font-size: 14px; - font-weight: 500; - line-height: 22px; - - text-overflow: ellipsis; - overflow: hidden; - - color: ${unsafeCSSVarV2('text/primary', '#141414')}; - } - - .edgeless-surface-ref-content > .surface-ref-heading svg { - color: ${unsafeCSSVarV2('text/primary', '#141414')}; - } - - .edgeless-surface-ref-content > .surface-ref-body { - font-size: 12px; - font-weight: 400; - line-height: 20px; - color: ${unsafeCSSVarV2('text/disable', '#7a7a7a')}; + .affine-edgeless-surface-ref-container.focused { + border-color: ${unsafeCSSVarV2('edgeless/frame/border/active')}; } `; @@ -113,6 +33,18 @@ export class EdgelessSurfaceRefBlockComponent extends BlockComponent { + this._focused = selList.some( + sel => sel.blockId === this.blockId && sel.is(BlockSelection) + ); + }) + ); } get gfx() { @@ -122,60 +54,21 @@ export class EdgelessSurfaceRefBlockComponent extends BlockComponent -
- ${modelNotFound - ? DeleteIcon({ width: '16px', height: '16px' }) - : matchedType.icon({ width: '16px', height: '16px' })} - - ${modelNotFound - ? `This ${matchedType.name} not available` - : `${title}`} - -
-
- - ${modelNotFound - ? `The ${matchedType.name.toLowerCase()} is deleted or not in this doc.` - : `The ${matchedType.name.toLowerCase()} is inserted but cannot display in edgeless mode. Switch to page mode to view the block.`} - -
- - ${modelNotFound - ? html`
- ${SurfaceRefNotFoundBackground} -
` - : nothing} - `; - } + @state() + accessor _focused = false; override renderBlock() { return html`
- ${this._renderRefContent(this._referenceModel)} +
`; } } diff --git a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts index cf1fbe50fd..989dcc88f0 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts @@ -32,7 +32,6 @@ import { type SerializedXYWH, } from '@blocksuite/global/gfx'; import { assertType } from '@blocksuite/global/utils'; -import { DeleteIcon } from '@blocksuite/icons/lit'; import { BlockComponent, BlockSelection, @@ -55,20 +54,9 @@ import { classMap } from 'lit/directives/class-map.js'; import { guard } from 'lit/directives/guard.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { noContentPlaceholder } from './utils.js'; - -const NO_CONTENT_TITLE = { - 'affine:frame': 'Frame', - group: 'Group', - DEFAULT: 'Content', -} as Record; - -const NO_CONTENT_REASON = { - group: 'This content was ungrouped or deleted on edgeless mode', - DEFAULT: 'This content was deleted on edgeless mode', -} as Record; - -@Peekable() +@Peekable({ + enableOn: (block: SurfaceRefBlockComponent) => !!block.referenceModel, +}) export class SurfaceRefBlockComponent extends BlockComponent { static override styles = css` affine-surface-ref { @@ -101,70 +89,6 @@ export class SurfaceRefBlockComponent extends BlockComponent .icon > svg { - color: var(--affine-icon-color); - width: 16px; - height: 16px; - display: block; - } - - .placeholder-reason { - margin: 72px auto 0; - padding: 10px; - - text-align: center; - font-size: 12px; - font-family: var(--affine-font-family); - line-height: 20px; - - color: var(--affine-warning-color); - background-color: var(--affine-background-error-color); - } - .ref-content { position: relative; background-color: var(--affine-background-primary-color); @@ -206,10 +130,6 @@ export class SurfaceRefBlockComponent extends BlockComponent { return [this.selection.create(BlockSelection, { blockId: this.blockId })]; @@ -474,27 +394,6 @@ export class SurfaceRefBlockComponent extends BlockComponent`; } - private _renderRefPlaceholder(model: SurfaceRefBlockModel) { - return html`
-
${noContentPlaceholder}
-
- No Such - ${NO_CONTENT_TITLE[model.props.refFlavour ?? 'DEFAULT'] ?? - NO_CONTENT_TITLE.DEFAULT} -
-
- -
-
- ${NO_CONTENT_REASON[model.props.refFlavour ?? 'DEFAULT'] ?? - NO_CONTENT_REASON.DEFAULT} -
-
`; - } - readonly open = ({ openMode, event, @@ -537,7 +436,10 @@ export class SurfaceRefBlockComponent extends BlockComponent` : this._renderRefContent(_referencedModel); const edgelessTheme = this.std.get(ThemeProvider).edgeless$.value; diff --git a/blocksuite/affine/blocks/block-surface-ref/src/utils.ts b/blocksuite/affine/blocks/block-surface-ref/src/utils.ts index 7544b18993..f63baa8aec 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/utils.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/utils.ts @@ -1,11 +1,23 @@ +import { isFrameBlock } from '@blocksuite/affine-block-frame'; import type { SurfaceBlockComponent } from '@blocksuite/affine-block-surface'; import { ExportManager } from '@blocksuite/affine-block-surface'; import type { SurfaceRefBlockComponent } from '@blocksuite/affine-block-surface-ref'; +import { + GroupElementModel, + MindmapElementModel, + ShapeElementModel, +} from '@blocksuite/affine-model'; import { BlockSuiteError } from '@blocksuite/global/exceptions'; import { Bound } from '@blocksuite/global/gfx'; import { assertType } from '@blocksuite/global/utils'; -import { GfxControllerIdentifier } from '@blocksuite/std/gfx'; -import { html } from 'lit'; +import { + EdgelessIcon, + FrameIcon, + GroupIcon, + MindmapIcon, +} from '@blocksuite/icons/lit'; +import { GfxControllerIdentifier, type GfxModel } from '@blocksuite/std/gfx'; +import { html, type TemplateResult } from 'lit'; export const noContentPlaceholder = html` { await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]); }; + +export const TYPE_ICON_MAP: { + [key: string]: { + name: string; + icon: TemplateResult; + }; +} = { + 'affine:frame': { + name: 'Frame', + icon: FrameIcon(), + }, + group: { + name: 'Group', + icon: GroupIcon(), + }, + mindmap: { + name: 'Mind map', + icon: MindmapIcon(), + }, + edgeless: { + name: 'Edgeless content', + icon: EdgelessIcon(), + }, +}; + +export const getReferenceModelTitle = (model: GfxModel) => { + if (model instanceof GroupElementModel) { + return model.title.toString(); + } + if (isFrameBlock(model)) { + return model.props.title.toString(); + } + if (model instanceof MindmapElementModel) { + const rootElement = model.tree.element; + if (rootElement instanceof ShapeElementModel) { + return rootElement.text?.toString() ?? ''; + } + } + return null; +};