tldraw
Version:
A tiny little drawing editor.
200 lines (199 loc) • 6.8 kB
JavaScript
import { jsx, jsxs } from "react/jsx-runtime";
import {
BaseBoxShapeUtil,
HTMLContainer,
T,
bookmarkShapeMigrations,
bookmarkShapeProps,
lerp,
tlenv,
toDomPrecision,
useEditor,
useSvgExportContext
} from "@tldraw/editor";
import classNames from "classnames";
import { useCallback, useState } from "react";
import { convertCommonTitleHTMLEntities } from "../../utils/text/text.mjs";
import { HyperlinkButton } from "../shared/HyperlinkButton.mjs";
import { LINK_ICON } from "../shared/icons-editor.mjs";
import { getRotatedBoxShadow } from "../shared/rotated-box-shadow.mjs";
import {
BOOKMARK_HEIGHT,
BOOKMARK_WIDTH,
getHumanReadableAddress,
setBookmarkHeight,
updateBookmarkAssetOnUrlChange
} from "./bookmarks.mjs";
class BookmarkShapeUtil extends BaseBoxShapeUtil {
static type = "bookmark";
static props = bookmarkShapeProps;
static migrations = bookmarkShapeMigrations;
canResize() {
return false;
}
hideSelectionBoundsFg() {
return true;
}
getText(shape) {
return shape.props.url;
}
getAriaDescriptor(shape) {
const asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null;
if (!asset?.props.title) return void 0;
return convertCommonTitleHTMLEntities(asset.props.title) + (asset.props.description ? ", " + asset.props.description : "");
}
getDefaultProps() {
return {
url: "",
w: BOOKMARK_WIDTH,
h: BOOKMARK_HEIGHT,
assetId: null
};
}
component(shape) {
const { assetId, url, h } = shape.props;
const rotation = this.editor.getShapePageTransform(shape).rotation();
return /* @__PURE__ */ jsx(BookmarkShapeComponent, { assetId, url, h, rotation });
}
indicator(shape) {
return /* @__PURE__ */ jsx(BookmarkIndicatorComponent, { w: shape.props.w, h: shape.props.h });
}
useLegacyIndicator() {
return false;
}
getIndicatorPath(shape) {
const path = new Path2D();
path.roundRect(0, 0, shape.props.w, shape.props.h, 6);
return path;
}
onBeforeCreate(next) {
return setBookmarkHeight(this.editor, next);
}
onBeforeUpdate(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);
}
}
if (prev.props.assetId !== shape.props.assetId) {
return setBookmarkHeight(this.editor, shape);
}
return void 0;
}
getInterpolatedProps(startShape, endShape, t) {
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)
};
}
}
function BookmarkIndicatorComponent({ w, h }) {
return /* @__PURE__ */ jsx("rect", { width: toDomPrecision(w), height: toDomPrecision(h), rx: "6", ry: "6" });
}
function BookmarkShapeComponent({
assetId,
rotation,
url,
h,
showImageContainer = true
}) {
const editor = useEditor();
const asset = assetId ? editor.getAsset(assetId) : 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 /* @__PURE__ */ jsx(HTMLContainer, { children: /* @__PURE__ */ jsxs(
"div",
{
className: classNames(
"tl-bookmark__container",
isSafariExport && "tl-bookmark__container--safariExport"
),
style: {
boxShadow: isSafariExport ? void 0 : getRotatedBoxShadow(rotation),
maxHeight: h
},
children: [
showImageContainer && (!asset || asset.props.image) && /* @__PURE__ */ jsxs("div", { className: "tl-bookmark__image_container", children: [
asset ? /* @__PURE__ */ jsx(
"img",
{
className: "tl-bookmark__image",
draggable: false,
referrerPolicy: "strict-origin-when-cross-origin",
src: asset?.props.image,
alt: asset?.props.title || ""
}
) : /* @__PURE__ */ jsx("div", { className: "tl-bookmark__placeholder" }),
asset?.props.image && /* @__PURE__ */ jsx(HyperlinkButton, { url })
] }),
/* @__PURE__ */ jsxs("div", { className: "tl-bookmark__copy_container", children: [
asset?.props.title ? /* @__PURE__ */ jsx(
"a",
{
className: "tl-bookmark__link",
href: url || "",
target: "_blank",
rel: "noopener noreferrer",
draggable: false,
onPointerDown: markAsHandledOnShiftKey,
onPointerUp: markAsHandledOnShiftKey,
children: /* @__PURE__ */ jsx("h2", { className: "tl-bookmark__heading", children: convertCommonTitleHTMLEntities(asset.props.title) })
}
) : null,
asset?.props.description && asset?.props.image ? /* @__PURE__ */ jsx("p", { className: "tl-bookmark__description", children: asset.props.description }) : null,
/* @__PURE__ */ jsxs(
"a",
{
className: "tl-bookmark__link",
href: url || "",
target: "_blank",
rel: "noopener noreferrer",
draggable: false,
onPointerDown: markAsHandledOnShiftKey,
onPointerUp: markAsHandledOnShiftKey,
children: [
isFaviconValid && asset?.props.favicon ? /* @__PURE__ */ jsx(
"img",
{
className: "tl-bookmark__favicon",
src: asset?.props.favicon,
referrerPolicy: "strict-origin-when-cross-origin",
onError: onFaviconError,
alt: `favicon of ${address}`
}
) : /* @__PURE__ */ jsx(
"div",
{
className: "tl-hyperlink__icon",
style: {
mask: `url("${LINK_ICON}") center 100% / 100% no-repeat`,
WebkitMask: `url("${LINK_ICON}") center 100% / 100% no-repeat`
}
}
),
/* @__PURE__ */ jsx("span", { children: address })
]
}
)
] })
]
}
) });
}
export {
BookmarkIndicatorComponent,
BookmarkShapeComponent,
BookmarkShapeUtil
};
//# sourceMappingURL=BookmarkShapeUtil.mjs.map