// oxlint-disable typescript/no-empty-object-type import { BaseBoxShapeUtil, HTMLContainer, T, TLAssetId, TLBookmarkAsset, TLBookmarkShape, TLBookmarkShapeProps, bookmarkShapeMigrations, bookmarkShapeProps, lerp, tlenv, useEditor, useSvgExportContext, } from '@tldraw/editor' import classNames from 'classnames' import { PointerEventHandler, useCallback, useState } from 'react' import { convertCommonTitleHTMLEntities } from '../../utils/text/text' import type { ShapeOptionsWithDisplayValues } from '../shared/getDisplayValues' import { HyperlinkButton } from '../shared/HyperlinkButton' import { LINK_ICON } from '../shared/icons-editor' import { getRotatedBoxShadow } from '../shared/rotated-box-shadow' import { BOOKMARK_HEIGHT, BOOKMARK_WIDTH, getHumanReadableAddress, setBookmarkHeight, updateBookmarkAssetOnUrlChange, } from './bookmarks' /** @public */ export type BookmarkShapeUtilDisplayValues = object /** @public */ export interface BookmarkShapeOptions extends ShapeOptionsWithDisplayValues< TLBookmarkShape, BookmarkShapeUtilDisplayValues > {} /** @public */ export class BookmarkShapeUtil extends BaseBoxShapeUtil { static override type = 'bookmark' as const static override props = bookmarkShapeProps static override migrations = bookmarkShapeMigrations override options: BookmarkShapeOptions = { getDefaultDisplayValues(): BookmarkShapeUtilDisplayValues { return {} }, getCustomDisplayValues(): Partial { return {} }, } override canResize(shape: TLBookmarkShape) { return false } override hideSelectionBoundsFg(shape: TLBookmarkShape) { return true } override getText(shape: TLBookmarkShape) { return shape.props.url } override getAriaDescriptor(shape: TLBookmarkShape) { const asset = ( shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null ) as TLBookmarkAsset | null if (!asset?.props.title) return undefined return ( convertCommonTitleHTMLEntities(asset.props.title) + (asset.props.description ? ', ' + asset.props.description : '') ) } override getDefaultProps(): TLBookmarkShape['props'] { return { url: '', w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, assetId: null, } } override component(shape: TLBookmarkShape) { const { assetId, url, h } = shape.props const rotation = this.editor.getShapePageTransform(shape)!.rotation() return } override getIndicatorPath(shape: TLBookmarkShape): Path2D { const path = new Path2D() path.rect(0, 0, shape.props.w, shape.props.h) return path } override onBeforeCreate(next: TLBookmarkShape) { return setBookmarkHeight(this.editor, next) } override onBeforeUpdate(prev: TLBookmarkShape, shape: TLBookmarkShape) { if (prev.props.url !== shape.props.url) { if (!T.linkUrl.isValid(shape.props.url)) { return { ...shape, props: { ...shape.props, url: prev.props.url } } } else { updateBookmarkAssetOnUrlChange(this.editor, shape) } } if (prev.props.assetId !== shape.props.assetId) { return setBookmarkHeight(this.editor, shape) } return undefined } override getInterpolatedProps( startShape: TLBookmarkShape, endShape: TLBookmarkShape, t: number ): TLBookmarkShapeProps { return { ...(t > 0.5 ? endShape.props : startShape.props), w: lerp(startShape.props.w, endShape.props.w, t), h: lerp(startShape.props.h, endShape.props.h, t), } } } export function BookmarkShapeComponent({ assetId, rotation, url, h, showImageContainer = true, }: { assetId: TLAssetId | null rotation: number h: number url: string showImageContainer?: boolean }) { const editor = useEditor() const asset = assetId ? (editor.getAsset(assetId) as TLBookmarkAsset) : null const isSafariExport = !!useSvgExportContext() && tlenv.isSafari const address = getHumanReadableAddress(url) const [isFaviconValid, setIsFaviconValid] = useState(true) const onFaviconError = () => setIsFaviconValid(false) const markAsHandledOnShiftKey = useCallback( (e) => { if (!editor.inputs.getShiftKey()) editor.markEventAsHandled(e) }, [editor] ) return (
{showImageContainer && (!asset || asset.props.image) && (
{asset ? ( {asset?.props.title ) : (
)} {asset?.props.image && }
)}
{asset?.props.title ? (

{convertCommonTitleHTMLEntities(asset.props.title)}

) : null} {asset?.props.description && asset?.props.image ? (

{asset.props.description}

) : null} {isFaviconValid && asset?.props.favicon ? ( {`favicon ) : (
) }