From 34681a3f4f75b788f0bf4e753c7b060830f3985d Mon Sep 17 00:00:00 2001 From: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:55:38 -0500 Subject: [PATCH] fixed map embeds to include directions, substack embeds, twitter embeds --- .env.example | 1 + src/shapes/EmbedShapeUtil.tsx | 115 ++++++++++++++++++++++------------ src/vite-env.d.ts | 11 +++- 3 files changed, 84 insertions(+), 43 deletions(-) diff --git a/.env.example b/.env.example index dcfe279..6640e05 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # Google API Credentials VITE_GOOGLE_CLIENT_ID='your_google_client_id' VITE_GOOGLE_API_KEY='your_google_api_key' +VITE_GOOGLE_MAPS_API_KEY='your_google_maps_api_key' # Cloudflare Worker CLOUDFLARE_API_TOKEN='your_cloudflare_token' diff --git a/src/shapes/EmbedShapeUtil.tsx b/src/shapes/EmbedShapeUtil.tsx index 85efa63..325dedd 100644 --- a/src/shapes/EmbedShapeUtil.tsx +++ b/src/shapes/EmbedShapeUtil.tsx @@ -1,5 +1,6 @@ import { BaseBoxShapeUtil, TLBaseShape } from "tldraw" import { useCallback, useState } from "react" +//import Embed from "react-embed" export type IEmbedShape = TLBaseShape< "Embed", @@ -10,6 +11,69 @@ export type IEmbedShape = TLBaseShape< } > +const transformUrl = (url: string): string => { + // YouTube + const youtubeMatch = url.match( + /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/, + ) + if (youtubeMatch) { + return `https://www.youtube.com/embed/${youtubeMatch[1]}` + } + + // Google Maps + if (url.includes("google.com/maps") || url.includes("goo.gl/maps")) { + // If it's already an embed URL, return as is + if (url.includes("google.com/maps/embed")) { + return url + } + + // Handle directions + const directionsMatch = url.match(/dir\/([^\/]+)\/([^\/]+)/) + if (directionsMatch || url.includes("/dir/")) { + const origin = url.match(/origin=([^&]+)/)?.[1] || directionsMatch?.[1] + const destination = + url.match(/destination=([^&]+)/)?.[1] || directionsMatch?.[2] + + if (origin && destination) { + return `https://www.google.com/maps/embed/v1/directions?key=${ + import.meta.env.VITE_GOOGLE_MAPS_API_KEY + }&origin=${encodeURIComponent(origin)}&destination=${encodeURIComponent( + destination, + )}&mode=driving` + } + } + + // Extract place ID + const placeMatch = url.match(/[?&]place_id=([^&]+)/) + if (placeMatch) { + return `https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2!2d0!3d0!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s${placeMatch[1]}!2s!5e0!3m2!1sen!2s!4v1` + } + + // For all other map URLs + return `https://www.google.com/maps/embed/v1/place?key=${ + import.meta.env.VITE_GOOGLE_MAPS_API_KEY + }&q=${encodeURIComponent(url)}` + } + + // Twitter/X + const tweetMatch = url.match(/(?:twitter\.com|x\.com)\/[^\/]+\/status\/(\d+)/) + if (tweetMatch) { + return `https://platform.x.com/embed/Tweet.html?id=${tweetMatch[1]}` + } + + // Medium + if (url.includes("medium.com")) { + return url.replace(/\/?$/, "?format=lite") + } + + // Gather.town + if (url.includes("app.gather.town")) { + return url.replace("app.gather.town", "gather.town/embed") + } + + return url +} + export class EmbedShape extends BaseBoxShapeUtil { static override type = "Embed" @@ -41,29 +105,11 @@ export class EmbedShape extends BaseBoxShapeUtil { ? inputUrl : `https://${inputUrl}` - // Handle YouTube links - if ( - completedUrl.includes("youtube.com") || - completedUrl.includes("youtu.be") - ) { - const videoId = extractYouTubeVideoId(completedUrl) - if (videoId) { - completedUrl = `https://www.youtube.com/embed/${videoId}` - } else { - setError("Invalid YouTube URL") - return - } - } - - // Handle Google Docs links - if (completedUrl.includes("docs.google.com")) { - const docId = completedUrl.match(/\/d\/([a-zA-Z0-9-_]+)/)?.[1] - if (docId) { - completedUrl = `https://docs.google.com/document/d/${docId}/preview` - } else { - setError("Invalid Google Docs URL") - return - } + // Basic URL validation + const isValidUrl = completedUrl.match(/(^\w+:|^)\/\//) + if (!isValidUrl) { + setError("Invalid URL") + return } this.editor.updateShape({ @@ -71,25 +117,11 @@ export class EmbedShape extends BaseBoxShapeUtil { type: "Embed", props: { ...shape.props, url: completedUrl }, }) - - // Check if the URL is valid - const isValidUrl = completedUrl.match(/(^\w+:|^)\/\//) - if (!isValidUrl) { - setError("Invalid website URL") - } else { - setError("") - } + setError("") }, [inputUrl], ) - const extractYouTubeVideoId = (url: string): string | null => { - const regExp = - /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ - const match = url.match(regExp) - return match && match[2].length === 11 ? match[2] : null - } - const wrapperStyle = { width: `${shape.props.w}px`, height: `${shape.props.h}px`, @@ -152,10 +184,11 @@ export class EmbedShape extends BaseBoxShapeUtil {