import type { AttachmentBlockModel, BookmarkBlockModel, CodeBlockModel, DatabaseBlockModel, ImageBlockModel, ListBlockModel, ParagraphBlockModel, RootBlockModel, } from '@blocksuite/affine-model'; import { DocDisplayMetaProvider } from '@blocksuite/affine-shared/services'; import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; import { noop } from '@blocksuite/global/utils'; import { LinkedPageIcon } from '@blocksuite/icons/lit'; import { ShadowlessElement } from '@blocksuite/std'; import type { BlockModel, DeltaInsert } from '@blocksuite/store'; import { consume } from '@lit/context'; import { html, nothing } from 'lit'; import { property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { placeholderMap, previewIconMap, type TocContext, tocContext, } from '../config.js'; import { isHeadingBlock, isRootBlock } from '../utils/query.js'; import * as styles from './outline-preview.css'; function assertType(value: unknown): asserts value is T { noop(value); } export const AFFINE_OUTLINE_BLOCK_PREVIEW = 'affine-outline-block-preview'; export class OutlineBlockPreview extends SignalWatcher( WithDisposable(ShadowlessElement) ) { private get _docDisplayMetaService() { return this._context.editor$.value.std.get(DocDisplayMetaProvider); } private _TextBlockPreview(block: ParagraphBlockModel | ListBlockModel) { const deltas: DeltaInsert[] = block.props.text.yText.toDelta(); if (!block.props.text.length) return nothing; const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon; const previewText = deltas.map(delta => { if (delta.attributes?.reference) { // If linked doc, render linked doc icon and the doc title. const refAttribute = delta.attributes.reference; const refMeta = block.store.workspace.meta.docMetas.find( doc => doc.id === refAttribute.pageId ); const unavailable = !refMeta; const icon = unavailable ? LinkedPageIcon({ width: '1.1em', height: '1.1em' }) : this._docDisplayMetaService.icon(refMeta.id).value; const title = unavailable ? 'Deleted doc' : this._docDisplayMetaService.title(refMeta.id).value; return html` ${icon} ${title.length ? title : 'Untitled'}`; } else { // If not linked doc, render the text. return delta.insert.toString().trim().length > 0 ? html`${delta.insert.toString()}` : nothing; } }); const headingClass = block.props.type in styles.subtypeStyles ? styles.subtypeStyles[ block.props.type as keyof typeof styles.subtypeStyles ] : ''; return html`${previewText} ${ this._context.showIcons$.value ? html`${previewIconMap[block.props.type]}` : nothing }`; } override render() { return html`
${this.renderBlockByFlavour()}
`; } renderBlockByFlavour() { const { block } = this; const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon; if ( !this._context.enableSorting$.value && !isHeadingBlock(block) && !isRootBlock(block) ) return nothing; const showPreviewIcon = this._context.showIcons$.value; switch (block.flavour) { case 'affine:page': assertType(block); return block.props.title.length > 0 ? html` ${block.props.title$.value} ` : nothing; case 'affine:paragraph': assertType(block); return this._TextBlockPreview(block); case 'affine:list': assertType(block); return this._TextBlockPreview(block); case 'affine:bookmark': assertType(block); return html` ${ block.props.title || block.props.url || placeholderMap['bookmark'] } ${ showPreviewIcon ? html`${previewIconMap['bookmark']}` : nothing } `; case 'affine:code': assertType(block); return html` ${block.props.language ?? placeholderMap['code']} ${ showPreviewIcon ? html`${previewIconMap['code']}` : nothing } `; case 'affine:database': assertType(block); return html` ${ block.props.title.toString().length ? block.props.title.toString() : placeholderMap['database'] } ${ showPreviewIcon ? html`${previewIconMap['table']}` : nothing } `; case 'affine:image': assertType(block); return html` ${ block.props.caption?.length ? block.props.caption : placeholderMap['image'] } ${ showPreviewIcon ? html`${previewIconMap['image']}` : nothing } `; case 'affine:attachment': assertType(block); return html` ${ block.props.name?.length ? block.props.name : placeholderMap['attachment'] } ${ showPreviewIcon ? html`${previewIconMap['attachment']}` : nothing } `; default: return nothing; } } @property({ attribute: false }) accessor block!: BlockModel; @property({ attribute: false }) accessor disabledIcon = false; @consume({ context: tocContext }) private accessor _context!: TocContext; } declare global { interface HTMLElementTagNameMap { [AFFINE_OUTLINE_BLOCK_PREVIEW]: OutlineBlockPreview; } }