mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-10 05:29:08 +08:00
83670ab335
Closes: [BS-3122](https://linear.app/affine-design/issue/BS-3122/footnote-definition-adapter-适配) Closes: [BS-3123](https://linear.app/affine-design/issue/BS-3123/几个-block-card-view-适配-footnote-态) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new citation card component and web element for displaying citations. - Added support for citation-style rendering in attachment, bookmark, and linked document blocks. - Enabled citation parsing from footnote definitions in markdown for attachments, bookmarks, and linked docs. - Added a feature flag to enable or disable citation features. - Provided new toolbar logic to disable downloads for citation-style attachments. - **Improvements** - Updated block models and properties to support citation identifiers. - Added localization and settings for the citation experimental feature. - Enhanced markdown adapters to recognize and process citation footnotes. - Included new constants and styles for citation card display. - **Bug Fixes** - Ensured readonly state is respected in block interactions and rendering for citation blocks. - **Documentation** - Added exports and effects for new citation components and features. - **Tests** - Updated snapshots to include citation-related properties in block data. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
import {
|
|
CaptionedBlockComponent,
|
|
SelectedStyle,
|
|
} from '@blocksuite/affine-components/caption';
|
|
import type { BookmarkBlockModel } from '@blocksuite/affine-model';
|
|
import { DocModeProvider } from '@blocksuite/affine-shared/services';
|
|
import { BlockSelection } from '@blocksuite/std';
|
|
import { computed, type ReadonlySignal } 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';
|
|
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
|
|
|
import { refreshBookmarkUrlData } from './utils.js';
|
|
|
|
export const BOOKMARK_MIN_WIDTH = 450;
|
|
|
|
export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBlockModel> {
|
|
selectedStyle$: ReadonlySignal<ClassInfo> | null = computed<ClassInfo>(
|
|
() => ({
|
|
'selected-style': this.selected$.value,
|
|
})
|
|
);
|
|
|
|
private _fetchAbortController?: AbortController;
|
|
|
|
blockDraggable = true;
|
|
|
|
protected containerStyleMap!: ReturnType<typeof styleMap>;
|
|
|
|
selectBlock = () => {
|
|
const selectionManager = this.std.selection;
|
|
const blockSelection = selectionManager.create(BlockSelection, {
|
|
blockId: this.blockId,
|
|
});
|
|
selectionManager.setGroup('note', [blockSelection]);
|
|
};
|
|
|
|
open = () => {
|
|
let link = this.model.props.url;
|
|
if (!link.match(/^[a-zA-Z]+:\/\//)) {
|
|
link = 'https://' + link;
|
|
}
|
|
window.open(link, '_blank');
|
|
};
|
|
|
|
refreshData = () => {
|
|
refreshBookmarkUrlData(this, this._fetchAbortController?.signal).catch(
|
|
console.error
|
|
);
|
|
};
|
|
|
|
get isCitation() {
|
|
return (
|
|
!!this.model.props.footnoteIdentifier &&
|
|
this.model.props.style === 'citation'
|
|
);
|
|
}
|
|
|
|
private readonly _renderCitationView = () => {
|
|
const { title, description, url, icon, footnoteIdentifier } =
|
|
this.model.props;
|
|
return html`
|
|
<affine-citation-card
|
|
.icon=${icon}
|
|
.citationTitle=${title || url}
|
|
.citationContent=${description}
|
|
.citationIdentifier=${footnoteIdentifier}
|
|
.onClickCallback=${this.selectBlock}
|
|
.onDoubleClickCallback=${this.open}
|
|
.active=${this.selected$.value}
|
|
></affine-citation-card>
|
|
`;
|
|
};
|
|
|
|
private readonly _renderCardView = () => {
|
|
return html`<bookmark-card
|
|
.bookmark=${this}
|
|
.loading=${this.loading}
|
|
.error=${this.error}
|
|
></bookmark-card>`;
|
|
};
|
|
|
|
override connectedCallback() {
|
|
super.connectedCallback();
|
|
|
|
const mode = this.std.get(DocModeProvider).getEditorMode();
|
|
const miniWidth = `${BOOKMARK_MIN_WIDTH}px`;
|
|
|
|
this.containerStyleMap = styleMap({
|
|
position: 'relative',
|
|
width: '100%',
|
|
...(mode === 'edgeless' ? { miniWidth } : {}),
|
|
});
|
|
|
|
this._fetchAbortController = new AbortController();
|
|
|
|
this.contentEditable = 'false';
|
|
|
|
if (!this.model.props.description && !this.model.props.title) {
|
|
if (this.doc.readonly) {
|
|
return;
|
|
}
|
|
this.refreshData();
|
|
}
|
|
|
|
this.disposables.add(
|
|
this.model.propsUpdated.subscribe(({ key }) => {
|
|
if (key === 'url') {
|
|
this.refreshData();
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
override disconnectedCallback(): void {
|
|
super.disconnectedCallback();
|
|
this._fetchAbortController?.abort();
|
|
}
|
|
|
|
override renderBlock() {
|
|
return html`
|
|
<div
|
|
draggable="${this.blockDraggable ? 'true' : 'false'}"
|
|
class=${classMap({
|
|
'affine-bookmark-container': true,
|
|
...this.selectedStyle$?.value,
|
|
})}
|
|
style=${this.containerStyleMap}
|
|
>
|
|
${this.isCitation ? this._renderCitationView() : this._renderCardView()}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
protected override accessor blockContainerStyles: StyleInfo = {
|
|
margin: '18px 0',
|
|
};
|
|
|
|
@query('bookmark-card')
|
|
accessor bookmarkCard!: HTMLElement;
|
|
|
|
@property({ attribute: false })
|
|
accessor error = false;
|
|
|
|
@property({ attribute: false })
|
|
accessor loading = false;
|
|
|
|
override accessor selectedStyle = SelectedStyle.Border;
|
|
|
|
override accessor useCaptionEditor = true;
|
|
|
|
override accessor useZeroWidth = true;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'affine-bookmark': BookmarkBlockComponent;
|
|
}
|
|
}
|