Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 1s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Mobile (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
247 lines
7.5 KiB
TypeScript
247 lines
7.5 KiB
TypeScript
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<T>(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<AffineTextAttributes>[] =
|
|
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`<span
|
|
class=${classMap({
|
|
[styles.linkedDocPreviewUnavailable]: unavailable,
|
|
[styles.linkedDocPreviewAvailable]: !unavailable,
|
|
})}
|
|
>
|
|
${icon}
|
|
<span
|
|
class=${classMap({
|
|
[styles.linkedDocText]: true,
|
|
[styles.linkedDocTextUnavailable]: unavailable,
|
|
})}
|
|
>${title.length ? title : 'Untitled'}</span
|
|
></span
|
|
>`;
|
|
} else {
|
|
// If not linked doc, render the text.
|
|
return delta.insert.toString().trim().length > 0
|
|
? html`<span class=${styles.textSpan}
|
|
>${delta.insert.toString()}</span
|
|
>`
|
|
: nothing;
|
|
}
|
|
});
|
|
|
|
const headingClass =
|
|
block.props.type in styles.subtypeStyles
|
|
? styles.subtypeStyles[
|
|
block.props.type as keyof typeof styles.subtypeStyles
|
|
]
|
|
: '';
|
|
|
|
return html`<span
|
|
data-testid="outline-block-preview-${block.props.type}"
|
|
class="${styles.text} ${styles.textGeneral} ${headingClass}"
|
|
>${previewText}</span
|
|
>
|
|
${
|
|
this._context.showIcons$.value
|
|
? html`<span class=${iconClass}
|
|
>${previewIconMap[block.props.type]}</span
|
|
>`
|
|
: nothing
|
|
}`;
|
|
}
|
|
|
|
override render() {
|
|
return html`<div class=${styles.outlineBlockPreview}>
|
|
${this.renderBlockByFlavour()}
|
|
</div>`;
|
|
}
|
|
|
|
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<RootBlockModel>(block);
|
|
return block.props.title.length > 0
|
|
? html`<span
|
|
data-testid="outline-block-preview-title"
|
|
class="${styles.text} ${styles.subtypeStyles.title}"
|
|
>
|
|
${block.props.title$.value}
|
|
</span>`
|
|
: nothing;
|
|
case 'affine:paragraph':
|
|
assertType<ParagraphBlockModel>(block);
|
|
return this._TextBlockPreview(block);
|
|
case 'affine:list':
|
|
assertType<ListBlockModel>(block);
|
|
return this._TextBlockPreview(block);
|
|
case 'affine:bookmark':
|
|
assertType<BookmarkBlockModel>(block);
|
|
return html`
|
|
<span class="${styles.text} ${styles.textGeneral}"
|
|
>${
|
|
block.props.title || block.props.url || placeholderMap['bookmark']
|
|
}</span
|
|
>
|
|
${
|
|
showPreviewIcon
|
|
? html`<span class=${iconClass}
|
|
>${previewIconMap['bookmark']}</span
|
|
>`
|
|
: nothing
|
|
}
|
|
`;
|
|
case 'affine:code':
|
|
assertType<CodeBlockModel>(block);
|
|
return html`
|
|
<span class="${styles.text} ${styles.textGeneral}"
|
|
>${block.props.language ?? placeholderMap['code']}</span
|
|
>
|
|
${
|
|
showPreviewIcon
|
|
? html`<span class=${iconClass}>${previewIconMap['code']}</span>`
|
|
: nothing
|
|
}
|
|
`;
|
|
case 'affine:database':
|
|
assertType<DatabaseBlockModel>(block);
|
|
return html`
|
|
<span class="${styles.text} ${styles.textGeneral}"
|
|
>${
|
|
block.props.title.toString().length
|
|
? block.props.title.toString()
|
|
: placeholderMap['database']
|
|
}</span
|
|
>
|
|
${
|
|
showPreviewIcon
|
|
? html`<span class=${iconClass}>${previewIconMap['table']}</span>`
|
|
: nothing
|
|
}
|
|
`;
|
|
case 'affine:image':
|
|
assertType<ImageBlockModel>(block);
|
|
return html`
|
|
<span class="${styles.text} ${styles.textGeneral}"
|
|
>${
|
|
block.props.caption?.length
|
|
? block.props.caption
|
|
: placeholderMap['image']
|
|
}</span
|
|
>
|
|
${
|
|
showPreviewIcon
|
|
? html`<span class=${iconClass}>${previewIconMap['image']}</span>`
|
|
: nothing
|
|
}
|
|
`;
|
|
case 'affine:attachment':
|
|
assertType<AttachmentBlockModel>(block);
|
|
return html`
|
|
<span class="${styles.text} ${styles.textGeneral}"
|
|
>${
|
|
block.props.name?.length
|
|
? block.props.name
|
|
: placeholderMap['attachment']
|
|
}</span
|
|
>
|
|
${
|
|
showPreviewIcon
|
|
? html`<span class=${iconClass}
|
|
>${previewIconMap['attachment']}</span
|
|
>`
|
|
: 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;
|
|
}
|
|
}
|