Files
AFFiNE-Mirror/blocksuite/affine/blocks/block-bookmark/src/components/bookmark-card.ts
T
2025-03-28 07:20:34 +00:00

160 lines
4.5 KiB
TypeScript

import { getEmbedCardIcons } from '@blocksuite/affine-block-embed';
import { WebIcon16 } from '@blocksuite/affine-components/icons';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { getHostName } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/lit';
import { OpenInNewIcon } from '@blocksuite/icons/lit';
import { BlockSelection, ShadowlessElement } from '@blocksuite/std';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import type { BookmarkBlockComponent } from '../bookmark-block.js';
import { styles } from '../styles.js';
export class BookmarkCard extends WithDisposable(ShadowlessElement) {
static override styles = styles;
private _handleClick(event: MouseEvent) {
event.stopPropagation();
const model = this.bookmark.model;
if (model.parent?.flavour !== 'affine:surface') {
this._selectBlock();
}
}
private _handleDoubleClick(event: MouseEvent) {
event.stopPropagation();
this.bookmark.open();
}
private _selectBlock() {
const selectionManager = this.bookmark.host.selection;
const blockSelection = selectionManager.create(BlockSelection, {
blockId: this.bookmark.blockId,
});
selectionManager.setGroup('note', [blockSelection]);
}
override connectedCallback(): void {
super.connectedCallback();
this.disposables.add(
this.bookmark.model.propsUpdated.subscribe(() => {
this.requestUpdate();
})
);
this.disposables.add(
this.bookmark.std
.get(ThemeProvider)
.theme$.subscribe(() => this.requestUpdate())
);
}
override render() {
const { icon, title, url, description, image, style } =
this.bookmark.model.props;
const cardClassMap = classMap({
loading: this.loading,
error: this.error,
[style]: true,
selected: this.bookmark.selected$.value,
});
const domainName = url.match(
/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n]+)/im
)?.[1];
const titleText = this.loading
? 'Loading...'
: !title
? this.error
? (domainName ?? 'Link card')
: ''
: title;
const theme = this.bookmark.std.get(ThemeProvider).theme;
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIconType =
!icon?.split('.').pop() || icon?.split('.').pop() === 'svg'
? 'svg+xml'
: icon?.split('.').pop();
const titleIcon = this.loading
? LoadingIcon
: icon
? html`<object
type="image/${titleIconType}"
data=${icon}
draggable="false"
>
${WebIcon16}
</object>`
: WebIcon16;
const descriptionText = this.loading
? ''
: !description
? this.error
? 'Failed to retrieve link information.'
: url
: (description ?? '');
const bannerImage =
!this.loading && image
? html`<object type="image/webp" data=${image} draggable="false">
${EmbedCardBannerIcon}
</object>`
: EmbedCardBannerIcon;
return html`
<div
class="affine-bookmark-card ${cardClassMap}"
@click=${this._handleClick}
@dblclick=${this._handleDoubleClick}
>
<div class="affine-bookmark-content">
<div class="affine-bookmark-content-title">
<div class="affine-bookmark-content-title-icon">${titleIcon}</div>
<div class="affine-bookmark-content-title-text">${titleText}</div>
</div>
<div class="affine-bookmark-content-description">
${descriptionText}
</div>
<div
class="affine-bookmark-content-url-wrapper"
@click=${this.bookmark.open}
>
<div class="affine-bookmark-content-url">
<span>${getHostName(url)}</span>
<div class="affine-bookmark-content-url-icon">
${OpenInNewIcon({ width: '12', height: '12' })}
</div>
</div>
</div>
</div>
<div class="affine-bookmark-banner">${bannerImage}</div>
</div>
`;
}
@property({ attribute: false })
accessor bookmark!: BookmarkBlockComponent;
@property({ attribute: false })
accessor error!: boolean;
@property({ attribute: false })
accessor loading!: boolean;
}
declare global {
interface HTMLElementTagNameMap {
'bookmark-card': BookmarkCard;
}
}