kopia lustrzana https://github.com/Tldraw/Tldraw
195 wiersze
4.9 KiB
TypeScript
195 wiersze
4.9 KiB
TypeScript
import {
|
|
AssetRecordType,
|
|
BaseBoxShapeUtil,
|
|
Editor,
|
|
HTMLContainer,
|
|
T,
|
|
TLAssetId,
|
|
TLBookmarkAsset,
|
|
TLBookmarkShape,
|
|
TLOnBeforeCreateHandler,
|
|
TLOnBeforeUpdateHandler,
|
|
bookmarkShapeMigrations,
|
|
bookmarkShapeProps,
|
|
debounce,
|
|
getHashForString,
|
|
stopEventPropagation,
|
|
toDomPrecision,
|
|
} from '@tldraw/editor'
|
|
import { truncateStringWithEllipsis } from '../../utils/text/text'
|
|
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
|
import { getRotatedBoxShadow } from '../shared/rotated-box-shadow'
|
|
|
|
/** @public */
|
|
export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
|
static override type = 'bookmark' as const
|
|
static override props = bookmarkShapeProps
|
|
static override migrations = bookmarkShapeMigrations
|
|
|
|
override canResize = () => false
|
|
|
|
override hideSelectionBoundsFg = () => true
|
|
|
|
override getDefaultProps(): TLBookmarkShape['props'] {
|
|
return {
|
|
url: '',
|
|
w: 300,
|
|
h: 320,
|
|
assetId: null,
|
|
}
|
|
}
|
|
|
|
override component(shape: TLBookmarkShape) {
|
|
const asset = (
|
|
shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null
|
|
) as TLBookmarkAsset
|
|
|
|
const pageRotation = this.editor.getShapePageTransform(shape)!.rotation()
|
|
|
|
const address = getHumanReadableAddress(shape)
|
|
|
|
return (
|
|
<HTMLContainer>
|
|
<div
|
|
className="tl-bookmark__container"
|
|
style={{
|
|
boxShadow: getRotatedBoxShadow(pageRotation),
|
|
}}
|
|
>
|
|
<div className="tl-bookmark__image_container">
|
|
{asset?.props.image ? (
|
|
<img
|
|
className="tl-bookmark__image"
|
|
draggable={false}
|
|
src={asset?.props.image}
|
|
alt={asset?.props.title || ''}
|
|
/>
|
|
) : (
|
|
<div className="tl-bookmark__placeholder" />
|
|
)}
|
|
<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.getZoomLevel()} />
|
|
</div>
|
|
<div className="tl-bookmark__copy_container">
|
|
{asset?.props.title && (
|
|
<h2 className="tl-bookmark__heading">
|
|
{truncateStringWithEllipsis(asset?.props.title || '', 54)}
|
|
</h2>
|
|
)}
|
|
{asset?.props.description && (
|
|
<p className="tl-bookmark__description">
|
|
{truncateStringWithEllipsis(asset?.props.description || '', 128)}
|
|
</p>
|
|
)}
|
|
<a
|
|
className="tl-bookmark__link"
|
|
href={shape.props.url || ''}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
onPointerDown={stopEventPropagation}
|
|
onPointerUp={stopEventPropagation}
|
|
onClick={stopEventPropagation}
|
|
>
|
|
{truncateStringWithEllipsis(address, 45)}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</HTMLContainer>
|
|
)
|
|
}
|
|
|
|
override indicator(shape: TLBookmarkShape) {
|
|
return (
|
|
<rect
|
|
width={toDomPrecision(shape.props.w)}
|
|
height={toDomPrecision(shape.props.h)}
|
|
rx="6"
|
|
ry="6"
|
|
/>
|
|
)
|
|
}
|
|
|
|
override onBeforeCreate?: TLOnBeforeCreateHandler<TLBookmarkShape> = (shape) => {
|
|
updateBookmarkAssetOnUrlChange(this.editor, shape)
|
|
}
|
|
|
|
override onBeforeUpdate?: TLOnBeforeUpdateHandler<TLBookmarkShape> = (prev, shape) => {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export const 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
|
|
}
|
|
}
|
|
|
|
function updateBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmarkShape) {
|
|
const { url } = shape.props
|
|
|
|
// Derive the asset id from the URL
|
|
const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url))
|
|
|
|
if (editor.getAsset(assetId)) {
|
|
// Existing asset for this URL?
|
|
if (shape.props.assetId !== assetId) {
|
|
editor.updateShapes<TLBookmarkShape>([
|
|
{
|
|
id: shape.id,
|
|
type: shape.type,
|
|
props: { assetId },
|
|
},
|
|
])
|
|
}
|
|
} else {
|
|
// No asset for this URL?
|
|
|
|
// First, clear out the existing asset reference
|
|
editor.updateShapes<TLBookmarkShape>([
|
|
{
|
|
id: shape.id,
|
|
type: shape.type,
|
|
props: { assetId: null },
|
|
},
|
|
])
|
|
|
|
// Then try to asyncronously create a new one
|
|
createBookmarkAssetOnUrlChange(editor, shape)
|
|
}
|
|
}
|
|
|
|
const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TLBookmarkShape) => {
|
|
const { url } = shape.props
|
|
|
|
// Create the asset using the external content manager's createAssetFromUrl method.
|
|
// This may be overwritten by the user (for example, we overwrite it on tldraw.com)
|
|
const asset = await editor.getAssetForExternalContent({ type: 'url', url })
|
|
|
|
if (!asset) {
|
|
// No asset? Just leave the bookmark as a null assetId.
|
|
return
|
|
}
|
|
|
|
// Create the new asset
|
|
editor.createAssets([asset])
|
|
|
|
// And update the shape
|
|
editor.updateShapes<TLBookmarkShape>([
|
|
{
|
|
id: shape.id,
|
|
type: shape.type,
|
|
props: { assetId: asset.id },
|
|
},
|
|
])
|
|
}, 500)
|