refactor: separate elements logic into a standalone package (#9285)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { EDITOR_LS_KEYS } from "../constants";
|
||||
import type { EDITOR_LS_KEYS } from "@excalidraw/common";
|
||||
|
||||
import type { JSONValue } from "../types";
|
||||
|
||||
export class EditorLocalStorage {
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import {
|
||||
IMAGE_MIME_TYPES,
|
||||
MIME_TYPES,
|
||||
bytesToHexString,
|
||||
isPromiseLike,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { clearElementsForExport } from "@excalidraw/element";
|
||||
|
||||
import type { ValueOf } from "@excalidraw/common/utility-types";
|
||||
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
|
||||
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
|
||||
import { CanvasError, ImageSceneDataError } from "../errors";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { decodeSvgBase64Payload } from "../scene/export";
|
||||
import { bytesToHexString, isPromiseLike } from "../utils";
|
||||
|
||||
import { base64ToString, stringToBase64, toByteString } from "./encode";
|
||||
import { nativeFileSystemSupported } from "./filesystem";
|
||||
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
||||
import { restore, restoreLibraryItems } from "./restore";
|
||||
|
||||
import type { FileSystemHandle } from "./filesystem";
|
||||
import type { ExcalidrawElement, FileId } from "../element/types";
|
||||
import type { AppState, DataURL, LibraryItem } from "../types";
|
||||
import type { ValueOf } from "../utility-types";
|
||||
|
||||
import type { FileSystemHandle } from "./filesystem";
|
||||
import type { ImportedLibraryData } from "./types";
|
||||
|
||||
const parseFileContents = async (blob: Blob | File): Promise<string> => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ENCRYPTION_KEY_BITS } from "../constants";
|
||||
import { ENCRYPTION_KEY_BITS } from "@excalidraw/common";
|
||||
|
||||
import { blobToArrayBuffer } from "./blob";
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
supported as nativeFileSystemSupported,
|
||||
} from "browser-fs-access";
|
||||
|
||||
import { EVENT, MIME_TYPES } from "../constants";
|
||||
import { EVENT, MIME_TYPES, debounce } from "@excalidraw/common";
|
||||
|
||||
import { AbortError } from "../errors";
|
||||
import { debounce } from "../utils";
|
||||
|
||||
import type { FileSystemHandle } from "browser-fs-access";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import tEXt from "png-chunk-text";
|
||||
import encodePng from "png-chunks-encode";
|
||||
import decodePng from "png-chunks-extract";
|
||||
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "@excalidraw/common";
|
||||
|
||||
import { blobToArrayBuffer } from "./blob";
|
||||
import { encode, decode } from "./encode";
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
import {
|
||||
copyBlobToClipboardAsPng,
|
||||
copyTextToSystemClipboard,
|
||||
} from "../clipboard";
|
||||
import {
|
||||
DEFAULT_EXPORT_PADDING,
|
||||
DEFAULT_FILENAME,
|
||||
IMAGE_MIME_TYPES,
|
||||
isFirefox,
|
||||
MIME_TYPES,
|
||||
} from "../constants";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { isFrameLikeElement } from "../element/typeChecks";
|
||||
import { getElementsOverlappingFrame } from "../frame";
|
||||
cloneJSON,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
|
||||
import { isFrameLikeElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { getElementsOverlappingFrame } from "@excalidraw/element/frame";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawFrameLikeElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import {
|
||||
copyBlobToClipboardAsPng,
|
||||
copyTextToSystemClipboard,
|
||||
} from "../clipboard";
|
||||
|
||||
import { t } from "../i18n";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||
import { cloneJSON } from "../utils";
|
||||
|
||||
import { canvasToBlob } from "./blob";
|
||||
import { fileSave } from "./filesystem";
|
||||
import { serializeAsJSON } from "./json";
|
||||
|
||||
import type { FileSystemHandle } from "./filesystem";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawFrameLikeElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
|
||||
import type { ExportType } from "../scene/types";
|
||||
import type { AppState, BinaryFiles } from "../types";
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
||||
import {
|
||||
DEFAULT_FILENAME,
|
||||
EXPORT_DATA_TYPES,
|
||||
EXPORT_SOURCE,
|
||||
MIME_TYPES,
|
||||
VERSIONS,
|
||||
} from "../constants";
|
||||
import { clearElementsForDatabase, clearElementsForExport } from "../element";
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
clearElementsForDatabase,
|
||||
clearElementsForExport,
|
||||
} from "@excalidraw/element";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
||||
|
||||
import { isImageFileHandle, loadFromBlob, normalizeFile } from "./blob";
|
||||
import { fileOpen, fileSave } from "./filesystem";
|
||||
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import type { AppState, BinaryFiles, LibraryItems } from "../types";
|
||||
import type {
|
||||
ExportedDataState,
|
||||
|
||||
@@ -7,29 +7,35 @@ import {
|
||||
EVENT,
|
||||
DEFAULT_SIDEBAR,
|
||||
LIBRARY_SIDEBAR_TAB,
|
||||
} from "../constants";
|
||||
import { atom, editorJotaiStore } from "../editor-jotai";
|
||||
import { hashElementsVersion, hashString } from "../element";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import { Emitter } from "../emitter";
|
||||
import { AbortError } from "../errors";
|
||||
import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
|
||||
import { t } from "../i18n";
|
||||
import { Queue } from "../queue";
|
||||
import {
|
||||
arrayToMap,
|
||||
cloneJSON,
|
||||
preventUnload,
|
||||
promiseTry,
|
||||
resolvablePromise,
|
||||
} from "../utils";
|
||||
toValidURL,
|
||||
Queue,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { hashElementsVersion, hashString } from "@excalidraw/element";
|
||||
|
||||
import { getCommonBoundingBox } from "@excalidraw/element/bounds";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { MaybePromise } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { atom, editorJotaiStore } from "../editor-jotai";
|
||||
|
||||
import { Emitter } from "../emitter";
|
||||
import { AbortError } from "../errors";
|
||||
import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
|
||||
import { t } from "../i18n";
|
||||
|
||||
import { loadLibraryFromBlob } from "./blob";
|
||||
import { restoreLibraryItems } from "./restore";
|
||||
import { toValidURL } from "./url";
|
||||
|
||||
import type App from "../components/App";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
|
||||
import type {
|
||||
LibraryItems,
|
||||
LibraryItem,
|
||||
@@ -37,7 +43,6 @@ import type {
|
||||
LibraryItemsSource,
|
||||
LibraryItems_anyVersion,
|
||||
} from "../types";
|
||||
import type { MaybePromise } from "../utility-types";
|
||||
|
||||
/**
|
||||
* format: hostname or hostname/pathname
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import { arrayToMap, isDevEnv, isTestEnv } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
orderByFractionalIndex,
|
||||
syncInvalidIndices,
|
||||
validateFractionalIndices,
|
||||
} from "../fractionalIndex";
|
||||
import { arrayToMap, isDevEnv, isTestEnv } from "../utils";
|
||||
} from "@excalidraw/element/fractionalIndex";
|
||||
|
||||
import type { OrderedExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
||||
|
||||
import type { OrderedExcalidrawElement } from "../element/types";
|
||||
import type { AppState } from "../types";
|
||||
import type { MakeBrand } from "../utility-types";
|
||||
|
||||
export type ReconciledExcalidrawElement = OrderedExcalidrawElement &
|
||||
MakeBrand<"ReconciledElement">;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import { getFileHandleType, isImageFileHandleType } from "./blob";
|
||||
|
||||
import { exportCanvas, prepareElementsForExport } from ".";
|
||||
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import type { AppState, BinaryFiles } from "../types";
|
||||
|
||||
export const resaveAsImageWithScene = async (
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { isFiniteNumber, pointFrom } from "@excalidraw/math";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
@@ -13,22 +10,24 @@ import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_GRID_SIZE,
|
||||
DEFAULT_GRID_STEP,
|
||||
} from "../constants";
|
||||
import {
|
||||
getNonDeletedElements,
|
||||
getNormalizedDimensions,
|
||||
isInvisiblySmallElement,
|
||||
refreshTextDimensions,
|
||||
} from "../element";
|
||||
import { normalizeFixedPoint } from "../element/binding";
|
||||
randomId,
|
||||
getUpdatedTimestamp,
|
||||
updateActiveTool,
|
||||
arrayToMap,
|
||||
getSizeFromPoints,
|
||||
normalizeLink,
|
||||
getLineHeight,
|
||||
} from "@excalidraw/common";
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
import { normalizeFixedPoint } from "@excalidraw/element/binding";
|
||||
import {
|
||||
updateElbowArrowPoints,
|
||||
validateElbowPoints,
|
||||
} from "../element/elbowArrow";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { bumpVersion } from "../element/mutateElement";
|
||||
import { getContainerElement } from "../element/textElement";
|
||||
import { detectLineHeight } from "../element/textMeasurements";
|
||||
} from "@excalidraw/element/elbowArrow";
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
import { bumpVersion } from "@excalidraw/element/mutateElement";
|
||||
import { getContainerElement } from "@excalidraw/element/textElement";
|
||||
import { detectLineHeight } from "@excalidraw/element/textMeasurements";
|
||||
import {
|
||||
isArrowElement,
|
||||
isElbowArrow,
|
||||
@@ -36,20 +35,17 @@ import {
|
||||
isLinearElement,
|
||||
isTextElement,
|
||||
isUsingAdaptiveRadius,
|
||||
} from "../element/typeChecks";
|
||||
import { getLineHeight } from "../fonts";
|
||||
import { syncInvalidIndices } from "../fractionalIndex";
|
||||
import { randomId } from "../random";
|
||||
import {
|
||||
getNormalizedGridSize,
|
||||
getNormalizedGridStep,
|
||||
getNormalizedZoom,
|
||||
} from "../scene";
|
||||
import { getUpdatedTimestamp, updateActiveTool } from "../utils";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { getSizeFromPoints } from "../points";
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { normalizeLink } from "./url";
|
||||
import { syncInvalidIndices } from "@excalidraw/element/fractionalIndex";
|
||||
|
||||
import { refreshTextDimensions } from "@excalidraw/element/newElement";
|
||||
|
||||
import { getNormalizedDimensions } from "@excalidraw/element/sizeHelpers";
|
||||
|
||||
import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawArrowElement,
|
||||
@@ -65,9 +61,19 @@ import type {
|
||||
OrderedExcalidrawElement,
|
||||
PointBinding,
|
||||
StrokeRoundness,
|
||||
} from "../element/types";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { MarkOptional, Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getDefaultAppState } from "../appState";
|
||||
|
||||
import {
|
||||
getNormalizedGridSize,
|
||||
getNormalizedGridStep,
|
||||
getNormalizedZoom,
|
||||
} from "../scene";
|
||||
|
||||
import type { AppState, BinaryFiles, LibraryItem } from "../types";
|
||||
import type { MarkOptional, Mutable } from "../utility-types";
|
||||
import type { ImportedDataState, LegacyAppState } from "./types";
|
||||
|
||||
type RestoredAppState = Omit<
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { pointFrom } from "@excalidraw/math";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { ExcalidrawArrowElement } from "@excalidraw/element/types";
|
||||
|
||||
import { convertToExcalidrawElements } from "./transform";
|
||||
|
||||
import type { ExcalidrawElementSkeleton } from "./transform";
|
||||
import type { ExcalidrawArrowElement } from "../element/types";
|
||||
|
||||
const opts = { regenerateIds: false };
|
||||
|
||||
|
||||
@@ -5,37 +5,39 @@ import {
|
||||
DEFAULT_FONT_SIZE,
|
||||
TEXT_ALIGN,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import {
|
||||
getCommonBounds,
|
||||
newElement,
|
||||
newLinearElement,
|
||||
redrawTextBoundingBox,
|
||||
} from "../element";
|
||||
import { bindLinearElement } from "../element/binding";
|
||||
import {
|
||||
newArrowElement,
|
||||
newFrameElement,
|
||||
newImageElement,
|
||||
newMagicFrameElement,
|
||||
newTextElement,
|
||||
} from "../element/newElement";
|
||||
import { measureText, normalizeText } from "../element/textMeasurements";
|
||||
import { isArrowElement } from "../element/typeChecks";
|
||||
import { getLineHeight } from "../fonts";
|
||||
import { syncInvalidIndices } from "../fractionalIndex";
|
||||
import { getSizeFromPoints } from "../points";
|
||||
import { randomId } from "../random";
|
||||
import {
|
||||
getSizeFromPoints,
|
||||
randomId,
|
||||
arrayToMap,
|
||||
assertNever,
|
||||
cloneJSON,
|
||||
getFontString,
|
||||
isDevEnv,
|
||||
toBrandedType,
|
||||
} from "../utils";
|
||||
getLineHeight,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { bindLinearElement } from "@excalidraw/element/binding";
|
||||
import {
|
||||
newArrowElement,
|
||||
newElement,
|
||||
newFrameElement,
|
||||
newImageElement,
|
||||
newLinearElement,
|
||||
newMagicFrameElement,
|
||||
newTextElement,
|
||||
} from "@excalidraw/element/newElement";
|
||||
import {
|
||||
measureText,
|
||||
normalizeText,
|
||||
} from "@excalidraw/element/textMeasurements";
|
||||
import { isArrowElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { syncInvalidIndices } from "@excalidraw/element/fractionalIndex";
|
||||
|
||||
import { redrawTextBoundingBox } from "@excalidraw/element/textElement";
|
||||
|
||||
import type { ElementConstructorOpts } from "@excalidraw/element/newElement";
|
||||
|
||||
import type { ElementConstructorOpts } from "../element/newElement";
|
||||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawArrowElement,
|
||||
@@ -55,8 +57,11 @@ import type {
|
||||
NonDeletedSceneElementsMap,
|
||||
TextAlign,
|
||||
VerticalAlign,
|
||||
} from "../element/types";
|
||||
import type { MarkOptional } from "../utility-types";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { MarkOptional } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getCommonBounds } from "..";
|
||||
|
||||
export type ValidLinearElement = {
|
||||
type: "arrow" | "line";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { VERSIONS } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { cleanAppStateForExport } from "../appState";
|
||||
import type { VERSIONS } from "../constants";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFiles,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { normalizeLink } from "./url";
|
||||
|
||||
describe("normalizeLink", () => {
|
||||
// NOTE not an extensive XSS test suite, just to check if we're not
|
||||
// regressing in sanitization
|
||||
it("should sanitize links", () => {
|
||||
expect(
|
||||
// eslint-disable-next-line no-script-url
|
||||
normalizeLink(`javascript://%0aalert(document.domain)`).startsWith(
|
||||
// eslint-disable-next-line no-script-url
|
||||
`javascript:`,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(normalizeLink("ola")).toBe("ola");
|
||||
expect(normalizeLink(" ola")).toBe("ola");
|
||||
|
||||
expect(normalizeLink("https://www.excalidraw.com")).toBe(
|
||||
"https://www.excalidraw.com",
|
||||
);
|
||||
expect(normalizeLink("www.excalidraw.com")).toBe("www.excalidraw.com");
|
||||
expect(normalizeLink("/ola")).toBe("/ola");
|
||||
expect(normalizeLink("http://test")).toBe("http://test");
|
||||
expect(normalizeLink("ftp://test")).toBe("ftp://test");
|
||||
expect(normalizeLink("file://")).toBe("file://");
|
||||
expect(normalizeLink("file://")).toBe("file://");
|
||||
expect(normalizeLink("[test](https://test)")).toBe("[test](https://test)");
|
||||
expect(normalizeLink("[[test]]")).toBe("[[test]]");
|
||||
expect(normalizeLink("<test>")).toBe("<test>");
|
||||
expect(normalizeLink("test&")).toBe("test&");
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||
|
||||
import { escapeDoubleQuotes } from "../utils";
|
||||
|
||||
export const normalizeLink = (link: string) => {
|
||||
link = link.trim();
|
||||
if (!link) {
|
||||
return link;
|
||||
}
|
||||
return sanitizeUrl(escapeDoubleQuotes(link));
|
||||
};
|
||||
|
||||
export const isLocalLink = (link: string | null) => {
|
||||
return !!(link?.includes(location.origin) || link?.startsWith("/"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns URL sanitized and safe for usage in places such as
|
||||
* iframe's src attribute or <a> href attributes.
|
||||
*/
|
||||
export const toValidURL = (link: string) => {
|
||||
link = normalizeLink(link);
|
||||
|
||||
// make relative links into fully-qualified urls
|
||||
if (link.startsWith("/")) {
|
||||
return `${location.origin}${link}`;
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(link);
|
||||
} catch {
|
||||
// if link does not parse as URL, assume invalid and return blank page
|
||||
return "about:blank";
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
Reference in New Issue
Block a user