fix: merge branch 'master' into alex-kim-dev/flip-multiple-elements
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
getOriginalContainerHeightFromCache,
|
||||
resetOriginalContainerCache,
|
||||
updateOriginalContainerCache,
|
||||
} from "../element/textWysiwyg";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
@@ -145,7 +146,11 @@ export const actionBindText = register({
|
||||
id: textElement.id,
|
||||
}),
|
||||
});
|
||||
const originalContainerHeight = container.height;
|
||||
redrawTextBoundingBox(textElement, container);
|
||||
// overwritting the cache with original container height so
|
||||
// it can be restored when unbind
|
||||
updateOriginalContainerCache(container.id, originalContainerHeight);
|
||||
|
||||
return {
|
||||
elements: pushTextAboveContainer(elements, container, textElement),
|
||||
@@ -191,8 +196,8 @@ const pushContainerBelowText = (
|
||||
return updatedElements;
|
||||
};
|
||||
|
||||
export const actionCreateContainerFromText = register({
|
||||
name: "createContainerFromText",
|
||||
export const actionWrapTextInContainer = register({
|
||||
name: "wrapTextInContainer",
|
||||
contextItemLabel: "labels.createContainerFromText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
@@ -281,6 +286,7 @@ export const actionCreateContainerFromText = register({
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const actionCopy = register({
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(elements, appState, true);
|
||||
|
||||
copyToClipboard(selectedElements, appState, app.files);
|
||||
copyToClipboard(selectedElements, app.files);
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
|
||||
@@ -84,7 +84,7 @@ import {
|
||||
isSomeElementSelected,
|
||||
} from "../scene";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||
@@ -314,9 +314,9 @@ export const actionChangeFillStyle = register({
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const allElementsZigZag = selectedElements.every(
|
||||
(el) => el.fillStyle === "zigzag",
|
||||
);
|
||||
const allElementsZigZag =
|
||||
selectedElements.length > 0 &&
|
||||
selectedElements.every((el) => el.fillStyle === "zigzag");
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
@@ -326,7 +326,9 @@ export const actionChangeFillStyle = register({
|
||||
options={[
|
||||
{
|
||||
value: "hachure",
|
||||
text: t("labels.hachure"),
|
||||
text: `${
|
||||
allElementsZigZag ? t("labels.zigzag") : t("labels.hachure")
|
||||
} (${getShortcutKey("Alt-Click")})`,
|
||||
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
||||
active: allElementsZigZag ? true : undefined,
|
||||
},
|
||||
|
||||
@@ -115,7 +115,7 @@ export type ActionName =
|
||||
| "toggleLinearEditor"
|
||||
| "toggleEraserTool"
|
||||
| "toggleHandTool"
|
||||
| "createContainerFromText";
|
||||
| "wrapTextInContainer";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
||||
+14
-3
@@ -20,9 +20,20 @@ export const trackEvent = (
|
||||
});
|
||||
}
|
||||
|
||||
// MATOMO event tracking _paq must be same as the one in index.html
|
||||
if (window._paq) {
|
||||
window._paq.push(["trackEvent", category, action, label, value]);
|
||||
if (window.sa_event) {
|
||||
window.sa_event(action, {
|
||||
category,
|
||||
label,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
if (window.fathom) {
|
||||
window.fathom.trackEvent(action, {
|
||||
category,
|
||||
label,
|
||||
value,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error during analytics", error);
|
||||
|
||||
+9
-8
@@ -1,5 +1,6 @@
|
||||
import oc from "open-color";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
@@ -23,18 +24,18 @@ export const getDefaultAppState = (): Omit<
|
||||
theme: THEME.LIGHT,
|
||||
collaborators: new Map(),
|
||||
currentChartType: "bar",
|
||||
currentItemBackgroundColor: "transparent",
|
||||
currentItemBackgroundColor: DEFAULT_ELEMENT_PROPS.backgroundColor,
|
||||
currentItemEndArrowhead: "arrow",
|
||||
currentItemFillStyle: "hachure",
|
||||
currentItemFillStyle: DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||
currentItemOpacity: 100,
|
||||
currentItemRoughness: 1,
|
||||
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
|
||||
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
|
||||
currentItemStartArrowhead: null,
|
||||
currentItemStrokeColor: oc.black,
|
||||
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||
currentItemRoundness: "round",
|
||||
currentItemStrokeStyle: "solid",
|
||||
currentItemStrokeWidth: 1,
|
||||
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||
cursorButton: "up",
|
||||
draggingElement: null,
|
||||
@@ -44,7 +45,7 @@ export const getDefaultAppState = (): Omit<
|
||||
activeTool: {
|
||||
type: "selection",
|
||||
customType: null,
|
||||
locked: false,
|
||||
locked: DEFAULT_ELEMENT_PROPS.locked,
|
||||
lastActiveTool: null,
|
||||
},
|
||||
penMode: false,
|
||||
|
||||
+1
-17
@@ -1,10 +1,5 @@
|
||||
import colors from "./colors";
|
||||
import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
ENV,
|
||||
VERTICAL_ALIGN,
|
||||
} from "./constants";
|
||||
import { DEFAULT_FONT_SIZE, ENV } from "./constants";
|
||||
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||
import { NonDeletedExcalidrawElement } from "./element/types";
|
||||
import { randomId } from "./random";
|
||||
@@ -166,17 +161,7 @@ const bgColors = colors.elementBackground.slice(
|
||||
// Put all the common properties here so when the whole chart is selected
|
||||
// the properties dialog shows the correct selected values
|
||||
const commonProps = {
|
||||
fillStyle: "hachure",
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
opacity: 100,
|
||||
roughness: 1,
|
||||
strokeColor: colors.elementStroke[0],
|
||||
roundness: null,
|
||||
strokeStyle: "solid",
|
||||
strokeWidth: 1,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
locked: false,
|
||||
} as const;
|
||||
|
||||
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
||||
@@ -323,7 +308,6 @@ const chartBaseElements = (
|
||||
x: x + chartWidth / 2,
|
||||
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
||||
roundness: null,
|
||||
strokeStyle: "solid",
|
||||
textAlign: "center",
|
||||
})
|
||||
: null;
|
||||
|
||||
+27
-11
@@ -2,12 +2,12 @@ import {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./element/types";
|
||||
import { AppState, BinaryFiles } from "./types";
|
||||
import { BinaryFiles } from "./types";
|
||||
import { SVG_EXPORT_TAG } from "./scene/export";
|
||||
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "./constants";
|
||||
import { isInitializedImageElement } from "./element/typeChecks";
|
||||
import { isPromiseLike } from "./utils";
|
||||
import { isPromiseLike, isTestEnv } from "./utils";
|
||||
|
||||
type ElementsClipboard = {
|
||||
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||
@@ -55,24 +55,40 @@ const clipboardContainsElements = (
|
||||
|
||||
export const copyToClipboard = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
files: BinaryFiles | null,
|
||||
) => {
|
||||
let foundFile = false;
|
||||
|
||||
const _files = elements.reduce((acc, element) => {
|
||||
if (isInitializedImageElement(element)) {
|
||||
foundFile = true;
|
||||
if (files && files[element.fileId]) {
|
||||
acc[element.fileId] = files[element.fileId];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {} as BinaryFiles);
|
||||
|
||||
if (foundFile && !files) {
|
||||
console.warn(
|
||||
"copyToClipboard: attempting to file element(s) without providing associated `files` object.",
|
||||
);
|
||||
}
|
||||
|
||||
// select binded text elements when copying
|
||||
const contents: ElementsClipboard = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
elements,
|
||||
files: files
|
||||
? elements.reduce((acc, element) => {
|
||||
if (isInitializedImageElement(element) && files[element.fileId]) {
|
||||
acc[element.fileId] = files[element.fileId];
|
||||
}
|
||||
return acc;
|
||||
}, {} as BinaryFiles)
|
||||
: undefined,
|
||||
files: files ? _files : undefined,
|
||||
};
|
||||
const json = JSON.stringify(contents);
|
||||
|
||||
if (isTestEnv()) {
|
||||
return json;
|
||||
}
|
||||
|
||||
CLIPBOARD = json;
|
||||
|
||||
try {
|
||||
PREFER_APP_CLIPBOARD = false;
|
||||
await copyTextToSystemClipboard(json);
|
||||
|
||||
+18
-6
@@ -60,6 +60,7 @@ import {
|
||||
ENV,
|
||||
EVENT,
|
||||
GRID_SIZE,
|
||||
IMAGE_MIME_TYPES,
|
||||
IMAGE_RENDER_TIMEOUT,
|
||||
isAndroid,
|
||||
isBrave,
|
||||
@@ -295,7 +296,7 @@ import {
|
||||
} from "../actions/actionCanvas";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
@@ -1589,6 +1590,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elements: data.elements,
|
||||
files: data.files || null,
|
||||
position: "cursor",
|
||||
retainSeed: isPlainPaste,
|
||||
});
|
||||
} else if (data.text) {
|
||||
this.addTextFromPaste(data.text, isPlainPaste);
|
||||
@@ -1602,6 +1604,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
files: BinaryFiles | null;
|
||||
position: { clientX: number; clientY: number } | "cursor" | "center";
|
||||
retainSeed?: boolean;
|
||||
}) => {
|
||||
const elements = restoreElements(opts.elements, null);
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
@@ -1639,6 +1642,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
y: element.y + gridY - minY,
|
||||
});
|
||||
}),
|
||||
{
|
||||
randomizeSeed: !opts.retainSeed,
|
||||
},
|
||||
);
|
||||
|
||||
const nextElements = [
|
||||
@@ -2732,7 +2738,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
strokeStyle: this.state.currentItemStrokeStyle,
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
roundness: null,
|
||||
text: "",
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@@ -2744,8 +2749,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
: DEFAULT_VERTICAL_ALIGN,
|
||||
containerId: shouldBindToContainer ? container?.id : undefined,
|
||||
groupIds: container?.groupIds ?? [],
|
||||
locked: false,
|
||||
lineHeight,
|
||||
angle: container?.angle ?? 0,
|
||||
});
|
||||
|
||||
if (!existingTextElement && shouldBindToContainer && container) {
|
||||
@@ -4721,7 +4726,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.drag.hasOccurred = true;
|
||||
// prevent dragging even if we're no longer holding cmd/ctrl otherwise
|
||||
// it would have weird results (stuff jumping all over the screen)
|
||||
if (selectedElements.length > 0 && !pointerDownState.withCmdOrCtrl) {
|
||||
// Checking for editingElement to avoid jump while editing on mobile #6503
|
||||
if (
|
||||
selectedElements.length > 0 &&
|
||||
!pointerDownState.withCmdOrCtrl &&
|
||||
!this.state.editingElement
|
||||
) {
|
||||
const [dragX, dragY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.drag.offset.x,
|
||||
pointerCoords.y - pointerDownState.drag.offset.y,
|
||||
@@ -5744,7 +5754,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
const imageFile = await fileOpen({
|
||||
description: "Image",
|
||||
extensions: ["jpg", "png", "svg", "gif"],
|
||||
extensions: Object.keys(
|
||||
IMAGE_MIME_TYPES,
|
||||
) as (keyof typeof IMAGE_MIME_TYPES)[],
|
||||
});
|
||||
|
||||
const imageElement = this.createImageElement({
|
||||
@@ -6366,7 +6378,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
actionGroup,
|
||||
actionUnbindText,
|
||||
actionBindText,
|
||||
actionCreateContainerFromText,
|
||||
actionWrapTextInContainer,
|
||||
actionUngroup,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
actionAddToLibrary,
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary-color);
|
||||
border: 0;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
font-family: inherit;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.2fr;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { canvasToBlob } from "../data/blob";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { exportToCanvas } from "../scene/export";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { clipboard } from "./icons";
|
||||
@@ -15,6 +14,7 @@ import { CheckboxItem } from "./CheckboxItem";
|
||||
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants";
|
||||
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { exportToCanvas } from "../packages/utils";
|
||||
|
||||
const supportsContextFilters =
|
||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||
@@ -83,7 +83,6 @@ const ImageExportModal = ({
|
||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
const { exportBackground, viewBackgroundColor } = appState;
|
||||
const [renderError, setRenderError] = useState<Error | null>(null);
|
||||
|
||||
const exportedElements = exportSelected
|
||||
@@ -99,10 +98,16 @@ const ImageExportModal = ({
|
||||
if (!previewNode) {
|
||||
return;
|
||||
}
|
||||
exportToCanvas(exportedElements, appState, files, {
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
const maxWidth = previewNode.offsetWidth;
|
||||
if (!maxWidth) {
|
||||
return;
|
||||
}
|
||||
exportToCanvas({
|
||||
elements: exportedElements,
|
||||
appState,
|
||||
files,
|
||||
exportPadding,
|
||||
maxWidthOrHeight: maxWidth,
|
||||
})
|
||||
.then((canvas) => {
|
||||
setRenderError(null);
|
||||
@@ -116,14 +121,7 @@ const ImageExportModal = ({
|
||||
console.error(error);
|
||||
setRenderError(error);
|
||||
});
|
||||
}, [
|
||||
appState,
|
||||
files,
|
||||
exportedElements,
|
||||
exportBackground,
|
||||
exportPadding,
|
||||
viewBackgroundColor,
|
||||
]);
|
||||
}, [appState, files, exportedElements, exportPadding]);
|
||||
|
||||
return (
|
||||
<div className="ExportDialog">
|
||||
|
||||
@@ -102,7 +102,7 @@ const LibraryMenuItems = ({
|
||||
...item,
|
||||
// duplicate each library item before inserting on canvas to confine
|
||||
// ids and bindings to each library item. See #6465
|
||||
elements: duplicateElements(item.elements),
|
||||
elements: duplicateElements(item.elements, { randomizeSeed: true }),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
// container in body where the actual tooltip is appended to
|
||||
.excalidraw-tooltip {
|
||||
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
|
||||
Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: var(--ui-font);
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
|
||||
|
||||
+38
-17
@@ -1,6 +1,7 @@
|
||||
import cssVariables from "./css/variables.module.scss";
|
||||
import { AppProps } from "./types";
|
||||
import { FontFamilyValues } from "./element/types";
|
||||
import { ExcalidrawElement, FontFamilyValues } from "./element/types";
|
||||
import oc from "open-color";
|
||||
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
export const isWindows = /^Win/.test(navigator.platform);
|
||||
@@ -104,20 +105,30 @@ export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
||||
|
||||
export const GRID_SIZE = 20; // TODO make it configurable?
|
||||
|
||||
export const MIME_TYPES = {
|
||||
excalidraw: "application/vnd.excalidraw+json",
|
||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||
json: "application/json",
|
||||
export const IMAGE_MIME_TYPES = {
|
||||
svg: "image/svg+xml",
|
||||
"excalidraw.svg": "image/svg+xml",
|
||||
png: "image/png",
|
||||
"excalidraw.png": "image/png",
|
||||
jpg: "image/jpeg",
|
||||
gif: "image/gif",
|
||||
webp: "image/webp",
|
||||
bmp: "image/bmp",
|
||||
ico: "image/x-icon",
|
||||
avif: "image/avif",
|
||||
jfif: "image/jfif",
|
||||
} as const;
|
||||
|
||||
export const MIME_TYPES = {
|
||||
json: "application/json",
|
||||
// excalidraw data
|
||||
excalidraw: "application/vnd.excalidraw+json",
|
||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||
// image-encoded excalidraw data
|
||||
"excalidraw.svg": "image/svg+xml",
|
||||
"excalidraw.png": "image/png",
|
||||
// binary
|
||||
binary: "application/octet-stream",
|
||||
// image
|
||||
...IMAGE_MIME_TYPES,
|
||||
} as const;
|
||||
|
||||
export const EXPORT_DATA_TYPES = {
|
||||
@@ -188,16 +199,6 @@ export const DEFAULT_EXPORT_PADDING = 10; // px
|
||||
|
||||
export const DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT = 1440;
|
||||
|
||||
export const ALLOWED_IMAGE_MIME_TYPES = [
|
||||
MIME_TYPES.png,
|
||||
MIME_TYPES.jpg,
|
||||
MIME_TYPES.svg,
|
||||
MIME_TYPES.gif,
|
||||
MIME_TYPES.webp,
|
||||
MIME_TYPES.bmp,
|
||||
MIME_TYPES.ico,
|
||||
] as const;
|
||||
|
||||
export const MAX_ALLOWED_FILE_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
export const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
@@ -254,3 +255,23 @@ export const ROUNDNESS = {
|
||||
/** key containt id of precedeing elemnt id we use in reconciliation during
|
||||
* collaboration */
|
||||
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";
|
||||
|
||||
export const DEFAULT_ELEMENT_PROPS: {
|
||||
strokeColor: ExcalidrawElement["strokeColor"];
|
||||
backgroundColor: ExcalidrawElement["backgroundColor"];
|
||||
fillStyle: ExcalidrawElement["fillStyle"];
|
||||
strokeWidth: ExcalidrawElement["strokeWidth"];
|
||||
strokeStyle: ExcalidrawElement["strokeStyle"];
|
||||
roughness: ExcalidrawElement["roughness"];
|
||||
opacity: ExcalidrawElement["opacity"];
|
||||
locked: ExcalidrawElement["locked"];
|
||||
} = {
|
||||
strokeColor: oc.black,
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
locked: false,
|
||||
};
|
||||
|
||||
@@ -354,6 +354,7 @@
|
||||
border-radius: var(--space-factor);
|
||||
border: 1px solid var(--button-gray-2);
|
||||
font-size: 0.8rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
background-image: var(--dropdown-icon);
|
||||
@@ -413,6 +414,7 @@
|
||||
bottom: 30px;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: all;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
|
||||
+4
-6
@@ -1,6 +1,6 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement, FileId } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
@@ -117,11 +117,9 @@ export const isImageFileHandle = (handle: FileSystemHandle | null) => {
|
||||
|
||||
export const isSupportedImageFile = (
|
||||
blob: Blob | null | undefined,
|
||||
): blob is Blob & { type: typeof ALLOWED_IMAGE_MIME_TYPES[number] } => {
|
||||
): blob is Blob & { type: ValueOf<typeof IMAGE_MIME_TYPES> } => {
|
||||
const { type } = blob || {};
|
||||
return (
|
||||
!!type && (ALLOWED_IMAGE_MIME_TYPES as readonly string[]).includes(type)
|
||||
);
|
||||
return !!type && (Object.values(IMAGE_MIME_TYPES) as string[]).includes(type);
|
||||
};
|
||||
|
||||
export const loadSceneOrLibraryFromBlob = async (
|
||||
@@ -157,7 +155,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
},
|
||||
localAppState,
|
||||
localElements,
|
||||
{ repairBindings: true, refreshDimensions: true },
|
||||
{ repairBindings: true, refreshDimensions: false },
|
||||
),
|
||||
};
|
||||
} else if (isValidLibrary(data)) {
|
||||
|
||||
+1
-10
@@ -8,16 +8,7 @@ import { EVENT, MIME_TYPES } from "../constants";
|
||||
import { AbortError } from "../errors";
|
||||
import { debounce } from "../utils";
|
||||
|
||||
type FILE_EXTENSION =
|
||||
| "gif"
|
||||
| "jpg"
|
||||
| "png"
|
||||
| "excalidraw.png"
|
||||
| "svg"
|
||||
| "excalidraw.svg"
|
||||
| "json"
|
||||
| "excalidraw"
|
||||
| "excalidrawlib";
|
||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||
|
||||
const INPUT_CHANGE_INTERVAL_MS = 500;
|
||||
|
||||
|
||||
+65
-24
@@ -20,7 +20,7 @@ import {
|
||||
isTestEnv,
|
||||
} from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
import { mutateElement, newElementWith } from "./mutateElement";
|
||||
import { bumpVersion, mutateElement, newElementWith } from "./mutateElement";
|
||||
import { getNewGroupIdsForDuplication } from "../groups";
|
||||
import { AppState } from "../types";
|
||||
import { getElementAbsoluteCoords } from ".";
|
||||
@@ -33,10 +33,17 @@ import {
|
||||
measureText,
|
||||
normalizeText,
|
||||
wrapText,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
getDefaultLineHeight,
|
||||
} from "./textElement";
|
||||
import { VERTICAL_ALIGN } from "../constants";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
DEFAULT_VERTICAL_ALIGN,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { isArrowElement } from "./typeChecks";
|
||||
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||
|
||||
@@ -51,6 +58,15 @@ type ElementConstructorOpts = MarkOptional<
|
||||
| "version"
|
||||
| "versionNonce"
|
||||
| "link"
|
||||
| "strokeStyle"
|
||||
| "fillStyle"
|
||||
| "strokeColor"
|
||||
| "backgroundColor"
|
||||
| "roughness"
|
||||
| "strokeWidth"
|
||||
| "roundness"
|
||||
| "locked"
|
||||
| "opacity"
|
||||
>;
|
||||
|
||||
const _newElementBase = <T extends ExcalidrawElement>(
|
||||
@@ -58,13 +74,13 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
{
|
||||
x,
|
||||
y,
|
||||
strokeColor,
|
||||
backgroundColor,
|
||||
fillStyle,
|
||||
strokeWidth,
|
||||
strokeStyle,
|
||||
roughness,
|
||||
opacity,
|
||||
strokeColor = DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||
backgroundColor = DEFAULT_ELEMENT_PROPS.backgroundColor,
|
||||
fillStyle = DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
strokeWidth = DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||
strokeStyle = DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||
roughness = DEFAULT_ELEMENT_PROPS.roughness,
|
||||
opacity = DEFAULT_ELEMENT_PROPS.opacity,
|
||||
width = 0,
|
||||
height = 0,
|
||||
angle = 0,
|
||||
@@ -72,7 +88,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
roundness = null,
|
||||
boundElements = null,
|
||||
link = null,
|
||||
locked,
|
||||
locked = DEFAULT_ELEMENT_PROPS.locked,
|
||||
...rest
|
||||
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
||||
) => {
|
||||
@@ -138,27 +154,39 @@ const getTextElementPositionOffsets = (
|
||||
export const newTextElement = (
|
||||
opts: {
|
||||
text: string;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamilyValues;
|
||||
textAlign: TextAlign;
|
||||
verticalAlign: VerticalAlign;
|
||||
fontSize?: number;
|
||||
fontFamily?: FontFamilyValues;
|
||||
textAlign?: TextAlign;
|
||||
verticalAlign?: VerticalAlign;
|
||||
containerId?: ExcalidrawTextContainer["id"];
|
||||
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
||||
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
||||
} & ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawTextElement> => {
|
||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(opts.fontFamily);
|
||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(fontFamily);
|
||||
const text = normalizeText(opts.text);
|
||||
const metrics = measureText(text, getFontString(opts), lineHeight);
|
||||
const offsets = getTextElementPositionOffsets(opts, metrics);
|
||||
const metrics = measureText(
|
||||
text,
|
||||
getFontString({ fontFamily, fontSize }),
|
||||
lineHeight,
|
||||
);
|
||||
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
||||
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
||||
const offsets = getTextElementPositionOffsets(
|
||||
{ textAlign, verticalAlign },
|
||||
metrics,
|
||||
);
|
||||
|
||||
const textElement = newElementWith(
|
||||
{
|
||||
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
||||
text,
|
||||
fontSize: opts.fontSize,
|
||||
fontFamily: opts.fontFamily,
|
||||
textAlign: opts.textAlign,
|
||||
verticalAlign: opts.verticalAlign,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
x: opts.x - offsets.x,
|
||||
y: opts.y - offsets.y,
|
||||
width: metrics.width,
|
||||
@@ -282,7 +310,7 @@ export const refreshTextDimensions = (
|
||||
text = wrapText(
|
||||
text,
|
||||
getFontString(textElement),
|
||||
getMaxContainerWidth(container),
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
}
|
||||
const dimensions = getAdjustedDimensions(textElement, text);
|
||||
@@ -511,8 +539,16 @@ export const duplicateElement = <TElement extends ExcalidrawElement>(
|
||||
* it's advised to supply the whole elements array, or sets of elements that
|
||||
* are encapsulated (such as library items), if the purpose is to retain
|
||||
* bindings to the cloned elements intact.
|
||||
*
|
||||
* NOTE by default does not randomize or regenerate anything except the id.
|
||||
*/
|
||||
export const duplicateElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
export const duplicateElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
opts?: {
|
||||
/** NOTE also updates version flags and `updated` */
|
||||
randomizeSeed: boolean;
|
||||
},
|
||||
) => {
|
||||
const clonedElements: ExcalidrawElement[] = [];
|
||||
|
||||
const origElementsMap = arrayToMap(elements);
|
||||
@@ -546,6 +582,11 @@ export const duplicateElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
|
||||
clonedElement.id = maybeGetNewId(element.id)!;
|
||||
|
||||
if (opts?.randomizeSeed) {
|
||||
clonedElement.seed = randomInteger();
|
||||
bumpVersion(clonedElement);
|
||||
}
|
||||
|
||||
if (clonedElement.groupIds) {
|
||||
clonedElement.groupIds = clonedElement.groupIds.map((groupId) => {
|
||||
if (!groupNewIdsMap.has(groupId)) {
|
||||
|
||||
@@ -46,10 +46,10 @@ import {
|
||||
getBoundTextElementId,
|
||||
getContainerElement,
|
||||
handleBindTextResize,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
getApproxMinLineHeight,
|
||||
measureText,
|
||||
getMaxContainerHeight,
|
||||
getBoundTextMaxHeight,
|
||||
} from "./textElement";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
|
||||
@@ -210,7 +210,7 @@ const measureFontSizeFromWidth = (
|
||||
if (hasContainer) {
|
||||
const container = getContainerElement(element);
|
||||
if (container) {
|
||||
width = getMaxContainerWidth(container);
|
||||
width = getBoundTextMaxWidth(container);
|
||||
}
|
||||
}
|
||||
const nextFontSize = element.fontSize * (nextWidth / width);
|
||||
@@ -441,8 +441,8 @@ export const resizeSingleElement = (
|
||||
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
getMaxContainerWidth(updatedElement),
|
||||
getMaxContainerHeight(updatedElement),
|
||||
getBoundTextMaxWidth(updatedElement),
|
||||
getBoundTextMaxHeight(updatedElement, boundTextElement),
|
||||
);
|
||||
if (nextFont === null) {
|
||||
return;
|
||||
|
||||
@@ -3,14 +3,15 @@ import { API } from "../tests/helpers/api";
|
||||
import {
|
||||
computeContainerDimensionForBoundText,
|
||||
getContainerCoords,
|
||||
getMaxContainerWidth,
|
||||
getMaxContainerHeight,
|
||||
getBoundTextMaxWidth,
|
||||
getBoundTextMaxHeight,
|
||||
wrapText,
|
||||
detectLineHeight,
|
||||
getLineHeightInPx,
|
||||
getDefaultLineHeight,
|
||||
parseTokens,
|
||||
} from "./textElement";
|
||||
import { FontString } from "./types";
|
||||
import { ExcalidrawTextElementWithContainer, FontString } from "./types";
|
||||
|
||||
describe("Test wrapText", () => {
|
||||
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
||||
@@ -183,6 +184,56 @@ now`,
|
||||
expect(wrapText(text, font, -1)).toEqual(text);
|
||||
expect(wrapText(text, font, Infinity)).toEqual(text);
|
||||
});
|
||||
|
||||
it("should wrap the text correctly when text contains hyphen", () => {
|
||||
let text =
|
||||
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
||||
const res = wrapText(text, font, 110);
|
||||
expect(res).toBe(
|
||||
`Wikipedia \nis hosted \nby \nWikimedia-\nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts\na range-of\nother \nprojects`,
|
||||
);
|
||||
|
||||
text = "Hello thereusing-now";
|
||||
expect(wrapText(text, font, 100)).toEqual("Hello \nthereusin\ng-now");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test parseTokens", () => {
|
||||
it("should split into tokens correctly", () => {
|
||||
let text = "Excalidraw is a virtual collaborative whiteboard";
|
||||
expect(parseTokens(text)).toEqual([
|
||||
"Excalidraw",
|
||||
"is",
|
||||
"a",
|
||||
"virtual",
|
||||
"collaborative",
|
||||
"whiteboard",
|
||||
]);
|
||||
|
||||
text =
|
||||
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
||||
expect(parseTokens(text)).toEqual([
|
||||
"Wikipedia",
|
||||
"is",
|
||||
"hosted",
|
||||
"by",
|
||||
"Wikimedia-",
|
||||
"",
|
||||
"Foundation,",
|
||||
"a",
|
||||
"non-",
|
||||
"profit",
|
||||
"organization",
|
||||
"that",
|
||||
"also",
|
||||
"hosts",
|
||||
"a",
|
||||
"range-",
|
||||
"of",
|
||||
"other",
|
||||
"projects",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test measureText", () => {
|
||||
@@ -260,7 +311,7 @@ describe("Test measureText", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getMaxContainerWidth", () => {
|
||||
describe("Test getBoundTextMaxWidth", () => {
|
||||
const params = {
|
||||
width: 178,
|
||||
height: 194,
|
||||
@@ -268,39 +319,76 @@ describe("Test measureText", () => {
|
||||
|
||||
it("should return max width when container is rectangle", () => {
|
||||
const container = API.createElement({ type: "rectangle", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(168);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(168);
|
||||
});
|
||||
|
||||
it("should return max width when container is ellipse", () => {
|
||||
const container = API.createElement({ type: "ellipse", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(116);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(116);
|
||||
});
|
||||
|
||||
it("should return max width when container is diamond", () => {
|
||||
const container = API.createElement({ type: "diamond", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(79);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(79);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getMaxContainerHeight", () => {
|
||||
describe("Test getBoundTextMaxHeight", () => {
|
||||
const params = {
|
||||
width: 178,
|
||||
height: 194,
|
||||
id: '"container-id',
|
||||
};
|
||||
|
||||
const boundTextElement = API.createElement({
|
||||
type: "text",
|
||||
id: "text-id",
|
||||
x: 560.51171875,
|
||||
y: 202.033203125,
|
||||
width: 154,
|
||||
height: 175,
|
||||
fontSize: 20,
|
||||
fontFamily: 1,
|
||||
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
||||
textAlign: "center",
|
||||
verticalAlign: "middle",
|
||||
containerId: params.id,
|
||||
}) as ExcalidrawTextElementWithContainer;
|
||||
|
||||
it("should return max height when container is rectangle", () => {
|
||||
const container = API.createElement({ type: "rectangle", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(184);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(184);
|
||||
});
|
||||
|
||||
it("should return max height when container is ellipse", () => {
|
||||
const container = API.createElement({ type: "ellipse", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(127);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(127);
|
||||
});
|
||||
|
||||
it("should return max height when container is diamond", () => {
|
||||
const container = API.createElement({ type: "diamond", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(87);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(87);
|
||||
});
|
||||
|
||||
it("should return max height when container is arrow", () => {
|
||||
const container = API.createElement({
|
||||
type: "arrow",
|
||||
...params,
|
||||
});
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(194);
|
||||
});
|
||||
|
||||
it("should return max height when container is arrow and height is less than threshold", () => {
|
||||
const container = API.createElement({
|
||||
type: "arrow",
|
||||
...params,
|
||||
height: 70,
|
||||
boundElements: [{ type: "text", id: "text-id" }],
|
||||
});
|
||||
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(
|
||||
boundTextElement.height,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+73
-57
@@ -65,7 +65,7 @@ export const redrawTextBoundingBox = (
|
||||
boundTextUpdates.text = textElement.text;
|
||||
|
||||
if (container) {
|
||||
maxWidth = getMaxContainerWidth(container);
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
boundTextUpdates.text = wrapText(
|
||||
textElement.originalText,
|
||||
getFontString(textElement),
|
||||
@@ -83,35 +83,28 @@ export const redrawTextBoundingBox = (
|
||||
boundTextUpdates.baseline = metrics.baseline;
|
||||
|
||||
if (container) {
|
||||
if (isArrowElement(container)) {
|
||||
const centerX = textElement.x + textElement.width / 2;
|
||||
const centerY = textElement.y + textElement.height / 2;
|
||||
const diffWidth = metrics.width - textElement.width;
|
||||
const diffHeight = metrics.height - textElement.height;
|
||||
boundTextUpdates.x = centerY - (textElement.height + diffHeight) / 2;
|
||||
boundTextUpdates.y = centerX - (textElement.width + diffWidth) / 2;
|
||||
} else {
|
||||
const containerDims = getContainerDims(container);
|
||||
let maxContainerHeight = getMaxContainerHeight(container);
|
||||
const containerDims = getContainerDims(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
|
||||
let nextHeight = containerDims.height;
|
||||
if (metrics.height > maxContainerHeight) {
|
||||
nextHeight = computeContainerDimensionForBoundText(
|
||||
metrics.height,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { height: nextHeight });
|
||||
maxContainerHeight = getMaxContainerHeight(container);
|
||||
updateOriginalContainerCache(container.id, nextHeight);
|
||||
}
|
||||
const updatedTextElement = {
|
||||
...textElement,
|
||||
...boundTextUpdates,
|
||||
} as ExcalidrawTextElementWithContainer;
|
||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
||||
boundTextUpdates.x = x;
|
||||
boundTextUpdates.y = y;
|
||||
let nextHeight = containerDims.height;
|
||||
if (metrics.height > maxContainerHeight) {
|
||||
nextHeight = computeContainerDimensionForBoundText(
|
||||
metrics.height,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { height: nextHeight });
|
||||
updateOriginalContainerCache(container.id, nextHeight);
|
||||
}
|
||||
const updatedTextElement = {
|
||||
...textElement,
|
||||
...boundTextUpdates,
|
||||
} as ExcalidrawTextElementWithContainer;
|
||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
||||
boundTextUpdates.x = x;
|
||||
boundTextUpdates.y = y;
|
||||
}
|
||||
|
||||
mutateElement(textElement, boundTextUpdates);
|
||||
@@ -183,8 +176,11 @@ export const handleBindTextResize = (
|
||||
let nextHeight = textElement.height;
|
||||
let nextWidth = textElement.width;
|
||||
const containerDims = getContainerDims(container);
|
||||
const maxWidth = getMaxContainerWidth(container);
|
||||
const maxHeight = getMaxContainerHeight(container);
|
||||
const maxWidth = getBoundTextMaxWidth(container);
|
||||
const maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
let containerHeight = containerDims.height;
|
||||
let nextBaseLine = textElement.baseline;
|
||||
if (transformHandleType !== "n" && transformHandleType !== "s") {
|
||||
@@ -256,8 +252,8 @@ export const computeBoundTextPosition = (
|
||||
);
|
||||
}
|
||||
const containerCoords = getContainerCoords(container);
|
||||
const maxContainerHeight = getMaxContainerHeight(container);
|
||||
const maxContainerWidth = getMaxContainerWidth(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
|
||||
let x;
|
||||
let y;
|
||||
@@ -419,6 +415,24 @@ export const getTextHeight = (
|
||||
return getLineHeightInPx(fontSize, lineHeight) * lineCount;
|
||||
};
|
||||
|
||||
export const parseTokens = (text: string) => {
|
||||
// Splitting words containing "-" as those are treated as separate words
|
||||
// by css wrapping algorithm eg non-profit => non-, profit
|
||||
const words = text.split("-");
|
||||
if (words.length > 1) {
|
||||
// non-proft org => ['non-', 'profit org']
|
||||
words.forEach((word, index) => {
|
||||
if (index !== words.length - 1) {
|
||||
words[index] = word += "-";
|
||||
}
|
||||
});
|
||||
}
|
||||
// Joining the words with space and splitting them again with space to get the
|
||||
// final list of tokens
|
||||
// ['non-', 'profit org'] =>,'non- proft org' => ['non-','profit','org']
|
||||
return words.join(" ").split(" ");
|
||||
};
|
||||
|
||||
export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
// if maxWidth is not finite or NaN which can happen in case of bugs in
|
||||
// computation, we need to make sure we don't continue as we'll end up
|
||||
@@ -444,17 +458,16 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
};
|
||||
|
||||
originalLines.forEach((originalLine) => {
|
||||
const currentLineWidth = getTextWidth(originalLine, font);
|
||||
|
||||
//Push the line if its <= maxWidth
|
||||
// Push the line if its <= maxWidth
|
||||
if (currentLineWidth <= maxWidth) {
|
||||
lines.push(originalLine);
|
||||
return; // continue
|
||||
}
|
||||
const words = originalLine.split(" ");
|
||||
|
||||
const words = parseTokens(originalLine);
|
||||
resetParams();
|
||||
|
||||
let index = 0;
|
||||
@@ -472,6 +485,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
else if (currentWordWidth > maxWidth) {
|
||||
// push current line since the current word exceeds the max width
|
||||
// so will be appended in next line
|
||||
|
||||
push(currentLine);
|
||||
|
||||
resetParams();
|
||||
@@ -492,15 +506,15 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
currentLine += currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
// push current line if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
} else {
|
||||
// space needs to be appended before next word
|
||||
// as currentLine contains chars which couldn't be appended
|
||||
// to previous line
|
||||
// to previous line unless the line ends with hyphen to sync
|
||||
// with css word-wrap
|
||||
} else if (!currentLine.endsWith("-")) {
|
||||
currentLine += " ";
|
||||
currentLineWidthTillNow += spaceWidth;
|
||||
}
|
||||
@@ -518,12 +532,23 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
currentLine += `${word} `;
|
||||
|
||||
// if word ends with "-" then we don't need to add space
|
||||
// to sync with css word-wrap
|
||||
const shouldAppendSpace = !word.endsWith("-");
|
||||
currentLine += word;
|
||||
|
||||
if (shouldAppendSpace) {
|
||||
currentLine += " ";
|
||||
}
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
const word = currentLine.slice(0, -1);
|
||||
push(word);
|
||||
if (shouldAppendSpace) {
|
||||
lines.push(currentLine.slice(0, -1));
|
||||
} else {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
resetParams();
|
||||
break;
|
||||
}
|
||||
@@ -861,18 +886,10 @@ export const computeContainerDimensionForBoundText = (
|
||||
return dimension + padding;
|
||||
};
|
||||
|
||||
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
||||
export const getBoundTextMaxWidth = (container: ExcalidrawElement) => {
|
||||
const width = getContainerDims(container).width;
|
||||
if (isArrowElement(container)) {
|
||||
const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2;
|
||||
if (containerWidth <= 0) {
|
||||
const boundText = getBoundTextElement(container);
|
||||
if (boundText) {
|
||||
return boundText.width;
|
||||
}
|
||||
return BOUND_TEXT_PADDING * 8 * 2;
|
||||
}
|
||||
return containerWidth;
|
||||
return width - BOUND_TEXT_PADDING * 8 * 2;
|
||||
}
|
||||
|
||||
if (container.type === "ellipse") {
|
||||
@@ -889,16 +906,15 @@ export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
||||
return width - BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const getMaxContainerHeight = (container: ExcalidrawElement) => {
|
||||
export const getBoundTextMaxHeight = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
) => {
|
||||
const height = getContainerDims(container).height;
|
||||
if (isArrowElement(container)) {
|
||||
const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2;
|
||||
if (containerHeight <= 0) {
|
||||
const boundText = getBoundTextElement(container);
|
||||
if (boundText) {
|
||||
return boundText.height;
|
||||
}
|
||||
return BOUND_TEXT_PADDING * 8 * 2;
|
||||
return boundTextElement.height;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
@@ -526,6 +526,36 @@ describe("textWysiwyg", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should set the text element angle to same as container angle when binding to rotated container", async () => {
|
||||
const rectangle = API.createElement({
|
||||
type: "rectangle",
|
||||
width: 90,
|
||||
height: 75,
|
||||
angle: 45,
|
||||
});
|
||||
h.elements = [rectangle];
|
||||
mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
expect(text.angle).toBe(rectangle.angle);
|
||||
mouse.down();
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
fireEvent.change(editor, { target: { value: "Hello World!" } });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
editor.blur();
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should compute the container height correctly and not throw error when height is updated while editing the text", async () => {
|
||||
const diamond = API.createElement({
|
||||
type: "diamond",
|
||||
@@ -1506,7 +1536,7 @@ describe("textWysiwyg", () => {
|
||||
expect.objectContaining({
|
||||
text: "Excalidraw is an opensource virtual collaborative whiteboard",
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
textAlign: TEXT_ALIGN.LEFT,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
boundElements: null,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -32,8 +32,8 @@ import {
|
||||
normalizeText,
|
||||
redrawTextBoundingBox,
|
||||
wrapText,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
computeContainerDimensionForBoundText,
|
||||
detectLineHeight,
|
||||
} from "./textElement";
|
||||
@@ -205,8 +205,11 @@ export const textWysiwyg = ({
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = getMaxContainerWidth(container);
|
||||
maxHeight = getMaxContainerHeight(container);
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
|
||||
// autogrow container height if text exceeds
|
||||
if (!isArrowElement(container) && textElementHeight > maxHeight) {
|
||||
@@ -377,7 +380,7 @@ export const textWysiwyg = ({
|
||||
const wrappedText = wrapText(
|
||||
`${editable.value}${data}`,
|
||||
font,
|
||||
getMaxContainerWidth(container),
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
const width = getTextWidth(wrappedText, font);
|
||||
editable.style.width = `${width}px`;
|
||||
@@ -394,7 +397,7 @@ export const textWysiwyg = ({
|
||||
const wrappedText = wrapText(
|
||||
normalizeText(editable.value),
|
||||
font,
|
||||
getMaxContainerWidth(container!),
|
||||
getBoundTextMaxWidth(container!),
|
||||
);
|
||||
const { width, height } = measureText(
|
||||
wrappedText,
|
||||
|
||||
@@ -65,7 +65,7 @@ export const reconcileElements = (
|
||||
|
||||
// Mark duplicate for removal as it'll be replaced with the remote element
|
||||
if (local) {
|
||||
// Unless the ramote and local elements are the same element in which case
|
||||
// Unless the remote and local elements are the same element in which case
|
||||
// we need to keep it as we'd otherwise discard it from the resulting
|
||||
// array.
|
||||
if (local[0] === remoteElement) {
|
||||
|
||||
@@ -263,7 +263,7 @@ export const loadScene = async (
|
||||
await importFromBackend(id, privateKey),
|
||||
localDataState?.appState,
|
||||
localDataState?.elements,
|
||||
{ repairBindings: true, refreshDimensions: true },
|
||||
{ repairBindings: true, refreshDimensions: false },
|
||||
);
|
||||
} else {
|
||||
data = restore(localDataState || null, null, null, {
|
||||
|
||||
Vendored
+2
-2
@@ -18,8 +18,8 @@ interface Window {
|
||||
EXCALIDRAW_EXPORT_SOURCE: string;
|
||||
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
||||
gtag: Function;
|
||||
_paq: any[];
|
||||
_mtm: any[];
|
||||
sa_event: Function;
|
||||
fathom: { trackEvent: Function };
|
||||
}
|
||||
|
||||
interface CanvasRenderingContext2D {
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "كبير جدا",
|
||||
"solid": "كامل",
|
||||
"hachure": "خطوط",
|
||||
"zigzag": "",
|
||||
"crossHatch": "خطوط متقطعة",
|
||||
"thin": "نحيف",
|
||||
"bold": "داكن",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Много голям",
|
||||
"solid": "Солиден",
|
||||
"hachure": "Хералдика",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Двойно-пресечено",
|
||||
"thin": "Тънък",
|
||||
"bold": "Ясно очертан",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "অনেক বড়",
|
||||
"solid": "দৃঢ়",
|
||||
"hachure": "ভ্রুলেখা",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ক্রস হ্যাচ",
|
||||
"thin": "পাতলা",
|
||||
"bold": "পুরু",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Molt gran",
|
||||
"solid": "Sòlid",
|
||||
"hachure": "Ratlletes",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Ratlletes creuades",
|
||||
"thin": "Fi",
|
||||
"bold": "Negreta",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Velmi velké",
|
||||
"solid": "Plný",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "Tenký",
|
||||
"bold": "Tlustý",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Meget stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skravering",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Krydsskravering",
|
||||
"thin": "Tynd",
|
||||
"bold": "Fed",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Sehr groß",
|
||||
"solid": "Deckend",
|
||||
"hachure": "Schraffiert",
|
||||
"zigzag": "Zickzack",
|
||||
"crossHatch": "Kreuzschraffiert",
|
||||
"thin": "Dünn",
|
||||
"bold": "Fett",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Πολύ μεγάλο",
|
||||
"solid": "Συμπαγής",
|
||||
"hachure": "Εκκόλαψη",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Διασταυρούμενη εκκόλαψη",
|
||||
"thin": "Λεπτή",
|
||||
"bold": "Έντονη",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Very large",
|
||||
"solid": "Solid",
|
||||
"hachure": "Hachure",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Cross-hatch",
|
||||
"thin": "Thin",
|
||||
"bold": "Bold",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Muy grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Folleto",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Rayado transversal",
|
||||
"thin": "Fino",
|
||||
"bold": "Grueso",
|
||||
@@ -207,8 +208,8 @@
|
||||
"collabSaveFailed": "No se pudo guardar en la base de datos del backend. Si los problemas persisten, debería guardar su archivo localmente para asegurarse de que no pierde su trabajo.",
|
||||
"collabSaveFailed_sizeExceeded": "No se pudo guardar en la base de datos del backend, el lienzo parece ser demasiado grande. Debería guardar el archivo localmente para asegurarse de que no pierde su trabajo.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"start": "Parece que estás usando el navegador Brave",
|
||||
"aggressive_block_fingerprint": "Bloquear huellas dactilares agresivamente",
|
||||
"setting_enabled": "ajuste activado",
|
||||
"break": "Esto podría resultar en romper los",
|
||||
"text_elements": "Elementos de texto",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "doble clic",
|
||||
"drag": "arrastrar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editar puntos de línea/flecha",
|
||||
"editText": "Editar texto / añadir etiqueta",
|
||||
"github": "¿Ha encontrado un problema? Envíelo",
|
||||
"howto": "Siga nuestras guías",
|
||||
"or": "o",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Oso handia",
|
||||
"solid": "Solidoa",
|
||||
"hachure": "Itzalduna",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Marraduna",
|
||||
"thin": "Mehea",
|
||||
"bold": "Lodia",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "بسیار بزرگ",
|
||||
"solid": "توپر",
|
||||
"hachure": "هاشور",
|
||||
"zigzag": "",
|
||||
"crossHatch": "هاشور متقاطع",
|
||||
"thin": "نازک",
|
||||
"bold": "ضخیم",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Erittäin suuri",
|
||||
"solid": "Yhtenäinen",
|
||||
"hachure": "Vinoviivoitus",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Ristiviivoitus",
|
||||
"thin": "Ohut",
|
||||
"bold": "Lihavoitu",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Très grande",
|
||||
"solid": "Solide",
|
||||
"hachure": "Hachures",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Hachures croisées",
|
||||
"thin": "Fine",
|
||||
"bold": "Épaisse",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "double-clic",
|
||||
"drag": "glisser",
|
||||
"editor": "Éditeur",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Modifier les points de ligne/flèche",
|
||||
"editText": "Modifier le texte / ajouter un libellé",
|
||||
"github": "Problème trouvé ? Soumettre",
|
||||
"howto": "Suivez nos guides",
|
||||
"or": "ou",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Moi grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Folleto",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Raiado transversal",
|
||||
"thin": "Estreito",
|
||||
"bold": "Groso",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "גדול מאוד",
|
||||
"solid": "מוצק",
|
||||
"hachure": "קווים מקבילים קצרים להצגת כיוון וחדות שיפוע במפה",
|
||||
"zigzag": "",
|
||||
"crossHatch": "קווים מוצלבים שתי וערב",
|
||||
"thin": "דק",
|
||||
"bold": "מודגש",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "बहुत बड़ा",
|
||||
"solid": "दृढ़",
|
||||
"hachure": "हैशूर",
|
||||
"zigzag": "तेढ़ी मेढ़ी",
|
||||
"crossHatch": "क्रॉस हैच",
|
||||
"thin": "पतला",
|
||||
"bold": "मोटा",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Nagyon nagy",
|
||||
"solid": "Kitöltött",
|
||||
"hachure": "Vonalkázott",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Keresztcsíkozott",
|
||||
"thin": "Vékony",
|
||||
"bold": "Félkövér",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Sangat besar",
|
||||
"solid": "Padat",
|
||||
"hachure": "Garis-garis",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Asiran silang",
|
||||
"thin": "Lembut",
|
||||
"bold": "Tebal",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Molto grande",
|
||||
"solid": "Pieno",
|
||||
"hachure": "Tratteggio obliquo",
|
||||
"zigzag": "Zig zag",
|
||||
"crossHatch": "Tratteggio incrociato",
|
||||
"thin": "Sottile",
|
||||
"bold": "Grassetto",
|
||||
@@ -319,7 +320,7 @@
|
||||
"doubleClick": "doppio-click",
|
||||
"drag": "trascina",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editLineArrowPoints": "Modifica punti linea/freccia",
|
||||
"editText": "Modifica testo / aggiungi etichetta",
|
||||
"github": "Trovato un problema? Segnalalo",
|
||||
"howto": "Segui le nostre guide",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "特大",
|
||||
"solid": "ベタ塗り",
|
||||
"hachure": "斜線",
|
||||
"zigzag": "",
|
||||
"crossHatch": "網掛け",
|
||||
"thin": "細",
|
||||
"bold": "太字",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Meqqer aṭas",
|
||||
"solid": "Aččuran",
|
||||
"hachure": "Azerreg",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Azerreg anmidag",
|
||||
"thin": "Arqaq",
|
||||
"bold": "Azuran",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Өте үлкен",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "",
|
||||
"bold": "",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "매우 크게",
|
||||
"solid": "단색",
|
||||
"hachure": "평행선",
|
||||
"zigzag": "지그재그",
|
||||
"crossHatch": "교차선",
|
||||
"thin": "얇게",
|
||||
"bold": "굵게",
|
||||
@@ -256,7 +257,7 @@
|
||||
"resize": "SHIFT 키를 누르면서 조정하면 크기의 비율이 제한됩니다.\nALT를 누르면서 조정하면 중앙을 기준으로 크기를 조정합니다.",
|
||||
"resizeImage": "SHIFT를 눌러서 자유롭게 크기를 변경하거나,\nALT를 눌러서 중앙을 고정하고 크기를 변경하기",
|
||||
"rotate": "SHIFT 키를 누르면서 회전하면 각도를 제한할 수 있습니다.",
|
||||
"lineEditor_info": "포인트를 편집하려면 Ctrl/Cmd을 누르고 더블 클릭을 하거나 Ctrl/Cmd + Enter를 누르세요",
|
||||
"lineEditor_info": "꼭짓점을 수정하려면 CtrlOrCmd 키를 누르고 더블 클릭을 하거나 CtrlOrCmd + Enter를 누르세요.",
|
||||
"lineEditor_pointSelected": "Delete 키로 꼭짓점을 제거하거나,\nCtrlOrCmd+D 로 복제하거나, 드래그 해서 이동시키기",
|
||||
"lineEditor_nothingSelected": "꼭짓점을 선택해서 수정하거나 (SHIFT를 눌러서 여러개 선택),\nAlt를 누르고 클릭해서 새로운 꼭짓점 추가하기",
|
||||
"placeImage": "클릭해서 이미지를 배치하거나, 클릭하고 드래그해서 사이즈를 조정하기",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "더블 클릭",
|
||||
"drag": "드래그",
|
||||
"editor": "에디터",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "직선 / 화살표 꼭짓점 수정",
|
||||
"editText": "텍스트 수정 / 라벨 추가",
|
||||
"github": "문제 제보하기",
|
||||
"howto": "가이드 참고하기",
|
||||
"or": "또는",
|
||||
@@ -382,8 +383,8 @@
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "라이브러리 제출됨",
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을 다음의 링크에서 확인할 수 있습니다.",
|
||||
"link": "여기"
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을",
|
||||
"link": "여기에서 확인하실 수 있습니다."
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "라이브러리 리셋",
|
||||
|
||||
+35
-34
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "دانانەوە",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteAsPlaintext": "دایبنێ وەک دەقی سادە",
|
||||
"pasteCharts": "دانانەوەی خشتەکان",
|
||||
"selectAll": "دیاریکردنی هەموو",
|
||||
"multiSelect": "زیادکردنی بۆ دیاریکراوەکان",
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "زۆر گهوره",
|
||||
"solid": "سادە",
|
||||
"hachure": "هاچور",
|
||||
"zigzag": "زیگزاگ",
|
||||
"crossHatch": "کرۆس هاتچ",
|
||||
"thin": "تەنک",
|
||||
"bold": "تۆخ",
|
||||
@@ -110,7 +111,7 @@
|
||||
"increaseFontSize": "زایدکردنی قەبارەی فۆنت",
|
||||
"unbindText": "دەقەکە جیابکەرەوە",
|
||||
"bindText": "دەقەکە ببەستەوە بە کۆنتەینەرەکەوە",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "دەق لە چوارچێوەیەکدا بپێچە",
|
||||
"link": {
|
||||
"edit": "دەستکاریکردنی بەستەر",
|
||||
"create": "دروستکردنی بەستەر",
|
||||
@@ -194,7 +195,7 @@
|
||||
"resetLibrary": "ئەمە کتێبخانەکەت خاوێن دەکاتەوە. ئایا دڵنیایت?",
|
||||
"removeItemsFromsLibrary": "سڕینەوەی {{count}} ئایتم(ەکان) لە کتێبخانە؟",
|
||||
"invalidEncryptionKey": "کلیلی رەمزاندن دەبێت لە 22 پیت بێت. هاوکاری ڕاستەوخۆ لە کارخراوە.",
|
||||
"collabOfflineWarning": ""
|
||||
"collabOfflineWarning": "هێڵی ئینتەرنێت بەردەست نییە.\n گۆڕانکارییەکانت سەیڤ ناکرێن!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "جۆری فایلی پشتگیری نەکراو.",
|
||||
@@ -204,22 +205,22 @@
|
||||
"invalidSVGString": "ئێس ڤی جی نادروستە.",
|
||||
"cannotResolveCollabServer": "ناتوانێت پەیوەندی بکات بە سێرڤەری کۆلاب. تکایە لاپەڕەکە دووبارە باربکەوە و دووبارە هەوڵ بدەوە.",
|
||||
"importLibraryError": "نەیتوانی کتێبخانە بار بکات",
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"start": "پێدەچێت وێبگەڕی Brave بەکاربهێنیت لەگەڵ",
|
||||
"aggressive_block_fingerprint": "بلۆککردنی Fingerprinting بەشێوەیەکی توندوتیژانە",
|
||||
"setting_enabled": "ڕێکخستن چالاک کراوە",
|
||||
"break": "ئەمە دەکرێت ببێتە هۆی تێکدانی",
|
||||
"text_elements": "دانە دەقییەکان",
|
||||
"in_your_drawings": "لە وێنەکێشانەکانتدا",
|
||||
"strongly_recommend": "بە توندی پێشنیار دەکەین ئەم ڕێکخستنە لەکاربخەیت. دەتوانیت بڕۆیت بە دوای",
|
||||
"steps": "ئەم هەنگاوانەدا",
|
||||
"how": "بۆ ئەوەی ئەنجامی بدەیت",
|
||||
"disable_setting": " ئەگەر لەکارخستنی ئەم ڕێکخستنە پیشاندانی توخمەکانی دەق چاک نەکاتەوە، تکایە هەڵبستە بە کردنەوەی",
|
||||
"issue": "کێشەیەک",
|
||||
"write": "لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لە",
|
||||
"discord": "دیسکۆرد"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
@@ -237,7 +238,7 @@
|
||||
"penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە",
|
||||
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
|
||||
"eraser": "سڕەر",
|
||||
"hand": ""
|
||||
"hand": "دەست (ئامرازی پانکردن)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "کردارەکانی تابلۆ",
|
||||
@@ -245,7 +246,7 @@
|
||||
"shapes": "شێوەکان"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "",
|
||||
"canvasPanning": "بۆ جوڵاندنی تابلۆ، ویلی ماوسەکەت یان دوگمەی سپەیس بگرە لەکاتی ڕاکێشاندە، یانیش ئامرازی دەستەکە بەکاربهێنە",
|
||||
"linearElement": "کرتە بکە بۆ دەستپێکردنی چەند خاڵێک، ڕایبکێشە بۆ یەک هێڵ",
|
||||
"freeDraw": "کرتە بکە و ڕایبکێشە، کاتێک تەواو بوویت دەست هەڵگرە",
|
||||
"text": "زانیاری: هەروەها دەتوانیت دەق زیادبکەیت بە دوو کرتەکردن لە هەر شوێنێک لەگەڵ ئامڕازی دەستنیشانکردن",
|
||||
@@ -256,7 +257,7 @@
|
||||
"resize": "دەتوانیت ڕێژەکان سنووردار بکەیت بە ڕاگرتنی SHIFT لەکاتی گۆڕینی قەبارەدا،\nALT ڕابگرە بۆ گۆڕینی قەبارە لە ناوەندەوە",
|
||||
"resizeImage": "دەتوانیت بە ئازادی قەبارە بگۆڕیت بە ڕاگرتنی SHIFT،\nALT ڕابگرە بۆ گۆڕینی قەبارە لە ناوەندەوە",
|
||||
"rotate": "دەتوانیت گۆشەکان سنووردار بکەیت بە ڕاگرتنی SHIFT لەکاتی سوڕانەوەدا",
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_info": "یان Ctrl یان Cmd بگرە و دوانە کلیک بکە یانیش پەنجە بنێ بە Ctrl یان Cmd + ئینتەر بۆ دەستکاریکردنی خاڵەکان",
|
||||
"lineEditor_pointSelected": "بۆ لابردنی خاڵەکان Delete دابگرە، Ctrl Cmd+D بکە بۆ لەبەرگرتنەوە، یان بۆ جووڵە ڕاکێشان بکە",
|
||||
"lineEditor_nothingSelected": "خاڵێک هەڵبژێرە بۆ دەستکاریکردن (SHIFT ڕابگرە بۆ هەڵبژاردنی چەندین)،\nیان Alt ڕابگرە و کلیک بکە بۆ زیادکردنی خاڵە نوێیەکان",
|
||||
"placeImage": "کلیک بکە بۆ دانانی وێنەکە، یان کلیک بکە و ڕایبکێشە بۆ ئەوەی قەبارەکەی بە دەستی دابنێیت",
|
||||
@@ -264,7 +265,7 @@
|
||||
"bindTextToElement": "بۆ زیادکردنی دەق enter بکە",
|
||||
"deepBoxSelect": "CtrlOrCmd ڕابگرە بۆ هەڵبژاردنی قووڵ، و بۆ ڕێگریکردن لە ڕاکێشان",
|
||||
"eraserRevert": "بۆ گەڕاندنەوەی ئەو توخمانەی کە بۆ سڕینەوە نیشانە کراون، Alt ڕابگرە",
|
||||
"firefox_clipboard_write": ""
|
||||
"firefox_clipboard_write": "ئەم تایبەتمەندییە بە ئەگەرێکی زۆرەوە دەتوانرێت چالاک بکرێت بە ڕێکخستنی ئاڵای \"dom.events.asyncClipboard.clipboardItem\" بۆ \"true\". بۆ گۆڕینی ئاڵاکانی وێبگەڕ لە فایەرفۆکسدا، سەردانی لاپەڕەی \"about:config\" بکە."
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "ناتوانرێ پێشبینین پیشان بدرێت",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "دوو گرتە",
|
||||
"drag": "راکێشان",
|
||||
"editor": "دەستکاریکەر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "دەستکاری خاڵەکانی هێڵ/تیر بکە",
|
||||
"editText": "دەستکاری دەق بکە / لەیبڵێک زیاد بکە",
|
||||
"github": "کێشەیەکت دۆزیەوە؟ پێشکەشکردن",
|
||||
"howto": "شوێن ڕینماییەکانمان بکەوە",
|
||||
"or": "یان",
|
||||
@@ -334,8 +335,8 @@
|
||||
"zoomToFit": "زووم بکە بۆ ئەوەی لەگەڵ هەموو توخمەکاندا بگونجێت",
|
||||
"zoomToSelection": "زووم بکە بۆ دەستنیشانکراوەکان",
|
||||
"toggleElementLock": "قفڵ/کردنەوەی دەستنیشانکراوەکان",
|
||||
"movePageUpDown": "",
|
||||
"movePageLeftRight": ""
|
||||
"movePageUpDown": "لاپەڕەکە بجوڵێنە بۆ سەرەوە/خوارەوە",
|
||||
"movePageLeftRight": "لاپەڕەکە بجوڵێنە بۆ چەپ/ڕاست"
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "تابلۆکە خاوێن بکەرەوە"
|
||||
@@ -417,7 +418,7 @@
|
||||
"fileSavedToFilename": "هەڵگیراوە بۆ {filename}",
|
||||
"canvas": "تابلۆ",
|
||||
"selection": "دەستنیشانکراوەکان",
|
||||
"pasteAsSingleElement": ""
|
||||
"pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە"
|
||||
},
|
||||
"colors": {
|
||||
"ffffff": "سپی",
|
||||
@@ -468,15 +469,15 @@
|
||||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
"center_heading": "هەموو داتاکانت لە ناوخۆی وێنگەڕەکەتدا پاشەکەوت کراوە.",
|
||||
"center_heading_plus": "ویستت بڕۆیت بۆ Excalidraw+?",
|
||||
"menuHint": "هەناردەکردن، ڕێکخستنەکان، زمانەکان، ..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
"menuHint": "هەناردەکردن، ڕێکخستنەکان، و زیاتر...",
|
||||
"center_heading": "دایاگرامەکان. ئاسان. کراون.",
|
||||
"toolbarHint": "ئامرازێک هەڵبگرە و دەستبکە بە کێشان!",
|
||||
"helpHint": "قەدبڕەکان و یارمەتی"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Labai didelis",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "Plonas",
|
||||
"bold": "Pastorintas",
|
||||
|
||||
+28
-27
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Ļoti liels",
|
||||
"solid": "Pilns",
|
||||
"hachure": "Svītrots",
|
||||
"zigzag": "Zigzaglīnija",
|
||||
"crossHatch": "Šķērssvītrots",
|
||||
"thin": "Šaurs",
|
||||
"bold": "Trekns",
|
||||
@@ -110,7 +111,7 @@
|
||||
"increaseFontSize": "Palielināt fonta izmēru",
|
||||
"unbindText": "Atdalīt tekstu",
|
||||
"bindText": "Piesaistīt tekstu figūrai",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "Ietilpināt tekstu figurā",
|
||||
"link": {
|
||||
"edit": "Rediģēt saiti",
|
||||
"create": "Izveidot saiti",
|
||||
@@ -194,7 +195,7 @@
|
||||
"resetLibrary": "Šī funkcija iztukšos bibliotēku. Vai turpināt?",
|
||||
"removeItemsFromsLibrary": "Vai izņemt {{count}} vienumu(s) no bibliotēkas?",
|
||||
"invalidEncryptionKey": "Šifrēšanas atslēgai jābūt 22 simbolus garai. Tiešsaistes sadarbība ir izslēgta.",
|
||||
"collabOfflineWarning": ""
|
||||
"collabOfflineWarning": "Nav pieejams interneta pieslēgums.\nJūsu izmaiņas netiks saglabātas!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Neatbalstīts datnes veids.",
|
||||
@@ -207,19 +208,19 @@
|
||||
"collabSaveFailed": "Darbs nav saglabāts datubāzē. Ja problēma turpinās, saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
||||
"collabSaveFailed_sizeExceeded": "Darbs nav saglabāts datubāzē, šķiet, ka tāfele ir pārāk liela. Saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"start": "Izskatās, ka izmanto Brave interneta plārlūku ar ieslēgtu",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "ieslēgtu iestatījumu",
|
||||
"break": "Tas var salauzt",
|
||||
"text_elements": "Teksta elementus",
|
||||
"in_your_drawings": "tavos zīmējumos",
|
||||
"strongly_recommend": "Mēs iesakām izslēgt šo iestatījumu. Tu vari sekot",
|
||||
"steps": "šiem soļiem",
|
||||
"how": "kā to izdarīt",
|
||||
"disable_setting": " Ja šī iestatījuma izslēgšana neatrisina teksta elementu attēlošanu, tad, lūdzu, atver",
|
||||
"issue": "problēmu",
|
||||
"write": "mūsu GitHub vai raksti mums",
|
||||
"discord": "Discord"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
@@ -237,7 +238,7 @@
|
||||
"penMode": "Pildspalvas režīms – novērst pieskaršanos",
|
||||
"link": "Pievienot/rediģēt atlasītās figūras saiti",
|
||||
"eraser": "Dzēšgumija",
|
||||
"hand": ""
|
||||
"hand": "Roka (panoramēšanas rīks)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Tāfeles darbības",
|
||||
@@ -245,7 +246,7 @@
|
||||
"shapes": "Formas"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "",
|
||||
"canvasPanning": "Lai bīdītu tāfeli, turiet nospiestu ritināšanas vai atstarpes taustiņu, vai izmanto rokas rīku",
|
||||
"linearElement": "Klikšķiniet, lai sāktu zīmēt vairākus punktus; velciet, lai zīmētu līniju",
|
||||
"freeDraw": "Spiediet un velciet; atlaidiet, kad pabeidzat",
|
||||
"text": "Ieteikums: lai pievienotu tekstu, varat arī jebkur dubultklikšķināt ar atlases rīku",
|
||||
@@ -264,7 +265,7 @@
|
||||
"bindTextToElement": "Spiediet ievades taustiņu, lai pievienotu tekstu",
|
||||
"deepBoxSelect": "Turient nospiestu Ctrl vai Cmd, lai atlasītu dziļumā un lai nepieļautu objektu pavilkšanu",
|
||||
"eraserRevert": "Turiet Alt, lai noņemtu elementus no dzēsšanas atlases",
|
||||
"firefox_clipboard_write": ""
|
||||
"firefox_clipboard_write": "Šis iestatījums var tikt ieslēgts ar \"dom.events.asyncClipboard.clipboardItem\" marķieri pārslēgtu uz \"true\". Lai mainītu pārlūka marķierus Firefox, apmeklē \"about:config\" lapu."
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Nevar rādīt priekšskatījumu",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "dubultklikšķis",
|
||||
"drag": "vilkt",
|
||||
"editor": "Redaktors",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Rediģēt līniju/bultu punktus",
|
||||
"editText": "Rediģēt tekstu/pievienot birku",
|
||||
"github": "Sastapāt kļūdu? Ziņot",
|
||||
"howto": "Sekojiet mūsu instrukcijām",
|
||||
"or": "vai",
|
||||
@@ -468,15 +469,15 @@
|
||||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
"center_heading": "Visi jūsu dati tiek glabāti uz vietas jūsu pārlūkā.",
|
||||
"center_heading_plus": "Vai tā vietā vēlies doties uz Excalidraw+?",
|
||||
"menuHint": "Eksportēšana, iestatījumi, valodas..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
"menuHint": "Eksportēšana, iestatījumi un vēl...",
|
||||
"center_heading": "Diagrammas. Izveidotas. Vienkārši.",
|
||||
"toolbarHint": "Izvēlies rīku un sāc zīmēt!",
|
||||
"helpHint": "Īsceļi un palīdzība"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "फार मोठं",
|
||||
"solid": "भरीव",
|
||||
"hachure": "हैशूर रेखांकन",
|
||||
"zigzag": "वाकडी तिकड़ी",
|
||||
"crossHatch": "आडव्या रेघा",
|
||||
"thin": "पातळ",
|
||||
"bold": "जाड",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "ပိုကြီး",
|
||||
"solid": "အပြည့်",
|
||||
"hachure": "မျဉ်းစောင်း",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ဇကာကွက်",
|
||||
"thin": "ပါး",
|
||||
"bold": "ထူ",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Svært stor",
|
||||
"solid": "Helfarge",
|
||||
"hachure": "Skravert",
|
||||
"zigzag": "Sikk-sakk",
|
||||
"crossHatch": "Krysskravert",
|
||||
"thin": "Tynn",
|
||||
"bold": "Tykk",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Zeer groot",
|
||||
"solid": "Ingekleurd",
|
||||
"hachure": "Arcering",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Tweemaal gearceerd",
|
||||
"thin": "Dun",
|
||||
"bold": "Vet",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Svært stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skravert",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Krysskravert",
|
||||
"thin": "Tynn",
|
||||
"bold": "Tjukk",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Gradassa",
|
||||
"solid": "Solide",
|
||||
"hachure": "Raia",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Raia crosada",
|
||||
"thin": "Fin",
|
||||
"bold": "Espés",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "ਬਹੁਤ ਵੱਡਾ",
|
||||
"solid": "ਠੋਸ",
|
||||
"hachure": "ਤਿਰਛੀਆਂ ਗਰਿੱਲਾਂ",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ਜਾਲੀ",
|
||||
"thin": "ਪਤਲੀ",
|
||||
"bold": "ਮੋਟੀ",
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"ar-SA": 89,
|
||||
"ar-SA": 88,
|
||||
"bg-BG": 52,
|
||||
"bn-BD": 57,
|
||||
"ca-ES": 96,
|
||||
"cs-CZ": 72,
|
||||
"ca-ES": 95,
|
||||
"cs-CZ": 71,
|
||||
"da-DK": 31,
|
||||
"de-DE": 100,
|
||||
"el-GR": 98,
|
||||
"en": 100,
|
||||
"es-ES": 99,
|
||||
"es-ES": 100,
|
||||
"eu-ES": 99,
|
||||
"fa-IR": 91,
|
||||
"fi-FI": 96,
|
||||
"fi-FI": 95,
|
||||
"fr-FR": 99,
|
||||
"gl-ES": 99,
|
||||
"he-IL": 99,
|
||||
@@ -19,35 +19,35 @@
|
||||
"hu-HU": 85,
|
||||
"id-ID": 98,
|
||||
"it-IT": 99,
|
||||
"ja-JP": 97,
|
||||
"ja-JP": 96,
|
||||
"kab-KAB": 93,
|
||||
"kk-KZ": 19,
|
||||
"ko-KR": 99,
|
||||
"ku-TR": 91,
|
||||
"ko-KR": 100,
|
||||
"ku-TR": 100,
|
||||
"lt-LT": 61,
|
||||
"lv-LV": 93,
|
||||
"lv-LV": 100,
|
||||
"mr-IN": 100,
|
||||
"my-MM": 40,
|
||||
"my-MM": 39,
|
||||
"nb-NO": 100,
|
||||
"nl-NL": 92,
|
||||
"nn-NO": 86,
|
||||
"nn-NO": 85,
|
||||
"oc-FR": 94,
|
||||
"pa-IN": 79,
|
||||
"pl-PL": 87,
|
||||
"pt-BR": 96,
|
||||
"pt-PT": 99,
|
||||
"pt-BR": 95,
|
||||
"pt-PT": 100,
|
||||
"ro-RO": 100,
|
||||
"ru-RU": 96,
|
||||
"ru-RU": 100,
|
||||
"si-LK": 8,
|
||||
"sk-SK": 99,
|
||||
"sl-SI": 100,
|
||||
"sv-SE": 99,
|
||||
"sv-SE": 100,
|
||||
"ta-IN": 90,
|
||||
"th-TH": 39,
|
||||
"tr-TR": 98,
|
||||
"uk-UA": 93,
|
||||
"tr-TR": 97,
|
||||
"uk-UA": 92,
|
||||
"vi-VN": 52,
|
||||
"zh-CN": 99,
|
||||
"zh-HK": 25,
|
||||
"zh-HK": 24,
|
||||
"zh-TW": 100
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Bardzo duży",
|
||||
"solid": "Pełne",
|
||||
"hachure": "Linie",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Zakreślone",
|
||||
"thin": "Cienkie",
|
||||
"bold": "Pogrubione",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Muito grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Hachura",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Hachura cruzada",
|
||||
"thin": "Fino",
|
||||
"bold": "Espesso",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Muito grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Eclosão",
|
||||
"zigzag": "ziguezague",
|
||||
"crossHatch": "Sombreado",
|
||||
"thin": "Fino",
|
||||
"bold": "Espesso",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "clique duplo",
|
||||
"drag": "arrastar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editar pontos de linha/seta",
|
||||
"editText": "Editar texto / adicionar etiqueta",
|
||||
"github": "Encontrou algum problema? Informe-nos",
|
||||
"howto": "Siga os nossos guias",
|
||||
"or": "ou",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Foarte mare",
|
||||
"solid": "Plină",
|
||||
"hachure": "Hașură",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Hașură transversală",
|
||||
"thin": "Subțire",
|
||||
"bold": "Îngroșată",
|
||||
|
||||
+17
-16
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Очень большой",
|
||||
"solid": "Однотонная",
|
||||
"hachure": "Штрихованная",
|
||||
"zigzag": "Зигзаг",
|
||||
"crossHatch": "Перекрестная",
|
||||
"thin": "Тонкая",
|
||||
"bold": "Жирная",
|
||||
@@ -110,7 +111,7 @@
|
||||
"increaseFontSize": "Увеличить шрифт",
|
||||
"unbindText": "Отвязать текст",
|
||||
"bindText": "Привязать текст к контейнеру",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "Поместить текст в контейнер",
|
||||
"link": {
|
||||
"edit": "Редактировать ссылку",
|
||||
"create": "Создать ссылку",
|
||||
@@ -207,19 +208,19 @@
|
||||
"collabSaveFailed": "Не удалось сохранить в базу данных. Если проблема повторится, нужно будет сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу.",
|
||||
"collabSaveFailed_sizeExceeded": "Не удалось сохранить в базу данных. Похоже, что холст слишком большой. Нужно сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"start": "Похоже, вы используете браузер Brave с",
|
||||
"aggressive_block_fingerprint": "Агрессивно блокировать фингерпринтинг",
|
||||
"setting_enabled": "параметр включен",
|
||||
"break": "Это может привести к поломке",
|
||||
"text_elements": "Текстовых элементов",
|
||||
"in_your_drawings": "в ваших рисунках",
|
||||
"strongly_recommend": "Мы настоятельно рекомендуем отключить эту настройку. Вы можете выполнить",
|
||||
"steps": "эти действия",
|
||||
"how": "для отключения",
|
||||
"disable_setting": " Если отключение этого параметра не исправит отображение текстовых элементов, пожалуйста, откройте",
|
||||
"issue": "issue",
|
||||
"write": "на нашем GitHub, или напишите нам в",
|
||||
"discord": "Discord"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "двойной клик",
|
||||
"drag": "перетащить",
|
||||
"editor": "Редактор",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Редактировать концы линий/стрелок",
|
||||
"editText": "Редактировать текст / добавить метку",
|
||||
"github": "Нашли проблему? Отправьте",
|
||||
"howto": "Следуйте нашим инструкциям",
|
||||
"or": "или",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "ඉතා විශාල",
|
||||
"solid": "විශාල",
|
||||
"hachure": "මධ්යම",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "කෙට්ටු",
|
||||
"bold": "තද",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Veľmi veľké",
|
||||
"solid": "Plná",
|
||||
"hachure": "Šrafovaná",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Mriežkovaná",
|
||||
"thin": "Tenká",
|
||||
"bold": "Hrubá",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "dvojklik",
|
||||
"drag": "potiahnutie",
|
||||
"editor": "Editovanie",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editácia bodov čiary/šípky",
|
||||
"editText": "Editácia textu / pridanie štítku",
|
||||
"github": "Objavili ste problém? Nahláste ho",
|
||||
"howto": "Postupujte podľa naších návodov",
|
||||
"or": "alebo",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Zelo velika",
|
||||
"solid": "Polno",
|
||||
"hachure": "Šrafura",
|
||||
"zigzag": "Cikcak",
|
||||
"crossHatch": "Križno",
|
||||
"thin": "Tanko",
|
||||
"bold": "Krepko",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Mycket stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skraffering",
|
||||
"zigzag": "Sicksack",
|
||||
"crossHatch": "Skraffera med kors",
|
||||
"thin": "Tunn",
|
||||
"bold": "Fet",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "dubbelklicka",
|
||||
"drag": "dra",
|
||||
"editor": "Redigerare",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Redigera linje-/pilpunkter",
|
||||
"editText": "Redigera text / lägg till etikett",
|
||||
"github": "Hittat ett problem? Rapportera",
|
||||
"howto": "Följ våra guider",
|
||||
"or": "eller",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "மிகப் பெரிய",
|
||||
"solid": "திடமான",
|
||||
"hachure": "மலைக்குறிக்கோடு",
|
||||
"zigzag": "",
|
||||
"crossHatch": "குறுக்குகதவு",
|
||||
"thin": "மெல்லிய",
|
||||
"bold": "பட்டை",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "ใหญ่มาก",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "บาง",
|
||||
"bold": "หนา",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Çok geniş",
|
||||
"solid": "Dolu",
|
||||
"hachure": "Taralı",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Çapraz-taralı",
|
||||
"thin": "İnce",
|
||||
"bold": "Kalın",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Дуже великий",
|
||||
"solid": "Суцільна",
|
||||
"hachure": "Штриховка",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Перехресна штриховка",
|
||||
"thin": "Тонкий",
|
||||
"bold": "Жирний",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "Rất lớn",
|
||||
"solid": "Đặc",
|
||||
"hachure": "Nét gạch gạch",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Nét gạch chéo",
|
||||
"thin": "Mỏng",
|
||||
"bold": "In đậm",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "加大",
|
||||
"solid": "实心",
|
||||
"hachure": "线条",
|
||||
"zigzag": "",
|
||||
"crossHatch": "交叉线条",
|
||||
"thin": "细",
|
||||
"bold": "粗",
|
||||
@@ -319,8 +320,8 @@
|
||||
"doubleClick": "双击",
|
||||
"drag": "拖动",
|
||||
"editor": "编辑器",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "编辑线条或箭头的点",
|
||||
"editText": "添加或编辑文本",
|
||||
"github": "提交问题",
|
||||
"howto": "帮助文档",
|
||||
"or": "或",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "勁大",
|
||||
"solid": "實心",
|
||||
"hachure": "斜線",
|
||||
"zigzag": "",
|
||||
"crossHatch": "交叉格仔",
|
||||
"thin": "幼",
|
||||
"bold": "粗",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"veryLarge": "特大",
|
||||
"solid": "實心",
|
||||
"hachure": "斜線筆觸",
|
||||
"zigzag": "Z字形",
|
||||
"crossHatch": "交叉筆觸",
|
||||
"thin": "細",
|
||||
"bold": "粗",
|
||||
|
||||
@@ -11,7 +11,39 @@ The change should be grouped under one of the below section and must contain PR
|
||||
Please add the latest change on the top under the correct section.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
## 0.15.2 (2023-04-20)
|
||||
|
||||
### Docs
|
||||
|
||||
- Fix docs link in readme [#6486](https://github.com/excalidraw/excalidraw/pull/6486)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Fixes
|
||||
|
||||
- Rotate the text element when binding to a rotated container [#6477](https://github.com/excalidraw/excalidraw/pull/6477)
|
||||
|
||||
- Support breaking words containing hyphen - [#6014](https://github.com/excalidraw/excalidraw/pull/6014)
|
||||
|
||||
- Incorrect background fill button active state [#6491](https://github.com/excalidraw/excalidraw/pull/6491)
|
||||
|
||||
---
|
||||
|
||||
## 0.15.1 (2023-04-18)
|
||||
|
||||
### Docs
|
||||
|
||||
- Add the readme back to the package which was mistakenly removed [#6484](https://github.com/excalidraw/excalidraw/pull/6484)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
---
|
||||
|
||||
## 0.15.0 (2023-04-18)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -37,6 +69,154 @@ For more details refer to the [docs](https://docs.excalidraw.com)
|
||||
|
||||
- Exporting labelled arrows via export utils [#6443](https://github.com/excalidraw/excalidraw/pull/6443)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Features
|
||||
|
||||
- Constrain export dialog preview size [#6475](https://github.com/excalidraw/excalidraw/pull/6475)
|
||||
|
||||
- Zigzag fill easter egg [#6439](https://github.com/excalidraw/excalidraw/pull/6439)
|
||||
|
||||
- Add container to multiple text elements [#6428](https://github.com/excalidraw/excalidraw/pull/6428)
|
||||
|
||||
- Starting migration from GA to Matomo for better privacy [#6398](https://github.com/excalidraw/excalidraw/pull/6398)
|
||||
|
||||
- Add line height attribute to text element [#6360](https://github.com/excalidraw/excalidraw/pull/6360)
|
||||
|
||||
- Add thai lang support [#6314](https://github.com/excalidraw/excalidraw/pull/6314)
|
||||
|
||||
- Create bound container from text [#6301](https://github.com/excalidraw/excalidraw/pull/6301)
|
||||
|
||||
- Improve text measurements in bound containers [#6187](https://github.com/excalidraw/excalidraw/pull/6187)
|
||||
|
||||
- Bind text to container if double clicked on filled shape or stroke [#6250](https://github.com/excalidraw/excalidraw/pull/6250)
|
||||
|
||||
- Make repair and refreshDimensions configurable in restoreElements [#6238](https://github.com/excalidraw/excalidraw/pull/6238)
|
||||
|
||||
- Show error message when not connected to internet while collabo… [#6165](https://github.com/excalidraw/excalidraw/pull/6165)
|
||||
|
||||
- Shortcut for clearCanvas confirmDialog [#6114](https://github.com/excalidraw/excalidraw/pull/6114)
|
||||
|
||||
- Disable canvas smoothing (antialiasing) for right-angled elements [#6186](https://github.com/excalidraw/excalidraw/pull/6186)Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
|
||||
|
||||
### Fixes
|
||||
|
||||
- Center align text when wrapped in container via context menu [#6480](https://github.com/excalidraw/excalidraw/pull/6480)
|
||||
|
||||
- Restore original container height when unbinding text which was binded via context menu [#6444](https://github.com/excalidraw/excalidraw/pull/6444)
|
||||
|
||||
- Mark more props as optional for element [#6448](https://github.com/excalidraw/excalidraw/pull/6448)
|
||||
|
||||
- Improperly cache-busting on canvas scale instead of zoom [#6473](https://github.com/excalidraw/excalidraw/pull/6473)
|
||||
|
||||
- Incorrectly duplicating items on paste/library insert [#6467](https://github.com/excalidraw/excalidraw/pull/6467)
|
||||
|
||||
- Library ids cross-contamination on multiple insert [#6466](https://github.com/excalidraw/excalidraw/pull/6466)
|
||||
|
||||
- Color picker keyboard handling not working [#6464](https://github.com/excalidraw/excalidraw/pull/6464)
|
||||
|
||||
- Abort freedraw line if second touch is detected [#6440](https://github.com/excalidraw/excalidraw/pull/6440)
|
||||
|
||||
- Utils leaking Scene state [#6461](https://github.com/excalidraw/excalidraw/pull/6461)
|
||||
|
||||
- Split "Edit selected shape" shortcut [#6457](https://github.com/excalidraw/excalidraw/pull/6457)
|
||||
|
||||
- Center align text when bind to container via context menu [#6451](https://github.com/excalidraw/excalidraw/pull/6451)
|
||||
|
||||
- Update coords when text unbinded from its container [#6445](https://github.com/excalidraw/excalidraw/pull/6445)
|
||||
|
||||
- Autoredirect to plus in prod only [#6446](https://github.com/excalidraw/excalidraw/pull/6446)
|
||||
|
||||
- Fixing popover overflow on small screen [#6433](https://github.com/excalidraw/excalidraw/pull/6433)
|
||||
|
||||
- Introduce baseline to fix the layout shift when switching to text editor [#6397](https://github.com/excalidraw/excalidraw/pull/6397)
|
||||
|
||||
- Don't refresh dimensions for deleted text elements [#6438](https://github.com/excalidraw/excalidraw/pull/6438)
|
||||
|
||||
- Element vanishes when zoomed in [#6417](https://github.com/excalidraw/excalidraw/pull/6417)
|
||||
|
||||
- Don't jump text to end when out of viewport in safari [#6416](https://github.com/excalidraw/excalidraw/pull/6416)
|
||||
|
||||
- GetDefaultLineHeight should return default font family line height for unknown font [#6399](https://github.com/excalidraw/excalidraw/pull/6399)
|
||||
|
||||
- Revert use `ideographic` textBaseline to improve layout shift when editing text" [#6400](https://github.com/excalidraw/excalidraw/pull/6400)
|
||||
|
||||
- Call stack size exceeded when paste large text [#6373](https://github.com/excalidraw/excalidraw/pull/6373) (#6396)
|
||||
|
||||
- Use `ideographic` textBaseline to improve layout shift when editing text [#6384](https://github.com/excalidraw/excalidraw/pull/6384)
|
||||
|
||||
- Chrome crashing when embedding scene on chrome arm [#6383](https://github.com/excalidraw/excalidraw/pull/6383)
|
||||
|
||||
- Division by zero in findFocusPointForEllipse leads to infinite loop in wrapText freezing Excalidraw [#6377](https://github.com/excalidraw/excalidraw/pull/6377)
|
||||
|
||||
- Containerizing text incorrectly updates arrow bindings [#6369](https://github.com/excalidraw/excalidraw/pull/6369)
|
||||
|
||||
- Ensure export preview is centered [#6337](https://github.com/excalidraw/excalidraw/pull/6337)
|
||||
|
||||
- Hide text align for labelled arrows [#6339](https://github.com/excalidraw/excalidraw/pull/6339)
|
||||
|
||||
- Refresh dimensions when elements loaded from shareable link and blob [#6333](https://github.com/excalidraw/excalidraw/pull/6333)
|
||||
|
||||
- Show error message when measureText API breaks in brave [#6336](https://github.com/excalidraw/excalidraw/pull/6336)
|
||||
|
||||
- Add an offset of 0.5px for text editor in containers [#6328](https://github.com/excalidraw/excalidraw/pull/6328)
|
||||
|
||||
- Move utility types out of `.d.ts` file to fix exported declaration files [#6315](https://github.com/excalidraw/excalidraw/pull/6315)
|
||||
|
||||
- More jotai scopes missing [#6313](https://github.com/excalidraw/excalidraw/pull/6313)
|
||||
|
||||
- Provide HelpButton title prop [#6209](https://github.com/excalidraw/excalidraw/pull/6209)
|
||||
|
||||
- Respect text align when wrapping in a container [#6310](https://github.com/excalidraw/excalidraw/pull/6310)
|
||||
|
||||
- Compute bounding box correctly for text element when multiple element resizing [#6307](https://github.com/excalidraw/excalidraw/pull/6307)
|
||||
|
||||
- Use jotai scope for editor-specific atoms [#6308](https://github.com/excalidraw/excalidraw/pull/6308)
|
||||
|
||||
- Consider arrow for bound text element [#6297](https://github.com/excalidraw/excalidraw/pull/6297)
|
||||
|
||||
- Text never goes beyond max width for unbound text elements [#6288](https://github.com/excalidraw/excalidraw/pull/6288)
|
||||
|
||||
- Svg text baseline [#6285](https://github.com/excalidraw/excalidraw/pull/6273)
|
||||
|
||||
- Compute container height from bound text correctly [#6273](https://github.com/excalidraw/excalidraw/pull/6273)
|
||||
|
||||
- Fit mobile toolbar and make scrollable [#6270](https://github.com/excalidraw/excalidraw/pull/6270)
|
||||
|
||||
- Indenting via `tab` clashing with IME compositor [#6258](https://github.com/excalidraw/excalidraw/pull/6258)
|
||||
|
||||
- Improve text wrapping inside rhombus and more fixes [#6265](https://github.com/excalidraw/excalidraw/pull/6265)
|
||||
|
||||
- Improve text wrapping in ellipse and alignment [#6172](https://github.com/excalidraw/excalidraw/pull/6172)
|
||||
|
||||
- Don't allow blank space in collab name [#6211](https://github.com/excalidraw/excalidraw/pull/6211)
|
||||
|
||||
- Docker build architecture:linux/amd64 error occur on linux/arm64 instance [#6197](https://github.com/excalidraw/excalidraw/pull/6197)
|
||||
|
||||
- Sort bound text elements to fix text duplication z-index error [#5130](https://github.com/excalidraw/excalidraw/pull/5130)
|
||||
|
||||
- Hide welcome screen on mobile once user interacts [#6185](https://github.com/excalidraw/excalidraw/pull/6185)
|
||||
|
||||
- Edit link in docs [#6182](https://github.com/excalidraw/excalidraw/pull/6182)
|
||||
|
||||
### Refactor
|
||||
|
||||
- Inline `SingleLibraryItem` into `PublishLibrary` [#6462](https://github.com/excalidraw/excalidraw/pull/6462)
|
||||
|
||||
- Make the example React app reusable without duplication [#6188](https://github.com/excalidraw/excalidraw/pull/6188)
|
||||
|
||||
### Performance
|
||||
|
||||
- Break early if the line width <= max width of the container [#6347](https://github.com/excalidraw/excalidraw/pull/6347)
|
||||
|
||||
### Build
|
||||
|
||||
- Move TS and types to devDependencies [#6346](https://github.com/excalidraw/excalidraw/pull/6346)
|
||||
|
||||
---
|
||||
|
||||
## 0.14.2 (2023-02-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -38,8 +38,8 @@ Excalidraw takes _100%_ of `width` and `height` of the containing block so make
|
||||
|
||||
## Integration
|
||||
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/package/integration)
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration)
|
||||
|
||||
## API
|
||||
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/package/api)
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.14.2",
|
||||
"version": "0.15.2",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
|
||||
+6
-10
@@ -79,7 +79,11 @@ export const exportToCanvas = ({
|
||||
|
||||
const max = Math.max(width, height);
|
||||
|
||||
const scale = maxWidthOrHeight / max;
|
||||
// if content is less then maxWidthOrHeight, fallback on supplied scale
|
||||
const scale =
|
||||
maxWidthOrHeight < max
|
||||
? maxWidthOrHeight / max
|
||||
: appState?.exportScale ?? 1;
|
||||
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
@@ -216,15 +220,7 @@ export const exportToClipboard = async (
|
||||
} else if (opts.type === "png") {
|
||||
await copyBlobToClipboardAsPng(exportToBlob(opts));
|
||||
} else if (opts.type === "json") {
|
||||
const appState = {
|
||||
offsetTop: 0,
|
||||
offsetLeft: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
...getDefaultAppState(),
|
||||
...opts.appState,
|
||||
};
|
||||
await copyToClipboard(opts.elements, appState, opts.files);
|
||||
await copyToClipboard(opts.elements, opts.files);
|
||||
} else {
|
||||
throw new Error("Invalid export type");
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ import {
|
||||
getContainerCoords,
|
||||
getContainerElement,
|
||||
getLineHeightInPx,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
@@ -864,17 +864,21 @@ const drawElementFromCanvas = (
|
||||
);
|
||||
|
||||
if (
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX &&
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX ===
|
||||
"true" &&
|
||||
hasBoundTextElement(element)
|
||||
) {
|
||||
const textElement = getBoundTextElement(
|
||||
element,
|
||||
) as ExcalidrawTextElementWithContainer;
|
||||
const coords = getContainerCoords(element);
|
||||
context.strokeStyle = "#c92a2a";
|
||||
context.lineWidth = 3;
|
||||
context.strokeRect(
|
||||
(coords.x + renderConfig.scrollX) * window.devicePixelRatio,
|
||||
(coords.y + renderConfig.scrollY) * window.devicePixelRatio,
|
||||
getMaxContainerWidth(element) * window.devicePixelRatio,
|
||||
getMaxContainerHeight(element) * window.devicePixelRatio,
|
||||
getBoundTextMaxWidth(element) * window.devicePixelRatio,
|
||||
getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { isTextElement, refreshTextDimensions } from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||
import { getFontString } from "../utils";
|
||||
@@ -52,7 +53,7 @@ export class Fonts {
|
||||
let didUpdate = false;
|
||||
|
||||
this.scene.mapElements((element) => {
|
||||
if (isTextElement(element)) {
|
||||
if (isTextElement(element) && !isBoundToContainer(element)) {
|
||||
invalidateShapeForElement(element);
|
||||
didUpdate = true;
|
||||
return newElementWith(element, {
|
||||
|
||||
@@ -121,7 +121,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -4518,7 +4518,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -5068,7 +5068,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -5917,7 +5917,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -6263,7 +6263,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { getTransformHandles } from "../element/transformHandles";
|
||||
import { API } from "./helpers/api";
|
||||
import { KEYS } from "../keys";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -277,7 +277,7 @@ describe("element binding", () => {
|
||||
|
||||
expect(h.state.selectedElementIds[text1.id]).toBe(true);
|
||||
|
||||
h.app.actionManager.executeAction(actionCreateContainerFromText);
|
||||
h.app.actionManager.executeAction(actionWrapTextInContainer);
|
||||
|
||||
// new text container will be placed before the text element
|
||||
const container = h.elements.at(-2)!;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { render, waitFor, GlobalTestState } from "./test-utils";
|
||||
import {
|
||||
render,
|
||||
waitFor,
|
||||
GlobalTestState,
|
||||
createPasteEvent,
|
||||
} from "./test-utils";
|
||||
import { Pointer, Keyboard } from "./helpers/ui";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { KEYS } from "../keys";
|
||||
@@ -9,6 +14,8 @@ import {
|
||||
} from "../element/textElement";
|
||||
import { getElementBounds } from "../element";
|
||||
import { NormalizedZoomValue } from "../types";
|
||||
import { API } from "./helpers/api";
|
||||
import { copyToClipboard } from "../clipboard";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -35,38 +42,28 @@ const setClipboardText = (text: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const sendPasteEvent = () => {
|
||||
const clipboardEvent = new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
});
|
||||
|
||||
// set `clipboardData` properties.
|
||||
// @ts-ignore
|
||||
clipboardEvent.clipboardData = {
|
||||
getData: () => window.navigator.clipboard.readText(),
|
||||
files: [],
|
||||
};
|
||||
|
||||
const sendPasteEvent = (text?: string) => {
|
||||
const clipboardEvent = createPasteEvent(
|
||||
text || (() => window.navigator.clipboard.readText()),
|
||||
);
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
|
||||
const pasteWithCtrlCmdShiftV = () => {
|
||||
const pasteWithCtrlCmdShiftV = (text?: string) => {
|
||||
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
||||
//triggering keydown with an empty clipboard
|
||||
Keyboard.keyPress(KEYS.V);
|
||||
//triggering paste event with faked clipboard
|
||||
sendPasteEvent();
|
||||
sendPasteEvent(text);
|
||||
});
|
||||
};
|
||||
|
||||
const pasteWithCtrlCmdV = () => {
|
||||
const pasteWithCtrlCmdV = (text?: string) => {
|
||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||
//triggering keydown with an empty clipboard
|
||||
Keyboard.keyPress(KEYS.V);
|
||||
//triggering paste event with faked clipboard
|
||||
sendPasteEvent();
|
||||
sendPasteEvent(text);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -89,6 +86,32 @@ beforeEach(async () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("general paste behavior", () => {
|
||||
it("should randomize seed on paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
pasteWithCtrlCmdV(clipboardJSON);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].seed).not.toBe(rectangle.seed);
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain seed on shift-paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
// assert we don't randomize seed on shift-paste
|
||||
pasteWithCtrlCmdShiftV(clipboardJSON);
|
||||
await waitFor(() => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].seed).toBe(rectangle.seed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("paste text as single lines", () => {
|
||||
it("should create an element for each line when copying with Ctrl/Cmd+V", async () => {
|
||||
const text = "sajgfakfn\naaksfnknas\nakefnkasf";
|
||||
|
||||
+7
-14
@@ -1,5 +1,10 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { GlobalTestState, render, waitFor } from "./test-utils";
|
||||
import {
|
||||
createPasteEvent,
|
||||
GlobalTestState,
|
||||
render,
|
||||
waitFor,
|
||||
} from "./test-utils";
|
||||
import { UI, Pointer } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||
@@ -693,19 +698,7 @@ describe("freedraw", () => {
|
||||
describe("image", () => {
|
||||
const createImage = async () => {
|
||||
const sendPasteEvent = (file?: File) => {
|
||||
const clipboardEvent = new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
});
|
||||
|
||||
// set `clipboardData` properties.
|
||||
// @ts-ignore
|
||||
clipboardEvent.clipboardData = {
|
||||
getData: () => window.navigator.clipboard.readText(),
|
||||
files: [file],
|
||||
};
|
||||
|
||||
const clipboardEvent = createPasteEvent("", file ? [file] : []);
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { resize, rotate } from "./utils";
|
||||
import {
|
||||
getBoundTextElementPosition,
|
||||
wrapText,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import * as textElementUtils from "../element/textElement";
|
||||
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
||||
@@ -729,7 +729,7 @@ describe("Test Linear Elements", () => {
|
||||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: wrapText(text, font, getMaxContainerWidth(container)),
|
||||
text: wrapText(text, font, getBoundTextMaxWidth(container)),
|
||||
containerId: container.id,
|
||||
width: 30,
|
||||
height: 20,
|
||||
@@ -1149,7 +1149,7 @@ describe("Test Linear Elements", () => {
|
||||
expect(rect.x).toBe(400);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard collaboration
|
||||
made easy"
|
||||
@@ -1172,7 +1172,7 @@ describe("Test Linear Elements", () => {
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard
|
||||
collaboration made
|
||||
|
||||
@@ -190,3 +190,24 @@ export const toggleMenu = (container: HTMLElement) => {
|
||||
// open menu
|
||||
fireEvent.click(container.querySelector(".dropdown-menu-button")!);
|
||||
};
|
||||
|
||||
export const createPasteEvent = (
|
||||
text:
|
||||
| string
|
||||
| /* getData function */ ((type: string) => string | Promise<string>),
|
||||
files?: File[],
|
||||
) => {
|
||||
return Object.assign(
|
||||
new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: typeof text === "string" ? () => text : text,
|
||||
files: files || [],
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -29,9 +29,9 @@ import { isOverScrollBars } from "./scene";
|
||||
import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||
import Library from "./data/library";
|
||||
import type { FileSystemHandle } from "./data/filesystem";
|
||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import { ContextMenuItems } from "./components/ContextMenu";
|
||||
import { Merge, ForwardRef } from "./utility-types";
|
||||
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
||||
import React from "react";
|
||||
|
||||
export type Point = Readonly<RoughPoint>;
|
||||
@@ -60,7 +60,7 @@ export type DataURL = string & { _brand: "DataURL" };
|
||||
|
||||
export type BinaryFileData = {
|
||||
mimeType:
|
||||
| typeof ALLOWED_IMAGE_MIME_TYPES[number]
|
||||
| ValueOf<typeof IMAGE_MIME_TYPES>
|
||||
// future user or unknown file type
|
||||
| typeof MIME_TYPES.binary;
|
||||
id: FileId;
|
||||
@@ -419,7 +419,7 @@ export type AppClassProperties = {
|
||||
FileId,
|
||||
{
|
||||
image: HTMLImageElement | Promise<HTMLImageElement>;
|
||||
mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number];
|
||||
mimeType: ValueOf<typeof IMAGE_MIME_TYPES>;
|
||||
}
|
||||
>;
|
||||
files: BinaryFiles;
|
||||
|
||||
Reference in New Issue
Block a user