fixed map embeds to include directions, substack embeds, twitter embeds
This commit is contained in:
parent
3515bce049
commit
7a1093b12a
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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<IEmbedShape> {
|
||||
static override type = "Embed"
|
||||
|
||||
|
|
@ -41,55 +105,23 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
|
|||
? 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")
|
||||
// Basic URL validation
|
||||
const isValidUrl = completedUrl.match(/(^\w+:|^)\/\//)
|
||||
if (!isValidUrl) {
|
||||
setError("Invalid 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
|
||||
}
|
||||
}
|
||||
|
||||
this.editor.updateShape<IEmbedShape>({
|
||||
id: shape.id,
|
||||
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("")
|
||||
}
|
||||
},
|
||||
[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<IEmbedShape> {
|
|||
<div style={wrapperStyle}>
|
||||
<div style={contentStyle}>
|
||||
<iframe
|
||||
src={shape.props.url}
|
||||
width="100%"
|
||||
height="100%"
|
||||
src={transformUrl(shape.props.url)}
|
||||
width={shape.props.w}
|
||||
height={shape.props.h}
|
||||
style={{ border: "none" }}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_TLDRAW_WORKER_URL: string
|
||||
readonly VITE_GOOGLE_MAPS_API_KEY: string
|
||||
readonly VITE_DAILY_API_KEY: string
|
||||
readonly VITE_CLOUDFLARE_API_TOKEN: string
|
||||
readonly VITE_CLOUDFLARE_ACCOUNT_ID: string
|
||||
readonly VITE_CLOUDFLARE_ZONE_ID: string
|
||||
readonly VITE_R2_BUCKET_NAME: string
|
||||
readonly VITE_R2_PREVIEW_BUCKET_NAME: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
|
|
|||
Loading…
Reference in New Issue