| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -12,11 +12,13 @@ import {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  NonDeletedExcalidrawElement,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  Theme,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				} from "./types";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import { sanitizeHTMLAttribute } from "../data/url";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				type EmbeddedLink =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  | ({
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      aspectRatio: { w: number; h: number };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      warning?: string;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      sandbox?: { allowSameOrigin?: boolean };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    } & (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      | { type: "video" | "generic"; link: string }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      | { type: "document"; srcdoc: (theme: Theme) => string }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -28,20 +30,21 @@ const embeddedLinkCache = new Map<string, EmbeddedLink>();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_YOUTUBE =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_VIMEO =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^(?:http(?:s)?:\/\/)?(?:(?:w){3}\.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_GH_GIST = /^https:\/\/gist\.github\.com/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_GH_GIST = /^https:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_GH_GIST_EMBED =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^<script[\s\S]*?\ssrc=["'](https:\/\/gist.github.com\/.*?)\.js["']/i;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^<script[\s\S]*?\ssrc=["'](https:\/\/gist\.github\.com\/.*?)\.js["']/i;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// not anchored to start to allow <blockquote> twitter embeds
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?twitter.com/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_TWITTER =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com\/[^/]+\/status\/(\d+)/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_TWITTER_EMBED =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^<blockquote[\s\S]*?\shref=["'](https:\/\/twitter.com\/[^"']*)/i;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_VALTOWN =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^https:\/\/(?:www\.)?val.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^https:\/\/(?:www\.)?val\.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const RE_GENERIC_EMBED =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -136,56 +139,46 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  if (RE_TWITTER.test(link)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    let ret: EmbeddedLink;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // assume embed code
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (/<blockquote/.test(link)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      const srcDoc = createSrcDoc(link);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ret = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        srcdoc: () => srcDoc,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        aspectRatio: { w: 480, h: 480 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // assume regular tweet url
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ret = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        srcdoc: (theme: string) =>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          createSrcDoc(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          ),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        aspectRatio: { w: 480, h: 480 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const postId = link.match(RE_TWITTER)![1];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // the embed srcdoc still supports twitter.com domain only.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Note that we don't attempt to parse the username as it can consist of
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // non-latin1 characters, and the username in the url can be set to anything
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // without affecting the embed.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const safeURL = sanitizeHTMLAttribute(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      `https://twitter.com/x/status/${postId}`,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const ret: EmbeddedLink = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      srcdoc: (theme: string) =>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        createSrcDoc(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${safeURL}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      aspectRatio: { w: 480, h: 480 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      sandbox: { allowSameOrigin: true },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    embeddedLinkCache.set(originalLink, ret);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return ret;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  if (RE_GH_GIST.test(link)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    let ret: EmbeddedLink;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // assume embed code
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (/<script>/.test(link)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      const srcDoc = createSrcDoc(link);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ret = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        srcdoc: () => srcDoc,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        aspectRatio: { w: 550, h: 720 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // assume regular url
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ret = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        srcdoc: () =>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          createSrcDoc(`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          <script src="${link}.js"></script>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const [, user, gistId] = link.match(RE_GH_GIST)!;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const safeURL = sanitizeHTMLAttribute(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      `https://gist.github.com/${user}/${gistId}`,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const ret: EmbeddedLink = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      type: "document",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      srcdoc: () =>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        createSrcDoc(`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          <script src="${safeURL}.js"></script>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          <style type="text/css">
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            * { margin: 0px; }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            table, .gist { height: 100%; }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          </style>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        `),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        aspectRatio: { w: 550, h: 720 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      aspectRatio: { w: 550, h: 720 },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    embeddedLinkCache.set(link, ret);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return ret;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |