supermemory/apps/web/lib/createEmbeds.ts
2024-07-16 19:26:47 -05:00

236 lines
5.1 KiB
TypeScript

// @ts-nocheck TODO: A LOT OF TS ERRORS HERE
import {
AssetRecordType,
Editor,
TLAsset,
TLAssetId,
TLBookmarkShape,
TLExternalContentSource,
TLShapePartial,
Vec,
VecLike,
createShapeId,
getEmbedInfo,
getHashForString,
} from "tldraw";
export default async function createEmbedsFromUrl({
url,
point,
sources,
editor,
}: {
url: string;
point?: VecLike | undefined;
sources?: TLExternalContentSource[] | undefined;
editor: Editor;
}) {
const position =
point ??
(editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center);
if (url?.includes("x.com") || url?.includes("twitter.com")) {
return editor.createShape({
type: "Twittercard",
x: position.x - 250,
y: position.y - 150,
props: { url: url },
});
}
// try to paste as an embed first
const embedInfo = getEmbedInfo(url);
if (embedInfo) {
return editor.putExternalContent({
type: "embed",
url: embedInfo.url,
point,
embed: embedInfo.definition,
});
}
const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
const shape = createEmptyBookmarkShape(editor, url, position);
// Use an existing asset if we have one, or else else create a new one
let asset = editor.getAsset(assetId) as TLAsset;
let shouldAlsoCreateAsset = false;
if (!asset) {
shouldAlsoCreateAsset = true;
try {
const bookmarkAsset = await editor.getAssetForExternalContent({
type: "url",
url,
});
const fetchWebsite: {
title?: string;
image?: string;
description?: string;
} = await (
await fetch(`/api/unfirlsite?website=${url}`, {
method: "POST",
})
).json();
if (bookmarkAsset) {
if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
if (fetchWebsite.description)
bookmarkAsset.props.description = fetchWebsite.description;
}
if (!bookmarkAsset) throw Error("Could not create an asset");
asset = bookmarkAsset;
} catch (e) {
console.log(e);
return;
}
}
editor.batch(() => {
if (shouldAlsoCreateAsset) {
editor.createAssets([asset]);
}
editor.updateShapes([
{
id: shape.id,
type: shape.type,
props: {
assetId: asset.id,
},
},
]);
});
}
function isURL(str: string) {
try {
new URL(str);
return true;
} catch {
return false;
}
}
function formatTextToRatio(text: string) {
const totalWidth = text.length;
const maxLineWidth = Math.floor(totalWidth / 10);
const words = text.split(" ");
let lines = [];
let currentLine = "";
words.forEach((word) => {
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? " " : "") + word;
} else {
lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) {
lines.push(currentLine);
}
return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
}
export function handleExternalDroppedContent({
text,
editor,
}: {
text: string;
editor: Editor;
}) {
const position = editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center;
if (isURL(text)) {
createEmbedsFromUrl({ editor, url: text });
} else {
// editor.createShape({
// type: "text",
// x: position.x - 75,
// y: position.y - 75,
// props: {
// text: text,
// size: "s",
// textAlign: "start",
// },
// });
const { height, width } = formatTextToRatio(text);
editor.createShape({
type: "Textcard",
x: position.x - width / 2,
y: position.y - height / 2,
props: {
content: text,
extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
w: width,
h: height,
},
});
}
}
function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
// Re-position shapes so that the center of the group is at the provided point
const viewportPageBounds = editor.getViewportPageBounds();
let selectionPageBounds = editor.getSelectionPageBounds();
if (selectionPageBounds) {
const offset = selectionPageBounds!.center.sub(position);
editor.updateShapes(
editor.getSelectedShapes().map((shape) => {
const localRotation = editor
.getShapeParentTransform(shape)
.decompose().rotation;
const localDelta = Vec.Rot(offset, -localRotation);
return {
id: shape.id,
type: shape.type,
x: shape.x! - localDelta.x,
y: shape.y! - localDelta.y,
};
}),
);
}
// Zoom out to fit the shapes, if necessary
selectionPageBounds = editor.getSelectionPageBounds();
if (
selectionPageBounds &&
!viewportPageBounds.contains(selectionPageBounds)
) {
editor.zoomToSelection();
}
}
export function createEmptyBookmarkShape(
editor: Editor,
url: string,
position: VecLike,
): TLBookmarkShape {
const partial: TLShapePartial = {
id: createShapeId(),
type: "bookmark",
x: position.x - 150,
y: position.y - 160,
opacity: 1,
props: {
assetId: null,
url,
},
};
editor.batch(() => {
editor.createShapes([partial]).select(partial.id);
centerSelectionAroundPoint(editor, position);
});
return editor.getShape(partial.id) as TLBookmarkShape;
}