mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 04:44:31 +01:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			54159b2309
			...
			dwelle/v0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7ba029807a | ||
| 
						 | 
					b4d9ad4f3c | ||
| 
						 | 
					3ae8af5a13 | ||
| 
						 | 
					053ca9058a | 
@@ -894,7 +894,11 @@ class App extends React.Component<AppProps, AppState> {
 | 
				
			|||||||
                      title="Excalidraw Embedded Content"
 | 
					                      title="Excalidraw Embedded Content"
 | 
				
			||||||
                      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
 | 
					                      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
 | 
				
			||||||
                      allowFullScreen={true}
 | 
					                      allowFullScreen={true}
 | 
				
			||||||
                      sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads"
 | 
					                      sandbox={`${
 | 
				
			||||||
 | 
					                        embedLink?.sandbox?.allowSameOrigin
 | 
				
			||||||
 | 
					                          ? "allow-same-origin"
 | 
				
			||||||
 | 
					                          : ""
 | 
				
			||||||
 | 
					                      } allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  )}
 | 
					                  )}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,15 @@
 | 
				
			|||||||
import { sanitizeUrl } from "@braintree/sanitize-url";
 | 
					import { sanitizeUrl } from "@braintree/sanitize-url";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sanitizeHTMLAttribute = (html: string) => {
 | 
				
			||||||
 | 
					  return html.replace(/"/g, """);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const normalizeLink = (link: string) => {
 | 
					export const normalizeLink = (link: string) => {
 | 
				
			||||||
  link = link.trim();
 | 
					  link = link.trim();
 | 
				
			||||||
  if (!link) {
 | 
					  if (!link) {
 | 
				
			||||||
    return link;
 | 
					    return link;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return sanitizeUrl(link);
 | 
					  return sanitizeUrl(sanitizeHTMLAttribute(link));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const isLocalLink = (link: string | null) => {
 | 
					export const isLocalLink = (link: string | null) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,13 @@ import {
 | 
				
			|||||||
  NonDeletedExcalidrawElement,
 | 
					  NonDeletedExcalidrawElement,
 | 
				
			||||||
  Theme,
 | 
					  Theme,
 | 
				
			||||||
} from "./types";
 | 
					} from "./types";
 | 
				
			||||||
 | 
					import { sanitizeHTMLAttribute } from "../data/url";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type EmbeddedLink =
 | 
					type EmbeddedLink =
 | 
				
			||||||
  | ({
 | 
					  | ({
 | 
				
			||||||
      aspectRatio: { w: number; h: number };
 | 
					      aspectRatio: { w: number; h: number };
 | 
				
			||||||
      warning?: string;
 | 
					      warning?: string;
 | 
				
			||||||
 | 
					      sandbox?: { allowSameOrigin?: boolean };
 | 
				
			||||||
    } & (
 | 
					    } & (
 | 
				
			||||||
      | { type: "video" | "generic"; link: string }
 | 
					      | { type: "video" | "generic"; link: string }
 | 
				
			||||||
      | { type: "document"; srcdoc: (theme: Theme) => string }
 | 
					      | { type: "document"; srcdoc: (theme: Theme) => string }
 | 
				
			||||||
@@ -28,20 +30,21 @@ const embeddedLinkCache = new Map<string, EmbeddedLink>();
 | 
				
			|||||||
const RE_YOUTUBE =
 | 
					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]*$/;
 | 
					  /^(?: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 =
 | 
					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_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 =
 | 
					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
 | 
					// 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 =
 | 
					const RE_TWITTER_EMBED =
 | 
				
			||||||
  /^<blockquote[\s\S]*?\shref=["'](https:\/\/twitter.com\/[^"']*)/i;
 | 
					  /^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const RE_VALTOWN =
 | 
					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 =
 | 
					const RE_GENERIC_EMBED =
 | 
				
			||||||
  /^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
 | 
					  /^<(?: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)) {
 | 
					  if (RE_TWITTER.test(link)) {
 | 
				
			||||||
    let ret: EmbeddedLink;
 | 
					    const postId = link.match(RE_TWITTER)![1];
 | 
				
			||||||
    // assume embed code
 | 
					    // the embed srcdoc still supports twitter.com domain only.
 | 
				
			||||||
    if (/<blockquote/.test(link)) {
 | 
					    // Note that we don't attempt to parse the username as it can consist of
 | 
				
			||||||
      const srcDoc = createSrcDoc(link);
 | 
					    // non-latin1 characters, and the username in the url can be set to anything
 | 
				
			||||||
      ret = {
 | 
					    // without affecting the embed.
 | 
				
			||||||
        type: "document",
 | 
					    const safeURL = sanitizeHTMLAttribute(
 | 
				
			||||||
        srcdoc: () => srcDoc,
 | 
					      `https://twitter.com/x/status/${postId}`,
 | 
				
			||||||
        aspectRatio: { w: 480, h: 480 },
 | 
					    );
 | 
				
			||||||
      };
 | 
					
 | 
				
			||||||
      // assume regular tweet url
 | 
					    const ret: EmbeddedLink = {
 | 
				
			||||||
    } else {
 | 
					      type: "document",
 | 
				
			||||||
      ret = {
 | 
					      srcdoc: (theme: string) =>
 | 
				
			||||||
        type: "document",
 | 
					        createSrcDoc(
 | 
				
			||||||
        srcdoc: (theme: string) =>
 | 
					          `<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>`,
 | 
				
			||||||
          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 },
 | 
				
			||||||
          ),
 | 
					      sandbox: { allowSameOrigin: true },
 | 
				
			||||||
        aspectRatio: { w: 480, h: 480 },
 | 
					    };
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    embeddedLinkCache.set(originalLink, ret);
 | 
					    embeddedLinkCache.set(originalLink, ret);
 | 
				
			||||||
    return ret;
 | 
					    return ret;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (RE_GH_GIST.test(link)) {
 | 
					  if (RE_GH_GIST.test(link)) {
 | 
				
			||||||
    let ret: EmbeddedLink;
 | 
					    const [, user, gistId] = link.match(RE_GH_GIST)!;
 | 
				
			||||||
    // assume embed code
 | 
					    const safeURL = sanitizeHTMLAttribute(
 | 
				
			||||||
    if (/<script>/.test(link)) {
 | 
					      `https://gist.github.com/${user}/${gistId}`,
 | 
				
			||||||
      const srcDoc = createSrcDoc(link);
 | 
					    );
 | 
				
			||||||
      ret = {
 | 
					    const ret: EmbeddedLink = {
 | 
				
			||||||
        type: "document",
 | 
					      type: "document",
 | 
				
			||||||
        srcdoc: () => srcDoc,
 | 
					      srcdoc: () =>
 | 
				
			||||||
        aspectRatio: { w: 550, h: 720 },
 | 
					        createSrcDoc(`
 | 
				
			||||||
      };
 | 
					          <script src="${safeURL}.js"></script>
 | 
				
			||||||
      // assume regular url
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      ret = {
 | 
					 | 
				
			||||||
        type: "document",
 | 
					 | 
				
			||||||
        srcdoc: () =>
 | 
					 | 
				
			||||||
          createSrcDoc(`
 | 
					 | 
				
			||||||
          <script src="${link}.js"></script>
 | 
					 | 
				
			||||||
          <style type="text/css">
 | 
					          <style type="text/css">
 | 
				
			||||||
            * { margin: 0px; }
 | 
					            * { margin: 0px; }
 | 
				
			||||||
            table, .gist { height: 100%; }
 | 
					            table, .gist { height: 100%; }
 | 
				
			||||||
            .gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
 | 
					            .gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
 | 
				
			||||||
          </style>
 | 
					          </style>
 | 
				
			||||||
        `),
 | 
					        `),
 | 
				
			||||||
        aspectRatio: { w: 550, h: 720 },
 | 
					      aspectRatio: { w: 550, h: 720 },
 | 
				
			||||||
      };
 | 
					    };
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    embeddedLinkCache.set(link, ret);
 | 
					    embeddedLinkCache.set(link, ret);
 | 
				
			||||||
    return ret;
 | 
					    return ret;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@excalidraw/excalidraw",
 | 
					  "name": "@excalidraw/excalidraw",
 | 
				
			||||||
  "version": "0.16.0",
 | 
					  "version": "0.16.3",
 | 
				
			||||||
  "main": "main.js",
 | 
					  "main": "main.js",
 | 
				
			||||||
  "types": "types/packages/excalidraw/index.d.ts",
 | 
					  "types": "types/packages/excalidraw/index.d.ts",
 | 
				
			||||||
  "files": [
 | 
					  "files": [
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user