@excalidraw/excalidraw 0.17.1-f597bd3 → 0.17.1-f59b4f6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-R7HUEHN5.js → chunk-O74FSJNG.js} +77 -49
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-R7HUEHN5.js.map → chunk-O74FSJNG.js.map} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-CAYW6EUH.js → image-F7EVY5ZQ.js} +2 -2
- package/dist/browser/dev/index.js +4 -3
- package/dist/browser/dev/index.js.map +2 -2
- package/dist/browser/prod/excalidraw-assets/chunk-UVJNWDMS.js +55 -0
- package/dist/browser/prod/excalidraw-assets/image-BSC5ABC2.js +1 -0
- package/dist/browser/prod/index.js +4 -4
- package/dist/dev/index.js +78 -47
- package/dist/dev/index.js.map +2 -2
- package/dist/excalidraw/components/App.js +2 -2
- package/dist/excalidraw/data/url.d.ts +1 -0
- package/dist/excalidraw/data/url.js +4 -1
- package/dist/excalidraw/element/embeddable.js +27 -45
- package/dist/excalidraw/element/types.d.ts +3 -0
- package/dist/excalidraw/renderer/staticScene.js +14 -3
- package/dist/excalidraw/renderer/staticSvgScene.js +10 -0
- package/dist/prod/index.js +26 -26
- package/dist/utils/geometry/shape.d.ts +2 -2
- package/dist/utils/geometry/shape.js +8 -2
- package/package.json +1 -1
- package/dist/browser/prod/excalidraw-assets/chunk-6VLWGOPS.js +0 -55
- package/dist/browser/prod/excalidraw-assets/image-2PF7RELG.js +0 -1
- /package/dist/browser/dev/excalidraw-assets-dev/{image-CAYW6EUH.js.map → image-F7EVY5ZQ.js.map} +0 -0
|
@@ -653,7 +653,7 @@ class App extends React.Component {
|
|
|
653
653
|
? src.srcdoc(this.state.theme)
|
|
654
654
|
: undefined, src: src?.type !== "document" ? src?.link ?? "" : undefined,
|
|
655
655
|
// https://stackoverflow.com/q/18470015
|
|
656
|
-
scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads
|
|
656
|
+
scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: `${src?.sandbox?.allowSameOrigin ? "allow-same-origin" : ""} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads` })) })] }) }, el.id));
|
|
657
657
|
}) }));
|
|
658
658
|
}
|
|
659
659
|
getFrameNameDOMId = (frameElement) => {
|
|
@@ -2849,7 +2849,7 @@ class App extends React.Component {
|
|
|
2849
2849
|
ShapeCache.generateElementShape(element, null)[0];
|
|
2850
2850
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
|
|
2851
2851
|
return shouldTestInside(element)
|
|
2852
|
-
? getClosedCurveShape(roughShape, [element.x, element.y], element.angle, [cx, cy])
|
|
2852
|
+
? getClosedCurveShape(element, roughShape, [element.x, element.y], element.angle, [cx, cy])
|
|
2853
2853
|
: getCurveShape(roughShape, [element.x, element.y], element.angle, [
|
|
2854
2854
|
cx,
|
|
2855
2855
|
cy,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { sanitizeUrl } from "@braintree/sanitize-url";
|
|
2
|
+
export const sanitizeHTMLAttribute = (html) => {
|
|
3
|
+
return html.replace(/"/g, """);
|
|
4
|
+
};
|
|
2
5
|
export const normalizeLink = (link) => {
|
|
3
6
|
link = link.trim();
|
|
4
7
|
if (!link) {
|
|
5
8
|
return link;
|
|
6
9
|
}
|
|
7
|
-
return sanitizeUrl(link);
|
|
10
|
+
return sanitizeUrl(sanitizeHTMLAttribute(link));
|
|
8
11
|
};
|
|
9
12
|
export const isLocalLink = (link) => {
|
|
10
13
|
return !!(link?.includes(location.origin) || link?.startsWith("/"));
|
|
@@ -5,16 +5,17 @@ import { setCursorForShape } from "../cursor";
|
|
|
5
5
|
import { newTextElement } from "./newElement";
|
|
6
6
|
import { wrapText } from "./textElement";
|
|
7
7
|
import { isIframeElement } from "./typeChecks";
|
|
8
|
+
import { sanitizeHTMLAttribute } from "../data/url";
|
|
8
9
|
const embeddedLinkCache = new Map();
|
|
9
10
|
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]*$/;
|
|
10
|
-
const RE_VIMEO = /^(?:http(?:s)?:\/\/)?(?:(?:w){3}
|
|
11
|
+
const RE_VIMEO = /^(?:http(?:s)?:\/\/)?(?:(?:w){3}\.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
|
|
11
12
|
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
|
|
12
|
-
const RE_GH_GIST = /^https:\/\/gist\.github\.com/;
|
|
13
|
-
const RE_GH_GIST_EMBED = /^<script[\s\S]*?\ssrc=["'](https:\/\/gist
|
|
13
|
+
const RE_GH_GIST = /^https:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)/;
|
|
14
|
+
const RE_GH_GIST_EMBED = /^<script[\s\S]*?\ssrc=["'](https:\/\/gist\.github\.com\/.*?)\.js["']/i;
|
|
14
15
|
// not anchored to start to allow <blockquote> twitter embeds
|
|
15
|
-
const RE_TWITTER = /(?:
|
|
16
|
-
const RE_TWITTER_EMBED = /^<blockquote[\s\S]*?\shref=["'](https
|
|
17
|
-
const RE_VALTOWN = /^https:\/\/(?:www\.)?val
|
|
16
|
+
const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com\/[^/]+\/status\/(\d+)/;
|
|
17
|
+
const RE_TWITTER_EMBED = /^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
|
|
18
|
+
const RE_VALTOWN = /^https:\/\/(?:www\.)?val\.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
|
|
18
19
|
const RE_GENERIC_EMBED = /^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
|
|
19
20
|
const RE_GIPHY = /giphy.com\/(?:clips|embed|gifs)\/[a-zA-Z0-9]*?-?([a-zA-Z0-9]+)(?:[^a-zA-Z0-9]|$)/;
|
|
20
21
|
const ALLOWED_DOMAINS = new Set([
|
|
@@ -115,55 +116,36 @@ export const getEmbedLink = (link) => {
|
|
|
115
116
|
return { link, intrinsicSize: aspectRatio, type };
|
|
116
117
|
}
|
|
117
118
|
if (RE_TWITTER.test(link)) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
ret = {
|
|
133
|
-
type: "document",
|
|
134
|
-
srcdoc: (theme) => 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>`),
|
|
135
|
-
intrinsicSize: { w: 480, h: 480 },
|
|
136
|
-
};
|
|
137
|
-
}
|
|
119
|
+
const postId = link.match(RE_TWITTER)[1];
|
|
120
|
+
// the embed srcdoc still supports twitter.com domain only.
|
|
121
|
+
// Note that we don't attempt to parse the username as it can consist of
|
|
122
|
+
// non-latin1 characters, and the username in the url can be set to anything
|
|
123
|
+
// without affecting the embed.
|
|
124
|
+
const safeURL = sanitizeHTMLAttribute(`https://twitter.com/x/status/${postId}`);
|
|
125
|
+
const ret = {
|
|
126
|
+
type: "document",
|
|
127
|
+
srcdoc: (theme) => 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>`),
|
|
128
|
+
intrinsicSize: { w: 480, h: 480 },
|
|
129
|
+
sandbox: { allowSameOrigin: true },
|
|
130
|
+
};
|
|
138
131
|
embeddedLinkCache.set(originalLink, ret);
|
|
139
132
|
return ret;
|
|
140
133
|
}
|
|
141
134
|
if (RE_GH_GIST.test(link)) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
srcdoc: () => srcDoc,
|
|
149
|
-
intrinsicSize: { w: 550, h: 720 },
|
|
150
|
-
};
|
|
151
|
-
// assume regular url
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
ret = {
|
|
155
|
-
type: "document",
|
|
156
|
-
srcdoc: () => createSrcDoc(`
|
|
157
|
-
<script src="${link}.js"></script>
|
|
135
|
+
const [, user, gistId] = link.match(RE_GH_GIST);
|
|
136
|
+
const safeURL = sanitizeHTMLAttribute(`https://gist.github.com/${user}/${gistId}`);
|
|
137
|
+
const ret = {
|
|
138
|
+
type: "document",
|
|
139
|
+
srcdoc: () => createSrcDoc(`
|
|
140
|
+
<script src="${safeURL}.js"></script>
|
|
158
141
|
<style type="text/css">
|
|
159
142
|
* { margin: 0px; }
|
|
160
143
|
table, .gist { height: 100%; }
|
|
161
144
|
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
|
|
162
145
|
</style>
|
|
163
146
|
`),
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
147
|
+
intrinsicSize: { w: 550, h: 720 },
|
|
148
|
+
};
|
|
167
149
|
embeddedLinkCache.set(link, ret);
|
|
168
150
|
return ret;
|
|
169
151
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { FRAME_STYLE } from "../constants";
|
|
2
2
|
import { getElementAbsoluteCoords } from "../element";
|
|
3
3
|
import { elementOverlapsWithFrame, getTargetFrame, isElementInFrame, } from "../frame";
|
|
4
|
-
import { isEmbeddableElement, isIframeLikeElement, } from "../element/typeChecks";
|
|
4
|
+
import { isEmbeddableElement, isIframeLikeElement, isTextElement, } from "../element/typeChecks";
|
|
5
5
|
import { renderElement } from "../renderer/renderElement";
|
|
6
6
|
import { createPlaceholderEmbeddableLabel } from "../element/embeddable";
|
|
7
7
|
import { EXTERNAL_LINK_IMG, getLinkHandleFromCoords, } from "../components/hyperlink/helpers";
|
|
8
8
|
import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
|
|
9
9
|
import { throttleRAF } from "../utils";
|
|
10
|
+
import { getBoundTextElement } from "../element/textElement";
|
|
10
11
|
const strokeGrid = (context, gridSize, scrollX, scrollY, zoom, width, height) => {
|
|
11
12
|
const BOLD_LINE_FREQUENCY = 5;
|
|
12
13
|
let GridLineColor;
|
|
@@ -121,21 +122,31 @@ const _renderStaticScene = ({ canvas, rc, elementsMap, allElementsMap, visibleEl
|
|
|
121
122
|
.forEach((element) => {
|
|
122
123
|
try {
|
|
123
124
|
const frameId = element.frameId || appState.frameToHighlight?.id;
|
|
125
|
+
if (isTextElement(element) &&
|
|
126
|
+
element.containerId &&
|
|
127
|
+
elementsMap.has(element.containerId)) {
|
|
128
|
+
// will be rendered with the container
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
context.save();
|
|
124
132
|
if (frameId &&
|
|
125
133
|
appState.frameRendering.enabled &&
|
|
126
134
|
appState.frameRendering.clip) {
|
|
127
|
-
context.save();
|
|
128
135
|
const frame = getTargetFrame(element, elementsMap, appState);
|
|
129
136
|
// TODO do we need to check isElementInFrame here?
|
|
130
137
|
if (frame && isElementInFrame(element, elementsMap, appState)) {
|
|
131
138
|
frameClip(frame, context, renderConfig, appState);
|
|
132
139
|
}
|
|
133
140
|
renderElement(element, elementsMap, allElementsMap, rc, context, renderConfig, appState);
|
|
134
|
-
context.restore();
|
|
135
141
|
}
|
|
136
142
|
else {
|
|
137
143
|
renderElement(element, elementsMap, allElementsMap, rc, context, renderConfig, appState);
|
|
138
144
|
}
|
|
145
|
+
const boundTextElement = getBoundTextElement(element, allElementsMap);
|
|
146
|
+
if (boundTextElement) {
|
|
147
|
+
renderElement(boundTextElement, elementsMap, allElementsMap, rc, context, renderConfig, appState);
|
|
148
|
+
}
|
|
149
|
+
context.restore();
|
|
139
150
|
if (!isExporting) {
|
|
140
151
|
renderLinkIcon(element, context, appState, elementsMap);
|
|
141
152
|
}
|
|
@@ -361,8 +361,18 @@ export const renderSceneToSvg = (elements, elementsMap, rsvg, svgRoot, files, re
|
|
|
361
361
|
.filter((el) => !isIframeLikeElement(el))
|
|
362
362
|
.forEach((element) => {
|
|
363
363
|
if (!element.isDeleted) {
|
|
364
|
+
if (isTextElement(element) &&
|
|
365
|
+
element.containerId &&
|
|
366
|
+
elementsMap.has(element.containerId)) {
|
|
367
|
+
// will be rendered with the container
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
364
370
|
try {
|
|
365
371
|
renderElementToSvg(element, elementsMap, rsvg, svgRoot, files, element.x + renderConfig.offsetX, element.y + renderConfig.offsetY, renderConfig);
|
|
372
|
+
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
373
|
+
if (boundTextElement) {
|
|
374
|
+
renderElementToSvg(boundTextElement, elementsMap, rsvg, svgRoot, files, boundTextElement.x + renderConfig.offsetX, boundTextElement.y + renderConfig.offsetY, renderConfig);
|
|
375
|
+
}
|
|
366
376
|
}
|
|
367
377
|
catch (error) {
|
|
368
378
|
console.error(error);
|