import { toDomPrecision } from '@tldraw/primitives' import { AssetRecordType, TLAssetId, TLBookmarkAsset, TLBookmarkShape } from '@tldraw/tlschema' import { debounce, getHashForString } from '@tldraw/utils' import { HTMLContainer } from '../../../components/HTMLContainer' import { DEFAULT_BOOKMARK_HEIGHT, DEFAULT_BOOKMARK_WIDTH, ROTATING_SHADOWS, } from '../../../constants' import { rotateBoxShadow, stopEventPropagation, truncateStringWithEllipsis, } from '../../../utils/dom' import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' import { TLOnBeforeCreateHandler, TLOnBeforeUpdateHandler } from '../ShapeUtil' import { HyperlinkButton } from '../shared/HyperlinkButton' /** @public */ export class BookmarkShapeUtil extends BaseBoxShapeUtil { static override type = 'bookmark' override canResize = () => false override hideSelectionBoundsBg = () => true override hideSelectionBoundsFg = () => true override defaultProps(): TLBookmarkShape['props'] { return { url: '', w: DEFAULT_BOOKMARK_WIDTH, h: DEFAULT_BOOKMARK_HEIGHT, assetId: null, } } override render(shape: TLBookmarkShape) { const asset = ( shape.props.assetId ? this.editor.getAssetById(shape.props.assetId) : null ) as TLBookmarkAsset const pageRotation = this.editor.getPageRotation(shape) const address = this.getHumanReadableAddress(shape) return (
{asset?.props.image ? ( {asset?.props.title ) : (
)}
{asset?.props.title && (

{truncateStringWithEllipsis(asset?.props.title || '', 54)}

)} {asset?.props.description && (

{truncateStringWithEllipsis(asset?.props.description || '', 128)}

)} {truncateStringWithEllipsis(address, 45)}
) } override indicator(shape: TLBookmarkShape) { return ( ) } override onBeforeCreate?: TLOnBeforeCreateHandler = (shape) => { this.updateBookmarkAsset(shape) } override onBeforeUpdate?: TLOnBeforeUpdateHandler = (prev, shape) => { if (prev.props.url !== shape.props.url) { this.updateBookmarkAsset(shape) } } getHumanReadableAddress(shape: TLBookmarkShape) { try { const url = new URL(shape.props.url) const path = url.pathname.replace(/\/*$/, '') return `${url.hostname}${path}` } catch (e) { return shape.props.url } } protected updateBookmarkAsset = debounce((shape: TLBookmarkShape) => { const { url } = shape.props const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url)) const existing = this.editor.getAssetById(assetId) if (existing) { // If there's an existing asset with the same URL, use // its asset id instead. if (shape.props.assetId !== existing.id) { this.editor.updateShapes([ { id: shape.id, type: shape.type, props: { assetId }, }, ]) } } else if (this.editor.onCreateBookmarkFromUrl) { // Create a bookmark asset for the URL. First get its meta // data, then create the asset and update the shape. this.editor.onCreateBookmarkFromUrl(url).then((meta) => { if (!meta) { this.editor.updateShapes([ { id: shape.id, type: shape.type, props: { assetId: undefined }, }, ]) return } this.editor.batch(() => { this.editor .createAssets([ { id: assetId, typeName: 'asset', type: 'bookmark', props: { src: url, description: meta.description, image: meta.image, title: meta.title, }, }, ]) .updateShapes([ { id: shape.id, type: shape.type, props: { assetId }, }, ]) }) }) } }, 500) }