|
@@ -182,6 +182,7 @@ import {
|
|
ExcalidrawIframeLikeElement,
|
|
ExcalidrawIframeLikeElement,
|
|
IframeData,
|
|
IframeData,
|
|
ExcalidrawIframeElement,
|
|
ExcalidrawIframeElement,
|
|
|
|
+ ExcalidrawEmbeddableElement,
|
|
} from "../element/types";
|
|
} from "../element/types";
|
|
import { getCenter, getDistance } from "../gesture";
|
|
import { getCenter, getDistance } from "../gesture";
|
|
import {
|
|
import {
|
|
@@ -271,11 +272,12 @@ import {
|
|
easeOut,
|
|
easeOut,
|
|
updateStable,
|
|
updateStable,
|
|
addEventListener,
|
|
addEventListener,
|
|
|
|
+ normalizeEOL,
|
|
} from "../utils";
|
|
} from "../utils";
|
|
import {
|
|
import {
|
|
createSrcDoc,
|
|
createSrcDoc,
|
|
embeddableURLValidator,
|
|
embeddableURLValidator,
|
|
- extractSrc,
|
|
|
|
|
|
+ maybeParseEmbedSrc,
|
|
getEmbedLink,
|
|
getEmbedLink,
|
|
} from "../element/embeddable";
|
|
} from "../element/embeddable";
|
|
import {
|
|
import {
|
|
@@ -2924,21 +2926,49 @@ class App extends React.Component<AppProps, AppState> {
|
|
retainSeed: isPlainPaste,
|
|
retainSeed: isPlainPaste,
|
|
});
|
|
});
|
|
} else if (data.text) {
|
|
} else if (data.text) {
|
|
- const maybeUrl = extractSrc(data.text);
|
|
|
|
|
|
+ const nonEmptyLines = normalizeEOL(data.text)
|
|
|
|
+ .split(/\n+/)
|
|
|
|
+ .map((s) => s.trim())
|
|
|
|
+ .filter(Boolean);
|
|
|
|
+
|
|
|
|
+ const embbeddableUrls = nonEmptyLines
|
|
|
|
+ .map((str) => maybeParseEmbedSrc(str))
|
|
|
|
+ .filter((string) => {
|
|
|
|
+ return (
|
|
|
|
+ embeddableURLValidator(string, this.props.validateEmbeddable) &&
|
|
|
|
+ (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(string) ||
|
|
|
|
+ getEmbedLink(string)?.type === "video")
|
|
|
|
+ );
|
|
|
|
+ });
|
|
|
|
|
|
if (
|
|
if (
|
|
- !isPlainPaste &&
|
|
|
|
- embeddableURLValidator(maybeUrl, this.props.validateEmbeddable) &&
|
|
|
|
- (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(maybeUrl) ||
|
|
|
|
- getEmbedLink(maybeUrl)?.type === "video")
|
|
|
|
|
|
+ !IS_PLAIN_PASTE &&
|
|
|
|
+ embbeddableUrls.length > 0 &&
|
|
|
|
+ // if there were non-embeddable text (lines) mixed in with embeddable
|
|
|
|
+ // urls, ignore and paste as text
|
|
|
|
+ embbeddableUrls.length === nonEmptyLines.length
|
|
) {
|
|
) {
|
|
- const embeddable = this.insertEmbeddableElement({
|
|
|
|
- sceneX,
|
|
|
|
- sceneY,
|
|
|
|
- link: normalizeLink(maybeUrl),
|
|
|
|
- });
|
|
|
|
- if (embeddable) {
|
|
|
|
- this.setState({ selectedElementIds: { [embeddable.id]: true } });
|
|
|
|
|
|
+ const embeddables: NonDeleted<ExcalidrawEmbeddableElement>[] = [];
|
|
|
|
+ for (const url of embbeddableUrls) {
|
|
|
|
+ const prevEmbeddable: ExcalidrawEmbeddableElement | undefined =
|
|
|
|
+ embeddables[embeddables.length - 1];
|
|
|
|
+ const embeddable = this.insertEmbeddableElement({
|
|
|
|
+ sceneX: prevEmbeddable
|
|
|
|
+ ? prevEmbeddable.x + prevEmbeddable.width + 20
|
|
|
|
+ : sceneX,
|
|
|
|
+ sceneY,
|
|
|
|
+ link: normalizeLink(url),
|
|
|
|
+ });
|
|
|
|
+ if (embeddable) {
|
|
|
|
+ embeddables.push(embeddable);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (embeddables.length) {
|
|
|
|
+ this.setState({
|
|
|
|
+ selectedElementIds: Object.fromEntries(
|
|
|
|
+ embeddables.map((embeddable) => [embeddable.id, true]),
|
|
|
|
+ ),
|
|
|
|
+ });
|
|
}
|
|
}
|
|
return;
|
|
return;
|
|
}
|
|
}
|