mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(editor): add local link preview data for bookmark block (#12085)
Closes: [BS-3343](https://linear.app/affine-design/issue/BS-3343/处理文档-readonly-时-bookmark-citation-preview-data-的获取和显示) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved bookmark blocks to display link previews even in readonly mode by dynamically fetching preview data when needed. - **Refactor** - Enhanced event handling for bookmark cards, providing more consistent behavior when selecting or opening bookmarks. - Refined how preview data is sourced and rendered for bookmarks, ensuring more accurate and up-to-date information is shown. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,10 +2,16 @@ import {
|
||||
CaptionedBlockComponent,
|
||||
SelectedStyle,
|
||||
} from '@blocksuite/affine-components/caption';
|
||||
import type { BookmarkBlockModel } from '@blocksuite/affine-model';
|
||||
import { DocModeProvider } from '@blocksuite/affine-shared/services';
|
||||
import type {
|
||||
BookmarkBlockModel,
|
||||
LinkPreviewData,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeProvider,
|
||||
LinkPreviewerService,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
||||
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
|
||||
@@ -28,6 +34,62 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
|
||||
protected containerStyleMap!: ReturnType<typeof styleMap>;
|
||||
|
||||
/**
|
||||
* @description Local link preview data
|
||||
* When the doc is in readonly mode, and the link preview data are not provided (stored in the block model),
|
||||
* We will use the local link preview data fetched from the link previewer service to render the block.
|
||||
*/
|
||||
private readonly _localLinkPreview$ = signal<LinkPreviewData>({
|
||||
icon: null,
|
||||
title: null,
|
||||
description: null,
|
||||
image: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Link preview data for actual rendering
|
||||
* When the doc is not in readonly mode, and the link preview data are provided (stored in the block model),
|
||||
* We will use the model props to render the block.
|
||||
* Otherwise, we will use the local link preview data to render the block.
|
||||
*/
|
||||
linkPreview$ = computed(() => {
|
||||
const modelProps = this.model.props;
|
||||
const local = this._localLinkPreview$.value;
|
||||
return {
|
||||
icon: modelProps.icon$.value ?? local.icon ?? null,
|
||||
title: modelProps.title$.value ?? local.title ?? null,
|
||||
description: modelProps.description$.value ?? local.description ?? null,
|
||||
image: modelProps.image$.value ?? local.image ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
private readonly _updateLocalLinkPreview = () => {
|
||||
// cancel any inflight request
|
||||
this._fetchAbortController?.abort();
|
||||
this._fetchAbortController = new AbortController();
|
||||
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
|
||||
this.std.store
|
||||
.get(LinkPreviewerService)
|
||||
.query(this.model.props.url, this._fetchAbortController.signal)
|
||||
.then(data => {
|
||||
this._localLinkPreview$.value = {
|
||||
icon: data.icon ?? null,
|
||||
title: data.title ?? null,
|
||||
description: data.description ?? null,
|
||||
image: data.image ?? null,
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
this.error = true;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
selectBlock = () => {
|
||||
const selectionManager = this.std.selection;
|
||||
const blockSelection = selectionManager.create(BlockSelection, {
|
||||
@@ -57,17 +119,30 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
);
|
||||
}
|
||||
|
||||
handleClick = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.model.parent?.flavour !== 'affine:surface' && !this.doc.readonly) {
|
||||
this.selectBlock();
|
||||
}
|
||||
};
|
||||
|
||||
handleDoubleClick = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
this.open();
|
||||
};
|
||||
|
||||
private readonly _renderCitationView = () => {
|
||||
const { title, description, url, icon, footnoteIdentifier } =
|
||||
this.model.props;
|
||||
const { url, footnoteIdentifier } = this.model.props;
|
||||
const { icon, title, description } = this.linkPreview$.value;
|
||||
return html`
|
||||
<affine-citation-card
|
||||
.icon=${icon}
|
||||
.citationTitle=${title || url}
|
||||
.citationContent=${description}
|
||||
.citationIdentifier=${footnoteIdentifier}
|
||||
.onClickCallback=${this.selectBlock}
|
||||
.onDoubleClickCallback=${this.open}
|
||||
.onClickCallback=${this.handleClick}
|
||||
.onDoubleClickCallback=${this.handleDoubleClick}
|
||||
.active=${this.selected$.value}
|
||||
></affine-citation-card>
|
||||
`;
|
||||
@@ -97,10 +172,17 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
|
||||
this.contentEditable = 'false';
|
||||
|
||||
if (!this.model.props.description && !this.model.props.title) {
|
||||
if (
|
||||
(!this.model.props.description && !this.model.props.title) ||
|
||||
(!this.model.props.image && this.model.props.style === 'vertical')
|
||||
) {
|
||||
// When the doc is readonly, and the preview data not provided
|
||||
// We should fetch the preview data and update the local link preview data
|
||||
if (this.doc.readonly) {
|
||||
this._updateLocalLinkPreview();
|
||||
return;
|
||||
}
|
||||
// Otherwise, we should refresh the data to the model props
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,20 +18,6 @@ export class BookmarkCard extends SignalWatcher(
|
||||
) {
|
||||
static override styles = styles;
|
||||
|
||||
private _handleClick(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
const model = this.bookmark.model;
|
||||
|
||||
if (model.parent?.flavour !== 'affine:surface') {
|
||||
this.bookmark.selectBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDoubleClick(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
this.bookmark.open();
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
@@ -49,8 +35,9 @@ export class BookmarkCard extends SignalWatcher(
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { icon, title, url, description, image, style } =
|
||||
this.bookmark.model.props;
|
||||
const { url, style } = this.bookmark.model.props;
|
||||
const { icon, title, description, image } =
|
||||
this.bookmark.linkPreview$.value;
|
||||
|
||||
const cardClassMap = classMap({
|
||||
loading: this.loading,
|
||||
@@ -98,8 +85,8 @@ export class BookmarkCard extends SignalWatcher(
|
||||
return html`
|
||||
<div
|
||||
class="affine-bookmark-card ${cardClassMap}"
|
||||
@click=${this._handleClick}
|
||||
@dblclick=${this._handleDoubleClick}
|
||||
@click=${this.bookmark.handleClick}
|
||||
@dblclick=${this.bookmark.handleDoubleClick}
|
||||
>
|
||||
<div class="affine-bookmark-content">
|
||||
<div class="affine-bookmark-content-title">
|
||||
|
||||
Reference in New Issue
Block a user