Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax-maint-stage

This commit is contained in:
Daniel J. Geiger 2023-11-03 19:11:26 -05:00
commit daf305af34
163 changed files with 9277 additions and 3561 deletions

View File

@ -1,3 +0,0 @@
## 2020-10-13
- Added ability to embed scene source into exported PNG/SVG files so you can import the scene from them (open via `Load` button or drag & drop). #2219

View File

@ -0,0 +1,22 @@
# Frames
## Ordering
Frames should be ordered where frame children come first, followed by the frame element itself:
```
[
other_element,
frame1_child1,
frame1_child2,
frame1,
other_element,
frame2_child1,
frame2_child2,
frame2,
other_element,
...
]
```
If not oredered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.

View File

@ -23,7 +23,11 @@ const sidebars = {
},
items: ["introduction/development", "introduction/contributing"],
},
{ type: "category", label: "Codebase", items: ["codebase/json-schema"] },
{
type: "category",
label: "Codebase",
items: ["codebase/json-schema", "codebase/frames"],
},
{
type: "category",
label: "@excalidraw/excalidraw",

View File

@ -145,6 +145,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
@ -202,6 +210,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@ -265,6 +283,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@ -280,6 +303,14 @@
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@ -287,6 +318,13 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-member-expression-to-functions@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
@ -374,11 +412,28 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
@ -412,11 +467,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.12.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8", "@babel/parser@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@ -1147,19 +1216,28 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98"
integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.9"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.9"
"@babel/types" "^7.18.9"
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@ -1171,6 +1249,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@ -1670,6 +1757,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@ -1688,6 +1780,19 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17":
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"

View File

@ -80,7 +80,8 @@ export const ExportToExcalidrawPlus: React.FC<{
appState: Partial<AppState>;
files: BinaryFiles;
onError: (error: Error) => void;
}> = ({ elements, appState, files, onError }) => {
onSuccess: () => void;
}> = ({ elements, appState, files, onError, onSuccess }) => {
const { t } = useI18n();
return (
<Card color="primary">
@ -107,6 +108,7 @@ export const ExportToExcalidrawPlus: React.FC<{
try {
trackEvent("export", "eplus", `ui (${getFrame()})`);
await exportToExcalidrawPlus(elements, appState, files);
onSuccess();
} catch (error: any) {
console.error(error);
if (error.name !== "AbortError") {

View File

@ -611,7 +611,7 @@ const ExcalidrawWrapper = () => {
canvas: HTMLCanvasElement,
) => {
if (exportedElements.length === 0) {
return window.alert(t("alerts.cannotExportEmptyCanvas"));
throw new Error(t("alerts.cannotExportEmptyCanvas"));
}
if (canvas) {
try {
@ -627,7 +627,7 @@ const ExcalidrawWrapper = () => {
);
if (errorMessage) {
setErrorMessage(errorMessage);
throw new Error(errorMessage);
}
if (url) {
@ -637,7 +637,7 @@ const ExcalidrawWrapper = () => {
if (error.name !== "AbortError") {
const { width, height } = canvas;
console.error(error, { width, height });
setErrorMessage(error.message);
throw new Error(error.message);
}
}
}
@ -717,6 +717,11 @@ const ExcalidrawWrapper = () => {
},
});
}}
onSuccess={() => {
excalidrawAPI?.updateScene({
appState: { openDialog: null },
});
}}
/>
);
},

View File

@ -51,12 +51,12 @@
"png-chunk-text": "1.0.0",
"png-chunks-encode": "1.0.0",
"png-chunks-extract": "1.0.0",
"points-on-curve": "0.2.0",
"points-on-curve": "1.0.1",
"postinstall-postinstall": "2.1.0",
"pwacompat": "2.0.17",
"react": "18.2.0",
"react-dom": "18.2.0",
"roughjs": "4.5.2",
"roughjs": "4.6.4",
"sass": "1.51.0",
"socket.io-client": "2.3.1",
"tunnel-rat": "0.1.2"

View File

@ -3,33 +3,43 @@ import { register } from "./register";
import {
copyTextToSystemClipboard,
copyToClipboard,
createPasteEvent,
probablySupportsClipboardBlob,
probablySupportsClipboardWriteText,
readSystemClipboard,
} from "../clipboard";
import { actionDeleteSelected } from "./actionDeleteSelected";
import { exportCanvas } from "../data/index";
import { getNonDeletedElements, isTextElement } from "../element";
import { t } from "../i18n";
import { isFirefox } from "../constants";
export const actionCopy = register({
name: "copy",
trackEvent: { category: "element" },
perform: (elements, appState, _, app) => {
perform: async (elements, appState, event: ClipboardEvent | null, app) => {
const elementsToCopy = app.scene.getSelectedElements({
selectedElementIds: appState.selectedElementIds,
includeBoundTextElement: true,
includeElementsInFrames: true,
});
copyToClipboard(elementsToCopy, app.files);
try {
await copyToClipboard(elementsToCopy, app.files, event);
} catch (error: any) {
return {
commitToHistory: false,
appState: {
...appState,
errorMessage: error.message,
},
};
}
return {
commitToHistory: false,
};
},
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.copy",
// don't supply a shortcut since we handle this conditionally via onCopy event
keyTest: undefined,
@ -38,15 +48,55 @@ export const actionCopy = register({
export const actionPaste = register({
name: "paste",
trackEvent: { category: "element" },
perform: (elements: any, appStates: any, data, app) => {
app.pasteFromClipboard(null);
perform: async (elements, appState, data, app) => {
let types;
try {
types = await readSystemClipboard();
} catch (error: any) {
if (error.name === "AbortError" || error.name === "NotAllowedError") {
// user probably aborted the action. Though not 100% sure, it's best
// to not annoy them with an error message.
return false;
}
console.error(`actionPaste ${error.name}: ${error.message}`);
if (isFirefox) {
return {
commitToHistory: false,
appState: {
...appState,
errorMessage: t("hints.firefox_clipboard_write"),
},
};
}
return {
commitToHistory: false,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnRead"),
},
};
}
try {
app.pasteFromClipboard(createPasteEvent({ types }));
} catch (error: any) {
console.error(error);
return {
commitToHistory: false,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnParse"),
},
};
}
return {
commitToHistory: false,
};
},
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.paste",
// don't supply a shortcut since we handle this conditionally via onCopy event
keyTest: undefined,
@ -55,13 +105,10 @@ export const actionPaste = register({
export const actionCut = register({
name: "cut",
trackEvent: { category: "element" },
perform: (elements, appState, data, app) => {
actionCopy.perform(elements, appState, data, app);
perform: (elements, appState, event: ClipboardEvent | null, app) => {
actionCopy.perform(elements, appState, event, app);
return actionDeleteSelected.perform(elements, appState);
},
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.cut",
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
});

View File

@ -46,6 +46,7 @@ const deleteSelectedElements = (
appState: {
...appState,
selectedElementIds: {},
selectedGroupIds: {},
},
};
};

View File

@ -191,7 +191,15 @@ export const actionSaveFileToDisk = register({
},
app.files,
);
return { commitToHistory: false, appState: { ...appState, fileHandle } };
return {
commitToHistory: false,
appState: {
...appState,
openDialog: null,
fileHandle,
toast: { message: t("toast.fileSaved") },
},
};
} catch (error: any) {
if (error?.name !== "AbortError") {
console.error(error);

View File

@ -0,0 +1,167 @@
import { Excalidraw } from "../packages/excalidraw/index";
import { queryByTestId } from "@testing-library/react";
import { render } from "../tests/test-utils";
import { UI } from "../tests/helpers/ui";
import { API } from "../tests/helpers/api";
import { COLOR_PALETTE, DEFAULT_ELEMENT_BACKGROUND_PICKS } from "../colors";
import { FONT_FAMILY, STROKE_WIDTH } from "../constants";
const { h } = window;
describe("element locking", () => {
beforeEach(async () => {
await render(<Excalidraw />);
});
describe("properties when tool selected", () => {
it("should show active background top picks", () => {
UI.clickTool("rectangle");
const color = DEFAULT_ELEMENT_BACKGROUND_PICKS[1];
// just in case we change it in the future
expect(color).not.toBe(COLOR_PALETTE.transparent);
h.setState({
currentItemBackgroundColor: color,
});
const activeColor = queryByTestId(
document.body,
`color-top-pick-${color}`,
);
expect(activeColor).toHaveClass("active");
});
it("should show fill style when background non-transparent", () => {
UI.clickTool("rectangle");
const color = DEFAULT_ELEMENT_BACKGROUND_PICKS[1];
// just in case we change it in the future
expect(color).not.toBe(COLOR_PALETTE.transparent);
h.setState({
currentItemBackgroundColor: color,
currentItemFillStyle: "hachure",
});
const hachureFillButton = queryByTestId(document.body, `fill-hachure`);
expect(hachureFillButton).toHaveClass("active");
h.setState({
currentItemFillStyle: "solid",
});
const solidFillStyle = queryByTestId(document.body, `fill-solid`);
expect(solidFillStyle).toHaveClass("active");
});
it("should not show fill style when background transparent", () => {
UI.clickTool("rectangle");
h.setState({
currentItemBackgroundColor: COLOR_PALETTE.transparent,
currentItemFillStyle: "hachure",
});
const hachureFillButton = queryByTestId(document.body, `fill-hachure`);
expect(hachureFillButton).toBe(null);
});
it("should show horizontal text align for text tool", () => {
UI.clickTool("text");
h.setState({
currentItemTextAlign: "right",
});
const centerTextAlign = queryByTestId(document.body, `align-right`);
expect(centerTextAlign).toBeChecked();
});
});
describe("properties when elements selected", () => {
it("should show active styles when single element selected", () => {
const rect = API.createElement({
type: "rectangle",
backgroundColor: "red",
fillStyle: "cross-hatch",
});
h.elements = [rect];
API.setSelectedElements([rect]);
const crossHatchButton = queryByTestId(document.body, `fill-cross-hatch`);
expect(crossHatchButton).toHaveClass("active");
});
it("should not show fill style selected element's background is transparent", () => {
const rect = API.createElement({
type: "rectangle",
backgroundColor: COLOR_PALETTE.transparent,
fillStyle: "cross-hatch",
});
h.elements = [rect];
API.setSelectedElements([rect]);
const crossHatchButton = queryByTestId(document.body, `fill-cross-hatch`);
expect(crossHatchButton).toBe(null);
});
it("should highlight common stroke width of selected elements", () => {
const rect1 = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.thin,
});
const rect2 = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.thin,
});
h.elements = [rect1, rect2];
API.setSelectedElements([rect1, rect2]);
const thinStrokeWidthButton = queryByTestId(
document.body,
`strokeWidth-thin`,
);
expect(thinStrokeWidthButton).toBeChecked();
});
it("should not highlight any stroke width button if no common style", () => {
const rect1 = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.thin,
});
const rect2 = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.bold,
});
h.elements = [rect1, rect2];
API.setSelectedElements([rect1, rect2]);
expect(queryByTestId(document.body, `strokeWidth-thin`)).not.toBe(null);
expect(
queryByTestId(document.body, `strokeWidth-thin`),
).not.toBeChecked();
expect(
queryByTestId(document.body, `strokeWidth-bold`),
).not.toBeChecked();
expect(
queryByTestId(document.body, `strokeWidth-extraBold`),
).not.toBeChecked();
});
it("should show properties of different element types when selected", () => {
const rect = API.createElement({
type: "rectangle",
strokeWidth: STROKE_WIDTH.bold,
});
const text = API.createElement({
type: "text",
fontFamily: FONT_FAMILY.Cascadia,
});
h.elements = [rect, text];
API.setSelectedElements([rect, text]);
expect(queryByTestId(document.body, `strokeWidth-bold`)).toBeChecked();
expect(queryByTestId(document.body, `font-family-code`)).toBeChecked();
});
});
});

View File

@ -1,4 +1,4 @@
import { AppState } from "../../src/types";
import { AppState, Primitive } from "../../src/types";
import {
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_PICKS,
@ -51,6 +51,7 @@ import {
DEFAULT_FONT_SIZE,
FONT_FAMILY,
ROUNDNESS,
STROKE_WIDTH,
VERTICAL_ALIGN,
} from "../constants";
import {
@ -82,7 +83,6 @@ import { getLanguage, t } from "../i18n";
import { KEYS } from "../keys";
import { randomInteger } from "../random";
import {
canChangeRoundness,
canHaveArrowheads,
getCommonAttributeOfSelectedElements,
getSelectedElements,
@ -118,25 +118,44 @@ export const changeProperty = (
});
};
export const getFormValue = function <T>(
export const getFormValue = function <T extends Primitive>(
elements: readonly ExcalidrawElement[],
appState: AppState,
getAttribute: (element: ExcalidrawElement) => T,
defaultValue: T,
isRelevantElement: true | ((element: ExcalidrawElement) => boolean),
defaultValue: T | ((isSomeElementSelected: boolean) => T),
): T {
const editingElement = appState.editingElement;
const nonDeletedElements = getNonDeletedElements(elements);
return (
(editingElement && getAttribute(editingElement)) ??
(isSomeElementSelected(nonDeletedElements, appState)
? getCommonAttributeOfSelectedElements(
nonDeletedElements,
let ret: T | null = null;
if (editingElement) {
ret = getAttribute(editingElement);
}
if (!ret) {
const hasSelection = isSomeElementSelected(nonDeletedElements, appState);
if (hasSelection) {
ret =
getCommonAttributeOfSelectedElements(
isRelevantElement === true
? nonDeletedElements
: nonDeletedElements.filter((el) => isRelevantElement(el)),
appState,
getAttribute,
)
: defaultValue) ??
defaultValue
);
) ??
(typeof defaultValue === "function"
? defaultValue(true)
: defaultValue);
} else {
ret =
typeof defaultValue === "function" ? defaultValue(false) : defaultValue;
}
}
return ret;
};
const offsetElementAfterFontResize = (
@ -247,6 +266,7 @@ export const actionChangeStrokeColor = register({
elements,
appState,
(element) => element.strokeColor,
true,
appState.currentItemStrokeColor,
)}
onChange={(color) => updateData({ currentItemStrokeColor: color })}
@ -289,6 +309,7 @@ export const actionChangeBackgroundColor = register({
elements,
appState,
(element) => element.backgroundColor,
true,
appState.currentItemBackgroundColor,
)}
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
@ -338,23 +359,28 @@ export const actionChangeFillStyle = register({
} (${getShortcutKey("Alt-Click")})`,
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
active: allElementsZigZag ? true : undefined,
testId: `fill-hachure`,
},
{
value: "cross-hatch",
text: t("labels.crossHatch"),
icon: FillCrossHatchIcon,
testId: `fill-cross-hatch`,
},
{
value: "solid",
text: t("labels.solid"),
icon: FillSolidIcon,
testId: `fill-solid`,
},
]}
value={getFormValue(
elements,
appState,
(element) => element.fillStyle,
appState.currentItemFillStyle,
(element) => element.hasOwnProperty("fillStyle"),
(hasSelection) =>
hasSelection ? null : appState.currentItemFillStyle,
)}
onClick={(value, event) => {
const nextValue =
@ -393,26 +419,31 @@ export const actionChangeStrokeWidth = register({
group="stroke-width"
options={[
{
value: 1,
value: STROKE_WIDTH.thin,
text: t("labels.thin"),
icon: StrokeWidthBaseIcon,
testId: "strokeWidth-thin",
},
{
value: 2,
value: STROKE_WIDTH.bold,
text: t("labels.bold"),
icon: StrokeWidthBoldIcon,
testId: "strokeWidth-bold",
},
{
value: 4,
value: STROKE_WIDTH.extraBold,
text: t("labels.extraBold"),
icon: StrokeWidthExtraBoldIcon,
testId: "strokeWidth-extraBold",
},
]}
value={getFormValue(
elements,
appState,
(element) => element.strokeWidth,
appState.currentItemStrokeWidth,
(element) => element.hasOwnProperty("strokeWidth"),
(hasSelection) =>
hasSelection ? null : appState.currentItemStrokeWidth,
)}
onChange={(value) => updateData(value)}
/>
@ -461,7 +492,9 @@ export const actionChangeSloppiness = register({
elements,
appState,
(element) => element.roughness,
appState.currentItemRoughness,
(element) => element.hasOwnProperty("roughness"),
(hasSelection) =>
hasSelection ? null : appState.currentItemRoughness,
)}
onChange={(value) => updateData(value)}
/>
@ -509,7 +542,9 @@ export const actionChangeStrokeStyle = register({
elements,
appState,
(element) => element.strokeStyle,
appState.currentItemStrokeStyle,
(element) => element.hasOwnProperty("strokeStyle"),
(hasSelection) =>
hasSelection ? null : appState.currentItemStrokeStyle,
)}
onChange={(value) => updateData(value)}
/>
@ -549,6 +584,7 @@ export const actionChangeOpacity = register({
elements,
appState,
(element) => element.opacity,
true,
appState.currentItemOpacity,
) ?? undefined
}
@ -607,7 +643,12 @@ export const actionChangeFontSize = register({
}
return null;
},
appState.currentItemFontSize || DEFAULT_FONT_SIZE,
(element) =>
isTextElement(element) || getBoundTextElement(element) !== null,
(hasSelection) =>
hasSelection
? null
: appState.currentItemFontSize || DEFAULT_FONT_SIZE,
)}
onChange={(value) => updateData(value)}
/>
@ -692,21 +733,25 @@ export const actionChangeFontFamily = register({
value: FontFamilyValues;
text: string;
icon: JSX.Element;
testId: string;
}[] = [
{
value: FONT_FAMILY.Virgil,
text: t("labels.handDrawn"),
icon: FreedrawIcon,
testId: "font-family-virgil",
},
{
value: FONT_FAMILY.Helvetica,
text: t("labels.normal"),
icon: FontFamilyNormalIcon,
testId: "font-family-normal",
},
{
value: FONT_FAMILY.Cascadia,
text: t("labels.code"),
icon: FontFamilyCodeIcon,
testId: "font-family-code",
},
];
@ -729,7 +774,12 @@ export const actionChangeFontFamily = register({
}
return null;
},
appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
(element) =>
isTextElement(element) || getBoundTextElement(element) !== null,
(hasSelection) =>
hasSelection
? null
: appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
)}
onChange={(value) => updateData(value)}
/>
@ -806,7 +856,10 @@ export const actionChangeTextAlign = register({
}
return null;
},
appState.currentItemTextAlign,
(element) =>
isTextElement(element) || getBoundTextElement(element) !== null,
(hasSelection) =>
hasSelection ? null : appState.currentItemTextAlign,
)}
onChange={(value) => updateData(value)}
/>
@ -882,7 +935,9 @@ export const actionChangeVerticalAlign = register({
}
return null;
},
VERTICAL_ALIGN.MIDDLE,
(element) =>
isTextElement(element) || getBoundTextElement(element) !== null,
(hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE),
)}
onChange={(value) => updateData(value)}
/>
@ -947,9 +1002,9 @@ export const actionChangeRoundness = register({
appState,
(element) =>
hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
(canChangeRoundness(appState.activeTool.type) &&
appState.currentItemRoundness) ||
null,
(element) => element.hasOwnProperty("roundness"),
(hasSelection) =>
hasSelection ? null : appState.currentItemRoundness,
)}
onChange={(value) => updateData(value)}
/>
@ -1043,6 +1098,7 @@ export const actionChangeArrowhead = register({
isLinearElement(element) && canHaveArrowheads(element.type)
? element.startArrowhead
: appState.currentItemStartArrowhead,
true,
appState.currentItemStartArrowhead,
)}
onChange={(value) => updateData({ position: "start", type: value })}
@ -1089,6 +1145,7 @@ export const actionChangeArrowhead = register({
isLinearElement(element) && canHaveArrowheads(element.type)
? element.endArrowhead
: appState.currentItemEndArrowhead,
true,
appState.currentItemEndArrowhead,
)}
onChange={(value) => updateData({ position: "end", type: value })}

View File

@ -151,10 +151,10 @@ export class ActionManager {
return true;
}
executeAction(
action: Action,
executeAction<T extends Action>(
action: T,
source: ActionSource = "api",
value: any = null,
value: Parameters<T["perform"]>[2] = null,
) {
const elements = this.getElementsIncludingDeleted();
const appState = this.getAppState();

View File

@ -1,27 +1,196 @@
import { parseClipboard } from "./clipboard";
import {
createPasteEvent,
parseClipboard,
serializeAsClipboardJSON,
} from "./clipboard";
import { API } from "./tests/helpers/api";
describe("Test parseClipboard", () => {
it("should parse valid json correctly", async () => {
let text = "123";
let clipboardData = await parseClipboard({
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
describe("parseClipboard()", () => {
it("should parse JSON as plaintext if not excalidraw-api/clipboard data", async () => {
let text;
let clipboardData;
// -------------------------------------------------------------------------
text = "123";
clipboardData = await parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);
// -------------------------------------------------------------------------
text = "[123]";
clipboardData = await parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);
clipboardData = await parseClipboard({
//@ts-ignore
clipboardData: {
getData: () => text,
},
});
// -------------------------------------------------------------------------
text = JSON.stringify({ val: 42 });
clipboardData = await parseClipboard(
createPasteEvent({ types: { "text/plain": text } }),
);
expect(clipboardData.text).toBe(text);
});
it("should parse valid excalidraw JSON if inside text/plain", async () => {
const rect = API.createElement({ type: "rectangle" });
const json = serializeAsClipboardJSON({ elements: [rect], files: null });
const clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/plain": json,
},
}),
);
expect(clipboardData.elements).toEqual([rect]);
});
it("should parse valid excalidraw JSON if inside text/html", async () => {
const rect = API.createElement({ type: "rectangle" });
let json;
let clipboardData;
// -------------------------------------------------------------------------
json = serializeAsClipboardJSON({ elements: [rect], files: null });
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": json,
},
}),
);
expect(clipboardData.elements).toEqual([rect]);
// -------------------------------------------------------------------------
json = serializeAsClipboardJSON({ elements: [rect], files: null });
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `<div> ${json}</div>`,
},
}),
);
expect(clipboardData.elements).toEqual([rect]);
// -------------------------------------------------------------------------
});
it("should parse <image> `src` urls out of text/html", async () => {
let clipboardData;
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `<img src="https://example.com/image.png" />`,
},
}),
);
expect(clipboardData.mixedContent).toEqual([
{
type: "imageUrl",
value: "https://example.com/image.png",
},
]);
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `<div><img src="https://example.com/image.png" /></div><a><img src="https://example.com/image2.png" /></a>`,
},
}),
);
expect(clipboardData.mixedContent).toEqual([
{
type: "imageUrl",
value: "https://example.com/image.png",
},
{
type: "imageUrl",
value: "https://example.com/image2.png",
},
]);
});
it("should parse text content alongside <image> `src` urls out of text/html", async () => {
const clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `<a href="https://example.com">hello </a><div><img src="https://example.com/image.png" /></div><b>my friend!</b>`,
},
}),
);
expect(clipboardData.mixedContent).toEqual([
{
type: "text",
// trimmed
value: "hello",
},
{
type: "imageUrl",
value: "https://example.com/image.png",
},
{
type: "text",
value: "my friend!",
},
]);
});
it("should parse spreadsheet from either text/plain and text/html", async () => {
let clipboardData;
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/plain": `a b
1 2
4 5
7 10`,
},
}),
);
expect(clipboardData.spreadsheet).toEqual({
title: "b",
labels: ["1", "4", "7"],
values: [2, 5, 10],
});
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `a b
1 2
4 5
7 10`,
},
}),
);
expect(clipboardData.spreadsheet).toEqual({
title: "b",
labels: ["1", "4", "7"],
values: [2, 5, 10],
});
// -------------------------------------------------------------------------
clipboardData = await parseClipboard(
createPasteEvent({
types: {
"text/html": `<html>
<body>
<!--StartFragment--><google-sheets-html-origin><style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style><table xmlns="http://www.w3.org/1999/xhtml" cellspacing="0" cellpadding="0" dir="ltr" border="1" style="table-layout:fixed;font-size:10pt;font-family:Arial;width:0px;border-collapse:collapse;border:none"><colgroup><col width="100"/><col width="100"/></colgroup><tbody><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;a&quot;}">a</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;b&quot;}">b</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:1}">1</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:2}">2</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:4}">4</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:5}">5</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:7}">7</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:10}">10</td></tr></tbody></table><!--EndFragment-->
</body>
</html>`,
"text/plain": `a b
1 2
4 5
7 10`,
},
}),
);
expect(clipboardData.spreadsheet).toEqual({
title: "b",
labels: ["1", "4", "7"],
values: [2, 5, 10],
});
});
});

View File

@ -3,14 +3,18 @@ import {
NonDeletedExcalidrawElement,
} from "./element/types";
import { AppState, 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 {
ALLOWED_PASTE_MIME_TYPES,
EXPORT_DATA_TYPES,
MIME_TYPES,
} from "./constants";
import { isInitializedImageElement } from "./element/typeChecks";
import { deepCopyElement } from "./element/newElement";
import { mutateElement } from "./element/mutateElement";
import { getContainingFrame } from "./frame";
import { isPromiseLike, isTestEnv } from "./utils";
import { isMemberOf, isPromiseLike } from "./utils";
import { t } from "./i18n";
type ElementsClipboard = {
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
@ -18,17 +22,23 @@ type ElementsClipboard = {
files: BinaryFiles | undefined;
};
export type PastedMixedContent = { type: "text" | "imageUrl"; value: string }[];
export interface ClipboardData {
spreadsheet?: Spreadsheet;
elements?: readonly ExcalidrawElement[];
files?: BinaryFiles;
text?: string;
mixedContent?: PastedMixedContent;
errorMessage?: string;
programmaticAPI?: boolean;
}
let CLIPBOARD = "";
let PREFER_APP_CLIPBOARD = false;
type AllowedPasteMimeTypes = typeof ALLOWED_PASTE_MIME_TYPES[number];
type ParsedClipboardEvent =
| { type: "text"; value: string }
| { type: "mixedContent"; value: PastedMixedContent };
export const probablySupportsClipboardReadText =
"clipboard" in navigator && "readText" in navigator.clipboard;
@ -58,10 +68,61 @@ const clipboardContainsElements = (
return false;
};
export const copyToClipboard = async (
elements: readonly NonDeletedExcalidrawElement[],
files: BinaryFiles | null,
) => {
export const createPasteEvent = ({
types,
files,
}: {
types?: { [key in AllowedPasteMimeTypes]?: string };
files?: File[];
}) => {
if (!types && !files) {
console.warn("createPasteEvent: no types or files provided");
}
const event = new ClipboardEvent("paste", {
clipboardData: new DataTransfer(),
});
if (types) {
for (const [type, value] of Object.entries(types)) {
try {
event.clipboardData?.setData(type, value);
if (event.clipboardData?.getData(type) !== value) {
throw new Error(`Failed to set "${type}" as clipboardData item`);
}
} catch (error: any) {
throw new Error(error.message);
}
}
}
if (files) {
let idx = -1;
for (const file of files) {
idx++;
try {
event.clipboardData?.items.add(file);
if (event.clipboardData?.files[idx] !== file) {
throw new Error(
`Failed to set file "${file.name}" as clipboardData item`,
);
}
} catch (error: any) {
throw new Error(error.message);
}
}
}
return event;
};
export const serializeAsClipboardJSON = ({
elements,
files,
}: {
elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles | null;
}) => {
const framesToCopy = new Set(
elements.filter((element) => element.type === "frame"),
);
@ -83,7 +144,7 @@ export const copyToClipboard = async (
);
}
// select binded text elements when copying
// select bound text elements when copying
const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard,
elements: elements.map((element) => {
@ -102,34 +163,20 @@ export const copyToClipboard = async (
}),
files: files ? _files : undefined,
};
const json = JSON.stringify(contents);
if (isTestEnv()) {
return json;
}
CLIPBOARD = json;
try {
PREFER_APP_CLIPBOARD = false;
await copyTextToSystemClipboard(json);
} catch (error: any) {
PREFER_APP_CLIPBOARD = true;
console.error(error);
}
return JSON.stringify(contents);
};
const getAppClipboard = (): Partial<ElementsClipboard> => {
if (!CLIPBOARD) {
return {};
}
try {
return JSON.parse(CLIPBOARD);
} catch (error: any) {
console.error(error);
return {};
}
export const copyToClipboard = async (
elements: readonly NonDeletedExcalidrawElement[],
files: BinaryFiles | null,
/** supply if available to make the operation more certain to succeed */
clipboardEvent?: ClipboardEvent | null,
) => {
await copyTextToSystemClipboard(
serializeAsClipboardJSON({ elements, files }),
clipboardEvent,
);
};
const parsePotentialSpreadsheet = (
@ -142,22 +189,137 @@ const parsePotentialSpreadsheet = (
return null;
};
/**
* Retrieves content from system clipboard (either from ClipboardEvent or
* via async clipboard API if supported)
*/
export const getSystemClipboard = async (
event: ClipboardEvent | null,
): Promise<string> => {
try {
const text = event
? event.clipboardData?.getData("text/plain")
: probablySupportsClipboardReadText &&
(await navigator.clipboard.readText());
/** internal, specific to parsing paste events. Do not reuse. */
function parseHTMLTree(el: ChildNode) {
let result: PastedMixedContent = [];
for (const node of el.childNodes) {
if (node.nodeType === 3) {
const text = node.textContent?.trim();
if (text) {
result.push({ type: "text", value: text });
}
} else if (node instanceof HTMLImageElement) {
const url = node.getAttribute("src");
if (url && url.startsWith("http")) {
result.push({ type: "imageUrl", value: url });
}
} else {
result = result.concat(parseHTMLTree(node));
}
}
return result;
}
return (text || "").trim();
const maybeParseHTMLPaste = (
event: ClipboardEvent,
): { type: "mixedContent"; value: PastedMixedContent } | null => {
const html = event.clipboardData?.getData("text/html");
if (!html) {
return null;
}
try {
const doc = new DOMParser().parseFromString(html, "text/html");
const content = parseHTMLTree(doc.body);
if (content.length) {
return { type: "mixedContent", value: content };
}
} catch (error: any) {
console.error(`error in parseHTMLFromPaste: ${error.message}`);
}
return null;
};
export const readSystemClipboard = async () => {
const types: { [key in AllowedPasteMimeTypes]?: string } = {};
try {
if (navigator.clipboard?.readText) {
return { "text/plain": await navigator.clipboard?.readText() };
}
} catch (error: any) {
// @ts-ignore
if (navigator.clipboard?.read) {
console.warn(
`navigator.clipboard.readText() failed (${error.message}). Failling back to navigator.clipboard.read()`,
);
} else {
throw error;
}
}
let clipboardItems: ClipboardItems;
try {
clipboardItems = await navigator.clipboard?.read();
} catch (error: any) {
if (error.name === "DataError") {
console.warn(
`navigator.clipboard.read() error, clipboard is probably empty: ${error.message}`,
);
return types;
}
throw error;
}
for (const item of clipboardItems) {
for (const type of item.types) {
if (!isMemberOf(ALLOWED_PASTE_MIME_TYPES, type)) {
continue;
}
try {
types[type] = await (await item.getType(type)).text();
} catch (error: any) {
console.warn(
`Cannot retrieve ${type} from clipboardItem: ${error.message}`,
);
}
}
}
if (Object.keys(types).length === 0) {
console.warn("No clipboard data found from clipboard.read().");
return types;
}
return types;
};
/**
* Parses "paste" ClipboardEvent.
*/
const parseClipboardEvent = async (
event: ClipboardEvent,
isPlainPaste = false,
): Promise<ParsedClipboardEvent> => {
try {
const mixedContent = !isPlainPaste && event && maybeParseHTMLPaste(event);
if (mixedContent) {
if (mixedContent.value.every((item) => item.type === "text")) {
return {
type: "text",
value:
event.clipboardData?.getData("text/plain") ||
mixedContent.value
.map((item) => item.value)
.join("\n")
.trim(),
};
}
return mixedContent;
}
const text = event.clipboardData?.getData("text/plain");
return { type: "text", value: (text || "").trim() };
} catch {
return "";
return { type: "text", value: "" };
}
};
@ -165,39 +327,37 @@ export const getSystemClipboard = async (
* Attempts to parse clipboard. Prefers system clipboard.
*/
export const parseClipboard = async (
event: ClipboardEvent | null,
event: ClipboardEvent,
isPlainPaste = false,
appState?: AppState,
): Promise<ClipboardData> => {
const systemClipboard = await getSystemClipboard(event);
const parsedEventData = await parseClipboardEvent(event, isPlainPaste);
// if system clipboard empty, couldn't be resolved, or contains previously
// copied excalidraw scene as SVG, fall back to previously copied excalidraw
// elements
if (
!systemClipboard ||
(!isPlainPaste && systemClipboard.includes(SVG_EXPORT_TAG))
) {
return getAppClipboard();
if (parsedEventData.type === "mixedContent") {
return {
mixedContent: parsedEventData.value,
};
}
// if system clipboard contains spreadsheet, use it even though it's
// technically possible it's staler than in-app clipboard
const spreadsheetResult =
!isPlainPaste && parsePotentialSpreadsheet(systemClipboard);
if (spreadsheetResult) {
if ("spreadsheet" in spreadsheetResult) {
spreadsheetResult.spreadsheet.activeSubtypes = appState?.activeSubtypes;
spreadsheetResult.spreadsheet.customData = appState?.customData;
}
return spreadsheetResult;
}
const appClipboardData = getAppClipboard();
try {
const systemClipboardData = JSON.parse(systemClipboard);
// if system clipboard contains spreadsheet, use it even though it's
// technically possible it's staler than in-app clipboard
const spreadsheetResult =
!isPlainPaste && parsePotentialSpreadsheet(parsedEventData.value);
if (spreadsheetResult) {
if ("spreadsheet" in spreadsheetResult) {
spreadsheetResult.spreadsheet.activeSubtypes = appState?.activeSubtypes;
spreadsheetResult.spreadsheet.customData = appState?.customData;
}
return spreadsheetResult;
}
} catch (error: any) {
console.error(error);
}
try {
const systemClipboardData = JSON.parse(parsedEventData.value);
const programmaticAPI =
systemClipboardData.type === EXPORT_DATA_TYPES.excalidrawClipboardWithAPI;
if (clipboardContainsElements(systemClipboardData)) {
@ -210,18 +370,9 @@ export const parseClipboard = async (
programmaticAPI,
};
}
} catch (e) {}
// system clipboard doesn't contain excalidraw elements → return plaintext
// unless we set a flag to prefer in-app clipboard because browser didn't
// support storing to system clipboard on copy
return PREFER_APP_CLIPBOARD && appClipboardData.elements
? {
...appClipboardData,
text: isPlainPaste
? JSON.stringify(appClipboardData.elements, null, 2)
: undefined,
}
: { text: systemClipboard };
} catch {}
return { text: parsedEventData.value };
};
export const copyBlobToClipboardAsPng = async (blob: Blob | Promise<Blob>) => {
@ -254,28 +405,49 @@ export const copyBlobToClipboardAsPng = async (blob: Blob | Promise<Blob>) => {
}
};
export const copyTextToSystemClipboard = async (text: string | null) => {
let copied = false;
export const copyTextToSystemClipboard = async (
text: string | null,
clipboardEvent?: ClipboardEvent | null,
) => {
// (1) first try using Async Clipboard API
if (probablySupportsClipboardWriteText) {
try {
// NOTE: doesn't work on FF on non-HTTPS domains, or when document
// not focused
await navigator.clipboard.writeText(text || "");
copied = true;
return;
} catch (error: any) {
console.error(error);
}
}
// Note that execCommand doesn't allow copying empty strings, so if we're
// clearing clipboard using this API, we must copy at least an empty char
if (!copied && !copyTextViaExecCommand(text || " ")) {
throw new Error("couldn't copy");
// (2) if fails and we have access to ClipboardEvent, use plain old setData()
try {
if (clipboardEvent) {
clipboardEvent.clipboardData?.setData("text/plain", text || "");
if (clipboardEvent.clipboardData?.getData("text/plain") !== text) {
throw new Error("Failed to setData on clipboardEvent");
}
return;
}
} catch (error: any) {
console.error(error);
}
// (3) if that fails, use document.execCommand
if (!copyTextViaExecCommand(text)) {
throw new Error(t("errors.copyToSystemClipboardFailed"));
}
};
// adapted from https://github.com/zenorocha/clipboard.js/blob/ce79f170aa655c408b6aab33c9472e8e4fa52e19/src/clipboard-action.js#L48
const copyTextViaExecCommand = (text: string) => {
const copyTextViaExecCommand = (text: string | null) => {
// execCommand doesn't allow copying empty strings, so if we're
// clearing clipboard using this API, we must copy at least an empty char
if (!text) {
text = " ";
}
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
const textarea = document.createElement("textarea");

View File

@ -2,13 +2,13 @@
.undo-redo-buttons {
background-color: var(--island-bg-color);
border-radius: var(--border-radius-lg);
box-shadow: 0 0 0 1px var(--color-surface-lowest);
}
.zoom-button,
.undo-redo-buttons button {
border: 1px solid var(--default-border-color) !important;
border-radius: 0 !important;
background-color: transparent !important;
background-color: var(--color-surface-low) !important;
font-size: 0.875rem !important;
width: var(--lg-button-size);
height: var(--lg-button-size);

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { ActionManager } from "../actions/manager";
import { getNonDeletedElements } from "../element";
import { ExcalidrawElement, PointerType } from "../element/types";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { useDevice } from "../components/App";
import {
@ -11,7 +11,6 @@ import {
hasBackground,
hasStrokeStyle,
hasStrokeWidth,
hasText,
} from "../scene";
import { SHAPES } from "../shapes";
import { AppClassProperties, UIAppState, Zoom } from "../types";
@ -21,7 +20,7 @@ import { ToolButton } from "./ToolButton";
import { SubtypeShapeActions, SubtypeToggles } from "./Subtypes";
import { hasStrokeColor } from "../scene/comparisons";
import { trackEvent } from "../analytics";
import { hasBoundTextElement } from "../element/typeChecks";
import { hasBoundTextElement, isTextElement } from "../element/typeChecks";
import clsx from "clsx";
import { actionToggleZenMode } from "../actions";
import { Tooltip } from "./Tooltip";
@ -67,7 +66,8 @@ export const SelectedShapeActions = ({
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
const showFillIcons =
hasBackground(appState.activeTool.type) ||
(hasBackground(appState.activeTool.type) &&
!isTransparent(appState.currentItemBackgroundColor)) ||
targetElements.some(
(element) =>
hasBackground(element.type) && !isTransparent(element.backgroundColor),
@ -125,14 +125,15 @@ export const SelectedShapeActions = ({
<>{renderAction("changeRoundness")}</>
)}
{(hasText(appState.activeTool.type) ||
targetElements.some((element) => hasText(element.type))) && (
{(appState.activeTool.type === "text" ||
targetElements.some(isTextElement)) && (
<>
{renderAction("changeFontSize")}
{renderAction("changeFontFamily")}
{suppportsHorizontalAlign(targetElements) &&
{(appState.activeTool.type === "text" ||
suppportsHorizontalAlign(targetElements)) &&
renderAction("changeTextAlign")}
</>
)}
@ -215,15 +216,11 @@ export const SelectedShapeActions = ({
};
export const ShapesSwitcher = ({
interactiveCanvas,
activeTool,
onImageAction,
appState,
app,
}: {
interactiveCanvas: HTMLCanvasElement | null;
activeTool: UIAppState["activeTool"];
onImageAction: (data: { pointerType: PointerType | null }) => void;
appState: UIAppState;
app: AppClassProperties;
}) => {
@ -265,9 +262,13 @@ export const ShapesSwitcher = ({
if (appState.activeTool.type !== value) {
trackEvent("toolbar", value, "ui");
}
app.setActiveTool({ type: value });
if (value === "image") {
onImageAction({ pointerType });
app.setActiveTool({
type: value,
insertOnCanvasDirectly: pointerType !== "mouse",
});
} else {
app.setActiveTool({ type: value });
}
}}
/>

View File

@ -47,7 +47,7 @@ import {
isEraserActive,
isHandToolActive,
} from "../appState";
import { parseClipboard } from "../clipboard";
import { PastedMixedContent, parseClipboard } from "../clipboard";
import {
APP_NAME,
CURSOR_TYPE,
@ -285,6 +285,7 @@ import {
generateIdFromFile,
getDataURL,
getFileFromEvent,
ImageURLToFile,
isImageFileHandle,
isSupportedImageFile,
loadSceneOrLibraryFromBlob,
@ -383,6 +384,7 @@ import {
resetCursor,
setCursorForShape,
} from "../cursor";
import { Emitter } from "../emitter";
const AppContext = React.createContext<AppClassProperties>(null!);
const AppPropsContext = React.createContext<AppProps>(null!);
@ -514,6 +516,30 @@ class App extends React.Component<AppProps, AppState> {
laserPathManager: LaserPathManager = new LaserPathManager(this);
onChangeEmitter = new Emitter<
[
elements: readonly ExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
]
>();
onPointerDownEmitter = new Emitter<
[
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: React.PointerEvent<HTMLElement>,
]
>();
onPointerUpEmitter = new Emitter<
[
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: PointerEvent,
]
>();
constructor(props: AppProps) {
super(props);
const defaultAppState = getDefaultAppState();
@ -585,6 +611,9 @@ class App extends React.Component<AppProps, AppState> {
resetCursor: this.resetCursor,
updateFrameRendering: this.updateFrameRendering,
toggleSidebar: this.toggleSidebar,
onChange: (cb) => this.onChangeEmitter.on(cb),
onPointerDown: (cb) => this.onPointerDownEmitter.on(cb),
onPointerUp: (cb) => this.onPointerUpEmitter.on(cb),
} as const;
if (typeof excalidrawRef === "function") {
excalidrawRef(api);
@ -1217,7 +1246,6 @@ class App extends React.Component<AppProps, AppState> {
>
<LayerUI
canvas={this.canvas}
interactiveCanvas={this.interactiveCanvas}
appState={this.state}
files={this.files}
setAppState={this.setAppState}
@ -1234,7 +1262,6 @@ class App extends React.Component<AppProps, AppState> {
this.state.zenModeEnabled
}
UIOptions={this.props.UIOptions}
onImageAction={this.onImageAction}
onExportImage={this.onExportImage}
renderWelcomeScreen={
!this.state.isLoading &&
@ -1277,6 +1304,12 @@ class App extends React.Component<AppProps, AppState> {
top={this.state.contextMenu.top}
left={this.state.contextMenu.left}
actionManager={this.actionManager}
onClose={(callback) => {
this.setState({ contextMenu: null }, () => {
this.focusContainer();
callback?.();
});
}}
/>
)}
<StaticCanvas
@ -1780,6 +1813,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.destroy();
this.library.destroy();
this.laserPathManager.destroy();
this.onChangeEmitter.destroy();
ShapeCache.destroy();
SnapCache.destroy();
clearTimeout(touchTimeout);
@ -2064,6 +2098,11 @@ class App extends React.Component<AppProps, AppState> {
this.state,
this.files,
);
this.onChangeEmitter.trigger(
this.scene.getElementsIncludingDeleted(),
this.state,
this.files,
);
}
}
@ -2106,7 +2145,7 @@ class App extends React.Component<AppProps, AppState> {
if (!isExcalidrawActive || isWritableElement(event.target)) {
return;
}
this.cutAll();
this.actionManager.executeAction(actionCut, "keyboard", event);
event.preventDefault();
event.stopPropagation();
});
@ -2118,19 +2157,11 @@ class App extends React.Component<AppProps, AppState> {
if (!isExcalidrawActive || isWritableElement(event.target)) {
return;
}
this.copyAll();
this.actionManager.executeAction(actionCopy, "keyboard", event);
event.preventDefault();
event.stopPropagation();
});
private cutAll = () => {
this.actionManager.executeAction(actionCut, "keyboard");
};
private copyAll = () => {
this.actionManager.executeAction(actionCopy, "keyboard");
};
private static resetTapTwice() {
didTapTwice = false;
}
@ -2191,8 +2222,8 @@ class App extends React.Component<AppProps, AppState> {
};
public pasteFromClipboard = withBatchedUpdates(
async (event: ClipboardEvent | null) => {
const isPlainPaste = !!(IS_PLAIN_PASTE && event);
async (event: ClipboardEvent) => {
const isPlainPaste = !!IS_PLAIN_PASTE;
// #686
const target = document.activeElement;
@ -2214,21 +2245,6 @@ class App extends React.Component<AppProps, AppState> {
return;
}
// must be called in the same frame (thus before any awaits) as the paste
// event else some browsers (FF...) will clear the clipboardData
// (something something security)
let file = event?.clipboardData?.files[0];
const data = await parseClipboard(event, isPlainPaste, this.state);
if (!file && data.text && !isPlainPaste) {
const string = data.text.trim();
if (string.startsWith("<svg") && string.endsWith("</svg>")) {
// ignore SVG validation/normalization which will be done during image
// initialization
file = SVGStringToFile(string);
}
}
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
{
clientX: this.lastViewportPosition.x,
@ -2237,6 +2253,29 @@ class App extends React.Component<AppProps, AppState> {
this.state,
);
// must be called in the same frame (thus before any awaits) as the paste
// event else some browsers (FF...) will clear the clipboardData
// (something something security)
let file = event?.clipboardData?.files[0];
const data = await parseClipboard(event, isPlainPaste, this.state);
if (!file && !isPlainPaste) {
if (data.mixedContent) {
return this.addElementsFromMixedContentPaste(data.mixedContent, {
isPlainPaste,
sceneX,
sceneY,
});
} else if (data.text) {
const string = data.text.trim();
if (string.startsWith("<svg") && string.endsWith("</svg>")) {
// ignore SVG validation/normalization which will be done during image
// initialization
file = SVGStringToFile(string);
}
}
}
// prefer spreadsheet data over image file (MS Office/Libre Office)
if (isSupportedImageFile(file) && !data.spreadsheet) {
const imageElement = this.createImageElement({ sceneX, sceneY });
@ -2290,6 +2329,7 @@ class App extends React.Component<AppProps, AppState> {
});
} else if (data.text) {
const maybeUrl = extractSrc(data.text);
if (
!isPlainPaste &&
embeddableURLValidator(maybeUrl, this.props.validateEmbeddable) &&
@ -2424,6 +2464,85 @@ class App extends React.Component<AppProps, AppState> {
this.setActiveTool({ type: "selection" });
};
// TODO rewrite this to paste both text & images at the same time if
// pasted data contains both
private async addElementsFromMixedContentPaste(
mixedContent: PastedMixedContent,
{
isPlainPaste,
sceneX,
sceneY,
}: { isPlainPaste: boolean; sceneX: number; sceneY: number },
) {
if (
!isPlainPaste &&
mixedContent.some((node) => node.type === "imageUrl")
) {
const imageURLs = mixedContent
.filter((node) => node.type === "imageUrl")
.map((node) => node.value);
const responses = await Promise.all(
imageURLs.map(async (url) => {
try {
return { file: await ImageURLToFile(url) };
} catch (error: any) {
return { errorMessage: error.message as string };
}
}),
);
let y = sceneY;
let firstImageYOffsetDone = false;
const nextSelectedIds: Record<ExcalidrawElement["id"], true> = {};
for (const response of responses) {
if (response.file) {
const imageElement = this.createImageElement({
sceneX,
sceneY: y,
});
const initializedImageElement = await this.insertImageElement(
imageElement,
response.file,
);
if (initializedImageElement) {
// vertically center first image in the batch
if (!firstImageYOffsetDone) {
firstImageYOffsetDone = true;
y -= initializedImageElement.height / 2;
}
// hack to reset the `y` coord because we vertically center during
// insertImageElement
mutateElement(initializedImageElement, { y }, false);
y = imageElement.y + imageElement.height + 25;
nextSelectedIds[imageElement.id] = true;
}
}
}
this.setState({
selectedElementIds: makeNextSelectedElementIds(
nextSelectedIds,
this.state,
),
});
const error = responses.find((response) => !!response.errorMessage);
if (error && error.errorMessage) {
this.setState({ errorMessage: error.errorMessage });
}
} else {
const textNodes = mixedContent.filter((node) => node.type === "text");
if (textNodes.length) {
this.addTextFromPaste(
textNodes.map((node) => node.value).join("\n\n"),
isPlainPaste,
);
}
}
}
private addTextFromPaste(text: string, isPlainPaste = false) {
const { x, y } = viewportCoordsToSceneCoords(
{
@ -3164,11 +3283,16 @@ class App extends React.Component<AppProps, AppState> {
});
setActiveTool = (
tool:
| {
type: ToolType;
}
| { type: "custom"; customType: string },
tool: (
| (
| { type: Exclude<ToolType, "image"> }
| {
type: Extract<ToolType, "image">;
insertOnCanvasDirectly?: boolean;
}
)
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {
const nextActiveTool = updateActiveTool(this.state, tool);
if (nextActiveTool.type === "hand") {
@ -3183,7 +3307,10 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ suggestedBindings: [] });
}
if (nextActiveTool.type === "image") {
this.onImageAction();
this.onImageAction({
insertOnCanvasDirectly:
(tool.type === "image" && tool.insertOnCanvasDirectly) ?? false,
});
}
this.setState((prevState) => {
@ -4428,6 +4555,7 @@ class App extends React.Component<AppProps, AppState> {
setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
}
}
private handleCanvasPointerDown = (
event: React.PointerEvent<HTMLElement>,
) => {
@ -4617,7 +4745,7 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState,
);
} else if (this.state.activeTool.type === "custom") {
setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
setCursorForShape(this.interactiveCanvas, this.state);
} else if (this.state.activeTool.type === "frame") {
this.createFrameElementOnPointerDown(pointerDownState);
} else if (this.state.activeTool.type === "laser") {
@ -4636,6 +4764,11 @@ class App extends React.Component<AppProps, AppState> {
}
this.props?.onPointerDown?.(this.state.activeTool, pointerDownState);
this.onPointerDownEmitter.trigger(
this.state.activeTool,
pointerDownState,
event,
);
const onPointerMove =
this.onPointerMoveFromPointerDownHandler(pointerDownState);
@ -6491,6 +6624,12 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ pendingImageElementId: null });
}
this.onPointerUpEmitter.trigger(
this.state.activeTool,
pointerDownState,
childEvent,
);
if (draggingElement?.type === "freedraw") {
const pointerCoords = viewportCoordsToSceneCoords(
childEvent,
@ -7332,7 +7471,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.addNewElement(imageElement);
try {
await this.initializeImage({
return await this.initializeImage({
imageFile,
imageElement,
showCursorImagePreview,
@ -7345,6 +7484,7 @@ class App extends React.Component<AppProps, AppState> {
this.setState({
errorMessage: error.message || t("errors.imageInsertError"),
});
return null;
}
};
@ -7387,9 +7527,11 @@ class App extends React.Component<AppProps, AppState> {
}
};
private onImageAction = async (
{ insertOnCanvasDirectly } = { insertOnCanvasDirectly: false },
) => {
private onImageAction = async ({
insertOnCanvasDirectly,
}: {
insertOnCanvasDirectly: boolean;
}) => {
try {
const clientX = this.state.width / 2 + this.state.offsetLeft;
const clientY = this.state.height / 2 + this.state.offsetTop;

View File

@ -55,6 +55,7 @@ export const TopPicks = ({
type="button"
title={color}
onClick={() => onChange(color)}
data-testid={`color-top-pick-${color}`}
>
<div className="color-picker__button-outline" />
</button>

View File

@ -9,11 +9,7 @@ import {
} from "../actions/shortcuts";
import { Action } from "../actions/types";
import { ActionManager } from "../actions/manager";
import {
useExcalidrawAppState,
useExcalidrawElements,
useExcalidrawSetAppState,
} from "./App";
import { useExcalidrawAppState, useExcalidrawElements } from "./App";
import React from "react";
export type ContextMenuItem = typeof CONTEXT_MENU_SEPARATOR | Action;
@ -25,14 +21,14 @@ type ContextMenuProps = {
items: ContextMenuItems;
top: number;
left: number;
onClose: (callback?: () => void) => void;
};
export const CONTEXT_MENU_SEPARATOR = "separator";
export const ContextMenu = React.memo(
({ actionManager, items, top, left }: ContextMenuProps) => {
({ actionManager, items, top, left, onClose }: ContextMenuProps) => {
const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState();
const elements = useExcalidrawElements();
const filteredItems = items.reduce((acc: ContextMenuItem[], item) => {
@ -54,7 +50,9 @@ export const ContextMenu = React.memo(
return (
<Popover
onCloseRequest={() => setAppState({ contextMenu: null })}
onCloseRequest={() => {
onClose();
}}
top={top}
left={left}
fitInViewport={true}
@ -102,7 +100,7 @@ export const ContextMenu = React.memo(
// we need update state before executing the action in case
// the action uses the appState it's being passed (that still
// contains a defined contextMenu) to return the next state.
setAppState({ contextMenu: null }, () => {
onClose(() => {
actionManager.executeAction(item, "contextMenu");
});
}}

View File

@ -12,32 +12,32 @@
&--color-primary {
&.ExcButton--variant-filled {
--text-color: var(--input-bg-color);
--text-color: var(--color-surface-lowest);
--back-color: var(--color-primary);
&:hover {
--back-color: var(--color-primary-darker);
--back-color: var(--color-brand-hover);
}
&:active {
--back-color: var(--color-primary-darkest);
--back-color: var(--color-brand-active);
}
}
&.ExcButton--variant-outlined,
&.ExcButton--variant-icon {
--text-color: var(--color-primary);
--border-color: var(--color-primary);
--back-color: var(--input-bg-color);
--border-color: var(--color-border-outline);
--back-color: transparent;
&:hover {
--text-color: var(--color-primary-darker);
--border-color: var(--color-primary-darker);
--text-color: var(--color-brand-hover);
--border-color: var(--color-brand-hover);
}
&:active {
--text-color: var(--color-primary-darkest);
--border-color: var(--color-primary-darkest);
--text-color: var(--color-brand-active);
--border-color: var(--color-brand-active);
}
}
}

View File

@ -19,20 +19,35 @@
}
&__btn {
--background: var(--color-surface-mid);
display: flex;
column-gap: 0.5rem;
align-items: center;
border: 1px solid var(--default-border-color);
background-color: var(--background);
padding: 0.625rem 1rem;
border: 1px solid var(--background);
border-radius: var(--border-radius-lg);
color: var(--text-primary-color);
font-weight: 600;
font-size: 0.75rem;
letter-spacing: 0.4px;
@at-root .excalidraw.theme--dark#{&} {
--background: var(--color-surface-high);
&:hover {
--background: #363541;
}
}
&:hover {
--background: var(--color-surface-high);
text-decoration: none;
}
&:active {
border-color: var(--color-primary);
}
}
&__link-icon {

View File

@ -23,12 +23,15 @@ export type ExportCB = (
const JSONExportModal = ({
elements,
appState,
setAppState,
files,
actionManager,
exportOpts,
canvas,
onCloseRequest,
}: {
appState: UIAppState;
setAppState: React.Component<any, UIAppState>["setState"];
files: BinaryFiles;
elements: readonly NonDeletedExcalidrawElement[];
actionManager: ActionManager;
@ -72,9 +75,14 @@ const JSONExportModal = ({
title={t("exportDialog.link_button")}
aria-label={t("exportDialog.link_button")}
showAriaLabel={true}
onClick={() => {
onExportToBackend(elements, appState, files, canvas);
trackEvent("export", "link", `ui (${getFrame()})`);
onClick={async () => {
try {
trackEvent("export", "link", `ui (${getFrame()})`);
await onExportToBackend(elements, appState, files, canvas);
onCloseRequest();
} catch (error: any) {
setAppState({ errorMessage: error.message });
}
}}
/>
</Card>
@ -114,6 +122,7 @@ export const JSONExportDialog = ({
<JSONExportModal
elements={elements}
appState={appState}
setAppState={setAppState}
files={files}
actionManager={actionManager}
onCloseRequest={handleClose}

View File

@ -62,7 +62,6 @@ interface LayerUIProps {
appState: UIAppState;
files: BinaryFiles;
canvas: HTMLCanvasElement;
interactiveCanvas: HTMLCanvasElement | null;
setAppState: React.Component<any, AppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
onLockToggle: () => void;
@ -73,7 +72,6 @@ interface LayerUIProps {
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
UIOptions: AppProps["UIOptions"];
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
onExportImage: AppClassProperties["onExportImage"];
renderWelcomeScreen: boolean;
children?: React.ReactNode;
@ -123,7 +121,6 @@ const LayerUI = ({
setAppState,
elements,
canvas,
interactiveCanvas,
onLockToggle,
onHandToolToggle,
onPenModeToggle,
@ -131,7 +128,6 @@ const LayerUI = ({
renderTopRightUI,
renderCustomStats,
UIOptions,
onImageAction,
onExportImage,
renderWelcomeScreen,
children,
@ -280,14 +276,8 @@ const LayerUI = ({
<ShapesSwitcher
appState={appState}
interactiveCanvas={interactiveCanvas}
activeTool={appState.activeTool}
app={app}
onImageAction={({ pointerType }) => {
onImageAction({
insertOnCanvasDirectly: pointerType !== "mouse",
});
}}
/>
</Stack.Row>
</Island>
@ -472,8 +462,6 @@ const LayerUI = ({
onLockToggle={onLockToggle}
onHandToolToggle={onHandToolToggle}
onPenModeToggle={onPenModeToggle}
interactiveCanvas={interactiveCanvas}
onImageAction={onImageAction}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars}
@ -560,18 +548,8 @@ const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
return false;
}
const {
canvas: _pC,
interactiveCanvas: _pIC,
appState: prevAppState,
...prev
} = prevProps;
const {
canvas: _nC,
interactiveCanvas: _nIC,
appState: nextAppState,
...next
} = nextProps;
const { canvas: _pC, appState: prevAppState, ...prev } = prevProps;
const { canvas: _nC, appState: nextAppState, ...next } = nextProps;
return (
isShallowEqual(

View File

@ -99,10 +99,10 @@
font-size: 0.75rem;
&:hover {
background-color: var(--color-primary-darker);
background-color: var(--color-brand-hover);
}
&:active {
background-color: var(--color-primary-darkest);
background-color: var(--color-brand-active);
}
}

View File

@ -36,9 +36,7 @@ type MobileMenuProps = {
onLockToggle: () => void;
onHandToolToggle: () => void;
onPenModeToggle: () => void;
interactiveCanvas: HTMLCanvasElement | null;
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
renderTopRightUI?: (
isMobile: boolean,
appState: UIAppState,
@ -58,8 +56,7 @@ export const MobileMenu = ({
onLockToggle,
onHandToolToggle,
onPenModeToggle,
interactiveCanvas,
onImageAction,
renderTopRightUI,
renderCustomStats,
renderSidebars,
@ -85,14 +82,8 @@ export const MobileMenu = ({
<Stack.Row gap={1}>
<ShapesSwitcher
appState={appState}
interactiveCanvas={interactiveCanvas}
activeTool={appState.activeTool}
app={app}
onImageAction={({ pointerType }) => {
onImageAction({
insertOnCanvasDirectly: pointerType !== "mouse",
});
}}
/>
</Stack.Row>
</Island>

View File

@ -1,27 +1,18 @@
@import "../css/variables.module";
.excalidraw {
--RadioGroup-background: #ffffff;
--RadioGroup-border: var(--color-gray-30);
--RadioGroup-background: var(--island-bg-color);
--RadioGroup-border: var(--color-surface-high);
--RadioGroup-choice-color-off: var(--color-primary);
--RadioGroup-choice-color-off-hover: var(--color-primary-darkest);
--RadioGroup-choice-background-off: white;
--RadioGroup-choice-background-off-active: var(--color-gray-20);
--RadioGroup-choice-color-off-hover: var(--color-brand-hover);
--RadioGroup-choice-background-off: var(--island-bg-color);
--RadioGroup-choice-background-off-active: var(--color-surface-high);
--RadioGroup-choice-color-on: white;
--RadioGroup-choice-color-on: var(--color-surface-lowest);
--RadioGroup-choice-background-on: var(--color-primary);
--RadioGroup-choice-background-on-hover: var(--color-primary-darker);
--RadioGroup-choice-background-on-active: var(--color-primary-darkest);
&.theme--dark {
--RadioGroup-background: var(--color-gray-85);
--RadioGroup-border: var(--color-gray-70);
--RadioGroup-choice-background-off: var(--color-gray-85);
--RadioGroup-choice-background-off-active: var(--color-gray-70);
--RadioGroup-choice-color-on: var(--color-gray-85);
}
--RadioGroup-choice-background-on-hover: var(--color-brand-hover);
--RadioGroup-choice-background-on-active: var(--color-brand-active);
.RadioGroup {
box-sizing: border-box;

View File

@ -3,8 +3,7 @@
.excalidraw {
.sidebar-trigger {
@include outlineButtonStyles;
background-color: var(--island-bg-color);
@include filledButtonOnCanvas;
width: auto;
height: var(--lg-button-size);

View File

@ -1,15 +1,13 @@
@import "../css/variables.module";
.excalidraw {
--Switch-disabled-color: #d6d6d6;
--Switch-track-background: white;
--Switch-thumb-background: #3d3d3d;
&.theme--dark {
--Switch-disabled-color: #5c5c5c;
--Switch-track-background: #242424;
--Switch-thumb-background: #b8b8b8;
}
--Switch-disabled-color: var(--color-border-outline);
--Switch-disabled-toggled-background: var(--color-border-outline-variant);
--Switch-disabled-border: var(--color-border-outline-variant);
--Switch-track-background: var(--island-bg-color);
--Switch-thumb-background: var(--color-on-surface);
--Switch-hover-background: var(--color-brand-hover);
--Switch-active-background: var(--color-brand-active);
.Switch {
position: relative;
@ -28,7 +26,11 @@
&:hover {
background: var(--Switch-track-background);
border: 1px solid #999999;
border: 1px solid var(--Switch-hover-background);
}
&:active {
border: 1px solid var(--Switch-active-background);
}
&.toggled {
@ -43,11 +45,11 @@
&.disabled {
background: var(--Switch-track-background);
border: 1px solid var(--Switch-disabled-color);
border: 1px solid var(--Switch-disabled-border);
&.toggled {
background: var(--Switch-disabled-color);
border: 1px solid var(--Switch-disabled-color);
background: var(--Switch-disabled-toggled-background);
border: 1px solid var(--Switch-disabled-toggled-background);
}
}
@ -92,7 +94,7 @@
}
&.disabled.toggled:before {
background: var(--color-gray-50);
background: var(--Switch-disabled-color);
}
& input {

View File

@ -1,25 +1,16 @@
@import "../css/variables.module";
.excalidraw {
--ExcTextField--color: var(--color-gray-80);
--ExcTextField--label-color: var(--color-gray-80);
--ExcTextField--background: white;
--ExcTextField--readonly--background: var(--color-gray-10);
--ExcTextField--readonly--color: var(--color-gray-80);
--ExcTextField--border: var(--color-gray-40);
--ExcTextField--border-hover: var(--color-gray-50);
--ExcTextField--placeholder: var(--color-gray-40);
&.theme--dark {
--ExcTextField--color: var(--color-gray-10);
--ExcTextField--label-color: var(--color-gray-20);
--ExcTextField--background: var(--color-gray-85);
--ExcTextField--readonly--background: var(--color-gray-80);
--ExcTextField--readonly--color: var(--color-gray-40);
--ExcTextField--border: var(--color-gray-70);
--ExcTextField--border-hover: var(--color-gray-60);
--ExcTextField--placeholder: var(--color-gray-80);
}
--ExcTextField--color: var(--color-on-surface);
--ExcTextField--label-color: var(--color-on-surface);
--ExcTextField--background: transparent;
--ExcTextField--readonly--background: var(--color-surface-high);
--ExcTextField--readonly--color: var(--color-on-surface);
--ExcTextField--border: var(--color-border-outline);
--ExcTextField--readonly--border: var(--color-border-outline-variant);
--ExcTextField--border-hover: var(--color-brand-hover);
--ExcTextField--border-active: var(--color-brand-active);
--ExcTextField--placeholder: var(--color-border-outline-variant);
.ExcTextField {
&--fullWidth {
@ -61,7 +52,7 @@
&:active,
&:focus-within {
border-color: var(--color-primary);
border-color: var(--ExcTextField--border-active);
}
}
@ -107,7 +98,7 @@
&--readonly {
background: var(--ExcTextField--readonly--background);
border-color: transparent;
border-color: var(--ExcTextField--readonly--border);
& input {
color: var(--ExcTextField--readonly--color);

View File

@ -83,12 +83,12 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
}
};
useEffect(
() => () => {
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
},
[],
);
};
}, []);
const lastPointerTypeRef = useRef<PointerType | null>(null);

View File

@ -97,10 +97,6 @@
}
}
// &:hover {
// background-color: var(--button-gray-2);
// }
&:active {
background-color: var(--button-gray-3);
}
@ -110,7 +106,6 @@
}
&--hide {
// visibility: hidden;
display: none !important;
}
}

View File

@ -22,6 +22,7 @@
.App-toolbar__extra-tools-trigger {
box-shadow: none;
border: 0;
background-color: transparent;
&:active {
background-color: var(--button-hover-bg);

View File

@ -16,7 +16,7 @@
.dropdown-menu-container {
padding: 8px 8px;
box-sizing: border-box;
background-color: var(--island-bg-color);
// background-color: var(--island-bg-color);
box-shadow: var(--shadow-island);
border-radius: var(--border-radius-lg);
position: relative;
@ -29,7 +29,7 @@
}
.dropdown-menu-container {
background-color: #fff !important;
background-color: var(--island-bg-color);
max-height: calc(100vh - 150px);
overflow-y: auto;
--gap: 2;
@ -40,7 +40,7 @@
padding: 0 0.625rem;
column-gap: 0.625rem;
font-size: 0.875rem;
color: var(--color-gray-100);
color: var(--color-on-surface);
width: 100%;
box-sizing: border-box;
font-weight: normal;
@ -49,7 +49,7 @@
.dropdown-menu-item {
background-color: transparent;
border: 0;
border: 1px solid transparent;
align-items: center;
height: 2rem;
cursor: pointer;
@ -80,6 +80,11 @@
text-decoration: none;
}
&:active {
background-color: var(--button-hover-bg);
border-color: var(--color-brand-active);
}
svg {
width: 1rem;
height: 1rem;
@ -98,22 +103,33 @@
font-weight: 500;
}
}
&.theme--dark {
.dropdown-menu-item {
color: var(--color-gray-40);
}
.dropdown-menu-container {
background-color: var(--color-gray-90) !important;
}
}
.dropdown-menu-button {
@include outlineButtonStyles;
background-color: var(--island-bg-color);
width: var(--lg-button-size);
height: var(--lg-button-size);
--background: var(--color-surface-mid);
background-color: var(--background);
@at-root .excalidraw.theme--dark#{&} {
--background: var(--color-surface-high);
&:hover {
--background: #363541;
}
}
&:hover {
--background: var(--color-surface-high);
background-color: var(--background);
text-decoration: none;
}
&:active {
border-color: var(--color-primary);
}
svg {
width: var(--lg-icon-size);
height: var(--lg-icon-size);

View File

@ -14,6 +14,8 @@
--button-active-bg: var(--color-primary-darker);
box-shadow: 0 0 0 1px var(--color-surface-lowest);
flex-shrink: 0;
// double .active to force specificity

View File

@ -43,6 +43,7 @@ const MainMenu = Object.assign(
});
}}
data-testid="main-menu-trigger"
className="main-menu-trigger"
>
{HamburgerMenuIcon}
</DropdownMenu.Trigger>

View File

@ -174,7 +174,7 @@
justify-content: space-between;
background: none;
border: none;
border: 1px solid transparent;
padding: 0.75rem;
@ -204,7 +204,7 @@
.welcome-screen-menu-item:hover {
text-decoration: none;
background: var(--color-gray-10);
background: var(--button-hover-bg);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
@ -216,7 +216,8 @@
}
.welcome-screen-menu-item:active {
background: var(--color-gray-20);
background: var(--button-hover-bg);
border-color: var(--color-brand-active);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
@ -247,8 +248,7 @@
}
.welcome-screen-menu-item:hover {
background: var(--color-gray-85);
background-color: var(--color-surface-low);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
@ -259,7 +259,6 @@
}
.welcome-screen-menu-item:active {
background-color: var(--color-gray-90);
.welcome-screen-menu-item__text {
color: var(--color-gray-10);
}

View File

@ -148,6 +148,8 @@ export const IMAGE_MIME_TYPES = {
jfif: "image/jfif",
} as const;
export const ALLOWED_PASTE_MIME_TYPES = ["text/plain", "text/html"] as const;
export const MIME_TYPES = {
json: "application/json",
// excalidraw data
@ -296,6 +298,18 @@ export const ROUNDNESS = {
* collaboration */
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";
export const ROUGHNESS = {
architect: 0,
artist: 1,
cartoonist: 2,
} as const;
export const STROKE_WIDTH = {
thin: 1,
bold: 2,
extraBold: 4,
} as const;
export const DEFAULT_ELEMENT_PROPS: {
strokeColor: ExcalidrawElement["strokeColor"];
backgroundColor: ExcalidrawElement["backgroundColor"];
@ -308,10 +322,10 @@ export const DEFAULT_ELEMENT_PROPS: {
} = {
strokeColor: COLOR_PALETTE.black,
backgroundColor: COLOR_PALETTE.transparent,
fillStyle: "hachure",
strokeWidth: 1,
fillStyle: "solid",
strokeWidth: 2,
strokeStyle: "solid",
roughness: 1,
roughness: ROUGHNESS.artist,
opacity: 100,
locked: false,
};

View File

@ -444,13 +444,14 @@
}
&:active {
border: 1px solid var(--color-primary-darkest);
border: 1px solid var(--button-active-border);
}
}
.help-icon {
@include outlineButtonStyles;
background-color: var(--island-bg-color);
@include filledButtonOnCanvas;
width: var(--lg-button-size);
height: var(--lg-button-size);
@ -621,6 +622,20 @@
padding: 0;
}
}
.main-menu-trigger {
@include filledButtonOnCanvas;
}
.App-menu__left {
--button-border: transparent;
--button-bg: var(--color-surface-mid);
@at-root .excalidraw.theme--dark#{&} {
--button-hover-bg: #363541;
--button-bg: var(--color-surface-high);
}
}
}
.ErrorSplash.excalidraw {

View File

@ -12,27 +12,30 @@
--dialog-border-color: var(--color-gray-20);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
--focus-highlight-color: #{$oc-blue-2};
--icon-fill-color: var(--color-gray-80);
--icon-fill-color: var(--color-on-surface);
--icon-green-fill-color: #{$oc-green-9};
--default-bg-color: #{$oc-white};
--input-bg-color: #{$oc-white};
--input-border-color: #{$oc-gray-4};
--input-hover-bg-color: #{$oc-gray-1};
--input-label-color: #{$oc-gray-7};
--island-bg-color: rgba(255, 255, 255, 0.96);
--island-bg-color: #ffffff;
--keybinding-color: var(--color-gray-40);
--link-color: #{$oc-blue-7};
--overlay-bg-color: #{transparentize($oc-white, 0.12)};
--popup-bg-color: #{$oc-white};
--popup-bg-color: var(--island-bg-color);
--popup-secondary-bg-color: #{$oc-gray-1};
--popup-text-color: #{$oc-black};
--popup-text-inverted-color: #{$oc-white};
--select-highlight-color: #{$oc-blue-5};
--shadow-island: 0px 7px 14px rgba(0, 0, 0, 0.05),
0px 0px 3.12708px rgba(0, 0, 0, 0.0798),
0px 0px 0.931014px rgba(0, 0, 0, 0.1702);
--button-hover-bg: var(--color-gray-10);
--default-border-color: var(--color-gray-30);
--shadow-island: 0px 0px 0.9310142993927002px 0px rgba(0, 0, 0, 0.17),
0px 0px 3.1270833015441895px 0px rgba(0, 0, 0, 0.08),
0px 7px 14px 0px rgba(0, 0, 0, 0.05);
--button-hover-bg: var(--color-surface-high);
--button-active-bg: var(--color-surface-high);
--button-active-border: var(--color-brand-active);
--default-border-color: var(--color-surface-high);
--default-button-size: 2rem;
--default-icon-size: 1rem;
@ -63,14 +66,14 @@
0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035),
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725),
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--sidebar-border-color: var(--color-gray-20);
--sidebar-bg-color: #fff;
--sidebar-border-color: var(--color-surface-high);
--sidebar-bg-color: var(--island-bg-color);
--library-dropdown-shadow: 0px 15px 6px rgba(0, 0, 0, 0.01),
0px 8px 5px rgba(0, 0, 0, 0.05), 0px 4px 4px rgba(0, 0, 0, 0.09),
0px 1px 2px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(0, 0, 0, 0.1);
--space-factor: 0.25rem;
--text-primary-color: var(--color-gray-80);
--text-primary-color: var(--color-on-surface);
--color-selection: #6965db;
@ -132,6 +135,19 @@
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--color-surface-high: hsl(244, 100%, 97%);
--color-surface-mid: hsl(240 25% 96%);
--color-surface-low: hsl(240 25% 94%);
--color-surface-lowest: #ffffff;
--color-on-surface: #1b1b1f;
--color-brand-hover: #5753d0;
--color-on-primary-container: #030064;
--color-surface-primary-container: #e0dfff;
--color-brand-active: #4440bf;
--color-border-outline: #767680;
--color-border-outline-variant: #c5c5d0;
--color-surface-primary-container: #e0dfff;
&.theme--dark {
&.theme--dark-background-none {
background: none;
@ -150,29 +166,24 @@
--dialog-border-color: var(--color-gray-80);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
--focus-highlight-color: #{$oc-blue-6};
--icon-fill-color: var(--color-gray-40);
--icon-green-fill-color: #{$oc-green-4};
--default-bg-color: #121212;
--input-bg-color: #121212;
--input-border-color: #2e2e2e;
--input-hover-bg-color: #181818;
--input-label-color: #{$oc-gray-2};
--island-bg-color: #262627;
--island-bg-color: #232329;
--keybinding-color: var(--color-gray-60);
--link-color: #{$oc-blue-4};
--overlay-bg-color: #{transparentize($oc-gray-8, 0.88)};
--popup-bg-color: #2c2c2c;
--popup-secondary-bg-color: #222;
--popup-text-color: #{$oc-gray-4};
--popup-text-inverted-color: #2c2c2c;
--select-highlight-color: #{$oc-blue-4};
--text-primary-color: var(--color-gray-40);
--button-hover-bg: var(--color-gray-80);
--default-border-color: var(--color-gray-80);
--shadow-island: 0px 13px 33px rgba(0, 0, 0, 0.07),
0px 4.13px 9.94853px rgba(0, 0, 0, 0.0456112),
0px 1.13px 4.13211px rgba(0, 0, 0, 0.035),
0px 0.769896px 1.4945px rgba(0, 0, 0, 0.0243888);
--shadow-island: 0px 0px 0.9310142993927002px 0px rgba(0, 0, 0, 0.17),
0px 0px 3.1270833015441895px 0px rgba(0, 0, 0, 0.08),
0px 7px 14px 0px rgba(0, 0, 0, 0.05);
--modal-shadow: 0px 100px 80px rgba(0, 0, 0, 0.07),
0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198),
0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275),
@ -180,8 +191,6 @@
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725),
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--avatar-border-color: var(--color-gray-85);
--sidebar-border-color: var(--color-gray-85);
--sidebar-bg-color: #191919;
--scrollbar-thumb: #{$oc-gray-8};
--scrollbar-thumb-hover: #{$oc-gray-7};
@ -224,5 +233,18 @@
--color-promo: #d297ff;
--color-logo-text: #e2dfff;
--color-surface-high: hsl(245, 10%, 21%);
--color-surface-low: hsl(240, 8%, 15%);
--color-surface-mid: hsl(240 6% 10%);
--color-surface-lowest: hsl(0, 0%, 7%);
--color-on-surface: #e3e3e8;
--color-brand-hover: #bbb8ff;
--color-on-primary-container: #e0dfff;
--color-surface-primary-container: #403e6a;
--color-brand-active: #d0ccff;
--color-border-outline: #8e8d9c;
--color-border-outline-variant: #46464f;
--color-surface-primary-container: #403e6a;
}
}

View File

@ -11,7 +11,7 @@
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
&:checked + .ToolIcon__icon {
--icon-fill-color: var(--color-primary-darker);
--icon-fill-color: var(--color-on-primary-container);
svg {
fill: var(--icon-fill-color);
@ -23,11 +23,11 @@
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
&:checked + .ToolIcon__icon {
background: var(--color-primary-light);
--keybinding-color: var(--color-gray-60);
background: var(--color-surface-primary-container);
--keybinding-color: var(--color-on-primary-container);
svg {
color: var(--color-primary-darker);
color: var(--color-on-primary-container);
}
}
}
@ -44,7 +44,11 @@
&:active {
background: var(--button-hover-bg);
border: 1px solid var(--color-primary-darkest);
border: 1px solid var(--button-active-border);
svg {
color: var(--color-on-primary-container);
}
}
}
}
@ -63,7 +67,7 @@
border-radius: var(--border-radius-lg);
cursor: pointer;
background-color: var(--button-bg, var(--island-bg-color));
color: var(--button-color, var(--text-primary-color));
color: var(--button-color, var(--color-on-surface));
svg {
width: var(--button-width, var(--lg-icon-size));
@ -88,22 +92,38 @@
}
&.active {
background-color: var(--button-selected-bg, var(--color-primary-light));
border-color: var(--button-selected-border, var(--color-primary-light));
background-color: var(
--button-selected-bg,
var(--color-surface-primary-container)
);
border-color: var(
--button-selected-border,
var(--color-surface-primary-container)
);
&:hover {
background-color: var(
--button-selected-hover-bg,
var(--color-primary-light)
var(--color-surface-primary-container)
);
}
svg {
color: var(--button-color, var(--color-primary-darker));
color: var(--button-color, var(--color-on-primary-container));
}
}
}
@mixin filledButtonOnCanvas {
border: none;
box-shadow: 0 0 0 1px var(--color-surface-lowest);
background-color: var(--color-surface-low);
&:active {
box-shadow: 0 0 0 1px var(--color-brand-active);
}
}
$theme-filter: "invert(93%) hue-rotate(180deg)";
$right-sidebar-width: "302px";

View File

@ -99,5 +99,7 @@ export const setCursorForShape = (
interactiveCanvas.style.cursor = `url(${url}), auto`;
} else if (!["image", "custom"].includes(appState.activeTool.type)) {
interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
} else {
interactiveCanvas.style.cursor = CURSOR_TYPE.AUTO;
}
};

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ import { t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { AppState, DataURL, LibraryItem } from "../types";
import { ValueOf } from "../utility-types";
import { bytesToHexString } from "../utils";
import { bytesToHexString, isPromiseLike } from "../utils";
import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
import { isValidExcalidrawData, isValidLibrary } from "./json";
import { restore, restoreLibraryItems } from "./restore";
@ -207,10 +207,13 @@ export const loadLibraryFromBlob = async (
};
export const canvasToBlob = async (
canvas: HTMLCanvasElement,
canvas: HTMLCanvasElement | Promise<HTMLCanvasElement>,
): Promise<Blob> => {
return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
try {
if (isPromiseLike(canvas)) {
canvas = await canvas;
}
canvas.toBlob((blob) => {
if (!blob) {
return reject(
@ -324,6 +327,31 @@ export const SVGStringToFile = (SVGString: string, filename: string = "") => {
}) as File & { type: typeof MIME_TYPES.svg };
};
export const ImageURLToFile = async (
imageUrl: string,
filename: string = "",
): Promise<File | undefined> => {
let response;
try {
response = await fetch(imageUrl);
} catch (error: any) {
throw new Error(t("errors.failedToFetchImage"));
}
if (!response.ok) {
throw new Error(t("errors.failedToFetchImage"));
}
const blob = await response.blob();
if (blob.type && isSupportedImageFile(blob)) {
const name = filename || blob.name || "";
return new File([blob], name, { type: blob.type });
}
throw new Error(t("errors.unsupportedFileType"));
};
export const getFileFromEvent = async (
event: React.DragEvent<HTMLDivElement>,
) => {

View File

@ -66,17 +66,14 @@ export const exportCanvas = async (
}
}
const tempCanvas = await exportToCanvas(elements, appState, files, {
const tempCanvas = exportToCanvas(elements, appState, files, {
exportBackground,
viewBackgroundColor,
exportPadding,
});
tempCanvas.style.display = "none";
document.body.appendChild(tempCanvas);
if (type === "png") {
let blob = await canvasToBlob(tempCanvas);
tempCanvas.remove();
if (appState.exportEmbedScene) {
blob = await (
await import(/* webpackChunkName: "image" */ "./image")
@ -114,11 +111,8 @@ export const exportCanvas = async (
} else {
throw new Error(t("alerts.couldNotCopyToClipboard"));
}
} finally {
tempCanvas.remove();
}
} else {
tempCanvas.remove();
// shouldn't happen
throw new Error("Unsupported export type");
}

View File

@ -193,7 +193,7 @@ const restoreElement = (
fontSize = parseFloat(fontPx);
fontFamily = getFontFamilyByName(_fontFamily);
}
const text = element.text ?? "";
const text = (typeof element.text === "string" && element.text) || "";
// line-height might not be specified either when creating elements
// programmatically, or when importing old diagrams.
@ -222,9 +222,17 @@ const restoreElement = (
baseline,
});
// if empty text, mark as deleted. We keep in array
// for data integrity purposes (collab etc.)
if (!text && !element.isDeleted) {
element = { ...element, originalText: text, isDeleted: true };
element = bumpVersion(element);
}
if (refreshDimensions) {
element = { ...element, ...refreshTextDimensions(element) };
}
return element;
case "freedraw": {
return restoreElementWithProperties(element, {
@ -299,6 +307,7 @@ const restoreElement = (
// We also don't want to throw, but instead return void so we filter
// out these unsupported elements from the restored array.
}
return null;
};
/**

View File

@ -5,7 +5,31 @@ import {
} from "./transform";
import { ExcalidrawArrowElement } from "../element/types";
const opts = { regenerateIds: false };
describe("Test Transform", () => {
it("should generate id unless opts.regenerateIds is set to false explicitly", () => {
const elements = [
{
type: "rectangle",
x: 100,
y: 100,
id: "rect-1",
},
];
let data = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
);
expect(data.length).toBe(1);
expect(data[0].id).toBe("id0");
data = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(data[0].id).toBe("rect-1");
});
it("should transform regular shapes", () => {
const elements = [
{
@ -59,6 +83,7 @@ describe("Test Transform", () => {
convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
).forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
@ -87,6 +112,7 @@ describe("Test Transform", () => {
];
convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
).forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
@ -128,6 +154,7 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
@ -210,6 +237,7 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(12);
@ -267,6 +295,7 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(8);
@ -300,6 +329,7 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
@ -321,7 +351,7 @@ describe("Test Transform", () => {
});
expect(text).toMatchObject({
x: 340,
x: 240,
y: 226.5,
type: "text",
text: "HELLO WORLD!!",
@ -341,7 +371,7 @@ describe("Test Transform", () => {
});
expect(ellipse).toMatchObject({
x: 555,
x: 355,
y: 189,
type: "ellipse",
boundElements: [
@ -383,10 +413,10 @@ describe("Test Transform", () => {
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
const [arrow, text1, text2, text3] = excaldrawElements;
expect(arrow).toMatchObject({
@ -406,7 +436,7 @@ describe("Test Transform", () => {
});
expect(text1).toMatchObject({
x: 340,
x: 240,
y: 226.5,
type: "text",
text: "HELLO WORLD!!",
@ -426,7 +456,7 @@ describe("Test Transform", () => {
});
expect(text3).toMatchObject({
x: 555,
x: 355,
y: 226.5,
type: "text",
boundElements: [
@ -499,6 +529,7 @@ describe("Test Transform", () => {
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(5);
@ -547,6 +578,7 @@ describe("Test Transform", () => {
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
@ -600,17 +632,18 @@ describe("Test Transform", () => {
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
const [, , arrow] = excaldrawElements;
const [, , arrow, text] = excaldrawElements;
expect(arrow).toMatchObject({
type: "arrow",
x: 255,
y: 239,
boundElements: [
{
id: "id46",
id: text.id,
type: "text",
},
],
@ -650,17 +683,18 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(2);
const [arrow, rect] = excaldrawElements;
expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({
elementId: "rect-1",
focus: 0,
gap: 5,
gap: 205,
});
expect(rect.boundElements).toStrictEqual([
{
id: "id47",
id: arrow.id,
type: "arrow",
},
]);
@ -692,6 +726,7 @@ describe("Test Transform", () => {
];
const excaldrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(1);

View File

@ -39,6 +39,8 @@ import {
} from "../element/types";
import { MarkOptional } from "../utility-types";
import { assertNever, getFontString } from "../utils";
import { getSizeFromPoints } from "../points";
import { randomId } from "../random";
export type ValidLinearElement = {
type: "arrow" | "line";
@ -159,7 +161,7 @@ export type ExcalidrawElementSkeleton =
} & Partial<ExcalidrawImageElement>);
const DEFAULT_LINEAR_ELEMENT_PROPS = {
width: 300,
width: 100,
height: 0,
};
@ -357,6 +359,48 @@ const bindLinearElementToElement = (
);
}
}
// Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.
const endPointIndex = linearElement.points.length - 1;
const delta = 0.5;
const newPoints = JSON.parse(JSON.stringify(linearElement.points));
// left to right so shift the arrow towards right
if (
linearElement.points[endPointIndex][0] >
linearElement.points[endPointIndex - 1][0]
) {
newPoints[0][0] = delta;
newPoints[endPointIndex][0] -= delta;
}
// right to left so shift the arrow towards left
if (
linearElement.points[endPointIndex][0] <
linearElement.points[endPointIndex - 1][0]
) {
newPoints[0][0] = -delta;
newPoints[endPointIndex][0] += delta;
}
// top to bottom so shift the arrow towards top
if (
linearElement.points[endPointIndex][1] >
linearElement.points[endPointIndex - 1][1]
) {
newPoints[0][1] = delta;
newPoints[endPointIndex][1] -= delta;
}
// bottom to top so shift the arrow towards bottom
if (
linearElement.points[endPointIndex][1] <
linearElement.points[endPointIndex - 1][1]
) {
newPoints[0][1] = -delta;
newPoints[endPointIndex][1] += delta;
}
Object.assign(linearElement, { points: newPoints });
return {
linearElement,
startBoundElement,
@ -384,18 +428,28 @@ class ElementStore {
}
export const convertToExcalidrawElements = (
elements: ExcalidrawElementSkeleton[] | null,
elementsSkeleton: ExcalidrawElementSkeleton[] | null,
opts?: { regenerateIds: boolean },
) => {
if (!elements) {
if (!elementsSkeleton) {
return [];
}
const elements: ExcalidrawElementSkeleton[] = JSON.parse(
JSON.stringify(elementsSkeleton),
);
const elementStore = new ElementStore();
const elementsWithIds = new Map<string, ExcalidrawElementSkeleton>();
const oldToNewElementIdMap = new Map<string, string>();
// Create individual elements
for (const element of elements) {
let excalidrawElement: ExcalidrawElement;
const originalId = element.id;
if (opts?.regenerateIds !== false) {
Object.assign(element, { id: randomId() });
}
switch (element.type) {
case "rectangle":
case "ellipse":
@ -444,6 +498,11 @@ export const convertToExcalidrawElements = (
],
...element,
});
Object.assign(
excalidrawElement,
getSizeFromPoints(excalidrawElement.points),
);
break;
}
case "text": {
@ -499,6 +558,9 @@ export const convertToExcalidrawElements = (
} else {
elementStore.add(excalidrawElement);
elementsWithIds.set(excalidrawElement.id, element);
if (originalId) {
oldToNewElementIdMap.set(originalId, excalidrawElement.id);
}
}
}
@ -524,6 +586,18 @@ export const convertToExcalidrawElements = (
element.type === "arrow" ? element?.start : undefined;
const originalEnd =
element.type === "arrow" ? element?.end : undefined;
if (originalStart && originalStart.id) {
const newStartId = oldToNewElementIdMap.get(originalStart.id);
if (newStartId) {
Object.assign(originalStart, { id: newStartId });
}
}
if (originalEnd && originalEnd.id) {
const newEndId = oldToNewElementIdMap.get(originalEnd.id);
if (newEndId) {
Object.assign(originalEnd, { id: newEndId });
}
}
const { linearElement, startBoundElement, endBoundElement } =
bindLinearElementToElement(
container as ExcalidrawArrowElement,
@ -539,13 +613,23 @@ export const convertToExcalidrawElements = (
} else {
switch (element.type) {
case "arrow": {
const { start, end } = element;
if (start && start.id) {
const newStartId = oldToNewElementIdMap.get(start.id);
Object.assign(start, { id: newStartId });
}
if (end && end.id) {
const newEndId = oldToNewElementIdMap.get(end.id);
Object.assign(end, { id: newEndId });
}
const { linearElement, startBoundElement, endBoundElement } =
bindLinearElementToElement(
excalidrawElement as ExcalidrawArrowElement,
element.start,
element.end,
start,
end,
elementStore,
);
elementStore.add(linearElement);
elementStore.add(startBoundElement);
elementStore.add(endBoundElement);

View File

@ -392,7 +392,7 @@ export const getLinkHandleFromCoords = (
[x1, y1, x2, y2]: Bounds,
angle: number,
appState: Pick<UIAppState, "zoom">,
): [x: number, y: number, width: number, height: number] => {
): Bounds => {
const size = DEFAULT_LINK_SIZE;
const linkWidth = size / appState.zoom.value;
const linkHeight = size / appState.zoom.value;

View File

@ -34,7 +34,12 @@ export type RectangleBox = {
type MaybeQuadraticSolution = [number | null, number | null] | false;
// x and y position of top left corner, x and y position of bottom right corner
export type Bounds = readonly [x1: number, y1: number, x2: number, y2: number];
export type Bounds = readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
];
export class ElementBounds {
private static boundsCache = new WeakMap<
@ -63,7 +68,7 @@ export class ElementBounds {
}
private static calculateBounds(element: ExcalidrawElement): Bounds {
let bounds: [number, number, number, number];
let bounds: Bounds;
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
@ -387,7 +392,7 @@ const getCubicBezierCurveBound = (
export const getMinMaxXYFromCurvePathOps = (
ops: Op[],
transformXY?: (x: number, y: number) => [number, number],
): [number, number, number, number] => {
): Bounds => {
let currentP: Point = [0, 0];
const { minX, minY, maxX, maxY } = ops.reduce(
@ -435,9 +440,9 @@ export const getMinMaxXYFromCurvePathOps = (
return [minX, minY, maxX, maxY];
};
const getBoundsFromPoints = (
export const getBoundsFromPoints = (
points: ExcalidrawFreeDrawElement["points"],
): [number, number, number, number] => {
): Bounds => {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
@ -589,7 +594,7 @@ const getLinearElementRotatedBounds = (
element: ExcalidrawLinearElement,
cx: number,
cy: number,
): [number, number, number, number] => {
): Bounds => {
if (element.points.length < 2) {
const [pointX, pointY] = element.points[0];
const [x, y] = rotate(
@ -600,7 +605,7 @@ const getLinearElementRotatedBounds = (
element.angle,
);
let coords: [number, number, number, number] = [x, y, x, y];
let coords: Bounds = [x, y, x, y];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
@ -625,12 +630,7 @@ const getLinearElementRotatedBounds = (
const transformXY = (x: number, y: number) =>
rotate(element.x + x, element.y + y, cx, cy, element.angle);
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
let coords: [number, number, number, number] = [
res[0],
res[1],
res[2],
res[3],
];
let coords: Bounds = [res[0], res[1], res[2], res[3]];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
@ -692,7 +692,7 @@ export const getResizedElementAbsoluteCoords = (
nextWidth: number,
nextHeight: number,
normalizePoints: boolean,
): [number, number, number, number] => {
): Bounds => {
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
return [
element.x,
@ -709,7 +709,7 @@ export const getResizedElementAbsoluteCoords = (
normalizePoints,
);
let bounds: [number, number, number, number];
let bounds: Bounds;
if (isFreeDrawElement(element)) {
// Free Draw
@ -740,7 +740,7 @@ export const getResizedElementAbsoluteCoords = (
export const getElementPointsCoords = (
element: ExcalidrawLinearElement,
points: readonly (readonly [number, number])[],
): [number, number, number, number] => {
): Bounds => {
// This might be computationally heavey
const gen = rough.generator();
const curve =

View File

@ -494,7 +494,9 @@ const hitTestFreeDrawElement = (
// for filled freedraw shapes, support
// selecting from inside
if (shape && shape.sets.length) {
return hitTestRoughShape(shape, x, y, threshold);
return element.fillStyle === "solid"
? hitTestCurveInside(shape, x, y, "round")
: hitTestRoughShape(shape, x, y, threshold);
}
return false;

View File

@ -1,5 +1,5 @@
import { updateBoundElements } from "./binding";
import { getCommonBounds } from "./bounds";
import { Bounds, getCommonBounds } from "./bounds";
import { mutateElement } from "./mutateElement";
import { getPerfectElementSize } from "./sizeHelpers";
import { NonDeletedExcalidrawElement } from "./types";
@ -8,7 +8,11 @@ import { getBoundTextElement } from "./textElement";
import { isSelectedViaGroup } from "../groups";
import { getGridPoint } from "../math";
import Scene from "../scene/Scene";
import { isFrameElement } from "./typeChecks";
import {
isArrowElement,
isBoundToContainer,
isFrameElement,
} from "./typeChecks";
export const dragSelectedElements = (
pointerDownState: PointerDownState,
@ -35,44 +39,41 @@ export const dragSelectedElements = (
if (frames.length > 0) {
const elementsInFrames = scene
.getNonDeletedElements()
.filter((e) => !isBoundToContainer(e))
.filter((e) => e.frameId !== null)
.filter((e) => frames.includes(e.frameId!));
elementsInFrames.forEach((element) => elementsToUpdate.add(element));
}
const commonBounds = getCommonBounds(
Array.from(elementsToUpdate).map(
(el) => pointerDownState.originalElements.get(el.id) ?? el,
),
);
const adjustedOffset = calculateOffset(
commonBounds,
offset,
snapOffset,
gridSize,
);
elementsToUpdate.forEach((element) => {
updateElementCoords(
pointerDownState,
element,
offset,
snapOffset,
gridSize,
);
updateElementCoords(pointerDownState, element, adjustedOffset);
// update coords of bound text only if we're dragging the container directly
// (we don't drag the group that it's part of)
if (
// Don't update coords of arrow label since we calculate its position during render
!isArrowElement(element) &&
// container isn't part of any group
// (perf optim so we don't check `isSelectedViaGroup()` in every case)
!element.groupIds.length ||
// container is part of a group, but we're dragging the container directly
(appState.editingGroupId && !isSelectedViaGroup(appState, element))
(!element.groupIds.length ||
// container is part of a group, but we're dragging the container directly
(appState.editingGroupId && !isSelectedViaGroup(appState, element)))
) {
const textElement = getBoundTextElement(element);
if (
textElement &&
// when container is added to a frame, so will its bound text
// so the text is already in `elementsToUpdate` and we should avoid
// updating its coords again
(!textElement.frameId || !frames.includes(textElement.frameId))
) {
updateElementCoords(
pointerDownState,
textElement,
offset,
snapOffset,
gridSize,
);
if (textElement) {
updateElementCoords(pointerDownState, textElement, adjustedOffset);
}
}
updateBoundElements(element, {
@ -81,23 +82,20 @@ export const dragSelectedElements = (
});
};
const updateElementCoords = (
pointerDownState: PointerDownState,
element: NonDeletedExcalidrawElement,
const calculateOffset = (
commonBounds: Bounds,
dragOffset: { x: number; y: number },
snapOffset: { x: number; y: number },
gridSize: AppState["gridSize"],
) => {
const originalElement =
pointerDownState.originalElements.get(element.id) ?? element;
let nextX = originalElement.x + dragOffset.x + snapOffset.x;
let nextY = originalElement.y + dragOffset.y + snapOffset.y;
): { x: number; y: number } => {
const [x, y] = commonBounds;
let nextX = x + dragOffset.x + snapOffset.x;
let nextY = y + dragOffset.y + snapOffset.y;
if (snapOffset.x === 0 || snapOffset.y === 0) {
const [nextGridX, nextGridY] = getGridPoint(
originalElement.x + dragOffset.x,
originalElement.y + dragOffset.y,
x + dragOffset.x,
y + dragOffset.y,
gridSize,
);
@ -109,6 +107,22 @@ const updateElementCoords = (
nextY = nextGridY;
}
}
return {
x: nextX - x,
y: nextY - y,
};
};
const updateElementCoords = (
pointerDownState: PointerDownState,
element: NonDeletedExcalidrawElement,
dragOffset: { x: number; y: number },
) => {
const originalElement =
pointerDownState.originalElements.get(element.id) ?? element;
const nextX = originalElement.x + dragOffset.x;
const nextY = originalElement.y + dragOffset.y;
mutateElement(element, {
x: nextX,

View File

@ -28,6 +28,7 @@ const embeddedLinkCache = new Map<string, EmbeddedLink>();
const RE_YOUTUBE =
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
const RE_VIMEO =
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
@ -47,6 +48,9 @@ const RE_VALTOWN =
const RE_GENERIC_EMBED =
/^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
const RE_GIPHY =
/giphy.com\/(?:clips|embed|gifs)\/[a-zA-Z0-9]*?-?([a-zA-Z0-9]+)(?:[^a-zA-Z0-9]|$)/;
const ALLOWED_DOMAINS = new Set([
"youtube.com",
"youtu.be",
@ -59,6 +63,7 @@ const ALLOWED_DOMAINS = new Set([
"*.simplepdf.eu",
"stackblitz.com",
"val.town",
"giphy.com",
]);
const createSrcDoc = (body: string) => {
@ -308,6 +313,10 @@ export const extractSrc = (htmlString: string): string => {
return gistMatch[1];
}
if (RE_GIPHY.test(htmlString)) {
return `https://giphy.com/embed/${RE_GIPHY.exec(htmlString)![1]}`;
}
const match = htmlString.match(RE_GENERIC_EMBED);
if (match && match.length === 2) {
return match[1];

View File

@ -21,6 +21,7 @@ import {
} from "../math";
import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from ".";
import {
Bounds,
getCurvePathOps,
getElementPointsCoords,
getMinMaxXYFromCurvePathOps,
@ -1316,7 +1317,7 @@ export class LinearElementEditor {
static getMinMaxXYWithBoundText = (
element: ExcalidrawLinearElement,
elementBounds: [number, number, number, number],
elementBounds: Bounds,
boundTextElement: ExcalidrawTextElementWithContainer,
): [number, number, number, number, number, number] => {
let [x1, y1, x2, y2] = elementBounds;

View File

@ -163,8 +163,8 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
*
* NOTE: does not trigger re-render.
*/
export const bumpVersion = (
element: Mutable<ExcalidrawElement>,
export const bumpVersion = <T extends Mutable<ExcalidrawElement>>(
element: T,
version?: ExcalidrawElement["version"],
) => {
element.version = (version ?? element.version) + 1;

View File

@ -13,6 +13,7 @@ import {
MaybeTransformHandleType,
} from "./transformHandles";
import { AppState, Zoom } from "../types";
import { Bounds } from "./bounds";
const isInsideTransformHandle = (
transformHandle: TransformHandle,
@ -87,7 +88,7 @@ export const getElementWithTransformHandleType = (
};
export const getTransformHandleTypeFromCoords = (
[x1, y1, x2, y2]: readonly [number, number, number, number],
[x1, y1, x2, y2]: Bounds,
scenePointerX: number,
scenePointerY: number,
zoom: Zoom,

View File

@ -1493,6 +1493,7 @@ const createMathActions = () => {
: element;
return isMathElement(el) && el.customData?.mathOnly;
},
true,
null,
);
if (mathOnly === null) {
@ -1554,6 +1555,7 @@ const createMathActions = () => {
? getMathProps.ensureMathProps(el.customData).mathOnly
: null;
},
true,
getMathProps.getMathOnly(appState),
);
return (

View File

@ -17,8 +17,8 @@ import {
} from "./types";
import { API } from "../tests/helpers/api";
import { mutateElement } from "./mutateElement";
import { resize } from "../tests/utils";
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
import { getTextEditor } from "../tests/queries/dom";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@ -26,12 +26,6 @@ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const tab = " ";
const mouse = new Pointer("mouse");
const getTextEditor = () => {
return document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
};
const updateTextEditor = (editor: HTMLTextAreaElement, value: string) => {
fireEvent.change(editor, { target: { value } });
editor.dispatchEvent(new Event("input"));
@ -186,7 +180,7 @@ describe("textWysiwyg", () => {
expect(h.state.editingElement?.id).toBe(boundText.id);
});
it("should edit text under cursor when clicked with text tool", () => {
it("should edit text under cursor when clicked with text tool", async () => {
const text = API.createElement({
type: "text",
text: "ola",
@ -201,14 +195,14 @@ describe("textWysiwyg", () => {
mouse.clickAt(text.x + 50, text.y + 50);
const editor = getTextEditor();
const editor = await getTextEditor(false);
expect(editor).not.toBe(null);
expect(h.state.editingElement?.id).toBe(text.id);
expect(h.elements.length).toBe(1);
});
it("should edit text under cursor when double-clicked with selection tool", () => {
it("should edit text under cursor when double-clicked with selection tool", async () => {
const text = API.createElement({
type: "text",
text: "ola",
@ -223,12 +217,26 @@ describe("textWysiwyg", () => {
mouse.doubleClickAt(text.x + 50, text.y + 50);
const editor = getTextEditor();
const editor = await getTextEditor(false);
expect(editor).not.toBe(null);
expect(h.state.editingElement?.id).toBe(text.id);
expect(h.elements.length).toBe(1);
});
// FIXME too flaky. No one knows why.
it.skip("should bump the version of a labeled arrow when the label is updated", async () => {
const arrow = UI.createElement("arrow", {
width: 300,
height: 0,
});
await UI.editText(arrow, "Hello");
const { version } = arrow;
await UI.editText(arrow, "Hello\nworld!");
expect(arrow.version).toEqual(version + 1);
});
});
describe("Test container-unbound text", () => {
@ -250,7 +258,7 @@ describe("textWysiwyg", () => {
textElement = UI.createElement("text");
mouse.clickOn(textElement);
textarea = getTextEditor();
textarea = await getTextEditor(true);
});
afterAll(() => {
@ -460,7 +468,7 @@ describe("textWysiwyg", () => {
UI.clickTool("text");
mouse.clickAt(750, 300);
textarea = getTextEditor();
textarea = await getTextEditor(true);
updateTextEditor(
textarea,
"Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
@ -512,7 +520,7 @@ describe("textWysiwyg", () => {
{ id: text.id, type: "text" },
]);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@ -540,7 +548,7 @@ describe("textWysiwyg", () => {
]);
expect(text.angle).toBe(rectangle.angle);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@ -567,7 +575,7 @@ describe("textWysiwyg", () => {
API.setSelectedElements([diamond]);
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
const value = new Array(1000).fill("1").join("\n");
@ -602,7 +610,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
editor.blur();
@ -617,7 +625,7 @@ describe("textWysiwyg", () => {
expect(text.containerId).toBe(rectangle.id);
mouse.down();
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
await new Promise((r) => setTimeout(r, 0));
@ -639,7 +647,7 @@ describe("textWysiwyg", () => {
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
@ -674,7 +682,7 @@ describe("textWysiwyg", () => {
{ id: text.id, type: "text" },
]);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
await new Promise((r) => setTimeout(r, 0));
@ -699,7 +707,7 @@ describe("textWysiwyg", () => {
freedraw.y + freedraw.height / 2,
);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
@ -733,7 +741,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@ -748,7 +756,7 @@ describe("textWysiwyg", () => {
UI.clickTool("text");
mouse.clickAt(20, 30);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(
editor,
@ -793,7 +801,7 @@ describe("textWysiwyg", () => {
mouse.down();
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello World!");
@ -806,7 +814,7 @@ describe("textWysiwyg", () => {
rectangle.y + rectangle.height / 2,
);
mouse.down();
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
fireEvent.click(screen.getByTitle(/code/i));
@ -839,7 +847,7 @@ describe("textWysiwyg", () => {
Keyboard.keyDown(KEYS.ENTER);
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
let editor = getTextEditor();
let editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@ -860,7 +868,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello");
await new Promise((r) => setTimeout(r, 0));
@ -889,7 +897,7 @@ describe("textWysiwyg", () => {
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
@ -926,7 +934,7 @@ describe("textWysiwyg", () => {
// Bind first text
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello World!");
editor.blur();
@ -947,13 +955,13 @@ describe("textWysiwyg", () => {
it("should respect text alignment when resizing", async () => {
Keyboard.keyPress(KEYS.ENTER);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
// should center align horizontally and vertically by default
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
85,
@ -964,7 +972,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
@ -977,7 +985,7 @@ describe("textWysiwyg", () => {
editor.blur();
// should left align horizontally and bottom vertically after resize
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
15,
@ -987,7 +995,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
@ -999,7 +1007,7 @@ describe("textWysiwyg", () => {
editor.blur();
// should right align horizontally and top vertically after resize
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
374.99999999999994,
@ -1025,7 +1033,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@ -1040,7 +1048,7 @@ describe("textWysiwyg", () => {
it("should scale font size correctly when resizing using shift", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@ -1049,7 +1057,7 @@ describe("textWysiwyg", () => {
expect(rectangle.height).toBe(75);
expect(textElement.fontSize).toBe(20);
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 50], {
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 50], {
shift: true,
});
expect(rectangle.width).toBe(200);
@ -1060,7 +1068,7 @@ describe("textWysiwyg", () => {
it("should bind text correctly when container duplicated with alt-drag", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@ -1092,7 +1100,7 @@ describe("textWysiwyg", () => {
it("undo should work", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@ -1129,7 +1137,7 @@ describe("textWysiwyg", () => {
it("should not allow bound text with only whitespaces", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, " ");
@ -1184,19 +1192,19 @@ describe("textWysiwyg", () => {
it("should reset the container height cache when resizing", async () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect(rectangle.height).toBeCloseTo(155, 8);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(null);
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
editor.blur();
@ -1212,7 +1220,7 @@ describe("textWysiwyg", () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
editor.blur();
@ -1237,7 +1245,7 @@ describe("textWysiwyg", () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
editor.blur();
expect(
@ -1269,12 +1277,12 @@ describe("textWysiwyg", () => {
beforeEach(async () => {
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello");
editor.blur();
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
});
@ -1385,7 +1393,7 @@ describe("textWysiwyg", () => {
it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
UI.clickTool("text");
mouse.clickAt(20, 30);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(
editor,
@ -1431,7 +1439,7 @@ describe("textWysiwyg", () => {
type: "text",
},
],
fillStyle: "hachure",
fillStyle: "solid",
groupIds: [],
height: 35,
isDeleted: false,
@ -1444,7 +1452,7 @@ describe("textWysiwyg", () => {
},
strokeColor: "#1e1e1e",
strokeStyle: "solid",
strokeWidth: 1,
strokeWidth: 2,
type: "rectangle",
updated: 1,
version: 1,
@ -1473,7 +1481,7 @@ describe("textWysiwyg", () => {
// Bind first text
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello!");
expect(
@ -1498,7 +1506,7 @@ describe("textWysiwyg", () => {
rectangle.x + rectangle.width / 2,
rectangle.y + rectangle.height / 2,
);
editor = getTextEditor();
editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Excalidraw");
editor.blur();
@ -1512,30 +1520,4 @@ describe("textWysiwyg", () => {
expect(text.text).toBe("Excalidraw");
});
});
it("should bump the version of labelled arrow when label updated", async () => {
await render(<Excalidraw handleKeyboardGlobally={true} />);
const arrow = UI.createElement("arrow", {
width: 300,
height: 0,
});
mouse.select(arrow);
Keyboard.keyPress(KEYS.ENTER);
let editor = getTextEditor();
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
const { version } = arrow;
mouse.select(arrow);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello\nworld!");
editor.blur();
expect(arrow.version).toEqual(version + 1);
});
});

View File

@ -634,7 +634,7 @@ export const textWysiwyg = ({
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("pointerup", bindBlurEvent);
window.removeEventListener("blur", handleSubmit);
window.removeEventListener("beforeunload", handleSubmit);
unbindUpdate();
editable.remove();
@ -751,6 +751,7 @@ export const textWysiwyg = ({
passive: false,
capture: true,
});
window.addEventListener("beforeunload", handleSubmit);
excalidrawContainer
?.querySelector(".excalidraw-textEditorContainer")!
.appendChild(editable);

View File

@ -4,7 +4,7 @@ import {
PointerType,
} from "./types";
import { getElementAbsoluteCoords } from "./bounds";
import { Bounds, getElementAbsoluteCoords } from "./bounds";
import { rotate } from "../math";
import { InteractiveCanvasAppState, Zoom } from "../types";
import { isTextElement } from ".";
@ -23,7 +23,7 @@ export type TransformHandleDirection =
export type TransformHandleType = TransformHandleDirection | "rotation";
export type TransformHandle = [number, number, number, number];
export type TransformHandle = Bounds;
export type TransformHandles = Partial<{
[T in TransformHandleType]: TransformHandle;
}>;

47
src/emitter.ts Normal file
View File

@ -0,0 +1,47 @@
type Subscriber<T extends any[]> = (...payload: T) => void;
export class Emitter<T extends any[] = []> {
public subscribers: Subscriber<T>[] = [];
public value: T | undefined;
private updateOnChangeOnly: boolean;
constructor(opts?: { initialState?: T; updateOnChangeOnly?: boolean }) {
this.updateOnChangeOnly = opts?.updateOnChangeOnly ?? false;
this.value = opts?.initialState;
}
/**
* Attaches subscriber
*
* @returns unsubscribe function
*/
on(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
const _handlers = handlers
.flat()
.filter((item) => typeof item === "function");
this.subscribers.push(..._handlers);
return () => this.off(_handlers);
}
off(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
const _handlers = handlers.flat();
this.subscribers = this.subscribers.filter(
(handler) => !_handlers.includes(handler),
);
}
trigger(...payload: T): any[] {
if (this.updateOnChangeOnly && this.value === payload) {
return [];
}
this.value = payload;
return this.subscribers.map((handler) => handler(...payload));
}
destroy() {
this.subscribers = [];
this.value = undefined;
}
}

View File

@ -123,7 +123,7 @@ describe("adding elements to frames", () => {
const commonTestCases = async (
func: typeof resizeFrameOverElement | typeof dragElementIntoFrame,
) => {
describe("when frame is in a layer below", async () => {
describe.skip("when frame is in a layer below", async () => {
it("should add an element", async () => {
h.elements = [frame, rect2];
@ -167,7 +167,7 @@ describe("adding elements to frames", () => {
});
});
describe("when frame is in a layer above", async () => {
describe.skip("when frame is in a layer above", async () => {
it("should add an element", async () => {
h.elements = [rect2, frame];
@ -177,7 +177,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect2, frame]);
});
it.skip("should add elements", async () => {
it("should add elements", async () => {
h.elements = [rect2, rect3, frame];
func(frame, rect2);
@ -188,7 +188,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect3, rect2, frame]);
});
it.skip("should add elements when there are other other elements in between", async () => {
it("should add elements when there are other other elements in between", async () => {
h.elements = [rect1, rect2, rect4, rect3, frame];
func(frame, rect2);
@ -199,7 +199,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect1, rect4, rect3, rect2, frame]);
});
it.skip("should add elements when there are other elements in between and the order is reversed", async () => {
it("should add elements when there are other elements in between and the order is reversed", async () => {
h.elements = [rect3, rect4, rect2, rect1, frame];
func(frame, rect2);
@ -212,7 +212,7 @@ describe("adding elements to frames", () => {
});
describe("when frame is in an inner layer", async () => {
it("should add elements", async () => {
it.skip("should add elements", async () => {
h.elements = [rect2, frame, rect3];
func(frame, rect2);
@ -223,7 +223,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect2, rect3, frame]);
});
it("should add elements when there are other other elements in between", async () => {
it.skip("should add elements when there are other other elements in between", async () => {
h.elements = [rect2, rect1, frame, rect4, rect3];
func(frame, rect2);
@ -289,7 +289,7 @@ describe("adding elements to frames", () => {
describe("resizing frame over elements", async () => {
await commonTestCases(resizeFrameOverElement);
it("resizing over text containers and labelled arrows", async () => {
it.skip("resizing over text containers and labelled arrows", async () => {
await resizingTest(
"rectangle",
["frame", "rectangle", "text"],
@ -339,7 +339,7 @@ describe("adding elements to frames", () => {
// );
});
it("should add arrow bound with text when frame is in a layer below", async () => {
it.skip("should add arrow bound with text when frame is in a layer below", async () => {
h.elements = [frame, arrow, text];
resizeFrameOverElement(frame, arrow);
@ -359,7 +359,7 @@ describe("adding elements to frames", () => {
expectEqualIds([arrow, text, frame]);
});
it("should add arrow bound with text when frame is in an inner layer", async () => {
it.skip("should add arrow bound with text when frame is in an inner layer", async () => {
h.elements = [arrow, frame, text];
resizeFrameOverElement(frame, arrow);
@ -371,7 +371,7 @@ describe("adding elements to frames", () => {
});
describe("resizing frame over elements but downwards", async () => {
it("should add elements when frame is in a layer below", async () => {
it.skip("should add elements when frame is in a layer below", async () => {
h.elements = [frame, rect1, rect2, rect3, rect4];
resizeFrameOverElement(frame, rect4);
@ -382,7 +382,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect2, rect3, frame, rect4, rect1]);
});
it("should add elements when frame is in a layer above", async () => {
it.skip("should add elements when frame is in a layer above", async () => {
h.elements = [rect1, rect2, rect3, rect4, frame];
resizeFrameOverElement(frame, rect4);
@ -393,7 +393,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect1, rect2, rect3, frame, rect4]);
});
it("should add elements when frame is in an inner layer", async () => {
it.skip("should add elements when frame is in an inner layer", async () => {
h.elements = [rect1, rect2, frame, rect3, rect4];
resizeFrameOverElement(frame, rect4);
@ -408,7 +408,7 @@ describe("adding elements to frames", () => {
describe("dragging elements into the frame", async () => {
await commonTestCases(dragElementIntoFrame);
it("should drag element inside, duplicate it and keep it in frame", () => {
it.skip("should drag element inside, duplicate it and keep it in frame", () => {
h.elements = [frame, rect2];
dragElementIntoFrame(frame, rect2);
@ -422,7 +422,7 @@ describe("adding elements to frames", () => {
expectEqualIds([rect2_copy, rect2, frame]);
});
it("should drag element inside, duplicate it and remove it from frame", () => {
it.skip("should drag element inside, duplicate it and remove it from frame", () => {
h.elements = [frame, rect2];
dragElementIntoFrame(frame, rect2);
@ -436,5 +436,121 @@ describe("adding elements to frames", () => {
expect(rect2.frameId).toBe(null);
expectEqualIds([rect2_copy, frame, rect2]);
});
it("random order 01", () => {
const frame1 = API.createElement({
type: "frame",
x: 0,
y: 0,
width: 100,
height: 100,
});
const frame2 = API.createElement({
type: "frame",
x: 200,
y: 0,
width: 100,
height: 100,
});
const frame3 = API.createElement({
type: "frame",
x: 300,
y: 0,
width: 100,
height: 100,
});
const rectangle1 = API.createElement({
type: "rectangle",
x: 25,
y: 25,
width: 50,
height: 50,
frameId: frame1.id,
});
const rectangle2 = API.createElement({
type: "rectangle",
x: 225,
y: 25,
width: 50,
height: 50,
frameId: frame2.id,
});
const rectangle3 = API.createElement({
type: "rectangle",
x: 325,
y: 25,
width: 50,
height: 50,
frameId: frame3.id,
});
const rectangle4 = API.createElement({
type: "rectangle",
x: 350,
y: 25,
width: 50,
height: 50,
frameId: frame3.id,
});
h.elements = [
frame1,
rectangle4,
rectangle1,
rectangle3,
frame3,
rectangle2,
frame2,
];
API.setSelectedElements([rectangle2]);
const origSize = h.elements.length;
expect(h.elements.length).toBe(origSize);
dragElementIntoFrame(frame3, rectangle2);
expect(h.elements.length).toBe(origSize);
});
it("random order 02", () => {
const frame1 = API.createElement({
type: "frame",
x: 0,
y: 0,
width: 100,
height: 100,
});
const frame2 = API.createElement({
type: "frame",
x: 200,
y: 0,
width: 100,
height: 100,
});
const rectangle1 = API.createElement({
type: "rectangle",
x: 25,
y: 25,
width: 50,
height: 50,
frameId: frame1.id,
});
const rectangle2 = API.createElement({
type: "rectangle",
x: 225,
y: 25,
width: 50,
height: 50,
frameId: frame2.id,
});
h.elements = [rectangle1, rectangle2, frame1, frame2];
API.setSelectedElements([rectangle2]);
expect(h.elements.length).toBe(4);
dragElementIntoFrame(frame2, rectangle1);
expect(h.elements.length).toBe(4);
});
});
});

View File

@ -19,10 +19,10 @@ import { mutateElement } from "./element/mutateElement";
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
import { getElementsWithinSelection, getSelectedElements } from "./scene";
import { isFrameElement } from "./element";
import { moveOneRight } from "./zindex";
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
import { getElementLineSegments } from "./element/bounds";
import { doLineSegmentsIntersect } from "./packages/utils";
// --------------------------- Frame State ------------------------------------
export const bindElementsToFramesAfterDuplication = (
@ -56,130 +56,21 @@ export const bindElementsToFramesAfterDuplication = (
}
};
// --------------------------- Frame Geometry ---------------------------------
class Point {
x: number;
y: number;
export function isElementIntersectingFrame(
element: ExcalidrawElement,
frame: ExcalidrawFrameElement,
) {
const frameLineSegments = getElementLineSegments(frame);
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const elementLineSegments = getElementLineSegments(element);
class LineSegment {
first: Point;
second: Point;
const intersecting = frameLineSegments.some((frameLineSegment) =>
elementLineSegments.some((elementLineSegment) =>
doLineSegmentsIntersect(frameLineSegment, elementLineSegment),
),
);
constructor(pointA: Point, pointB: Point) {
this.first = pointA;
this.second = pointB;
}
public getBoundingBox(): [Point, Point] {
return [
new Point(
Math.min(this.first.x, this.second.x),
Math.min(this.first.y, this.second.y),
),
new Point(
Math.max(this.first.x, this.second.x),
Math.max(this.first.y, this.second.y),
),
];
}
}
// https://martin-thoma.com/how-to-check-if-two-line-segments-intersect/
class FrameGeometry {
private static EPSILON = 0.000001;
private static crossProduct(a: Point, b: Point) {
return a.x * b.y - b.x * a.y;
}
private static doBoundingBoxesIntersect(
a: [Point, Point],
b: [Point, Point],
) {
return (
a[0].x <= b[1].x &&
a[1].x >= b[0].x &&
a[0].y <= b[1].y &&
a[1].y >= b[0].y
);
}
private static isPointOnLine(a: LineSegment, b: Point) {
const aTmp = new LineSegment(
new Point(0, 0),
new Point(a.second.x - a.first.x, a.second.y - a.first.y),
);
const bTmp = new Point(b.x - a.first.x, b.y - a.first.y);
const r = this.crossProduct(aTmp.second, bTmp);
return Math.abs(r) < this.EPSILON;
}
private static isPointRightOfLine(a: LineSegment, b: Point) {
const aTmp = new LineSegment(
new Point(0, 0),
new Point(a.second.x - a.first.x, a.second.y - a.first.y),
);
const bTmp = new Point(b.x - a.first.x, b.y - a.first.y);
return this.crossProduct(aTmp.second, bTmp) < 0;
}
private static lineSegmentTouchesOrCrossesLine(
a: LineSegment,
b: LineSegment,
) {
return (
this.isPointOnLine(a, b.first) ||
this.isPointOnLine(a, b.second) ||
(this.isPointRightOfLine(a, b.first)
? !this.isPointRightOfLine(a, b.second)
: this.isPointRightOfLine(a, b.second))
);
}
private static doLineSegmentsIntersect(
a: [readonly [number, number], readonly [number, number]],
b: [readonly [number, number], readonly [number, number]],
) {
const aSegment = new LineSegment(
new Point(a[0][0], a[0][1]),
new Point(a[1][0], a[1][1]),
);
const bSegment = new LineSegment(
new Point(b[0][0], b[0][1]),
new Point(b[1][0], b[1][1]),
);
const box1 = aSegment.getBoundingBox();
const box2 = bSegment.getBoundingBox();
return (
this.doBoundingBoxesIntersect(box1, box2) &&
this.lineSegmentTouchesOrCrossesLine(aSegment, bSegment) &&
this.lineSegmentTouchesOrCrossesLine(bSegment, aSegment)
);
}
public static isElementIntersectingFrame(
element: ExcalidrawElement,
frame: ExcalidrawFrameElement,
) {
const frameLineSegments = getElementLineSegments(frame);
const elementLineSegments = getElementLineSegments(element);
const intersecting = frameLineSegments.some((frameLineSegment) =>
elementLineSegments.some((elementLineSegment) =>
this.doLineSegmentsIntersect(frameLineSegment, elementLineSegment),
),
);
return intersecting;
}
return intersecting;
}
export const getElementsCompletelyInFrame = (
@ -207,10 +98,7 @@ export const isElementContainingFrame = (
export const getElementsIntersectingFrame = (
elements: readonly ExcalidrawElement[],
frame: ExcalidrawFrameElement,
) =>
elements.filter((element) =>
FrameGeometry.isElementIntersectingFrame(element, frame),
);
) => elements.filter((element) => isElementIntersectingFrame(element, frame));
export const elementsAreInFrameBounds = (
elements: readonly ExcalidrawElement[],
@ -236,7 +124,7 @@ export const elementOverlapsWithFrame = (
) => {
return (
elementsAreInFrameBounds([element], frame) ||
FrameGeometry.isElementIntersectingFrame(element, frame) ||
isElementIntersectingFrame(element, frame) ||
isElementContainingFrame([frame], element, frame)
);
};
@ -273,7 +161,7 @@ export const groupsAreAtLeastIntersectingTheFrame = (
return !!elementsInGroup.find(
(element) =>
elementsAreInFrameBounds([element], frame) ||
FrameGeometry.isElementIntersectingFrame(element, frame),
isElementIntersectingFrame(element, frame),
);
};
@ -294,7 +182,7 @@ export const groupsAreCompletelyOutOfFrame = (
elementsInGroup.find(
(element) =>
elementsAreInFrameBounds([element], frame) ||
FrameGeometry.isElementIntersectingFrame(element, frame),
isElementIntersectingFrame(element, frame),
) === undefined
);
};
@ -354,7 +242,7 @@ export const getElementsInResizingFrame = (
);
for (const element of elementsNotCompletelyInFrame) {
if (!FrameGeometry.isElementIntersectingFrame(element, frame)) {
if (!isElementIntersectingFrame(element, frame)) {
if (element.groupIds.length === 0) {
nextElementsInFrame.delete(element);
}
@ -452,21 +340,27 @@ export const getContainingFrame = (
};
// --------------------------- Frame Operations -------------------------------
/**
* Retains (or repairs for target frame) the ordering invriant where children
* elements come right before the parent frame:
* [el, el, child, child, frame, el]
*/
export const addElementsToFrame = (
allElements: ExcalidrawElementsIncludingDeleted,
elementsToAdd: NonDeletedExcalidrawElement[],
frame: ExcalidrawFrameElement,
) => {
const currTargetFrameChildrenMap = new Map(
allElements.reduce(
(acc: [ExcalidrawElement["id"], ExcalidrawElement][], element) => {
if (element.frameId === frame.id) {
acc.push([element.id, element]);
}
return acc;
},
[],
),
const { currTargetFrameChildrenMap } = allElements.reduce(
(acc, element, index) => {
if (element.frameId === frame.id) {
acc.currTargetFrameChildrenMap.set(element.id, true);
}
return acc;
},
{
currTargetFrameChildrenMap: new Map<ExcalidrawElement["id"], true>(),
},
);
const suppliedElementsToAddSet = new Set(elementsToAdd.map((el) => el.id));
@ -493,42 +387,6 @@ export const addElementsToFrame = (
}
}
const finalElementsToAddSet = new Set(finalElementsToAdd.map((el) => el.id));
const nextElements: ExcalidrawElement[] = [];
const processedElements = new Set<ExcalidrawElement["id"]>();
for (const element of allElements) {
if (processedElements.has(element.id)) {
continue;
}
processedElements.add(element.id);
if (
finalElementsToAddSet.has(element.id) ||
(element.frameId && element.frameId === frame.id)
) {
// will be added in bulk once we process target frame
continue;
}
// target frame
if (element.id === frame.id) {
const currFrameChildren = getFrameElements(allElements, frame.id);
currFrameChildren.forEach((child) => {
processedElements.add(child.id);
});
// console.log(currFrameChildren, finalElementsToAdd, element);
nextElements.push(...currFrameChildren, ...finalElementsToAdd, element);
continue;
}
// console.log("(2)", element.frameId);
nextElements.push(element);
}
for (const element of finalElementsToAdd) {
mutateElement(
element,
@ -538,8 +396,7 @@ export const addElementsToFrame = (
false,
);
}
return nextElements;
return allElements.slice();
};
export const removeElementsFromFrame = (
@ -547,20 +404,34 @@ export const removeElementsFromFrame = (
elementsToRemove: NonDeletedExcalidrawElement[],
appState: AppState,
) => {
const _elementsToRemove: ExcalidrawElement[] = [];
const _elementsToRemove = new Map<
ExcalidrawElement["id"],
ExcalidrawElement
>();
const toRemoveElementsByFrame = new Map<
ExcalidrawFrameElement["id"],
ExcalidrawElement[]
>();
for (const element of elementsToRemove) {
if (element.frameId) {
_elementsToRemove.push(element);
_elementsToRemove.set(element.id, element);
const arr = toRemoveElementsByFrame.get(element.frameId) || [];
arr.push(element);
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
_elementsToRemove.push(boundTextElement);
_elementsToRemove.set(boundTextElement.id, boundTextElement);
arr.push(boundTextElement);
}
toRemoveElementsByFrame.set(element.frameId, arr);
}
}
for (const element of _elementsToRemove) {
for (const [, element] of _elementsToRemove) {
mutateElement(
element,
{
@ -570,13 +441,7 @@ export const removeElementsFromFrame = (
);
}
const nextElements = moveOneRight(
allElements,
appState,
Array.from(_elementsToRemove),
);
return nextElements;
return allElements.slice();
};
export const removeAllElementsFromFrame = (

View File

@ -50,7 +50,7 @@
"veryLarge": "كبير جدا",
"solid": "كامل",
"hachure": "خطوط",
"zigzag": "",
"zigzag": "متعرج",
"crossHatch": "خطوط متقطعة",
"thin": "نحيف",
"bold": "داكن",
@ -106,11 +106,15 @@
"increaseFontSize": "تكبير حجم الخط",
"unbindText": "فك ربط النص",
"bindText": "ربط النص بالحاوية",
"createContainerFromText": "",
"createContainerFromText": "نص مغلف في حاوية",
"link": {
"edit": "تعديل الرابط",
"editEmbed": "تحرير الرابط وإدراجه",
"create": "إنشاء رابط",
"label": "رابط"
"createEmbed": "إنشاء رابط و إدراجه",
"label": "رابط",
"labelEmbed": "رابط و إدراج",
"empty": "لم يتم تعيين رابط"
},
"lineEditor": {
"edit": "تحرير السطر",
@ -124,9 +128,9 @@
},
"statusPublished": "نُشر",
"sidebarLock": "إبقاء الشريط الجانبي مفتوح",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"selectAllElementsInFrame": "تحديد جميع العناصر في الإطار",
"removeAllElementsFromFrame": "إزالة جميع العناصر من الإطار",
"eyeDropper": "اختيار اللون من القماش"
},
"library": {
"noItems": "لا توجد عناصر أضيفت بعد...",
@ -160,13 +164,16 @@
"darkMode": "الوضع المظلم",
"lightMode": "الوضع المضيء",
"zenMode": "وضع التأمل",
"objectsSnapMode": "التقط إلى العناصر",
"exitZenMode": "إلغاء الوضع الليلى",
"cancel": "إلغاء",
"clear": "مسح",
"remove": "إزالة",
"embed": "تبديل الإدراج",
"publishLibrary": "انشر",
"submit": "أرسل",
"confirm": "تأكيد"
"confirm": "تأكيد",
"embeddableInteractionButton": "اضغط للتفاعل"
},
"alerts": {
"clearReset": "هذا سيُزيل كامل اللوحة. هل أنت متأكد؟",
@ -189,23 +196,28 @@
"resetLibrary": "هذا سوف يمسح مكتبتك. هل أنت متأكد؟",
"removeItemsFromsLibrary": "حذف {{count}} عنصر (عناصر) من المكتبة؟",
"invalidEncryptionKey": "مفتاح التشفير يجب أن يكون من 22 حرفاً. التعاون المباشر معطل.",
"collabOfflineWarning": ""
"collabOfflineWarning": "لا يوجد اتصال بالانترنت.\nلن يتم حفظ التغييرات التي قمت بها!"
},
"errors": {
"unsupportedFileType": "نوع الملف غير مدعوم.",
"imageInsertError": "تعذر إدراج الصورة. حاول مرة أخرى لاحقاً...",
"fileTooBig": "الملف كبير جداً. الحد الأقصى المسموح به للحجم هو {{maxSize}}.",
"svgImageInsertError": "تعذر إدراج صورة SVG. يبدو أن ترميز SVG غير صحيح.",
"failedToFetchImage": "",
"invalidSVGString": "SVG غير صالح.",
"cannotResolveCollabServer": "تعذر الاتصال بخادم التعاون. الرجاء إعادة تحميل الصفحة والمحاولة مرة أخرى.",
"importLibraryError": "تعذر تحميل المكتبة",
"collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.",
"collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك.",
"brave_measure_text_error": {
"line1": "",
"line2": "",
"line3": "",
"line4": ""
"line1": "يبدو أنك تستخدم متصفح Brave مع إعداد <bold>حظر صارم لتتبع البصمة</bold>.",
"line2": "قد يؤدي هذا إلى كسر <bold>عناصر النص</bold> في الرسومات الخاصة بك.",
"line3": "من المستحسن إلغاء تفعيل هذا الإعداد. يمكنك اتباع <link>هذه الخطوات</link> لفعل ذلك.",
"line4": "إذا لم يصلح تعطيل هذا الإعداد طريقة عرض النصوص، الرجاء كتابة <issueLink>بلاغ</issueLink> على حسابنا في GitHub، أو راسلنا على <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "لا يمكن إضافة العناصر القابلة للتضمين في المكتبة.",
"image": "سوف يتم دعم إضافة صور إلى المكتبة قريباً!"
}
},
"toolBar": {
@ -223,9 +235,11 @@
"penMode": "وضع القلم - امنع اللمس",
"link": "إضافة/تحديث الرابط للشكل المحدد",
"eraser": "ممحاة",
"frame": "",
"hand": "",
"extraTools": ""
"frame": "أداة الإطار",
"embeddable": "تضمين ويب",
"laser": "مؤشر ليزر",
"hand": "يد (أداة الإزاحة)",
"extraTools": "المزيد من أﻷدوات"
},
"headings": {
"canvasActions": "إجراءات اللوحة",
@ -237,6 +251,7 @@
"linearElement": "انقر لبدء نقاط متعددة، اسحب لخط واحد",
"freeDraw": "انقر واسحب، افرج عند الانتهاء",
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
"embeddable": "اضغط مع السحب لإنشاء موقع ويب مضمّن",
"text_selected": "انقر نقراً مزدوجاً أو اضغط ادخال لتعديل النص",
"text_editing": "اضغط على Esc أو (Ctrl أو Cmd) + Enter لإنهاء التعديل",
"linearElementMulti": "انقر فوق النقطة الأخيرة أو اضغط على Esc أو Enter للإنهاء",
@ -245,14 +260,15 @@
"resizeImage": "يمكنك تغيير الحجم بحرية بالضغط بأستمرار على SHIFT،\nاضغط بأستمرار على ALT أيضا لتغيير الحجم من المركز",
"rotate": "يمكنك تقييد الزوايا من خلال الضغط على SHIFT أثناء الدوران",
"lineEditor_info": "اضغط على مفتاح (Ctrl أو Cmd) و انقر بشكل مزدوج، أو اضغط على مفتاحي (Ctrl أو Cmd) و (Enter) لتعديل النقاط",
"lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "",
"placeImage": "",
"lineEditor_pointSelected": "اضغط على حذف لإزالة النقطة (النِّقَاط)، Ctrl/Cmd+D للتكرار، أو اسحب للانتقال",
"lineEditor_nothingSelected": "اختر نقطة لتعديلها (اضغط على SHIFT لتحديد عدة نِقَاط),\nأو اضغط على ALT و انقر بالفأرة لإضافة نِقَاط جديدة",
"placeImage": "انقر لوضع الصورة، أو انقر واسحب لتعيين حجمها يدوياً",
"publishLibrary": "نشر مكتبتك",
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"bindTextToElement": "اضغط على إدخال لإضافة نص",
"deepBoxSelect": "اضغط على Ctrl\\Cmd للاختيار العميق، ولمنع السحب",
"eraserRevert": "اضغط على Alt لاستعادة العناصر المعلَّمة للحذف",
"firefox_clipboard_write": "يمكن على الأرجح تمكين هذه الميزة عن طريق تعيين علم \"dom.events.asyncClipboard.clipboardItem\" إلى \"true\". لتغيير أعلام المتصفح في Firefox، قم بزيارة صفحة \"about:config\".",
"disableSnapping": "اضغط على Ctrl أو Cmd لتعطيل الالتقاط"
},
"canvasError": {
"cannotShowPreview": "تعذر عرض المعاينة",
@ -260,11 +276,11 @@
"canvasTooBigTip": "نصيحة: حاول تحريك العناصر البعيدة بشكل أقرب قليلاً."
},
"errorSplash": {
"headingMain": "",
"headingMain": "حدث خطأ. حاول <button>تحديث الصفحة</button>.",
"clearCanvasMessage": "إذا لم تعمل إعادة التحميل، حاول مرة أخرى ",
"clearCanvasCaveat": " هذا سيؤدي إلى فقدان العمل ",
"trackedToSentry": "",
"openIssueMessage": "",
"trackedToSentry": "تم تتبع الخطأ في المعرف {{eventId}} على نظامنا.",
"openIssueMessage": "حرصنا على عدم إضافة معلومات المشهد في بلاغ الخطأ. في حال كون مشهدك لا يحمل أي معلومات خاصة نرجو المتابعة على <button>نظام تتبع الأخطاء</button>. نرجو إضافة المعلومات أدناه بنسخها ولصقها في محتوى البلاغ على GitHub.",
"sceneContent": "محتوى المشهد:"
},
"roomDialog": {
@ -294,16 +310,16 @@
"helpDialog": {
"blog": "اقرأ مدونتنا",
"click": "انقر",
"deepSelect": "",
"deepBoxSelect": "",
"deepSelect": "تحديد عميق",
"deepBoxSelect": "تحديد عميق داخل المربع، ومنع السحب",
"curvedArrow": "سهم مائل",
"curvedLine": "خط مائل",
"documentation": "دليل الاستخدام",
"doubleClick": "انقر مرتين",
"drag": "اسحب",
"editor": "المحرر",
"editLineArrowPoints": "",
"editText": "",
"editLineArrowPoints": "تحرير سطر/نقاط سهم",
"editText": "تعديل النص / إضافة تسمية",
"github": "عثرت على مشكلة؟ إرسال",
"howto": "اتبع التعليمات",
"or": "أو",
@ -316,9 +332,9 @@
"view": "عرض",
"zoomToFit": "تكبير للملائمة",
"zoomToSelection": "تكبير للعنصر المحدد",
"toggleElementLock": "",
"movePageUpDown": "",
"movePageLeftRight": ""
"toggleElementLock": "إغلاق/فتح المحدد",
"movePageUpDown": "نقل الصفحة أعلى/أسفل",
"movePageLeftRight": "نقل الصفحة يسار/يمين"
},
"clearCanvasDialog": {
"title": "مسح اللوحة"
@ -336,20 +352,20 @@
"authorName": "اسمك أو اسم المستخدم",
"libraryName": "اسم مكتبتك",
"libraryDesc": "وصف مكتبتك لمساعدة الناس على فهم استخدامها",
"githubHandle": "",
"twitterHandle": "",
"website": ""
"githubHandle": "معالج GitHub (اختياري)، حتى تتمكن من تحرير المكتبة عند إرسالها للمراجعة",
"twitterHandle": "اسم مستخدم تويتر (اختياري)، حتى نعرف من الذي سيتم الإشارة إليه عند الترويج عبر تويتر",
"website": "رابط إلى موقعك الشخصي أو في مكان آخر (اختياري)"
},
"errors": {
"required": "مطلوب",
"website": "أدخل عنوان URL صالح"
},
"noteDescription": "",
"noteGuidelines": "",
"noteLicense": "",
"noteDescription": "تقديم مكتبتك لتضمينها في مستودع المكتبة العامة <link></link> لأشخاص آخرين لاستخدامها في رسومهم.",
"noteGuidelines": "تحتاج المكتبة إلى الموافقة أولا. يرجى قراءة <link>المعايير</link> قبل تقديمها. سوف تحتاج إلى حساب GitHub للتواصل وإجراء التغييرات عند الطلب، ولكن ليس مطلوبا بشكل صارم.",
"noteLicense": "تقديمك يعني موافقتك على نشر المكتبة المقدمة تحت <link>MIT ترخيص</link>، ما يعني أن لأي أحد الحق في استخدامها دون قيود.",
"noteItems": "يجب أن يكون لكل عنصر مكتبة اسمه الخاص حتى يكون قابلاً للتصفية. سيتم تضمين عناصر المكتبة التالية:",
"atleastOneLibItem": "يرجى تحديد عنصر مكتبة واحد على الأقل للبدء",
"republishWarning": ""
"republishWarning": "ملاحظة: بعض العناصر المحددة معينة على أنه نشرها أو تقديمها من قبل. يجب عليك فقط إعادة إرسال العناصر عند تحديث مكتبة موجودة أو إرسالها."
},
"publishSuccessDialog": {
"title": "تم إرسال المكتبة",
@ -360,27 +376,27 @@
"removeItemsFromLib": "إزالة العناصر المحددة من المكتبة"
},
"imageExportDialog": {
"header": "",
"header": "تصدير الصورة",
"label": {
"withBackground": "",
"onlySelected": "",
"darkMode": "",
"embedScene": "",
"scale": "",
"padding": ""
"withBackground": "الخلفية",
"onlySelected": "المحدد فقط",
"darkMode": "الوضع الداكن",
"embedScene": "تضمين المشهد",
"scale": "الحجم",
"padding": "الهوامش"
},
"tooltip": {
"embedScene": ""
"embedScene": "سيتم حفظ بيانات المشهد في ملف PNG/SVG المصدّر بحيث يمكن استعادة المشهد منه.\nسيزيد حجم الملف المصدر."
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "تصدير بصيغة PNG",
"exportToSvg": "تصدير بصيغة SVG",
"copyPngToClipboard": "نسخ الـ PNG إلى الحافظة"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "نسخ إلى الحافظة"
}
},
"encrypted": {
@ -411,43 +427,76 @@
"fileSavedToFilename": "حفظ باسم {filename}",
"canvas": "لوحة الرسم",
"selection": "العنصر المحدد",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "استخدم {{shortcut}} للصق كعنصر واحد،\nأو لصق في محرر نص موجود",
"unableToEmbed": "تضمين هذا الرابط غير مسموح حاليًا. افتح بلاغاً على GitHub لطلب عنوان Url القائمة البيضاء",
"unrecognizedLinkFormat": "الرابط الذي ضمنته لا يتطابق مع التنسيق المتوقع. الرجاء محاولة لصق النص 'المضمن' المُزوَد من موقع المصدر"
},
"colors": {
"transparent": "شفاف",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"black": "أسود",
"white": "أبيض",
"red": "أحمر",
"pink": "وردي",
"grape": "عنبي",
"violet": "بنفسجي",
"gray": "رمادي",
"blue": "أزرق",
"cyan": "سماوي",
"teal": "أزرق مخضر",
"green": "أخضر",
"yellow": "أصفر",
"orange": "برتقالي",
"bronze": "برونزي"
},
"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": "الاختصارات و المساعدة"
}
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
"mostUsedCustomColors": "الألوان المخصصة الأكثر استخداما",
"colors": "الألوان",
"shades": "الدرجات",
"hexCode": "رمز Hex",
"noShades": "لا تتوفر درجات لهذا اللون"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "تصدير كصورة",
"button": "تصدير كصورة",
"description": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقاً."
},
"saveToDisk": {
"title": "حفظ الملف للجهاز",
"button": "حفظ الملف للجهاز",
"description": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقاً."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "تصدير إلى Excalidraw+",
"description": "حفظ المشهد إلى مساحة العمل +Excalidraw الخاصة بك."
}
},
"modal": {
"loadFromFile": {
"title": "تحميل من ملف",
"button": "تحميل من ملف",
"description": "سيتم التحميل من الملف <bold>استبدال المحتوى الموجود</bold>.<br></br>يمكنك النسخ الاحتياطي لرسمك أولاً باستخدام أحد الخيارات أدناه."
},
"shareableLink": {
"title": "تحميل من رابط",
"button": "استبدال محتواي",
"description": "سيتسبب تحميل رسمة خارجية <bold>باستبدال محتواك الموجود حالياً</bold>.<br></br>بإمكانك إجراء النسخ الاحتياطي لرسمتك الحالية باستخدام أحد الخيارات أدناه."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "",
"lightMode": "",
"zenMode": "",
"objectsSnapMode": "",
"exitZenMode": "",
"cancel": "",
"clear": "",
"remove": "",
"embed": "",
"publishLibrary": "",
"submit": "",
"confirm": ""
"confirm": "",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "",
@ -196,6 +203,7 @@
"imageInsertError": "",
"fileTooBig": "",
"svgImageInsertError": "",
"failedToFetchImage": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "",
"freeDraw": "",
"text": "",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "",
@ -411,7 +427,9 @@
"fileSavedToFilename": "",
"canvas": "",
"selection": "",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -1,7 +1,7 @@
{
"labels": {
"paste": "Постави",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Постави като обикновен текст",
"pasteCharts": "Постави графики",
"selectAll": "Маркирай всичко",
"multiSelect": "Добави елемент към селекция",
@ -50,7 +50,7 @@
"veryLarge": "Много голям",
"solid": "Солиден",
"hachure": "Хералдика",
"zigzag": "",
"zigzag": "Зигзаг",
"crossHatch": "Двойно-пресечено",
"thin": "Тънък",
"bold": "Ясно очертан",
@ -63,7 +63,7 @@
"cartoonist": "Карикатурист",
"fileTitle": "Име на файл",
"colorPicker": "Избор на цвят",
"canvasColors": "",
"canvasColors": "Използван на платно",
"canvasBackground": "Фон на платно",
"drawingCanvas": "Платно за рисуване",
"layers": "Слоеве",
@ -99,37 +99,41 @@
"share": "Сподели",
"showStroke": "",
"showBackground": "",
"toggleTheme": "",
"personalLib": "",
"excalidrawLib": "",
"decreaseFontSize": "",
"increaseFontSize": "",
"toggleTheme": "Включи тема",
"personalLib": "Лична Библиотека",
"excalidrawLib": "Excalidraw Библиотека",
"decreaseFontSize": "Намали размера на шрифта",
"increaseFontSize": "Увеличи размера на шрифта",
"unbindText": "",
"bindText": "",
"createContainerFromText": "",
"link": {
"edit": "",
"edit": "Редактирай линк",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "Линк",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
"exit": ""
},
"elementLock": {
"lock": "",
"unlock": "",
"lockAll": "",
"unlockAll": ""
"lock": "Заключи",
"unlock": "Отключи",
"lockAll": "Заключи всички",
"unlockAll": "Отключи всички"
},
"statusPublished": "",
"statusPublished": "Публикувани",
"sidebarLock": "",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"eyeDropper": "Избери цвят от платното"
},
"library": {
"noItems": "",
"noItems": "Няма добавени неща все още...",
"hint_emptyLibrary": "",
"hint_emptyPrivateLibrary": ""
},
@ -137,11 +141,11 @@
"clearReset": "Нулиране на платно",
"exportJSON": "",
"exportImage": "",
"export": "",
"export": "Запази на...",
"copyToClipboard": "Копиране в клипборда",
"save": "",
"save": "Запази към текущ файл",
"saveAs": "Запиши като",
"load": "",
"load": "Отвори",
"getShareableLink": "Получаване на връзка за споделяне",
"close": "Затвори",
"selectLanguage": "Избор на език",
@ -160,13 +164,16 @@
"darkMode": "Тъмен режим",
"lightMode": "Светъл режим",
"zenMode": "Режим Zen",
"objectsSnapMode": "",
"exitZenMode": "Спиране на Zen режим",
"cancel": "Отмени",
"clear": "Изчисти",
"remove": "Премахване",
"embed": "",
"publishLibrary": "Публикувай",
"submit": "Изпрати",
"confirm": "Потвърждаване"
"confirm": "Потвърждаване",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Това ще изчисти цялото платно. Сигурни ли сте?",
@ -175,37 +182,42 @@
"couldNotLoadInvalidFile": "Невалиден файл не може да се зареди",
"importBackendFailed": "Импортирането от бекенд не беше успешно.",
"cannotExportEmptyCanvas": "Не може да се експортира празно платно.",
"couldNotCopyToClipboard": "",
"couldNotCopyToClipboard": "Не можем да копираме в клипбоарда.",
"decryptFailed": "Данните не можаха да се дешифрират.",
"uploadedSecurly": "Качването е защитено с криптиране от край до край, което означава, че сървърът Excalidraw и трети страни не могат да четат съдържанието.",
"loadSceneOverridePrompt": "Зареждането на външна рисунка ще презапише настоящото ви съдържание. Желаете ли да продължите?",
"collabStopOverridePrompt": "Прекратяването на сесията ще презапише предишната, локално запазена, рисунка. Сигурни ли сте?\n\n(Ако искате да продължите с локалната рисунка, просто затворете таба на браузъра.)",
"errorAddingToLibrary": "",
"errorRemovingFromLibrary": "",
"errorAddingToLibrary": "Не можем да заредим от библиотеката",
"errorRemovingFromLibrary": "Не можем да премахнем елемент от библиотеката",
"confirmAddLibrary": "Ще се добавят {{numShapes}} фигура(и) във вашата библиотека. Сигурни ли сте?",
"imageDoesNotContainScene": "",
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл",
"invalidSceneUrl": "",
"resetLibrary": "",
"removeItemsFromsLibrary": "",
"removeItemsFromsLibrary": "Изтрий {{count}} елемент(а) от библиотеката?",
"invalidEncryptionKey": "",
"collabOfflineWarning": ""
},
"errors": {
"unsupportedFileType": "Този файлов формат не се поддържа.",
"imageInsertError": "",
"fileTooBig": "",
"fileTooBig": "Файлът е твърде голям. Максималния допустим размер е {{maxSize}}.",
"svgImageInsertError": "",
"invalidSVGString": "",
"failedToFetchImage": "",
"invalidSVGString": "Невалиден SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": "",
"importLibraryError": "Не можем да заредим библиотеката",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": "",
"brave_measure_text_error": {
"line1": "",
"line2": "",
"line3": "",
"line3": "Силно препоръчваме да изключите тази настройка. Можете да следвате <link>тези стъпки</link> за това как да го направите.",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -222,10 +234,12 @@
"lock": "Поддържайте избрания инструмент активен след рисуване",
"penMode": "",
"link": "",
"eraser": "",
"eraser": "Гума",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
"extraTools": "Още инструменти"
},
"headings": {
"canvasActions": "Действия по платното",
@ -237,6 +251,7 @@
"linearElement": "Кликнете, за да стартирате няколко точки, плъзнете за една линия",
"freeDraw": "Натиснете и влачете, пуснете като сте готови",
"text": "Подсказка: Можете също да добавите текст като натиснете някъде два път с инструмента за селекция",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "Кликнете върху последната точка или натиснете Escape или Enter, за да завършите",
@ -252,7 +267,8 @@
"bindTextToElement": "Натиснете Enter, за да добавите",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Невъзможност за показване на preview",
@ -288,7 +304,7 @@
"link_details": "",
"link_button": "",
"excalidrawplus_description": "",
"excalidrawplus_button": "",
"excalidrawplus_button": "Експорт",
"excalidrawplus_exportError": ""
},
"helpDialog": {
@ -299,7 +315,7 @@
"curvedArrow": "Извита стрелка",
"curvedLine": "Извита линия",
"documentation": "Документация",
"doubleClick": "",
"doubleClick": "двойно-щракване",
"drag": "плъзнете",
"editor": "Редактор",
"editLineArrowPoints": "",
@ -308,41 +324,41 @@
"howto": "Следвайте нашите ръководства",
"or": "или",
"preventBinding": "Спри прилепяне на стрелките",
"tools": "",
"tools": "Инструменти",
"shortcuts": "Клавиши за бърз достъп",
"textFinish": "",
"textNewLine": "",
"textFinish": "Завърши редактиране (текстов редактор)",
"textNewLine": "Добави нова линия (текстов редактор)",
"title": "Помощ",
"view": "Преглед",
"zoomToFit": "Приближи докато се виждат всички елементи",
"zoomToSelection": "Приближи селекцията",
"toggleElementLock": "",
"movePageUpDown": "",
"movePageLeftRight": ""
"toggleElementLock": "Заключи/Отключи селекция",
"movePageUpDown": "Премести страница нагоре/надолу",
"movePageLeftRight": "Премести страница наляво/надясно"
},
"clearCanvasDialog": {
"title": ""
"title": "Изчисти платното"
},
"publishDialog": {
"title": "",
"itemName": "",
"authorName": "",
"githubUsername": "",
"twitterUsername": "",
"libraryName": "",
"libraryDesc": "",
"website": "",
"title": "Публикувай библиотека",
"itemName": "Име",
"authorName": "Авторско име",
"githubUsername": "GitHub потребителско име",
"twitterUsername": "Twitter потребителско име",
"libraryName": "Име на библиотеката",
"libraryDesc": "Описание на библиотеката",
"website": "Уебсайт",
"placeholder": {
"authorName": "",
"libraryName": "",
"libraryDesc": "",
"authorName": "Името или потребителското Ви име",
"libraryName": "Име на библиотеката Ви",
"libraryDesc": "Описание на библиотеката ви, за да помогнете на хората да разберат приложенията ѝ",
"githubHandle": "",
"twitterHandle": "",
"website": ""
},
"errors": {
"required": "",
"website": ""
"required": "Задължително",
"website": "Въведете валиден URL адрес"
},
"noteDescription": "",
"noteGuidelines": "",
@ -356,15 +372,15 @@
"content": ""
},
"confirmDialog": {
"resetLibrary": "",
"resetLibrary": "Нулирай библиотека",
"removeItemsFromLib": ""
},
"imageExportDialog": {
"header": "",
"label": {
"withBackground": "",
"onlySelected": "",
"darkMode": "",
"withBackground": "Фон",
"onlySelected": "Само избраното",
"darkMode": "Тъмен режим",
"embedScene": "",
"scale": "",
"padding": ""
@ -373,14 +389,14 @@
"embedScene": ""
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "Изнасяне в PNG",
"exportToSvg": "Изнасяне в SVG",
"copyPngToClipboard": "Копирай PNG в клипборда"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Копиране в клипборда"
}
},
"encrypted": {
@ -403,51 +419,84 @@
"width": "Широчина"
},
"toast": {
"addedToLibrary": "",
"addedToLibrary": "Добавена към библиотеката",
"copyStyles": "Копирани стилове.",
"copyToClipboard": "Копирано в клипборда.",
"copyToClipboardAsPng": "",
"fileSaved": "",
"fileSavedToFilename": "",
"canvas": "",
"selection": "",
"pasteAsSingleElement": ""
"copyToClipboardAsPng": "Копира {{exportSelection}} в клипборда като PNG\n({{exportColorScheme}})",
"fileSaved": "Файлът е запазен.",
"fileSavedToFilename": "Запазен към {filename}",
"canvas": "платно",
"selection": "селекция",
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"transparent": "Прозрачен",
"black": "Черен",
"white": "Бял",
"red": "Червен",
"pink": "Розов",
"grape": "Грозде",
"violet": "Виолетово",
"gray": "Сив",
"blue": "Син",
"cyan": "Синьозелено",
"teal": "Тъмно синьо-зелено",
"green": "Зелено",
"yellow": "Жълто",
"orange": "Оранжево",
"bronze": "Бронзово"
},
"welcomeScreen": {
"app": {
"center_heading": "",
"center_heading": "Всичките Ви данни са запазени локално в браузъра Ви.",
"center_heading_plus": "",
"menuHint": ""
"menuHint": "Експорт, предпочитания, езици, ..."
},
"defaults": {
"menuHint": "",
"center_heading": "",
"toolbarHint": "",
"helpHint": ""
"menuHint": "Експорт, предпочитания, и още...",
"center_heading": "Диаграми. Направени. Просто.",
"toolbarHint": "Изберете инструмент & Започнете да рисувате!",
"helpHint": "Преки пътища & помощ"
}
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"mostUsedCustomColors": "Най-често използвани цветове",
"colors": "Цветове",
"shades": "Нюанси",
"hexCode": "Шестнадесетичен код",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Изнеси като изображение",
"button": "Изнеси като изображение",
"description": ""
},
"saveToDisk": {
"title": "Запази към диск",
"button": "Запази към диск",
"description": ""
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Експортирай към Excalidraw+",
"description": "Запази сцената към Excalidraw+ работното място."
}
},
"modal": {
"loadFromFile": {
"title": "Зареди от файл",
"button": "Зареди от файл",
"description": ""
},
"shareableLink": {
"title": "Зареди от линк",
"button": "Замени моето съдържание",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "লিঙ্ক সংশোধন",
"editEmbed": "",
"create": "লিঙ্ক তৈরী",
"label": "লিঙ্ক নামকরণ"
"createEmbed": "",
"label": "লিঙ্ক নামকরণ",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "ডার্ক মোড",
"lightMode": "লাইট মোড",
"zenMode": "জেন মোড",
"objectsSnapMode": "",
"exitZenMode": "জেন মোড বন্ধ করুন",
"cancel": "বাতিল",
"clear": "সাফ",
"remove": "বিয়োগ",
"embed": "",
"publishLibrary": "সংগ্রহ প্রকাশ করুন",
"submit": "জমা করুন",
"confirm": "নিশ্চিত করুন"
"confirm": "নিশ্চিত করুন",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "এটি পুরো ক্যানভাস সাফ করবে। আপনি কি নিশ্চিত?",
@ -196,6 +203,7 @@
"imageInsertError": "ছবি সন্নিবেশ করা যায়নি। পরে আবার চেষ্টা করুন...",
"fileTooBig": "ফাইলটি খুব বড়। সর্বাধিক অনুমোদিত আকার হল {{maxSize}}৷",
"svgImageInsertError": "এসভীজী ছবি সন্নিবেশ করা যায়নি। এসভীজী মার্কআপটি অবৈধ মনে হচ্ছে৷",
"failedToFetchImage": "",
"invalidSVGString": "এসভীজী মার্কআপটি অবৈধ মনে হচ্ছে৷",
"cannotResolveCollabServer": "কোল্যাব সার্ভারের সাথে সংযোগ করা যায়নি। পৃষ্ঠাটি পুনরায় লোড করে আবার চেষ্টা করুন।",
"importLibraryError": "সংগ্রহ লোড করা যায়নি",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন",
"eraser": "ঝাড়ন",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "একাধিক বিন্দু শুরু করতে ক্লিক করুন, একক লাইনের জন্য টেনে আনুন",
"freeDraw": "ক্লিক করুন এবং টেনে আনুন, আপনার কাজ শেষ হলে ছেড়ে দিন",
"text": "বিশেষ্য: আপনি নির্বাচন টুলের সাথে যে কোনো জায়গায় ডাবল-ক্লিক করে পাঠ্য যোগ করতে পারেন",
"embeddable": "",
"text_selected": "লেখা সম্পাদনা করতে ডাবল-ক্লিক করুন বা এন্টার টিপুন",
"text_editing": "লেখা সম্পাদনা শেষ করতে এসকেপ বা কন্ট্রোল/কম্যান্ড যোগে এন্টার টিপুন",
"linearElementMulti": "শেষ বিন্দুতে ক্লিক করুন অথবা শেষ করতে এসকেপ বা এন্টার টিপুন",
@ -252,7 +267,8 @@
"bindTextToElement": "লেখা যোগ করতে এন্টার টিপুন",
"deepBoxSelect": "",
"eraserRevert": "মুছে ফেলার জন্য চিহ্নিত উপাদানগুলিকে ফিরিয়ে আনতে অল্ট ধরে রাখুন",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "প্রিভিউ দেখাতে অপারগ",
@ -411,7 +427,9 @@
"fileSavedToFilename": "",
"canvas": "",
"selection": "বাছাই",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Edita l'enllaç",
"editEmbed": "",
"create": "Crea un enllaç",
"label": "Enllaç"
"createEmbed": "",
"label": "Enllaç",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Editar línia",
@ -160,13 +164,16 @@
"darkMode": "Mode fosc",
"lightMode": "Mode clar",
"zenMode": "Mode zen",
"objectsSnapMode": "",
"exitZenMode": "Surt de mode zen",
"cancel": "Cancel·la",
"clear": "Neteja",
"remove": "Suprimeix",
"embed": "",
"publishLibrary": "Publica",
"submit": "Envia",
"confirm": "Confirma"
"confirm": "Confirma",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "S'esborrarà tot el llenç. N'esteu segur?",
@ -196,6 +203,7 @@
"imageInsertError": "No s'ha pogut insertar la imatge, torneu-ho a provar més tard...",
"fileTooBig": "El fitxer és massa gros. La mida màxima permesa és {{maxSize}}.",
"svgImageInsertError": "No ha estat possible inserir la imatge SVG. Les marques SVG semblen invàlides.",
"failedToFetchImage": "",
"invalidSVGString": "SVG no vàlid.",
"cannotResolveCollabServer": "No ha estat possible connectar amb el servidor collab. Si us plau recarregueu la pàgina i torneu a provar.",
"importLibraryError": "No s'ha pogut carregar la biblioteca",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Afegeix / actualitza l'enllaç per a la forma seleccionada",
"eraser": "Esborrador",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Mà (eina de desplaçament)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Feu clic per a dibuixar múltiples punts; arrossegueu per a una sola línia",
"freeDraw": "Feu clic i arrossegueu, deixeu anar per a finalitzar",
"text": "Consell: també podeu afegir text fent doble clic en qualsevol lloc amb l'eina de selecció",
"embeddable": "",
"text_selected": "Feu doble clic o premeu Retorn per a editar el text",
"text_editing": "Premeu Escapada o Ctrl+Retorn (o Ordre+Retorn) per a finalitzar l'edició",
"linearElementMulti": "Feu clic a l'ultim punt, o pitgeu Esc o Retorn per a finalitzar",
@ -252,7 +267,8 @@
"bindTextToElement": "Premeu enter per a afegir-hi text",
"deepBoxSelect": "Manteniu CtrlOrCmd per a selecció profunda, i per a evitar l'arrossegament",
"eraserRevert": "Mantingueu premuda Alt per a revertir els elements seleccionats per a esborrar",
"firefox_clipboard_write": "És probable que aquesta funció es pugui activar posant la marca \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Per canviar les marques del navegador al Firefox, visiteu la pàgina \"about:config\"."
"firefox_clipboard_write": "És probable que aquesta funció es pugui activar posant la marca \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Per canviar les marques del navegador al Firefox, visiteu la pàgina \"about:config\".",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "No es pot mostrar la previsualització",
@ -411,7 +427,9 @@
"fileSavedToFilename": "S'ha desat a {filename}",
"canvas": "el llenç",
"selection": "la selecció",
"pasteAsSingleElement": "Fer servir {{shortcut}} per enganxar com un sol element,\no enganxeu-lo en un editor de text existent"
"pasteAsSingleElement": "Fer servir {{shortcut}} per enganxar com un sol element,\no enganxeu-lo en un editor de text existent",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Transparent",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Zabalit text do kontejneru",
"link": {
"edit": "Upravit odkaz",
"editEmbed": "",
"create": "Vytvořit odkaz",
"label": "Odkaz"
"createEmbed": "",
"label": "Odkaz",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Upravit čáru",
@ -160,13 +164,16 @@
"darkMode": "Tmavý režim",
"lightMode": "Světlý režim",
"zenMode": "Zen mód",
"objectsSnapMode": "",
"exitZenMode": "Opustit zen mód",
"cancel": "Zrušit",
"clear": "Vyčistit",
"remove": "Odstranit",
"embed": "",
"publishLibrary": "Zveřejnit",
"submit": "Odeslat",
"confirm": "Potvrdit"
"confirm": "Potvrdit",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Toto vymaže celé plátno. Jste si jisti?",
@ -196,6 +203,7 @@
"imageInsertError": "Nelze vložit obrázek. Zkuste to později...",
"fileTooBig": "Soubor je příliš velký. Maximální povolená velikost je {{maxSize}}.",
"svgImageInsertError": "Nelze vložit SVG obrázek. Značení SVG je neplatné.",
"failedToFetchImage": "",
"invalidSVGString": "Neplatný SVG.",
"cannotResolveCollabServer": "Nelze se připojit ke sdílenému serveru. Prosím obnovte stránku a zkuste to znovu.",
"importLibraryError": "Nelze načíst knihovnu",
@ -206,6 +214,10 @@
"line2": "To by mohlo vést k narušení <bold>Textových elementů</bold> ve vašich výkresech.",
"line3": "Důrazně doporučujeme zakázat toto nastavení. Můžete sledovat <link>tyto kroky</link> jak to udělat.",
"line4": "Pokud vypnutí tohoto nastavení neopravuje zobrazení textových prvků, prosím, otevřete <issueLink>problém</issueLink> na našem GitHubu, nebo nám napište na <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Přidat/aktualizovat odkaz pro vybraný tvar",
"eraser": "Guma",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Ruka (nástroj pro posouvání)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Kliknutím pro více bodů, táhnutím pro jednu čáru",
"freeDraw": "Klikněte a táhněte, pro ukončení pusťte",
"text": "Tip: Text můžete také přidat dvojitým kliknutím kdekoli pomocí nástroje pro výběr",
"embeddable": "",
"text_selected": "Dvojklikem nebo stisknutím klávesy ENTER upravíte text",
"text_editing": "Stiskněte Escape nebo Ctrl/Cmd+ENTER pro dokončení úprav",
"linearElementMulti": "Klikněte na poslední bod nebo stiskněte Escape anebo Enter pro dokončení",
@ -252,7 +267,8 @@
"bindTextToElement": "Stiskněte Enter pro přidání textu",
"deepBoxSelect": "Podržte Ctrl/Cmd pro hluboký výběr a pro zabránění táhnutí",
"eraserRevert": "Podržením klávesy Alt vrátíte prvky označené pro smazání",
"firefox_clipboard_write": "Tato funkce může být povolena nastavením vlajky \"dom.events.asyncClipboard.clipboardItem\" na \"true\". Chcete-li změnit vlajky prohlížeče ve Firefoxu, navštivte stránku \"about:config\"."
"firefox_clipboard_write": "Tato funkce může být povolena nastavením vlajky \"dom.events.asyncClipboard.clipboardItem\" na \"true\". Chcete-li změnit vlajky prohlížeče ve Firefoxu, navštivte stránku \"about:config\".",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Náhled nelze zobrazit",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Uloženo do {filename}",
"canvas": "plátno",
"selection": "výběr",
"pasteAsSingleElement": "Pomocí {{shortcut}} vložte jako jeden prvek,\nnebo vložte do existujícího textového editoru"
"pasteAsSingleElement": "Pomocí {{shortcut}} vložte jako jeden prvek,\nnebo vložte do existujícího textového editoru",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Průhledná",
@ -449,5 +467,36 @@
"shades": "Stíny",
"hexCode": "Hex kód",
"noShades": "Pro tuto barvu nejsou k dispozici žádné odstíny"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -1,7 +1,7 @@
{
"labels": {
"paste": "Indsæt",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Indsæt som klartekst",
"pasteCharts": "Indsæt diagrammer",
"selectAll": "Marker alle",
"multiSelect": "Tilføj element til markering",
@ -50,7 +50,7 @@
"veryLarge": "Meget stor",
"solid": "Solid",
"hachure": "Skravering",
"zigzag": "",
"zigzag": "Zigzag",
"crossHatch": "Krydsskravering",
"thin": "Tynd",
"bold": "Fed",
@ -69,8 +69,8 @@
"layers": "Lag",
"actions": "Handlinger",
"language": "Sprog",
"liveCollaboration": "",
"duplicateSelection": "",
"liveCollaboration": "Live samarbejde...",
"duplicateSelection": "Duplikér",
"untitled": "Unavngivet",
"name": "Navn",
"yourName": "Dit navn",
@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "Mørk tilstand",
"lightMode": "Lys baggrund",
"zenMode": "Zentilstand",
"objectsSnapMode": "",
"exitZenMode": "Stop zentilstand",
"cancel": "Annuller",
"clear": "Ryd",
"remove": "Fjern",
"embed": "",
"publishLibrary": "Publicér",
"submit": "Gem",
"confirm": "Bekræft"
"confirm": "Bekræft",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Dette vil rydde hele lærredet. Er du sikker?",
@ -196,6 +203,7 @@
"imageInsertError": "",
"fileTooBig": "",
"svgImageInsertError": "",
"failedToFetchImage": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "",
"freeDraw": "Klik og træk, slip når du er færdig",
"text": "",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Gemt som {filename}",
"canvas": "canvas",
"selection": "markering",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Text in Container einbetten",
"link": {
"edit": "Link bearbeiten",
"editEmbed": "Link bearbeiten & einbetten",
"create": "Link erstellen",
"label": "Link"
"createEmbed": "Link erstellen & einbetten",
"label": "Link",
"labelEmbed": "Verlinken & einbetten",
"empty": "Kein Link festgelegt"
},
"lineEditor": {
"edit": "Linie bearbeiten",
@ -160,13 +164,16 @@
"darkMode": "Dunkles Design",
"lightMode": "Helles Design",
"zenMode": "Zen-Modus",
"objectsSnapMode": "Einrasten an Objekten",
"exitZenMode": "Zen-Modus verlassen",
"cancel": "Abbrechen",
"clear": "Löschen",
"remove": "Entfernen",
"embed": "Einbettung umschalten",
"publishLibrary": "Veröffentlichen",
"submit": "Absenden",
"confirm": "Bestätigen"
"confirm": "Bestätigen",
"embeddableInteractionButton": "Klicken, um zu interagieren"
},
"alerts": {
"clearReset": "Dies wird die ganze Zeichenfläche löschen. Bist du dir sicher?",
@ -196,6 +203,7 @@
"imageInsertError": "Das Bild konnte nicht eingefügt werden. Versuche es später erneut...",
"fileTooBig": "Die Datei ist zu groß. Die maximal zulässige Größe ist {{maxSize}}.",
"svgImageInsertError": "SVG-Bild konnte nicht eingefügt werden. Das SVG-Markup sieht ungültig aus.",
"failedToFetchImage": "Bild konnte nicht abgerufen werden.",
"invalidSVGString": "Ungültige SVG.",
"cannotResolveCollabServer": "Konnte keine Verbindung zum Collab-Server herstellen. Bitte lade die Seite neu und versuche es erneut.",
"importLibraryError": "Bibliothek konnte nicht geladen werden",
@ -206,6 +214,10 @@
"line2": "Dies könnte dazu führen, dass die <bold>Textelemente</bold> in Ihren Zeichnungen zerstört werden.",
"line3": "Wir empfehlen dringend, diese Einstellung zu deaktivieren. Dazu kannst Du <link>diesen Schritten</link> folgen.",
"line4": "Wenn die Deaktivierung dieser Einstellung die fehlerhafte Anzeige von Textelementen nicht behebt, öffne bitte ein <issueLink>Ticket</issueLink> auf unserem GitHub oder schreibe uns auf <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "Einbettbare Elemente können der Bibliothek nicht hinzugefügt werden.",
"image": "Unterstützung für das Hinzufügen von Bildern in die Bibliothek kommt bald!"
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Link für ausgewählte Form hinzufügen / aktualisieren",
"eraser": "Radierer",
"frame": "Rahmenwerkzeug",
"embeddable": "Web-Einbettung",
"laser": "Laserpointer",
"hand": "Hand (Schwenkwerkzeug)",
"extraTools": "Weitere Werkzeuge"
},
@ -237,6 +251,7 @@
"linearElement": "Klicken für Linie mit mehreren Punkten, Ziehen für einzelne Linie",
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
"text": "Tipp: Du kannst auch Text hinzufügen, indem du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
"embeddable": "Klicken und ziehen, um eine Webseiten-Einbettung zu erstellen",
"text_selected": "Doppelklicken oder Eingabetaste drücken, um Text zu bearbeiten",
"text_editing": "Drücke Escape oder CtrlOrCmd+Eingabetaste, um die Bearbeitung abzuschließen",
"linearElementMulti": "Zum Beenden auf den letzten Punkt klicken oder Escape oder Eingabe drücken",
@ -252,7 +267,8 @@
"bindTextToElement": "Zum Hinzufügen Eingabetaste drücken",
"deepBoxSelect": "Halte CtrlOrCmd gedrückt, um innerhalb der Gruppe auszuwählen, und um Ziehen zu vermeiden",
"eraserRevert": "Halte Alt gedrückt, um die zum Löschen markierten Elemente zurückzusetzen",
"firefox_clipboard_write": "Diese Funktion kann wahrscheinlich aktiviert werden, indem die Einstellung \"dom.events.asyncClipboard.clipboardItem\" auf \"true\" gesetzt wird. Um die Browsereinstellungen in Firefox zu ändern, besuche die Seite \"about:config\"."
"firefox_clipboard_write": "Diese Funktion kann wahrscheinlich aktiviert werden, indem die Einstellung \"dom.events.asyncClipboard.clipboardItem\" auf \"true\" gesetzt wird. Um die Browsereinstellungen in Firefox zu ändern, besuche die Seite \"about:config\".",
"disableSnapping": "Halte CtrlOrCmd gedrückt, um das Einrasten zu deaktivieren"
},
"canvasError": {
"cannotShowPreview": "Vorschau kann nicht angezeigt werden",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Als {filename} gespeichert",
"canvas": "Zeichenfläche",
"selection": "Auswahl",
"pasteAsSingleElement": "Verwende {{shortcut}} , um als einzelnes Element\neinzufügen oder in einen existierenden Texteditor einzufügen"
"pasteAsSingleElement": "Verwende {{shortcut}} , um als einzelnes Element\neinzufügen oder in einen existierenden Texteditor einzufügen",
"unableToEmbed": "Einbetten dieser URL ist derzeit nicht zulässig. Erstelle einen Issue auf GitHub, um die URL freigeben zu lassen",
"unrecognizedLinkFormat": "Der Link, den Du eingebettet hast, stimmt nicht mit dem erwarteten Format überein. Bitte versuche den 'embed' String einzufügen, der von der Quellseite zur Verfügung gestellt wird"
},
"colors": {
"transparent": "Transparent",
@ -449,5 +467,36 @@
"shades": "Schattierungen",
"hexCode": "Hex-Code",
"noShades": "Keine Schattierungen für diese Farbe verfügbar"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Als Bild exportieren",
"button": "Als Bild exportieren",
"description": "Exportiere die Zeichnungsdaten als ein Bild, von dem Du später importieren kannst."
},
"saveToDisk": {
"title": "Auf Festplatte speichern",
"button": "Auf Festplatte speichern",
"description": "Exportiere die Zeichnungsdaten in eine Datei, von der Du später importieren kannst."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Export nach Excalidraw+",
"description": "Speichere die Szene in deinem Excalidraw+-Arbeitsbereich."
}
},
"modal": {
"loadFromFile": {
"title": "Aus Datei laden",
"button": "Aus Datei laden",
"description": "Das Laden aus einer Datei wird <bold>Deinen vorhandenen Inhalt ersetzen</bold>.<br></br>Du kannst Deine Zeichnung zuerst mit einer der folgenden Optionen sichern."
},
"shareableLink": {
"title": "Aus Link laden",
"button": "Meinen Inhalt ersetzen",
"description": "Das Laden einer externen Zeichnung wird <bold>Deinen vorhandenen Inhalt ersetzen</bold>.<br></br>Du kannst Deine Zeichnung zuerst mit einer der folgenden Optionen sichern."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Επεξεργασία συνδέσμου",
"editEmbed": "",
"create": "Δημιουργία συνδέσμου",
"label": "Σύνδεσμος"
"createEmbed": "",
"label": "Σύνδεσμος",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Επεξεργασία γραμμής",
@ -160,13 +164,16 @@
"darkMode": "Σκοτεινή λειτουργία",
"lightMode": "Φωτεινή λειτουργία",
"zenMode": "Λειτουργία Zεν",
"objectsSnapMode": "",
"exitZenMode": "Έξοδος από την λειτουργία Zen",
"cancel": "Ακύρωση",
"clear": "Καθαρισμός",
"remove": "Κατάργηση",
"embed": "",
"publishLibrary": "Δημοσίευση",
"submit": "Υποβολή",
"confirm": "Επιβεβαίωση"
"confirm": "Επιβεβαίωση",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Αυτό θα σβήσει ολόκληρο τον καμβά. Είσαι σίγουρος;",
@ -196,6 +203,7 @@
"imageInsertError": "Αδυναμία εισαγωγής εικόνας. Προσπαθήστε ξανά αργότερα...",
"fileTooBig": "Το αρχείο είναι πολύ μεγάλο. Το μέγιστο επιτρεπόμενο μέγεθος είναι {{maxSize}}.",
"svgImageInsertError": "Αδυναμία εισαγωγής εικόνας SVG. Η σήμανση της SVG δεν φαίνεται έγκυρη.",
"failedToFetchImage": "",
"invalidSVGString": "Μη έγκυρο SVG.",
"cannotResolveCollabServer": "Αδυναμία σύνδεσης με τον διακομιστή συνεργασίας. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.",
"importLibraryError": "Αδυναμία φόρτωσης βιβλιοθήκης",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα",
"eraser": "Γόμα",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
"freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει",
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
"embeddable": "",
"text_selected": "Κάντε διπλό κλικ ή πατήστε ENTER για να επεξεργαστείτε το κείμενο",
"text_editing": "Πατήστε Escape ή CtrlOrCmd+ENTER για να ολοκληρώσετε την επεξεργασία",
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
@ -252,7 +267,8 @@
"bindTextToElement": "Πατήστε Enter για προσθήκη κειμένου",
"deepBoxSelect": "Κρατήστε πατημένο το CtrlOrCmd για να επιλέξετε βαθιά, και να αποτρέψετε τη μεταφορά",
"eraserRevert": "Κρατήστε πατημένο το Alt για να επαναφέρετε τα στοιχεία που σημειώθηκαν για διαγραφή",
"firefox_clipboard_write": "Αυτή η επιλογή μπορεί πιθανώς να ενεργοποιηθεί αλλάζοντας την ρύθμιση \"dom.events.asyncClipboard.clipboardItem\" σε \"true\". Για να αλλάξετε τις ρυθμίσεις του προγράμματος περιήγησης στο Firefox, επισκεφθείτε τη σελίδα \"about:config\"."
"firefox_clipboard_write": "Αυτή η επιλογή μπορεί πιθανώς να ενεργοποιηθεί αλλάζοντας την ρύθμιση \"dom.events.asyncClipboard.clipboardItem\" σε \"true\". Για να αλλάξετε τις ρυθμίσεις του προγράμματος περιήγησης στο Firefox, επισκεφθείτε τη σελίδα \"about:config\".",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Αδυναμία εμφάνισης προεπισκόπησης",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Αποθηκεύτηκε στο {filename}",
"canvas": "καμβάς",
"selection": "επιλογή",
"pasteAsSingleElement": "Χρησιμοποίησε το {{shortcut}} για να επικολλήσεις ως ένα μόνο στοιχείο,\nή να επικολλήσεις σε έναν υπάρχοντα επεξεργαστή κειμένου"
"pasteAsSingleElement": "Χρησιμοποίησε το {{shortcut}} για να επικολλήσεις ως ένα μόνο στοιχείο,\nή να επικολλήσεις σε έναν υπάρχοντα επεξεργαστή κειμένου",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Διαφανές",
@ -449,5 +467,36 @@
"shades": "Αποχρώσεις",
"hexCode": "Κωδικός Hex",
"noShades": "Δεν υπάρχουν διαθέσιμες αποχρώσεις για αυτό το χρώμα"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -203,6 +203,7 @@
"imageInsertError": "Couldn't insert image. Try again later...",
"fileTooBig": "File is too big. Maximum allowed size is {{maxSize}}.",
"svgImageInsertError": "Couldn't insert SVG image. The SVG markup looks invalid.",
"failedToFetchImage": "Failed to fetch image.",
"invalidSVGString": "Invalid SVG.",
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
"importLibraryError": "Couldn't load library",
@ -217,7 +218,10 @@
"libraryElementTypeError": {
"embeddable": "Embeddable elements cannot be added to the library.",
"image": "Support for adding images to the library coming soon!"
}
},
"asyncPasteFailedOnRead": "Couldn't paste (couldn't read from system clipboard).",
"asyncPasteFailedOnParse": "Couldn't paste.",
"copyToSystemClipboardFailed": "Couldn't copy to clipboard."
},
"toolBar": {
"selection": "Selection",

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Envolver el texto en un contenedor",
"link": {
"edit": "Editar enlace",
"editEmbed": "Editar enlace e incrustar",
"create": "Crear enlace",
"label": "Enlace"
"createEmbed": "Crear enlace e incrustar",
"label": "Enlace",
"labelEmbed": "Enlazar e incrustar",
"empty": "No se ha establecido un enlace"
},
"lineEditor": {
"edit": "Editar línea",
@ -124,9 +128,9 @@
},
"statusPublished": "Publicado",
"sidebarLock": "Mantener barra lateral abierta",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"selectAllElementsInFrame": "Seleccionar todos los elementos en el marco",
"removeAllElementsFromFrame": "Eliminar todos los elementos del marco",
"eyeDropper": "Seleccionar un color del lienzo"
},
"library": {
"noItems": "No hay elementos añadidos todavía...",
@ -160,13 +164,16 @@
"darkMode": "Modo oscuro",
"lightMode": "Modo claro",
"zenMode": "Modo Zen",
"objectsSnapMode": "Ajustar a los objetos",
"exitZenMode": "Salir del modo Zen",
"cancel": "Cancelar",
"clear": "Borrar",
"remove": "Eliminar",
"embed": "",
"publishLibrary": "Publicar",
"submit": "Enviar",
"confirm": "Confirmar"
"confirm": "Confirmar",
"embeddableInteractionButton": "Pulsa para interactuar"
},
"alerts": {
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
@ -196,16 +203,21 @@
"imageInsertError": "No se pudo insertar la imagen. Inténtelo de nuevo más tarde...",
"fileTooBig": "Archivo demasiado grande. El tamaño máximo permitido es {{maxSize}}.",
"svgImageInsertError": "No se pudo insertar la imagen SVG. El código SVG parece inválido.",
"failedToFetchImage": "",
"invalidSVGString": "SVG no válido.",
"cannotResolveCollabServer": "No se pudo conectar al servidor colaborador. Por favor, vuelva a cargar la página y vuelva a intentarlo.",
"importLibraryError": "No se pudo cargar la librería",
"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": {
"line1": "",
"line2": "",
"line3": "",
"line1": "Parece que estás usando el navegador Brave con el ajuste <bold>Forzar el bloqueo de huellas digitales</bold> habilitado.",
"line2": "Esto podría resultar en errores en los <bold>Elementos de Texto</bold> en tus dibujos.",
"line3": "Recomendamos fuertemente deshabilitar esta configuración. Puedes seguir <link>estos pasos</link> sobre cómo hacerlo.",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,8 +236,10 @@
"link": "Añadir/Actualizar enlace para una forma seleccionada",
"eraser": "Borrar",
"frame": "",
"embeddable": "Incrustar Web",
"laser": "Puntero láser",
"hand": "Mano (herramienta de panoramización)",
"extraTools": ""
"extraTools": "Más herramientas"
},
"headings": {
"canvasActions": "Acciones del lienzo",
@ -237,6 +251,7 @@
"linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea",
"freeDraw": "Haz clic y arrastra, suelta al terminar",
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
"embeddable": "Haga clic y arrastre para crear un sitio web incrustado",
"text_selected": "Doble clic o pulse ENTER para editar el texto",
"text_editing": "Pulse Escape o Ctrl/Cmd + ENTER para terminar de editar",
"linearElementMulti": "Haz clic en el último punto o presiona Escape o Enter para finalizar",
@ -252,7 +267,8 @@
"bindTextToElement": "Presione Entrar para agregar",
"deepBoxSelect": "Mantén CtrlOrCmd para seleccionar en profundidad, y para evitar arrastrar",
"eraserRevert": "Mantenga pulsado Alt para revertir los elementos marcados para su eliminación",
"firefox_clipboard_write": "Esta característica puede ser habilitada estableciendo la bandera \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar las banderas del navegador en Firefox, visite la página \"about:config\"."
"firefox_clipboard_write": "Esta característica puede ser habilitada estableciendo la bandera \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar las banderas del navegador en Firefox, visite la página \"about:config\".",
"disableSnapping": "Mantén pulsado CtrlOrCmd para desactivar el ajuste"
},
"canvasError": {
"cannotShowPreview": "No se puede mostrar la vista previa",
@ -360,27 +376,27 @@
"removeItemsFromLib": "Eliminar elementos seleccionados de la biblioteca"
},
"imageExportDialog": {
"header": "",
"header": "Exportar imagen",
"label": {
"withBackground": "",
"onlySelected": "",
"darkMode": "",
"embedScene": "",
"scale": "",
"padding": ""
"withBackground": "Fondo",
"onlySelected": "Sólo seleccionados",
"darkMode": "Modo oscuro",
"embedScene": "Incrustar escena",
"scale": "Escalar",
"padding": "Espaciado"
},
"tooltip": {
"embedScene": ""
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "Exportar a PNG",
"exportToSvg": "Exportar a SVG",
"copyPngToClipboard": "Copiar PNG al portapapeles"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Copiar al portapapeles"
}
},
"encrypted": {
@ -411,24 +427,26 @@
"fileSavedToFilename": "Guardado en {filename}",
"canvas": "lienzo",
"selection": "selección",
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un solo elemento,\no pegar en un editor de texto existente"
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un solo elemento,\no pegar en un editor de texto existente",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Transparente",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"black": "Negro",
"white": "Blanco",
"red": "Rojo",
"pink": "Rosa",
"grape": "Uva",
"violet": "Violeta",
"gray": "Gris",
"blue": "Azul",
"cyan": "Cian",
"teal": "Turquesa",
"green": "Verde",
"yellow": "Amarillo",
"orange": "Naranja",
"bronze": "Bronce"
},
"welcomeScreen": {
"app": {
@ -444,10 +462,41 @@
}
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"mostUsedCustomColors": "Colores personalizados más utilizados",
"colors": "Colores",
"shades": "",
"hexCode": "",
"hexCode": "Código Hexadecimal",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Exportar como imagen",
"button": "Exportar como imagen",
"description": ""
},
"saveToDisk": {
"title": "Guardar en el disco",
"button": "Guardar en el disco",
"description": "Exporta los datos de la escena a un archivo desde el cual podrás importar más tarde."
},
"excalidrawPlus": {
"title": "",
"button": "Exportar a Excalidraw+",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "Cargar desde un archivo",
"button": "Cargar desde un archivo",
"description": ""
},
"shareableLink": {
"title": "Cargar desde un enlace",
"button": "Reemplazar mi contenido",
"description": "Cargar un dibujo externo <bold>reemplazará tu contenido existente</bold>.<br></br>Puedes primero hacer una copia de seguridad de tu dibujo usando una de las opciones de abajo."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Bilatu testua edukiontzi batean",
"link": {
"edit": "Editatu esteka",
"editEmbed": "Editatu esteka eta kapsulatu",
"create": "Sortu esteka",
"label": "Esteka"
"createEmbed": "Sortu esteka eta kapsulatu",
"label": "Esteka",
"labelEmbed": "Esteka eta kapsula",
"empty": "Ez da estekarik ezarri"
},
"lineEditor": {
"edit": "Editatu lerroa",
@ -160,13 +164,16 @@
"darkMode": "Modu iluna",
"lightMode": "Modu argia",
"zenMode": "Zen modua",
"objectsSnapMode": "Atxiki objektuei",
"exitZenMode": "Irten Zen modutik",
"cancel": "Utzi",
"clear": "Garbitu",
"remove": "Kendu",
"embed": "Aldatu kapsulatzea",
"publishLibrary": "Argitaratu",
"submit": "Bidali",
"confirm": "Bai"
"confirm": "Bai",
"embeddableInteractionButton": "Egin klik elkar eragiteko"
},
"alerts": {
"clearReset": "Honek oihal osoa garbituko du. Ziur zaude?",
@ -196,6 +203,7 @@
"imageInsertError": "Ezin izan da irudia txertatu. Saiatu berriro geroago...",
"fileTooBig": "Fitxategia handiegia da. Onartutako gehienezko tamaina {{maxSize}} da.",
"svgImageInsertError": "Ezin izan da SVG irudia txertatu. SVG markak baliogabea dirudi.",
"failedToFetchImage": "",
"invalidSVGString": "SVG baliogabea.",
"cannotResolveCollabServer": "Ezin izan da elkarlaneko zerbitzarira konektatu. Mesedez, berriro kargatu orria eta saiatu berriro.",
"importLibraryError": "Ezin izan da liburutegia kargatu",
@ -206,6 +214,10 @@
"line2": "Honek zure marrazkietako <bold>Testu-elementuak</bold> hautsi ditzake.",
"line3": "Ezarpen hau desgaitzea gomendatzen dugu. <link>urrats hauek</link> jarrai ditzakezu hori nola egin jakiteko.",
"line4": "Ezarpen hau desgaituz gero, testu-elementuen bistaratzea konpontzen ez bada, ireki <issueLink>arazo</issueLink> gure GitHub-en edo idatzi iezaguzu <discordLink>Discord</discordLink> helbidera"
},
"libraryElementTypeError": {
"embeddable": "Kapsulatutako elementuak ezin dira liburutegira gehitu.",
"image": "Laster egongo da irudiak liburutegian gehitzeko laguntza!"
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Gehitu / Eguneratu esteka hautatutako forma baterako",
"eraser": "Borragoma",
"frame": "Marko tresna",
"embeddable": "Web kapsulatzea",
"laser": "Laser punteroa",
"hand": "Eskua (panoratze tresna)",
"extraTools": "Tresna gehiago"
},
@ -237,6 +251,7 @@
"linearElement": "Egin klik hainbat puntu hasteko, arrastatu lerro bakarrerako",
"freeDraw": "Egin klik eta arrastatu, askatu amaitutakoan",
"text": "Aholkua: testua gehitu dezakezu edozein lekutan klik bikoitza eginez hautapen tresnarekin",
"embeddable": "Egin klik eta arrastatu webgunea kapsulatzeko",
"text_selected": "Egin klik bikoitza edo sakatu SARTU testua editatzeko",
"text_editing": "Sakatu Esc edo Ctrl+SARTU editatzen amaitzeko",
"linearElementMulti": "Egin klik azken puntuan edo sakatu Esc edo Sartu amaitzeko",
@ -252,7 +267,8 @@
"bindTextToElement": "Sakatu Sartu testua gehitzeko",
"deepBoxSelect": "Eutsi Ctrl edo Cmd sakatuta aukeraketa sakona egiteko eta arrastatzea saihesteko",
"eraserRevert": "Eduki Alt sakatuta ezabatzeko markatutako elementuak leheneratzeko",
"firefox_clipboard_write": "Ezaugarri hau \"dom.events.asyncClipboard.clipboardItem\" marka \"true\" gisa ezarrita gaitu daiteke. Firefox-en arakatzailearen banderak aldatzeko, bisitatu \"about:config\" orrialdera."
"firefox_clipboard_write": "Ezaugarri hau \"dom.events.asyncClipboard.clipboardItem\" marka \"true\" gisa ezarrita gaitu daiteke. Firefox-en arakatzailearen banderak aldatzeko, bisitatu \"about:config\" orrialdera.",
"disableSnapping": "Eduki sakatuta Ctrl edo Cmd tekla atxikipena desgaitzeko"
},
"canvasError": {
"cannotShowPreview": "Ezin da oihala aurreikusi",
@ -411,7 +427,9 @@
"fileSavedToFilename": "{filename}-n gorde da",
"canvas": "oihala",
"selection": "hautapena",
"pasteAsSingleElement": "Erabili {{shortcut}} elementu bakar gisa itsasteko,\nedo itsatsi lehendik dagoen testu-editore batean"
"pasteAsSingleElement": "Erabili {{shortcut}} elementu bakar gisa itsasteko,\nedo itsatsi lehendik dagoen testu-editore batean",
"unableToEmbed": "Url hau txertatzea ez da une honetan onartzen. Sortu issue bat GitHub-en Urla zerrenda zurian sartzea eskatzeko",
"unrecognizedLinkFormat": "Kapsulatu duzun esteka ez dator bat espero den formatuarekin. Mesedez, saiatu iturburu-guneak emandako 'kapsulatu' katea itsasten"
},
"colors": {
"transparent": "Gardena",
@ -449,5 +467,36 @@
"shades": "Ñabardurak",
"hexCode": "Hez kodea",
"noShades": "Kolore honetarako ez dago ñabardurarik eskuragarri"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Esportatu irudi gisa",
"button": "Esportatu irudi gisa",
"description": "Esportatu eszenaren datuak geroago inportatu ahal izango duzun irudi gisa."
},
"saveToDisk": {
"title": "Gorde diskoan",
"button": "Gorde diskoan",
"description": "Esportatu eszenaren datuak geroago inportatu ahal izango duzun fitxategi batan."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Esportatu Excalidraw+ra",
"description": "Gorde eszena zure Excalidraw+ laneko areara."
}
},
"modal": {
"loadFromFile": {
"title": "Fitxategitik kargatu",
"button": "Fitxategitik kargatu",
"description": "Fitxategi batetik kargatzeak <bold>lehendik duzun edukia ordezkatuko du</bold>.<br></br>Lehenengo marrazkiaren babeskopia egin dezakezu beheko aukeretako bat erabiliz."
},
"shareableLink": {
"title": "Estekatik kargatu",
"button": "Ordeztu nire edukia",
"description": "Kanpoko irudi bat kargatzeak <bold>lehendik duzun edukia ordezkatuko du</bold>.<br></br>. Zure marrazkiaren babeskopia egin dezakezu lehenik beheko aukeretako bat erabiliz."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "متن را در یک جایگاه بپیچید",
"link": {
"edit": "ویرایش لینک",
"editEmbed": "",
"create": "ایجاد پیوند",
"label": "لینک"
"createEmbed": "",
"label": "لینک",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "ویرایش لینک",
@ -126,7 +130,7 @@
"sidebarLock": "باز نگه داشتن سایدبار",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"eyeDropper": "انتخاب رنگ از کرباس"
},
"library": {
"noItems": "آیتمی به اینجا اضافه نشده...",
@ -160,13 +164,16 @@
"darkMode": "حالت تیره",
"lightMode": "حالت روشن",
"zenMode": "حالت ذن",
"objectsSnapMode": "",
"exitZenMode": "خروج از حالت تمرکز",
"cancel": "لغو",
"clear": "پاک کردن",
"remove": "پاک کردن",
"embed": "",
"publishLibrary": "انتشار",
"submit": "ارسال",
"confirm": "تایید"
"confirm": "تایید",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "این کار کل صفحه را پاک میکند. آیا مطمئنید؟",
@ -196,6 +203,7 @@
"imageInsertError": "عکس ارسال نشد. بعداً دوباره تلاش کنید...",
"fileTooBig": "فایل خیلی بزرگ است حداکثر اندازه مجاز {{maxSize}}.",
"svgImageInsertError": "تصویر SVG وارد نشد. نشانه گذاری SVG نامعتبر به نظر می رسد.",
"failedToFetchImage": "",
"invalidSVGString": "SVG نادرست.",
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
"importLibraryError": "داده‌ها بارگذاری نشدند",
@ -206,6 +214,10 @@
"line2": "این می تواند منجر به شکستن <bold>عناصر متن</bold> در نقاشی های شما شود.",
"line3": "اکیداً توصیه می کنیم این تنظیم را غیرفعال کنید. برای نحوه انجام این کار می‌توانید <link>این مراحل</link> را دنبال کنید.",
"line4": "اگر غیرفعال کردن این تنظیم نمایش عناصر متنی را برطرف نکرد، لطفاً یک <issueLink>مشکل</issueLink> را در GitHub ما باز کنید یا برای ما در <discordLink>Discord</discordLink> بنویسید."
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,8 +236,10 @@
"link": "افزودن/به‌روزرسانی پیوند برای شکل انتخابی",
"eraser": "پاک کن",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "دست (ابزار پانینگ)",
"extraTools": ""
"extraTools": "ابزارهای بیشتر"
},
"headings": {
"canvasActions": "عملیات روی بوم",
@ -237,6 +251,7 @@
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
"embeddable": "",
"text_selected": "دوبار کلیک کنید یا Enter را فشار دهید تا نقاط را ویرایش کنید",
"text_editing": "Escape یا CtrlOrCmd+ENTER را فشار دهید تا ویرایش تمام شود",
"linearElementMulti": "روی آخرین نقطه کلیک کنید یا کلید ESC را بزنید یا کلید Enter را بزنید برای اتمام کار",
@ -252,7 +267,8 @@
"bindTextToElement": "برای افزودن اینتر را بزنید",
"deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید",
"eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند",
"firefox_clipboard_write": "احتمالاً می‌توان این ویژگی را با تنظیم پرچم «dom.events.asyncClipboard.clipboardItem» روی «true» فعال کرد. برای تغییر پرچم های مرورگر در فایرفاکس، از صفحه \"about:config\" دیدن کنید."
"firefox_clipboard_write": "احتمالاً می‌توان این ویژگی را با تنظیم پرچم «dom.events.asyncClipboard.clipboardItem» روی «true» فعال کرد. برای تغییر پرچم های مرورگر در فایرفاکس، از صفحه \"about:config\" دیدن کنید.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "پیش نمایش نشان داده نمی شود",
@ -362,7 +378,7 @@
"imageExportDialog": {
"header": "",
"label": {
"withBackground": "",
"withBackground": "پس زمینه",
"onlySelected": "",
"darkMode": "",
"embedScene": "",
@ -411,24 +427,26 @@
"fileSavedToFilename": "ذخیره در {filename}",
"canvas": "بوم",
"selection": "انتخاب",
"pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید"
"pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "شفاف",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"black": "سیاه",
"white": "سفید",
"red": "قرمز",
"pink": "صورتی",
"grape": "یاقوتی",
"violet": "بنفش",
"gray": "خاکستری",
"blue": "آبی",
"cyan": "فیروزه‌ای",
"teal": "سبزآبی",
"green": "سبز",
"yellow": "زرد",
"orange": "نارنجی",
"bronze": "برنزی"
},
"welcomeScreen": {
"app": {
@ -445,9 +463,40 @@
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"colors": "رنگ‌ها",
"shades": "جلوه‌ها",
"hexCode": "کدِ هگز",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "ذخیره در دیسک",
"button": "ذخیره در دیسک",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "بارگذاری از فایل",
"button": "بارگذاری از فایل",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Muokkaa linkkiä",
"editEmbed": "",
"create": "Luo linkki",
"label": "Linkki"
"createEmbed": "",
"label": "Linkki",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Muokkaa riviä",
@ -160,13 +164,16 @@
"darkMode": "Tumma tila",
"lightMode": "Vaalea tila",
"zenMode": "Zen-tila",
"objectsSnapMode": "",
"exitZenMode": "Poistu zen-tilasta",
"cancel": "Peruuta",
"clear": "Pyyhi",
"remove": "Poista",
"embed": "",
"publishLibrary": "Julkaise",
"submit": "Lähetä",
"confirm": "Vahvista"
"confirm": "Vahvista",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Tämä tyhjentää koko piirtoalueen. Jatketaanko?",
@ -196,6 +203,7 @@
"imageInsertError": "Kuvan lisääminen epäonnistui. Yritä myöhemmin uudelleen...",
"fileTooBig": "Tiedosto on liian suuri. Suurin sallittu koko on {{maxSize}}.",
"svgImageInsertError": "SVG- kuvaa ei voitu lisätä. Tiedoston SVG-sisältö näyttää virheelliseltä.",
"failedToFetchImage": "",
"invalidSVGString": "Virheellinen SVG.",
"cannotResolveCollabServer": "Yhteyden muodostaminen collab-palvelimeen epäonnistui. Virkistä sivu ja yritä uudelleen.",
"importLibraryError": "Kokoelman lataaminen epäonnistui",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Lisää/päivitä linkki valitulle muodolle",
"eraser": "Poistotyökalu",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Käsi (panning-työkalu)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva",
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
"embeddable": "",
"text_selected": "Kaksoisnapsauta tai paina ENTER muokataksesi tekstiä",
"text_editing": "Paina Escape tai CtrlOrCmd+ENTER lopettaaksesi muokkaamisen",
"linearElementMulti": "Lopeta klikkaamalla viimeistä pistettä, painamalla Escape- tai Enter-näppäintä",
@ -252,7 +267,8 @@
"bindTextToElement": "Lisää tekstiä painamalla enter",
"deepBoxSelect": "Käytä syvävalintaa ja estä raahaus painamalla CtrlOrCmd",
"eraserRevert": "Pidä Alt alaspainettuna, kumotaksesi merkittyjen elementtien poistamisen",
"firefox_clipboard_write": "Tämä ominaisuus voidaan todennäköisesti ottaa käyttöön asettamalla \"dom.events.asyncClipboard.clipboardItem\" kohta \"true\":ksi. Vaihtaaksesi selaimen kohdan Firefoxissa, käy \"about:config\" sivulla."
"firefox_clipboard_write": "Tämä ominaisuus voidaan todennäköisesti ottaa käyttöön asettamalla \"dom.events.asyncClipboard.clipboardItem\" kohta \"true\":ksi. Vaihtaaksesi selaimen kohdan Firefoxissa, käy \"about:config\" sivulla.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Esikatselua ei voitu näyttää",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Tallennettiin kohteeseen {filename}",
"canvas": "piirtoalue",
"selection": "valinta",
"pasteAsSingleElement": "Käytä {{shortcut}} liittääksesi yhtenä elementtinä,\ntai liittääksesi olemassa olevaan tekstieditoriin"
"pasteAsSingleElement": "Käytä {{shortcut}} liittääksesi yhtenä elementtinä,\ntai liittääksesi olemassa olevaan tekstieditoriin",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Läpinäkyvä",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -50,7 +50,7 @@
"veryLarge": "Très grande",
"solid": "Solide",
"hachure": "Hachures",
"zigzag": "",
"zigzag": "Zigzag",
"crossHatch": "Hachures croisées",
"thin": "Fine",
"bold": "Épaisse",
@ -109,8 +109,12 @@
"createContainerFromText": "Encadrer le texte dans un conteneur",
"link": {
"edit": "Modifier le lien",
"editEmbed": "Éditer le lien & intégrer",
"create": "Ajouter un lien",
"label": "Lien"
"createEmbed": "Créer un lien & intégrer",
"label": "Lien",
"labelEmbed": "",
"empty": "Aucun lien défini"
},
"lineEditor": {
"edit": "Modifier la ligne",
@ -124,8 +128,8 @@
},
"statusPublished": "Publié",
"sidebarLock": "Maintenir la barre latérale ouverte",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"selectAllElementsInFrame": "Sélectionner tous les éléments du cadre",
"removeAllElementsFromFrame": "Supprimer tous les éléments du cadre",
"eyeDropper": ""
},
"library": {
@ -160,13 +164,16 @@
"darkMode": "Mode sombre",
"lightMode": "Mode clair",
"zenMode": "Mode zen",
"objectsSnapMode": "Aimanter aux objets",
"exitZenMode": "Quitter le mode zen",
"cancel": "Annuler",
"clear": "Effacer",
"remove": "Supprimer",
"embed": "Activer/Désactiver l'intégration",
"publishLibrary": "Publier",
"submit": "Envoyer",
"confirm": "Confirmer"
"confirm": "Confirmer",
"embeddableInteractionButton": "Cliquez pour interagir"
},
"alerts": {
"clearReset": "L'intégralité du canevas va être effacée. Êtes-vous sûr ?",
@ -196,16 +203,21 @@
"imageInsertError": "Impossible d'insérer l'image. Réessayez plus tard...",
"fileTooBig": "Le fichier est trop volumineux. La taille maximale autorisée est de {{maxSize}}.",
"svgImageInsertError": "Impossible d'insérer l'image SVG. Le balisage SVG semble invalide.",
"failedToFetchImage": "",
"invalidSVGString": "SVG invalide.",
"cannotResolveCollabServer": "Impossible de se connecter au serveur collaboratif. Veuillez recharger la page et réessayer.",
"importLibraryError": "Impossible de charger la bibliothèque",
"collabSaveFailed": "Impossible d'enregistrer dans la base de données en arrière-plan. Si des problèmes persistent, vous devriez enregistrer votre fichier localement pour vous assurer de ne pas perdre votre travail.",
"collabSaveFailed_sizeExceeded": "Impossible d'enregistrer dans la base de données en arrière-plan, le tableau semble trop grand. Vous devriez enregistrer le fichier localement pour vous assurer de ne pas perdre votre travail.",
"brave_measure_text_error": {
"line1": "",
"line2": "",
"line3": "",
"line4": ""
"line1": "On dirait que vous utilisez le navigateur Brave avec l'option <bold>Bloquer agressivement le fichage</bold> activée.",
"line2": "Cela pourrait entraîner des problèmes avec les <bold>Éléments Textuels</bold> dans vos dessins.",
"line3": "Nous recommandons fortement de désactiver cette option. Vous pouvez suivre <link>ces instructions</link> pour savoir comment faire.",
"line4": "Si désactiver cette option de résout pas le problème d'affichage des éléments textuels, veuillez ouvrir un <issueLink>ticket</issueLink> sur notre GitHub, ou écrivez-nous sur notre <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "Les éléments intégrés ne peuvent pas être ajoutés à la librairie.",
"image": "Le support pour l'ajout d'images à la librairie arrive bientôt !"
}
},
"toolBar": {
@ -223,9 +235,11 @@
"penMode": "Mode stylo - évite le toucher",
"link": "Ajouter/mettre à jour le lien pour une forme sélectionnée",
"eraser": "Gomme",
"frame": "",
"frame": "Outil de cadre",
"embeddable": "Intégration Web",
"laser": "",
"hand": "Mains (outil de déplacement de la vue)",
"extraTools": ""
"extraTools": "Plus d'outils"
},
"headings": {
"canvasActions": "Actions du canevas",
@ -237,6 +251,7 @@
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
"text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
"embeddable": "Cliquez et glissez pour créer une intégration de site web",
"text_selected": "Double-cliquez ou appuyez sur ENTRÉE pour modifier le texte",
"text_editing": "Appuyez sur ÉCHAP ou Ctrl/Cmd+ENTRÉE pour terminer l'édition",
"linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
@ -252,7 +267,8 @@
"bindTextToElement": "Appuyer sur Entrée pour ajouter du texte",
"deepBoxSelect": "Maintenir Ctrl ou Cmd pour sélectionner dans les groupes et empêcher le déplacement",
"eraserRevert": "Maintenez Alt enfoncé pour annuler les éléments marqués pour suppression",
"firefox_clipboard_write": "Cette fonctionnalité devrait pouvoir être activée en définissant l'option \"dom.events.asyncClipboard.clipboard.clipboardItem\" à \"true\". Pour modifier les paramètres du navigateur dans Firefox, visitez la page \"about:config\"."
"firefox_clipboard_write": "Cette fonctionnalité devrait pouvoir être activée en définissant l'option \"dom.events.asyncClipboard.clipboard.clipboardItem\" à \"true\". Pour modifier les paramètres du navigateur dans Firefox, visitez la page \"about:config\".",
"disableSnapping": "Maintenez CtrlOuCmd pour désactiver l'aimantation"
},
"canvasError": {
"cannotShowPreview": "Impossible dafficher laperçu",
@ -360,27 +376,27 @@
"removeItemsFromLib": "Enlever les éléments sélectionnés de la bibliothèque"
},
"imageExportDialog": {
"header": "",
"header": "Exporter l'image",
"label": {
"withBackground": "",
"onlySelected": "",
"darkMode": "",
"embedScene": "",
"scale": "",
"withBackground": "Fond",
"onlySelected": "Uniquement la sélection",
"darkMode": "Mode sombre",
"embedScene": "Intégrer la scène",
"scale": "Échelle",
"padding": ""
},
"tooltip": {
"embedScene": ""
"embedScene": "Les données de la scène seront sauvegardées dans le fichier PNG/SVG exporté afin que la scène puisse être restaurée depuis celui-ci.\nCela augmentera la taille du fichier exporté."
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "Exporter en PNG",
"exportToSvg": "Exporter en SVG",
"copyPngToClipboard": "Copier le PNG dans le presse-papier"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Copier dans le presse-papier"
}
},
"encrypted": {
@ -411,24 +427,26 @@
"fileSavedToFilename": "Enregistré sous {filename}",
"canvas": "canevas",
"selection": "sélection",
"pasteAsSingleElement": "Utiliser {{shortcut}} pour coller comme un seul élément,\nou coller dans un éditeur de texte existant"
"pasteAsSingleElement": "Utiliser {{shortcut}} pour coller comme un seul élément,\nou coller dans un éditeur de texte existant",
"unableToEmbed": "Intégrer cet URL n'est actuellement pas autorisé. Ouvrez un ticket sur GitHub pour demander son ajout à la liste blanche",
"unrecognizedLinkFormat": "Le lien que vous avez intégré ne correspond pas au format attendu. Veuillez essayer de coller la chaîne d'intégration fournie par le site source"
},
"colors": {
"transparent": "Transparent",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"black": "Noir",
"white": "Blanc",
"red": "Rouge",
"pink": "Rose",
"grape": "Mauve",
"violet": "Violet",
"gray": "Gris",
"blue": "Bleu",
"cyan": "Cyan",
"teal": "Turquoise",
"green": "Vert",
"yellow": "Jaune",
"orange": "Orange",
"bronze": "Bronze"
},
"welcomeScreen": {
"app": {
@ -444,10 +462,41 @@
}
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
"mostUsedCustomColors": "Couleurs personnalisées les plus fréquemment utilisées",
"colors": "Couleurs",
"shades": "Nuances",
"hexCode": "Code hex",
"noShades": "Aucune nuance disponible pour cette couleur"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Exporter en image",
"button": "Exporter en image",
"description": "Exporter les données de la scène comme une image que vous pourrez importer ultérieurement."
},
"saveToDisk": {
"title": "Sauvegarder sur le disque",
"button": "Sauvegarder sur le disque",
"description": "Exporter les données de la scène comme un fichier que vous pourrez importer ultérieurement."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Exporter vers Excalidraw+",
"description": "Enregistrer la scène dans votre espace de travail Excalidraw+."
}
},
"modal": {
"loadFromFile": {
"title": "Charger depuis un fichier",
"button": "Charger depuis un fichier",
"description": "Charger depuis un fichier va <bold>remplacer votre contenu existant</bold>.<br></br>Vous pouvez d'abord sauvegarder votre dessin en utilisant l'une des options ci-dessous."
},
"shareableLink": {
"title": "Charger depuis un lien",
"button": "Remplacer mon contenu",
"description": "Charger un dessin externe va <bold>remplacer votre contenu existant</bold>.<br></br>Vous pouvez d'abord sauvegarder votre dessin en utilisant l'une des options ci-dessous."
}
}
}
}

View File

@ -50,7 +50,7 @@
"veryLarge": "Moi grande",
"solid": "Sólido",
"hachure": "Folleto",
"zigzag": "",
"zigzag": "Zigzag",
"crossHatch": "Raiado transversal",
"thin": "Estreito",
"bold": "Groso",
@ -109,8 +109,12 @@
"createContainerFromText": "Envolver o texto nun contedor",
"link": {
"edit": "Editar ligazón",
"editEmbed": "",
"create": "Crear ligazón",
"label": "Ligazón"
"createEmbed": "",
"label": "Ligazón",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Editar liña",
@ -160,13 +164,16 @@
"darkMode": "Modo escuro",
"lightMode": "Modo claro",
"zenMode": "Modo zen",
"objectsSnapMode": "",
"exitZenMode": "Saír do modo zen",
"cancel": "Cancelar",
"clear": "Limpar",
"remove": "Eliminar",
"embed": "",
"publishLibrary": "Publicar",
"submit": "Enviar",
"confirm": "Confirmar"
"confirm": "Confirmar",
"embeddableInteractionButton": "Faga clic para interactuar"
},
"alerts": {
"clearReset": "Isto limpará todo o lenzo. Estás seguro?",
@ -196,6 +203,7 @@
"imageInsertError": "Non se puido inserir a imaxe. Probe de novo máis tarde...",
"fileTooBig": "O ficheiro é demasiado grande. O tamaño máximo permitido é {{maxSize}}.",
"svgImageInsertError": "Non se puido inserir como imaxe SVG. O marcado SVG semella inválido.",
"failedToFetchImage": "",
"invalidSVGString": "SVG inválido.",
"cannotResolveCollabServer": "Non se puido conectar ao servidor de colaboración. Por favor recargue a páxina e probe de novo.",
"importLibraryError": "Non se puido cargar a biblioteca",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,8 +236,10 @@
"link": "Engadir/ Actualizar ligazón para a forma seleccionada",
"eraser": "Goma de borrar",
"frame": "",
"embeddable": "Inserir na web",
"laser": "Punteiro láser",
"hand": "Man (ferramenta de desprazamento)",
"extraTools": ""
"extraTools": "Máis ferramentas"
},
"headings": {
"canvasActions": "Accións do lenzo",
@ -237,6 +251,7 @@
"linearElement": "Faga clic para iniciar varios puntos, arrastre para unha sola liña",
"freeDraw": "Fai clic e arrastra, solta cando acabes",
"text": "Consello: tamén podes engadir texto facendo dobre-clic en calquera lugar coa ferramenta de selección",
"embeddable": "Faga clic e arrastre para crear un sitio web embebido",
"text_selected": "Dobre-clic ou prema ENTER para editar o texto",
"text_editing": "Prema Escape ou CtrlOrCmd+ENTER para finalizar a edición",
"linearElementMulti": "Faga clic no último punto ou prema Escape ou Enter para rematar",
@ -252,7 +267,8 @@
"bindTextToElement": "Prema a tecla enter para engadir texto",
"deepBoxSelect": "Manteña pulsado CtrlOrCmd para seleccionar en profundidade e evitar o arrastre",
"eraserRevert": "Manteña pulsado Alt para reverter os elementos marcados para a súa eliminación",
"firefox_clipboard_write": "Esta función pódese activar establecendo a opción \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar as opcións do navegador en Firefox, visita a páxina \"about:config\"."
"firefox_clipboard_write": "Esta función pódese activar establecendo a opción \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar as opcións do navegador en Firefox, visita a páxina \"about:config\".",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Non se pode mostrar a vista previa",
@ -360,11 +376,11 @@
"removeItemsFromLib": "Eliminar os elementos seleccionados da biblioteca"
},
"imageExportDialog": {
"header": "",
"header": "Exportar imaxe",
"label": {
"withBackground": "",
"withBackground": "Fondo",
"onlySelected": "",
"darkMode": "",
"darkMode": "Modo escuro",
"embedScene": "",
"scale": "",
"padding": ""
@ -373,14 +389,14 @@
"embedScene": ""
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "Exportar a PNG",
"exportToSvg": "Exportar a SVG",
"copyPngToClipboard": "Copiar PNG ao portapapeis"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Copiar ao portapapeis"
}
},
"encrypted": {
@ -411,24 +427,26 @@
"fileSavedToFilename": "Gardado en {filename}",
"canvas": "lenzo",
"selection": "selección",
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un único elemento\nou pega nun editor de texto existente"
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un único elemento\nou pega nun editor de texto existente",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Transparente",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"black": "Negro",
"white": "Branco",
"red": "Vermello",
"pink": "Rosa",
"grape": "Uva",
"violet": "Violeta",
"gray": "Gris",
"blue": "Azul",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"green": "Verde",
"yellow": "Marelo",
"orange": "Laranxa",
"bronze": "Bronce"
},
"welcomeScreen": {
"app": {
@ -445,9 +463,40 @@
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"colors": "Cores",
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Exportar como imaxe",
"button": "Exportar como imaxe",
"description": ""
},
"saveToDisk": {
"title": "Gardar no disco",
"button": "Gardar no disco",
"description": ""
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Exportar a Excalidraw+",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "Cargar dende arquivo",
"button": "Cargar dende arquivo",
"description": ""
},
"shareableLink": {
"title": "Cargar dende un enlace",
"button": "Substituír o meu contido",
"description": ""
}
}
}
}

View File

@ -50,7 +50,7 @@
"veryLarge": "גדול מאוד",
"solid": "מוצק",
"hachure": "קווים מקבילים קצרים להצגת כיוון וחדות שיפוע במפה",
"zigzag": "",
"zigzag": "זיגזג",
"crossHatch": "קווים מוצלבים שתי וערב",
"thin": "דק",
"bold": "מודגש",
@ -109,8 +109,12 @@
"createContainerFromText": "ארוז טקסט במיכל",
"link": {
"edit": "עריכת קישור",
"editEmbed": "ערוך קישור ושבץ",
"create": "יצירת קישור",
"label": "קישור"
"createEmbed": "צור קישור ושבץ",
"label": "קישור",
"labelEmbed": "קשר ושבץ",
"empty": "לא נקבע קישור"
},
"lineEditor": {
"edit": "ערוך קו",
@ -124,8 +128,8 @@
},
"statusPublished": "פורסם",
"sidebarLock": "שמור את סרגל הצד פתוח",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"selectAllElementsInFrame": "בחר את כל האלמנטים במסגרת",
"removeAllElementsFromFrame": "הסר את כל האלמנטים שבמסגרת",
"eyeDropper": ""
},
"library": {
@ -160,13 +164,16 @@
"darkMode": "מצב כהה",
"lightMode": "מצב בהיר",
"zenMode": "מצב זן",
"objectsSnapMode": "",
"exitZenMode": "צא ממצב זן",
"cancel": "ביטול",
"clear": "ניקוי",
"remove": "הסר",
"embed": "",
"publishLibrary": "פרסום",
"submit": "שליחה",
"confirm": "אשר"
"confirm": "אשר",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "פעולה זו תנקה את כל הקנבס. אתה בטוח?",
@ -196,6 +203,7 @@
"imageInsertError": "לא ניתן היה להוסיף את התמונה. אנא נסה שוב מאוחר יותר...",
"fileTooBig": "הקובץ גדול מדי. הגודל המירבי המותר הינו {{maxSize}}.",
"svgImageInsertError": "לא ניתן היה להוסיף את תמונת ה-SVG. הסימונים בתוך קובץ ה-SVG עשויים להיות שגויים.",
"failedToFetchImage": "",
"invalidSVGString": "SVG שגוי.",
"cannotResolveCollabServer": "לא הצלחתי להתחבר לשרת השיתוף. אנא רענן את הדף ונסה שוב.",
"importLibraryError": "לא ניתן היה לטעון את הספריה",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "הוספה/עדכון קישור של הצורה שנבחרה",
"eraser": "מחק",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "יד (כלי הזזה)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "לחץ להתחלת מספר נקודות, גרור לקו יחיד",
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
"embeddable": "",
"text_selected": "לחץ לחיצה כפולה או הקש על אנטר לעריכת הטקסט",
"text_editing": "כדי לסיים את העריכה לחץ על מקש Escape או על Ctrl (Cmd במחשבי אפל) ומקש Enter",
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
@ -252,7 +267,8 @@
"bindTextToElement": "הקש Enter כדי להוספת טקסט",
"deepBoxSelect": "החזק Ctrl / Cmd לבחירה עמוקה ולמניעת גרירה",
"eraserRevert": "החזק Alt להחזרת רכיבים מסומנים למחיקה",
"firefox_clipboard_write": "יכולות זה ניתנת להפעלה על ידי שינוי הדגל של \"dom.events.asyncClipboard.clipboardItem\" למצב \"true\". כדי לשנות את הדגל בדפדפן Firefox, בקר בעמוד ״about:config״."
"firefox_clipboard_write": "יכולות זה ניתנת להפעלה על ידי שינוי הדגל של \"dom.events.asyncClipboard.clipboardItem\" למצב \"true\". כדי לשנות את הדגל בדפדפן Firefox, בקר בעמוד ״about:config״.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "לא ניתן להראות תצוגה מקדימה",
@ -411,7 +427,9 @@
"fileSavedToFilename": "נשמר לקובץ {filename}",
"canvas": "קנבאס",
"selection": "בחירה",
"pasteAsSingleElement": "השתמש ב- {{shortcut}} כדי להדביק כפריט יחיד,\nאו הדבק לתוך עורך טקסט קיים"
"pasteAsSingleElement": "השתמש ב- {{shortcut}} כדי להדביק כפריט יחיד,\nאו הדבק לתוך עורך טקסט קיים",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "שקוף",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "मूलपाठ कंटेनर में मोड के दिखाए",
"link": {
"edit": "",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "रेखा संपादित करे",
@ -160,13 +164,16 @@
"darkMode": "डार्क मोड",
"lightMode": "लाइट मोड",
"zenMode": "ज़ेन मोड",
"objectsSnapMode": "वस्तुओं से पकड़े",
"exitZenMode": "जेन मोड से बाहर निकलें",
"cancel": "",
"clear": "साफ़ करे",
"remove": "हटाएं",
"embed": "",
"publishLibrary": "प्रकाशित करें",
"submit": "प्रस्तुत करे",
"confirm": "पुष्टि करें"
"confirm": "पुष्टि करें",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "इससे पूरा कैनवास साफ हो जाएगा। क्या आपको यकीन है?",
@ -196,6 +203,7 @@
"imageInsertError": "छवि सम्मिलित नहीं की जा सकी. पुनः प्रयत्न करे...",
"fileTooBig": "फ़ाइल ज़रूरत से ज़्यादा बड़ी हैं. अधिकतम अनुमित परिमाण {{maxSize}} हैं",
"svgImageInsertError": "एसवीजी छवि सम्मिलित नहीं कर सके, एसवीजी रचना अनुचित हैं",
"failedToFetchImage": "",
"invalidSVGString": "अनुचित SVG",
"cannotResolveCollabServer": "कॉलेब सर्वर से कनेक्शन नहीं हो पा रहा. कृपया पृष्ठ को पुनः लाने का प्रयास करे.",
"importLibraryError": "संग्रह प्रतिष्ठापित नहीं किया जा सका",
@ -206,6 +214,10 @@
"line2": "यह आपके चित्रों के <bold>पाठ तत्वों</bold>को खंडित कर सकता हैं",
"line3": "हमें आपसे ठोस आग्रह है की आप सेट्टिंग में इस विकल्प का चयन ना करे.<link> इस अनुक्रम </link> का पालन करके इसका पता लगा सकते हैं",
"line4": "यदि इस सेटिंग्स को अक्षम करने पर भी पृष्ठ ठीक नहीं दिखता हो तो, हमारे GitHub पर एक <issueLink>मुद्दा प्रस्तुत</issueLink> करे, या हमें <discordLink>डिस्कोर्ड</discordLink> पर लिखित सम्पर्क करें"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "रबड़",
"frame": "",
"embeddable": "",
"laser": "लेसर टॉर्च",
"hand": "हाथ ( खिसकाने का औज़ार)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
"text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "अंतिम बिंदु पर क्लिक करें या समाप्त होने के लिए एस्केप या एंटर दबाएं",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "मिटाने के लिए चुने हुए चीजों को ना चुनने के लिए Alt साथ में दबाए",
"firefox_clipboard_write": "\"dom.events.asyncClipboard.clipboardItem\" फ़्लैग को \"true\" पर सेट करके इस सुविधा को संभवतः सक्षम किया जा सकता है। Firefox में ब्राउज़र फ़्लैग बदलने के लिए, \"about:config\" पृष्ठ पर जाएँ।"
"firefox_clipboard_write": "\"dom.events.asyncClipboard.clipboardItem\" फ़्लैग को \"true\" पर सेट करके इस सुविधा को संभवतः सक्षम किया जा सकता है। Firefox में ब्राउज़र फ़्लैग बदलने के लिए, \"about:config\" पृष्ठ पर जाएँ।",
"disableSnapping": "स्नैपिंग को निष्क्रिय करने के लिए CtrlOrCmd दबाए रखें"
},
"canvasError": {
"cannotShowPreview": "पूर्वावलोकन नहीं दिखा सकते हैं",
@ -411,7 +427,9 @@
"fileSavedToFilename": "",
"canvas": "",
"selection": "",
"pasteAsSingleElement": "एक अवयव के रूप में चिपकाने के लिए {{shortcut}} का उपयोग करें,\nया किसी मौजूदा पाठ संपादक में चिपकायें"
"pasteAsSingleElement": "एक अवयव के रूप में चिपकाने के लिए {{shortcut}} का उपयोग करें,\nया किसी मौजूदा पाठ संपादक में चिपकायें",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "छाया",
"hexCode": "हेक्स कोड",
"noShades": "इस रंग की कोई छाया उपलब्ध नहीं हैं"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "छवि स्वरूप में निर्यात करे",
"button": "छवि स्वरूप निर्यात करे",
"description": "दृष्य डेटा छवि स्वरूप में निर्यात करे, उस स्वरूप से आप उसे पुनः आयात कर सकते हो"
},
"saveToDisk": {
"title": "डिस्क में सम्हाले",
"button": "डिस्क में सम्हाले",
"description": "दृष्य डेटा बाहरी फ़ाइल में निर्यात करे, जहाँसे आप उसे पुनः आयात कर सकते हो"
},
"excalidrawPlus": {
"title": "एक्षकालीड्रॉ+",
"button": "एक्षकालीड्रॉ+ में निर्यात करे",
"description": "दृष्य को आपके एक्षकालीड्रॉ+ के कर्यस्थल में सम्हाले"
}
},
"modal": {
"loadFromFile": {
"title": "फ़ाइल से लोड करें:",
"button": "फ़ाइल से लोड करें:",
"description": "फ़ाइल से लोड करने पर <bold>यह आपके कार्य की जगह लेलेगा </bold><br></br>आपकी ड्रॉइंग निम्न दर्शित विकल्पो में से एक चुनके और उपयोग करके सम्हाल सकते हैं"
},
"shareableLink": {
"title": "लिंक से लोड करें:",
"button": "इस जगह प्रतिस्थापित करे",
"description": "बाहर का चित्र लोड करने पर <bold>यह आपके कार्य की जगह लेलेगा </bold><br></br>आप आपकी ड्रॉइंग पहले निम्न दर्शित विकल्पो में से एक चुनके और उपयोग करके सम्हाल सकते हों."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Hivatkozás szerkesztése",
"editEmbed": "",
"create": "Hivatkozás létrehozása",
"label": "Hivatkozás"
"createEmbed": "",
"label": "Hivatkozás",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "Sötét mód",
"lightMode": "Világos mód",
"zenMode": "Letisztult mód",
"objectsSnapMode": "",
"exitZenMode": "Kilépés a letisztult módból",
"cancel": "Mégsem",
"clear": "Kiűrítés",
"remove": "Eltávolítás",
"embed": "",
"publishLibrary": "Közzététel",
"submit": "Elküldés",
"confirm": "Megerősítés"
"confirm": "Megerősítés",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Ez a művelet törli a vászont. Biztos benne?",
@ -196,6 +203,7 @@
"imageInsertError": "Nem sikerült beszúrni a képet. Próbáld újra később...",
"fileTooBig": "A fájl túl nagy. A megengedett maximális méret {{maxSize}}.",
"svgImageInsertError": "Nem sikerült beszúrni az SVG-képet. Az SVG szintaktika érvénytelennek tűnik.",
"failedToFetchImage": "",
"invalidSVGString": "Érvénytelen SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz",
"eraser": "",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz",
"freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél",
"text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre",
"embeddable": "",
"text_selected": "Kattints duplán, vagy nyomj entert a szöveg szerkesztéséhez",
"text_editing": "Nyomjd meg az Escape vagy a Ctrl/Cmd+ENTER billentyűkombinációt a szerkesztés befejezéséhez",
"linearElementMulti": "Kattints a következő ív pozíciójára, vagy fejezd be a nyilat az Escape vagy Enter megnyomásával",
@ -252,7 +267,8 @@
"bindTextToElement": "Nyomd meg az Entert szöveg hozzáadáshoz",
"deepBoxSelect": "Tartsd lenyomva a Ctrl/Cmd billentyűt a mély kijelöléshez és a húzás megakadályozásához",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Előnézet nem jeleníthető meg",
@ -360,7 +376,7 @@
"removeItemsFromLib": "A kiválasztott elemek eltávolítása a könyvtárból"
},
"imageExportDialog": {
"header": "",
"header": "Kép exportálása",
"label": {
"withBackground": "",
"onlySelected": "",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Mentve mint {filename}",
"canvas": "rajzvászon",
"selection": "kijelölés",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Átlátszó",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Bungkus teks dalam kontainer",
"link": {
"edit": "Edit tautan",
"editEmbed": "",
"create": "Buat tautan",
"label": "Tautan"
"createEmbed": "",
"label": "Tautan",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Edit tautan",
@ -124,9 +128,9 @@
},
"statusPublished": "Telah terbit",
"sidebarLock": "Biarkan sidebar tetap terbuka",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"selectAllElementsInFrame": "Pilih semua elemen di bingkai",
"removeAllElementsFromFrame": "Hapus semua elemen dari bingkai",
"eyeDropper": "Ambil warna dari kanvas"
},
"library": {
"noItems": "Belum ada item yang ditambahkan...",
@ -160,13 +164,16 @@
"darkMode": "Mode gelap",
"lightMode": "Mode terang",
"zenMode": "Mode zen",
"objectsSnapMode": "",
"exitZenMode": "Keluar dari mode zen",
"cancel": "Batal",
"clear": "Hapus",
"remove": "Hapus",
"embed": "",
"publishLibrary": "Terbitkan",
"submit": "Kirimkan",
"confirm": "Konfirmasi"
"confirm": "Konfirmasi",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Ini akan menghapus semua yang ada dikanvas. Apakah kamu yakin ?",
@ -196,6 +203,7 @@
"imageInsertError": "Tidak dapat menyisipkan gambar. Coba lagi nanti...",
"fileTooBig": "File terlalu besar. Ukuran maksimum yang dibolehkan {{maxSize}}.",
"svgImageInsertError": "Tidak dapat menyisipkan gambar SVG. Markup SVG sepertinya tidak valid.",
"failedToFetchImage": "",
"invalidSVGString": "SVG tidak valid.",
"cannotResolveCollabServer": "Tidak dapat terhubung ke server kolab. Muat ulang laman dan coba lagi.",
"importLibraryError": "Tidak dapat memuat pustaka",
@ -206,6 +214,10 @@
"line2": "Ini dapat membuat <bold>Elemen Teks</bold> dalam gambar mu.",
"line3": "Kami sangat menyarankan mematikan pengaturan ini. Anda dapat mengikuti <link>langkah-langkah ini</link> untuk melakukannya.",
"line4": "Jika mematikan pengaturan ini tidak membenarkan tampilan elemen teks, mohon buka\n<issueLink>isu</issueLink> di GitHub kami, atau chat kami di <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -223,9 +235,11 @@
"penMode": "Mode pena - mencegah sentuhan",
"link": "Tambah/Perbarui tautan untuk bentuk yang dipilih",
"eraser": "Penghapus",
"frame": "",
"frame": "Alat bingkai",
"embeddable": "",
"laser": "",
"hand": "Tangan (alat panning)",
"extraTools": ""
"extraTools": "Alat-alat lain"
},
"headings": {
"canvasActions": "Opsi Kanvas",
@ -237,6 +251,7 @@
"linearElement": "Klik untuk memulai banyak poin, seret untuk satu baris",
"freeDraw": "Klik dan seret, lepaskan jika Anda selesai",
"text": "Tip: Anda juga dapat menambahkan teks dengan klik ganda di mana saja dengan alat pemilihan",
"embeddable": "",
"text_selected": "Klik ganda atau tekan ENTER untuk edit teks",
"text_editing": "Tekan Escape atau CtrlAtauCmd+ENTER untuk selesai mengedit",
"linearElementMulti": "Klik pada titik akhir atau tekan Escape atau Enter untuk menyelesaikan",
@ -252,7 +267,8 @@
"bindTextToElement": "Tekan enter untuk tambahkan teks",
"deepBoxSelect": "Tekan Ctrl atau Cmd untuk memilih yang di dalam, dan mencegah penggeseran",
"eraserRevert": "Tahan Alt untuk mengembalikan elemen yang ditandai untuk dihapus",
"firefox_clipboard_write": "Fitur ini dapat diaktifkan melalui pengaturan flag \"dom.events.asyncClipboard.clipboardItem\" ke \"true\". Untuk mengganti flag di Firefox, pergi ke laman \"about:config\"."
"firefox_clipboard_write": "Fitur ini dapat diaktifkan melalui pengaturan flag \"dom.events.asyncClipboard.clipboardItem\" ke \"true\". Untuk mengganti flag di Firefox, pergi ke laman \"about:config\".",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Tidak dapat menampilkan pratinjau",
@ -360,27 +376,27 @@
"removeItemsFromLib": "Hapus item yang dipilih dari pustaka"
},
"imageExportDialog": {
"header": "",
"header": "Ekspor gambar",
"label": {
"withBackground": "",
"onlySelected": "",
"darkMode": "",
"embedScene": "",
"scale": "",
"padding": ""
"withBackground": "Latar",
"onlySelected": "Hanya yang dipilih",
"darkMode": "Mode gelap",
"embedScene": "Sematkan pemandangan",
"scale": "Skala",
"padding": "Lapisan"
},
"tooltip": {
"embedScene": ""
"embedScene": "Data pemandangan akan disimpan dalam file PNG/SVG yang diekspor sehingga pemandangan itu dapat dipulihkan darinya.\nAkan membesarkan ukuran file yang diekspor."
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "Ekspor ke PNG",
"exportToSvg": "Ekspor ke SVG",
"copyPngToClipboard": "Salin PNG ke papan klip"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Salin ke papan klip"
}
},
"encrypted": {
@ -411,7 +427,9 @@
"fileSavedToFilename": "Disimpan ke {filename}",
"canvas": "kanvas",
"selection": "pilihan",
"pasteAsSingleElement": "Gunakan {{shortcut}} untuk menempelkan sebagai satu elemen,\natau tempelkan ke teks editor yang ada"
"pasteAsSingleElement": "Gunakan {{shortcut}} untuk menempelkan sebagai satu elemen,\natau tempelkan ke teks editor yang ada",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Transparan",
@ -449,5 +467,36 @@
"shades": "Nuansa",
"hexCode": "Kode hexa",
"noShades": "Tidak ada nuansa untuk warna ini"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Ekspor sebagai gambar",
"button": "Ekspor sebagai gambar",
"description": "Ekspor data pemandangan sebagai gambar yang dapat anda impor nanti."
},
"saveToDisk": {
"title": "Simpan ke disk",
"button": "Simpan ke disk",
"description": "Ekspor data pemandangan ke file yang dapat Anda dapat impor nanti."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Ekspor ke Excalidraw+",
"description": "Simpan pemandangan ke ruang kerja Excalidraw+ Anda."
}
},
"modal": {
"loadFromFile": {
"title": "Muat dari file",
"button": "Muat dari file",
"description": "Memuat dari file yang akan <bold>menggantikan konten Anda sekarang</bold>.<br></br>Anda dapat mencadangkan gambar anda dulu menggunakan opsi-opsi ini."
},
"shareableLink": {
"title": "Muat dari link",
"button": "Ganti konten saya",
"description": "Memuat dari file yang akan <bold>menggantikan konten Anda sekarang</bold>.<br></br>Anda dapat mencadangkan gambar anda dulu menggunakan opsi-opsi ini."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Avvolgi il testo in un container",
"link": {
"edit": "Modifica link",
"editEmbed": "Modifica collegamento e incorpora",
"create": "Crea link",
"label": "Link"
"createEmbed": "Crea collegamento e incorpora",
"label": "Link",
"labelEmbed": "Collega & incorpora",
"empty": "Nessun collegamento impostato"
},
"lineEditor": {
"edit": "Modifica linea",
@ -124,9 +128,9 @@
},
"statusPublished": "Pubblicato",
"sidebarLock": "Mantieni aperta la barra laterale",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"selectAllElementsInFrame": "Seleziona tutti gli elementi nel riquadro",
"removeAllElementsFromFrame": "Rimuovi tutti gli elementi dal riquadro",
"eyeDropper": "Scegli il colore della tela"
},
"library": {
"noItems": "Nessun elemento ancora aggiunto...",
@ -160,13 +164,16 @@
"darkMode": "Tema scuro",
"lightMode": "Tema chiaro",
"zenMode": "Modalità Zen",
"objectsSnapMode": "Aggancia agli oggetti",
"exitZenMode": "Uscire dalla modalità zen",
"cancel": "Annulla",
"clear": "Cancella",
"remove": "Rimuovi",
"embed": "Attiva/disattiva incorporamento",
"publishLibrary": "Pubblica",
"submit": "Invia",
"confirm": "Conferma"
"confirm": "Conferma",
"embeddableInteractionButton": "Clicca per interagire"
},
"alerts": {
"clearReset": "Questa azione cancellerà l'intera tela. Sei sicuro?",
@ -196,6 +203,7 @@
"imageInsertError": "Non è stato possibile inserire l'immagine. Riprova più tardi...",
"fileTooBig": "Il file è troppo grande. La dimensione massima consentita è {{maxSize}}.",
"svgImageInsertError": "Impossibile inserire l'immagine SVG. Il markup SVG non sembra corretto.",
"failedToFetchImage": "",
"invalidSVGString": "SVG non valido.",
"cannotResolveCollabServer": "Impossibile connettersi al server di collab. Ricarica la pagina e riprova.",
"importLibraryError": "Impossibile caricare la libreria",
@ -206,6 +214,10 @@
"line2": "Ciò potrebbe causare la rottura degli <bold>Elementi di testo</bold> nei tuoi disegni.",
"line3": "Consigliamo vivamente di disabilitare questa impostazione. Puoi seguire <link>questi passaggi</link> su come farlo.",
"line4": "Se la disattivazione di questa impostazione non risolve la visualizzazione degli elementi di testo, apri un <issueLink>problema</issueLink> sul nostro GitHub o scrivici su <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "Gli elementi incorporabili non possono essere aggiunti alla libreria.",
"image": "Il supporto per l'aggiunta d'immagini alla libreria verrà aggiunto a breve!"
}
},
"toolBar": {
@ -223,7 +235,9 @@
"penMode": "Modalità penna - previene il tocco",
"link": "Aggiungi/ aggiorna il link per una forma selezionata",
"eraser": "Gomma",
"frame": "",
"frame": "Strumento riquadro",
"embeddable": "Incorporamento Web",
"laser": "Puntatore laser",
"hand": "Mano (strumento di panoramica)",
"extraTools": "Altri strumenti"
},
@ -237,6 +251,7 @@
"linearElement": "Clicca per iniziare una linea in più punti, trascina per singola linea",
"freeDraw": "Clicca e trascina, rilascia quando avrai finito",
"text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione",
"embeddable": "Fare click e trascina per creare un incorporamento web",
"text_selected": "Fai doppio click o premi INVIO per modificare il testo",
"text_editing": "Premi ESC o CtrlOCmd+INVIO per completare le modifiche",
"linearElementMulti": "Clicca sull'ultimo punto o premi Esc o Invio per finire",
@ -252,7 +267,8 @@
"bindTextToElement": "Premi invio per aggiungere il testo",
"deepBoxSelect": "Tieni premuto CtrlOCmd per selezionare in profondità e per impedire il trascinamento",
"eraserRevert": "Tieni premuto Alt per ripristinare gli elementi contrassegnati per l'eliminazione",
"firefox_clipboard_write": "Questa funzione può essere abilitata impostando il flag \"dom.events.asyncClipboard.clipboardItem\" su \"true\". Per modificare i flag del browser in Firefox, visitare la pagina \"about:config\"."
"firefox_clipboard_write": "Questa funzione può essere abilitata impostando il flag \"dom.events.asyncClipboard.clipboardItem\" su \"true\". Per modificare i flag del browser in Firefox, visitare la pagina \"about:config\".",
"disableSnapping": "Tieni premuto Ctrl o Cmd per disabilitare lo snap"
},
"canvasError": {
"cannotShowPreview": "Impossibile visualizzare l'anteprima",
@ -367,7 +383,7 @@
"darkMode": "Tema scuro",
"embedScene": "Includi scena",
"scale": "Scala",
"padding": ""
"padding": "Rientro"
},
"tooltip": {
"embedScene": "I dati della scena saranno salvati nel file PNG/SVG esportato in modo che la scena possa essere ripristinata da esso.\nQuesto aumenterà la dimensione del file esportato."
@ -411,7 +427,9 @@
"fileSavedToFilename": "Salvato in {filename}",
"canvas": "tela",
"selection": "selezione",
"pasteAsSingleElement": "Usa {{shortcut}} per incollare come un singolo elemento,\no incollare in un editor di testo esistente"
"pasteAsSingleElement": "Usa {{shortcut}} per incollare come un singolo elemento,\no incollare in un editor di testo esistente",
"unableToEmbed": "Incorporare questo url non è permesso. Crea una issue su GitHub per richiedere che l'url sia autorizzato",
"unrecognizedLinkFormat": "Il link che hai incorporato non corrisponde al formato previsto. Prova a incollare la stringa 'embed' fornita dal sito di origine"
},
"colors": {
"transparent": "Trasparente",
@ -449,5 +467,36 @@
"shades": "Sfumature",
"hexCode": "Codice esadecimale",
"noShades": "Nessuna sfumatura disponibile per questo colore"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Esporta come immagine",
"button": "Esporta come immagine",
"description": "Esporta i dati della scena come immagine, che potrai importare in seguito."
},
"saveToDisk": {
"title": "Salva su disco",
"button": "Salva su disco",
"description": "Esporta i dati della scena su file, che potrai importare in seguito."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Esporta su Excalidraw+",
"description": "Salva la scena sul tuo spazio di lavoro Excalidraw+."
}
},
"modal": {
"loadFromFile": {
"title": "Carica da file",
"button": "Carica da file",
"description": "Il caricamento da file sostituirà <bold>il contenuto esistente</bold>.<br></br>Puoi salvare il tuo disegno prima usando una delle opzioni qui sotto."
},
"shareableLink": {
"title": "Carica da link",
"button": "Sostituisci il mio contenuto",
"description": "Il caricamento da file sostituirà <bold>il contenuto esistente</bold>.<br></br>Puoi salvare il tuo disegno prima usando una delle opzioni qui sotto."
}
}
}
}

View File

@ -50,7 +50,7 @@
"veryLarge": "特大",
"solid": "ベタ塗り",
"hachure": "斜線",
"zigzag": "",
"zigzag": "ジグザグ",
"crossHatch": "網掛け",
"thin": "細",
"bold": "太字",
@ -106,11 +106,15 @@
"increaseFontSize": "フォントサイズを拡大",
"unbindText": "テキストのバインド解除",
"bindText": "テキストをコンテナにバインド",
"createContainerFromText": "",
"createContainerFromText": "コンテナ内でテキストを折り返す",
"link": {
"edit": "リンクを編集",
"editEmbed": "リンクの編集と埋め込み",
"create": "リンクを作成",
"label": "リンク"
"createEmbed": "リンクの作成と埋め込み",
"label": "リンク",
"labelEmbed": "リンクと埋め込み",
"empty": "リンクが設定されていません"
},
"lineEditor": {
"edit": "行を編集",
@ -124,9 +128,9 @@
},
"statusPublished": "公開済み",
"sidebarLock": "サイドバーを開いたままにする",
"selectAllElementsInFrame": "",
"removeAllElementsFromFrame": "",
"eyeDropper": ""
"selectAllElementsInFrame": "フレーム内のすべての要素を選択",
"removeAllElementsFromFrame": "フレーム内のすべての要素を削除",
"eyeDropper": "キャンバスから色を選択"
},
"library": {
"noItems": "まだアイテムが追加されていません…",
@ -160,13 +164,16 @@
"darkMode": "ダークモード",
"lightMode": "ライトモード",
"zenMode": "Zenモード",
"objectsSnapMode": "",
"exitZenMode": "集中モードをやめる",
"cancel": "キャンセル",
"clear": "消去",
"remove": "削除",
"embed": "埋め込みの切り替え",
"publishLibrary": "公開",
"submit": "送信",
"confirm": "確認"
"confirm": "確認",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "この操作によってキャンバス全体が消えます。よろしいですか?",
@ -196,16 +203,21 @@
"imageInsertError": "画像を挿入できませんでした。後でもう一度お試しください...",
"fileTooBig": "ファイルが大きすぎます。許可される最大サイズは {{maxSize}} です。",
"svgImageInsertError": "SVGイメージを挿入できませんでした。SVGマークアップは無効に見えます。",
"failedToFetchImage": "",
"invalidSVGString": "無効なSVGです。",
"cannotResolveCollabServer": "コラボレーションサーバに接続できませんでした。ページを再読み込みして、もう一度お試しください。",
"importLibraryError": "ライブラリを読み込めませんでした。",
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
"brave_measure_text_error": {
"line1": "",
"line2": "",
"line3": "",
"line4": ""
"line1": "<bold>Aggressly Block Fingerprinting</bold> の設定が有効なBraveブラウザを使用しているようです。",
"line2": "これにより、図面の <bold>テキスト要素</bold> が壊れる可能性があります。",
"line3": "この設定を無効にすることを強く推奨します。 <link>設定手順</link> をこちらから確認できます。",
"line4": "この設定を無効にすると、テキスト要素の表示が修正されません。 GitHub で <issueLink>Issue</issueLink> を開くか、 <discordLink>Discord</discordLink> にご記入ください"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -223,9 +235,11 @@
"penMode": "ペンモード - タッチ防止",
"link": "選択した図形のリンクを追加/更新",
"eraser": "消しゴム",
"frame": "",
"frame": "フレームツール",
"embeddable": "Web埋め込み",
"laser": "",
"hand": "手 (パンニングツール)",
"extraTools": ""
"extraTools": "その他のツール"
},
"headings": {
"canvasActions": "キャンバス操作",
@ -237,6 +251,7 @@
"linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線",
"freeDraw": "クリックしてドラッグします。離すと終了します",
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
"embeddable": "",
"text_selected": "テキストを編集するには、ダブルクリックまたはEnterキーを押します",
"text_editing": "Esc キーまたは CtrlOrCmd+ENTER キーを押して編集を終了します",
"linearElementMulti": "最後のポイントをクリックするか、エスケープまたはEnterを押して終了します",
@ -252,7 +267,8 @@
"bindTextToElement": "Enterを押してテキストを追加",
"deepBoxSelect": "CtrlOrCmd を押し続けることでドラッグを抑止し、深い選択を行います",
"eraserRevert": "Alt を押し続けることで削除マークされた要素を元に戻す",
"firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。"
"firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "プレビューを表示できません",
@ -360,27 +376,27 @@
"removeItemsFromLib": "選択したアイテムをライブラリから削除"
},
"imageExportDialog": {
"header": "",
"header": "画像をエクスポート",
"label": {
"withBackground": "",
"withBackground": "背景",
"onlySelected": "",
"darkMode": "",
"darkMode": "ダークモード",
"embedScene": "",
"scale": "",
"padding": ""
"scale": "スケール",
"padding": "余白"
},
"tooltip": {
"embedScene": ""
},
"title": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG にエクスポート",
"exportToSvg": "SVG にエクスポート",
"copyPngToClipboard": "クリップボードにPNGをコピー"
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "クリップボードにコピー"
}
},
"encrypted": {
@ -411,24 +427,26 @@
"fileSavedToFilename": "{filename} に保存しました",
"canvas": "キャンバス",
"selection": "選択",
"pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け"
"pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "透明",
"black": "",
"white": "",
"red": "",
"pink": "",
"grape": "",
"violet": "",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"bronze": ""
"black": "",
"white": "",
"red": "",
"pink": "ピンク",
"grape": "グレープ",
"violet": "バイオレット",
"gray": "灰色",
"blue": "",
"cyan": "シアン",
"teal": "ティール",
"green": "",
"yellow": "",
"orange": "オレンジ",
"bronze": "ブロンズ"
},
"welcomeScreen": {
"app": {
@ -444,10 +462,41 @@
}
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"mostUsedCustomColors": "最も使用されているカスタム色",
"colors": "",
"shades": "",
"hexCode": "Hexコード",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "画像としてエクスポート",
"button": "画像としてエクスポート",
"description": ""
},
"saveToDisk": {
"title": "ディスクに保存",
"button": "ディスクに保存",
"description": ""
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Excalidraw+にエクスポート",
"description": "Excalidraw+ ワークスペースにシーンを保存します。"
}
},
"modal": {
"loadFromFile": {
"title": "ファイルからロード",
"button": "ファイルからロード",
"description": ""
},
"shareableLink": {
"title": "リンクからロード",
"button": "",
"description": ""
}
}
}
}

View File

@ -1,14 +1,14 @@
{
"labels": {
"paste": "",
"pasteAsPlaintext": "",
"pasteCharts": "",
"paste": "Qoyıw",
"pasteAsPlaintext": "Ápiwayı tekst retinde qoyıw",
"pasteCharts": "Diagrammalardı qoyıw",
"selectAll": "Barlıǵın tańlaw",
"multiSelect": "",
"moveCanvas": "",
"cut": "",
"cut": "Qıyıw",
"copy": "Kóshirip alıw",
"copyAsPng": "",
"copyAsPng": "Almasıw buferine PNG retinde kóshirip alıw",
"copyAsSvg": "",
"copyText": "",
"bringForward": "",
@ -18,8 +18,8 @@
"delete": "Óshiriw",
"copyStyles": "",
"pasteStyles": "",
"stroke": "",
"background": "",
"stroke": "Jiyek",
"background": "Fon",
"fill": "",
"strokeWidth": "",
"strokeStyle": "",
@ -29,31 +29,31 @@
"sloppiness": "",
"opacity": "",
"textAlign": "",
"edges": "",
"edges": "Qırlar",
"sharp": "",
"round": "",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_arrow": "Jebe",
"arrowhead_bar": "",
"arrowhead_dot": "",
"arrowhead_dot": "Noqat",
"arrowhead_triangle": "",
"fontSize": "Shrift ólshemi",
"fontFamily": "",
"addWatermark": "",
"handDrawn": "",
"normal": "",
"code": "",
"code": "Kod",
"small": "",
"medium": "",
"large": "",
"veryLarge": "",
"large": "Úlken",
"veryLarge": "Júdá úlken",
"solid": "",
"hachure": "",
"zigzag": "",
"zigzag": "Zigzag",
"crossHatch": "",
"thin": "",
"bold": "",
"thin": "Jińishke",
"bold": "Qalıń",
"left": "",
"center": "",
"right": "",
@ -71,19 +71,19 @@
"language": "Til",
"liveCollaboration": "",
"duplicateSelection": "Nusqa",
"untitled": "",
"untitled": "Atamasız",
"name": "Ataması",
"yourName": "Atıńız",
"madeWithExcalidraw": "",
"madeWithExcalidraw": "Excalidraw járdeminde islengen",
"group": "",
"ungroup": "",
"collaborators": "",
"collaborators": "Qatnasıwshılar",
"showGrid": "",
"addToLibrary": "Kitapxanaǵa qosıw",
"removeFromLibrary": "Kitapxanadan alıp taslaw",
"libraryLoadingMessage": "Kitapxana júklenbekte…",
"libraries": "Kitapxanalardı kóriw",
"loadingScene": "",
"loadingScene": "Saxna júklenbekte…",
"align": "",
"alignTop": "",
"alignBottom": "",
@ -99,22 +99,26 @@
"share": "Bólisiw",
"showStroke": "",
"showBackground": "",
"toggleTheme": "",
"personalLib": "",
"excalidrawLib": "",
"decreaseFontSize": "",
"increaseFontSize": "",
"toggleTheme": "Temanı ózgertiw",
"personalLib": "Jeke kitapxana",
"excalidrawLib": "Excalidraw kitapxanası",
"decreaseFontSize": "Shrift ólshemin kishireytiw",
"increaseFontSize": "Shrift ólshemin úlkeytiw",
"unbindText": "",
"bindText": "",
"createContainerFromText": "",
"link": {
"edit": "Siltemeni ózgertiw",
"editEmbed": "",
"create": "Siltemeni jaratıw",
"label": "Silteme"
"createEmbed": "",
"label": "Silteme",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
"exit": ""
"edit": "Qatardı ózgertiw",
"exit": "Qatardı ózgertiw redaktorınan shıǵıw"
},
"elementLock": {
"lock": "Qulıplaw",
@ -136,11 +140,11 @@
"buttons": {
"clearReset": "",
"exportJSON": "",
"exportImage": "",
"export": "",
"exportImage": "Súwretti eksportlaw...",
"export": "Retinde saqlaw...",
"copyToClipboard": "Almasıw buferine kóshirip alındı",
"save": "",
"saveAs": "",
"save": "Ámeldegi faylǵa saqlaw",
"saveAs": "Retinde saqlaw",
"load": "Ashıw",
"getShareableLink": "",
"close": "Jabıw",
@ -160,13 +164,16 @@
"darkMode": "Qarańǵı tema",
"lightMode": "Jaqtı tema",
"zenMode": "",
"objectsSnapMode": "",
"exitZenMode": "",
"cancel": "Biykarlaw",
"clear": "Tazalaw",
"remove": "Óshiriw",
"embed": "",
"publishLibrary": "",
"submit": "Jiberiw",
"confirm": "Tastıyıqlaw"
"confirm": "Tastıyıqlaw",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "",
@ -196,6 +203,7 @@
"imageInsertError": "",
"fileTooBig": "",
"svgImageInsertError": "",
"failedToFetchImage": "",
"invalidSVGString": "Jaramsız SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": "Kitapxananı júklew ámelge aspadı",
@ -206,12 +214,16 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
"selection": "",
"image": "Súwret qoyıw",
"rectangle": "",
"rectangle": "Tórt múyeshlik",
"diamond": "",
"ellipse": "",
"arrow": "",
@ -224,19 +236,22 @@
"link": "",
"eraser": "Óshirgish",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
"headings": {
"canvasActions": "",
"selectedShapeActions": "",
"shapes": ""
"shapes": "Figuralar"
},
"hints": {
"canvasPanning": "",
"linearElement": "",
"freeDraw": "",
"text": "",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "",
@ -249,10 +264,11 @@
"lineEditor_nothingSelected": "",
"placeImage": "",
"publishLibrary": "",
"bindTextToElement": "",
"bindTextToElement": "Tekst qosıw ushın Enter túymesin basıń",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "",
@ -281,18 +297,18 @@
"title": "Qátelik"
},
"exportDialog": {
"disk_title": "",
"disk_title": "Diskke saqlaw",
"disk_details": "",
"disk_button": "",
"disk_button": "Faylǵa saqlaw",
"link_title": "",
"link_details": "",
"link_button": "",
"link_button": "Siltemege eksportlaw",
"excalidrawplus_description": "",
"excalidrawplus_button": "Eksportlaw",
"excalidrawplus_exportError": ""
},
"helpDialog": {
"blog": "",
"blog": "Biziń blogtı oqıń",
"click": "basıw",
"deepSelect": "",
"deepBoxSelect": "",
@ -301,7 +317,7 @@
"documentation": "Hújjetshilik",
"doubleClick": "",
"drag": "",
"editor": "",
"editor": "Redaktor",
"editLineArrowPoints": "",
"editText": "",
"github": "",
@ -333,7 +349,7 @@
"libraryDesc": "",
"website": "Veb-sayt",
"placeholder": {
"authorName": "",
"authorName": "Atıńız yamasa paydalanıwshı atı",
"libraryName": "",
"libraryDesc": "",
"githubHandle": "",
@ -356,15 +372,15 @@
"content": ""
},
"confirmDialog": {
"resetLibrary": "",
"resetLibrary": "Kitapxananı qayta ornatıw",
"removeItemsFromLib": ""
},
"imageExportDialog": {
"header": "",
"header": "Súwretti eksportlaw",
"label": {
"withBackground": "",
"withBackground": "Fon",
"onlySelected": "",
"darkMode": "",
"darkMode": "Qarańǵı tema",
"embedScene": "",
"scale": "",
"padding": ""
@ -378,9 +394,9 @@
"copyPngToClipboard": ""
},
"button": {
"exportToPng": "",
"exportToSvg": "",
"copyPngToClipboard": ""
"exportToPng": "PNG",
"exportToSvg": "SVG",
"copyPngToClipboard": "Almasıw buferine kóshirip alıw"
}
},
"encrypted": {
@ -389,8 +405,8 @@
},
"stats": {
"angle": "",
"element": "",
"elements": "",
"element": "Element",
"elements": "Elementler",
"height": "",
"scene": "Saxna",
"selected": "Tańlandı",
@ -403,31 +419,33 @@
"width": ""
},
"toast": {
"addedToLibrary": "",
"addedToLibrary": "Kitapxanaǵa qosıldı",
"copyStyles": "",
"copyToClipboard": "",
"copyToClipboard": "Almasıw buferine kóshirip alındı.",
"copyToClipboardAsPng": "",
"fileSaved": "Fayl saqlandı.",
"fileSavedToFilename": "{filename} saqlandı",
"canvas": "",
"selection": "",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
"black": "",
"white": "",
"red": "",
"pink": "",
"black": "Qara",
"white": "Aq",
"red": "Qızıl",
"pink": "Qıılt",
"grape": "",
"violet": "",
"violet": "Qıılt kók",
"gray": "",
"blue": "",
"cyan": "",
"teal": "",
"green": "",
"yellow": "",
"orange": "",
"blue": "Kók",
"cyan": "Kók aspan",
"teal": "Piruza",
"green": "Jasıl",
"yellow": "Sarı",
"orange": "Qıılt sarı",
"bronze": ""
},
"welcomeScreen": {
@ -445,9 +463,40 @@
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"colors": "Reńler",
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Súwret retinde eksportlaw",
"button": "Súwret retinde eksportlaw",
"description": ""
},
"saveToDisk": {
"title": "Diskke saqlaw",
"button": "Diskke saqlaw",
"description": ""
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "Fayldan júklew",
"button": "Fayldan júklew",
"description": ""
},
"shareableLink": {
"title": "Siltemeden júklew",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Ẓreg aseɣwen",
"editEmbed": "",
"create": "Snulfu-d aseɣwen",
"label": "Aseɣwen"
"createEmbed": "",
"label": "Aseɣwen",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Ẓreg izirig",
@ -160,13 +164,16 @@
"darkMode": "Askar imsulles",
"lightMode": "Askar afaw",
"zenMode": "Askar Zen",
"objectsSnapMode": "",
"exitZenMode": "Ffeɣ seg uskar Zen",
"cancel": "Sefsex",
"clear": "Sfeḍ",
"remove": "Kkes",
"embed": "",
"publishLibrary": "Ẓreg",
"submit": "Azen",
"confirm": "Sentem"
"confirm": "Sentem",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Ayagi ad isfeḍ akk taɣzut n usuneɣ. Tetḥeqqeḍ?",
@ -196,6 +203,7 @@
"imageInsertError": "D awezɣi tugra n tugna. Eɛreḍ tikkelt-nniḍen ardeqqal...",
"fileTooBig": "Afaylu meqqer aṭas. Tiddi tafellayt yurgen d {{maxSize}}.",
"svgImageInsertError": "D awezɣi tugra n tugna SVG. Acraḍ SVG yettban-d d armeɣtu.",
"failedToFetchImage": "",
"invalidSVGString": "SVG armeɣtu.",
"cannotResolveCollabServer": "Ulamek tuqqna s aqeddac n umyalel. Ma ulac uɣilif ales asali n usebter sakin eɛreḍ tikkelt-nniḍen.",
"importLibraryError": "Ur d-ssalay ara tamkarḍit",
@ -206,6 +214,10 @@
"line2": "Ayagi yezmer ad d-iglu s truẓi n<bold>Iferdisen n uḍris</bold>deg wunuɣen-inek.",
"line3": "Ad k-nsemter ad tsexsiḍ aɣewwar-agi. Tzemreḍ ad tḍefreḍ<link>isurifen-agi</link> ɣef wamek ara txedmeḍ.",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Rnu/leqqem aseɣwen i talɣa yettwafernen",
"eraser": "Sfeḍ",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Afus (afecku n usmutti n tmuɣli)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
"freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
"text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
"embeddable": "",
"text_selected": "Ssit snat n tikkal neɣ ssed taqeffalt Kcem akken ad tẓergeḍ aḍris",
"text_editing": "Ssit Escape neɣ CtrlOrCmd+ENTER akken ad tfakkeḍ asiẓreg",
"linearElementMulti": "Ssit ɣef tenqiḍt taneggarut neɣ ssed taqeffalt Escape neɣ taqeffalt Kcem akken ad tfakkeḍ",
@ -252,7 +267,8 @@
"bindTextToElement": "Ssed ɣef kcem akken ad ternuḍ aḍris",
"deepBoxSelect": "Ṭṭef CtrlOrCmd akken ad tferneḍ s telqey, yerna ad trewleḍ i uzuɣer",
"eraserRevert": "Ssed Alt akken ad tsefsxeḍ iferdisen yettwacerḍen i tukksa",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Ulamek abeqqeḍ n teskant",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Yettwasekles di {filename}",
"canvas": "taɣzut n usuneɣ",
"selection": "tafrayt",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Afrawan",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "",
"lightMode": "",
"zenMode": "",
"objectsSnapMode": "",
"exitZenMode": "",
"cancel": "",
"clear": "",
"remove": "",
"embed": "",
"publishLibrary": "",
"submit": "",
"confirm": ""
"confirm": "",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "",
@ -196,6 +203,7 @@
"imageInsertError": "Суретті жүктеу мүмкін болмады. Кейінірек қайталап көріңіз...",
"fileTooBig": "Файл өте үлкен. Максималды рұқсат етілген көлем {{maxSize}}.",
"svgImageInsertError": "",
"failedToFetchImage": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "",
"freeDraw": "",
"text": "",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "",
@ -411,7 +427,9 @@
"fileSavedToFilename": "{filename} сақталды",
"canvas": "",
"selection": "таңдау",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "រុំអត្ថបទក្នុងប្រអប់មួយ",
"link": {
"edit": "កែតំណភ្ជាប់",
"editEmbed": "",
"create": "បង្កើតតំណភ្ជាប់",
"label": "តំណ"
"createEmbed": "",
"label": "តំណ",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "កែសម្រួលបន្ទាត់",
@ -160,13 +164,16 @@
"darkMode": "ម៉ូដងងឹត",
"lightMode": "ម៉ូដភ្លឺ",
"zenMode": "ម៉ូត Zen",
"objectsSnapMode": "",
"exitZenMode": "ចេញពី zen ម៉ូត",
"cancel": "បោះបង់",
"clear": "សម្អាត",
"remove": "ដកចេញ",
"embed": "",
"publishLibrary": "បោះពុម្ពផ្សាយ",
"submit": "ដាក់​ស្នើ",
"confirm": "បញ្ជាក់"
"confirm": "បញ្ជាក់",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "វានឹងសម្អាតបាវទាំងមូល។ តើ​អ្នក​ប្រាកដ​ឬ​អត់?",
@ -196,6 +203,7 @@
"imageInsertError": "មិនអាចបញ្ចូលរូបភាពបានទេ។ សូម​ព្យាយាម​ម្តង​ទៀត​នៅ​ពេល​ក្រោយ……",
"fileTooBig": "ឯកសារធំពេក។ ទំហំអតិបរមាដែលអនុញ្ញាតគឺ {{maxSize}}។",
"svgImageInsertError": "មិនអាចបញ្ចូលរូបភាព SVG បានទេ។ ស្លាក SVG ហាក់ដូចជាមិនត្រឹមត្រូវ។",
"failedToFetchImage": "",
"invalidSVGString": "SVG មិន​ត្រឹមត្រូវ។",
"cannotResolveCollabServer": "មិនអាចភ្ជាប់ទៅម៉ាស៊ីនមេសហការផ្ទាល់បានទេ។ សូមផ្ទុកទំព័រឡើងវិញ ហើយព្យាយាមម្តងទៀត។",
"importLibraryError": "មិនអាចផ្ទុកបណ្ណាល័យបានទេ។",
@ -206,6 +214,10 @@
"line2": "វាអាចបណ្តាលឱ្យមានការបំបែក <bold>ធាតុអត្ថបទ</bold> នៅក្នុងគំនូររបស់អ្នក។",
"line3": "យើងណែនាំយ៉ាងមុតមាំឱ្យបិទការកំណត់នេះ។ អ្នកអាចអនុវត្តតាម <link>ជំហានទាំងនេះ</link> ដើម្បីបិទការកំណត់នេះ។",
"line4": "ប្រសិនបើការបិទការកំណត់នេះមិនបានជួសជុលការបង្ហាញធាតុអត្ថបទទេ សូមដាក់ <issueLink>issue</issueLink> នៅលើ GitHub ឬរាយការណ៍នៅលើ <discordLink>Discord</discordLink> របស់យើង"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "បន្ថែម/ធ្វើបច្ចុប្បន្នភាពតំណភ្ជាប់សម្រាប់រូបរាងដែលបានជ្រើសរើស",
"eraser": "ជ័រលុប",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "ដៃ (panning tool)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "ចុចដើម្បីបង្កើតចំណុចច្រើន អូសដើម្បីបង្កើតបន្ទាត់មួយ",
"freeDraw": "ចុច​ហើយ​អូស លែង​ពេល​រួចរាល់",
"text": "គន្លឹះ៖ អ្នកក៏អាចបន្ថែមអត្ថបទដោយចុចពីរដងនៅកន្លែងណាមួយដោយប្រើឧបករណ៍ជ្រើសរើស",
"embeddable": "",
"text_selected": "ចុចពីរដង ឬចុច ENTER ដើម្បីកែសម្រួលអត្ថបទ",
"text_editing": "ចុច Escape ឬ CtrlOrCmd +ENTER ដើម្បីបញ្ចប់ការកែសម្រួល",
"linearElementMulti": "ចុចលើចំណុចចុងក្រោយ ឬចុច Esc/Enter ដើម្បីបញ្ចប់",
@ -252,7 +267,8 @@
"bindTextToElement": "ចុច Enter ដើម្បីបន្ថែមអត្ថបទ",
"deepBoxSelect": "សង្កត់ CtrlOrCmd ដើម្បីជ្រើសរើសយ៉ាងជ្រៅ និងជៀសវាងការអូស",
"eraserRevert": "សង្កត់ Alt ដើម្បីដកការជ្រើសរើសធាតុដែលត្រូវបានសម្គាល់សម្រាប់ការលុប",
"firefox_clipboard_write": "បើកមុខងារនេះដោយកំណត់ទង់ \"dom.events.asyncClipboard.clipboardItem\" ទៅ \"true\" \nដើម្បីផ្លាស់ប្តូរទង់កម្មវិធីរុករកនៅក្នុង Firefox សូមចូលទៅកាន់ទំព័រ \"about:config\"។"
"firefox_clipboard_write": "បើកមុខងារនេះដោយកំណត់ទង់ \"dom.events.asyncClipboard.clipboardItem\" ទៅ \"true\" \nដើម្បីផ្លាស់ប្តូរទង់កម្មវិធីរុករកនៅក្នុង Firefox សូមចូលទៅកាន់ទំព័រ \"about:config\"។",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "មិនអាចបង្ហាញការមើលជាមុនបាន",
@ -411,7 +427,9 @@
"fileSavedToFilename": "បានរក្សាទុកនៅក្នុង {filename}",
"canvas": "តំបន់គំនូរ",
"selection": "ការជ្រើសរើស",
"pasteAsSingleElement": "ប្រើ {{shortcut}} ដើម្បីបិទភ្ជាប់ជាធាតុតែមួយ,\nឬបិទភ្ជាប់ទៅក្នុងកម្មវិធីនិពន្ធអត្ថបទដែលមានស្រាប់"
"pasteAsSingleElement": "ប្រើ {{shortcut}} ដើម្បីបិទភ្ជាប់ជាធាតុតែមួយ,\nឬបិទភ្ជាប់ទៅក្នុងកម្មវិធីនិពន្ធអត្ថបទដែលមានស្រាប់",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "ថ្លាមើលធ្លុះ",
@ -449,5 +467,36 @@
"shades": "ស្រមោល",
"hexCode": "លេខកូដ hex",
"noShades": "មិនមានស្រមោលសម្រាប់ពណ៌នេះទេ"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "텍스트를 컨테이너에 담기",
"link": {
"edit": "링크 수정하기",
"editEmbed": "링크 & 임베드 수정하기",
"create": "링크 만들기",
"label": "링크"
"createEmbed": "링크 & 임베드 만들기",
"label": "링크",
"labelEmbed": "링크 & 임베드",
"empty": "링크를 지정하지 않았습니다"
},
"lineEditor": {
"edit": "선 수정하기",
@ -160,13 +164,16 @@
"darkMode": "다크 모드",
"lightMode": "밝은 모드",
"zenMode": "젠 모드",
"objectsSnapMode": "다른 요소들에 정렬시키기",
"exitZenMode": "젠 모드 종료하기",
"cancel": "취소",
"clear": "지우기",
"remove": "삭제",
"embed": "임베딩 토글",
"publishLibrary": "게시하기",
"submit": "제출",
"confirm": "확인"
"confirm": "확인",
"embeddableInteractionButton": "클릭하여 상호작용"
},
"alerts": {
"clearReset": "모든 작업 내용이 초기화됩니다. 계속하시겠습니까?",
@ -196,6 +203,7 @@
"imageInsertError": "이미지를 삽입할 수 없습니다. 나중에 다시 시도 하십시오",
"fileTooBig": "파일이 너무 큽니다. 최대 크기는 {{maxSize}} 입니다.",
"svgImageInsertError": "SVG 이미지를 삽입하지 못했습니다. SVG 문법이 유효하지 않은 것 같습니다.",
"failedToFetchImage": "이미지를 가져오는데 실패했습니다.",
"invalidSVGString": "유효하지 않은 SVG입니다.",
"cannotResolveCollabServer": "협업 서버에 접속하는데 실패했습니다. 페이지를 새로고침하고 다시 시도해보세요.",
"importLibraryError": "라이브러리를 불러오지 못했습니다.",
@ -206,6 +214,10 @@
"line2": "이 기능으로 인해 화이트보드의 <bold>텍스트 요소들</bold>이 손상될 수 있습니다.",
"line3": "저희는 해당 기능을 비활성화하는 것을 강력히 권장 드립니다. 비활성화 방법에 대해서는 <link>이 게시글</link>을 참고해주세요.",
"line4": "만약 이 설정을 껐음에도 텍스트 요소들이 올바르게 표시되지 않는다면, 저희 Github에 <issueLink>이슈</issueLink>를 올려주시거나 <discordLink>Discord</discordLink>로 알려주세요."
},
"libraryElementTypeError": {
"embeddable": "임베드 요소들은 라이브러리에 추가할 수 없습니다.",
"image": "라이브러리에 이미지 삽입 기능은 곧 지원될 예정입니다!"
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "선택한 도형에 대해서 링크를 추가/업데이트",
"eraser": "지우개",
"frame": "프레임 도구",
"embeddable": "웹 임베드",
"laser": "레이저 포인터",
"hand": "손 (패닝 도구)",
"extraTools": "다른 도구"
},
@ -237,6 +251,7 @@
"linearElement": "여러 점을 연결하려면 클릭하고, 직선을 그리려면 바로 드래그하세요.",
"freeDraw": "클릭 후 드래그하세요. 완료되면 놓으세요.",
"text": "팁: 선택 툴로 아무 곳이나 더블 클릭해 텍스트를 추가할 수도 있습니다.",
"embeddable": "클릭 및 드래그하여 웹사이트 임베드 만들기",
"text_selected": "더블 클릭 또는 ENTER를 눌러서 텍스트 수정",
"text_editing": "ESC나 CtrlOrCmd+ENTER를 눌러서 수정을 종료하기",
"linearElementMulti": "마지막 지점을 클릭하거나 Esc 또는 Enter 키를 눌러 완료하세요.",
@ -252,7 +267,8 @@
"bindTextToElement": "Enter 키를 눌러서 텍스트 추가하기",
"deepBoxSelect": "CtrlOrCmd 키를 눌러서 깊게 선택하고, 드래그하지 않도록 하기",
"eraserRevert": "Alt를 눌러서 삭제하도록 지정된 요소를 되돌리기",
"firefox_clipboard_write": "이 기능은 설정에서 \"dom.events.asyncClipboard.clipboardItem\" 플래그를 \"true\"로 설정하여 활성화할 수 있습니다. Firefox에서 브라우저 플래그를 수정하려면, \"about:config\" 페이지에 접속하세요."
"firefox_clipboard_write": "이 기능은 설정에서 \"dom.events.asyncClipboard.clipboardItem\" 플래그를 \"true\"로 설정하여 활성화할 수 있습니다. Firefox에서 브라우저 플래그를 수정하려면, \"about:config\" 페이지에 접속하세요.",
"disableSnapping": "CtrlOrCmd 키를 눌러서 다른 요소와의 정렬 무시하기"
},
"canvasError": {
"cannotShowPreview": "미리보기를 볼 수 없습니다",
@ -411,7 +427,9 @@
"fileSavedToFilename": "{filename} 로 저장되었습니다",
"canvas": "캔버스",
"selection": "선택한 요소",
"pasteAsSingleElement": "단일 요소로 붙여넣거나, 기존 텍스트 에디터에 붙여넣으려면 {{shortcut}} 을 사용하세요."
"pasteAsSingleElement": "단일 요소로 붙여넣거나, 기존 텍스트 에디터에 붙여넣으려면 {{shortcut}} 을 사용하세요.",
"unableToEmbed": "이 URL의 임베딩이 허용되지 않았습니다. GitHub에 이슈를 남겨서 이 URL이 화이트리스트에 등재될 수 있도록 요청하세요",
"unrecognizedLinkFormat": "임베딩하려는 링크의 형식이 잘못된 것 같습니다. 원본 사이트에서 제공하는 \"임베딩\" 텍스트를 그대로 붙여 넣어 주세요"
},
"colors": {
"transparent": "투명",
@ -444,10 +462,41 @@
}
},
"colorPicker": {
"mostUsedCustomColors": "많이 쓰이는 사용자 색상들",
"mostUsedCustomColors": "가장 많이 사용된 색상들",
"colors": "색상",
"shades": "색조",
"hexCode": "Hex 코드",
"noShades": "사용할 수 있는 색조가 없음"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "이미지로 내보내기",
"button": "이미지로 내보내기",
"description": "나중에 다시 불러올 수 있도록 화면 데이터를 이미지로 내보냅니다."
},
"saveToDisk": {
"title": "디스크에 저장",
"button": "디스크에 저장",
"description": "나중에 다시 불러올 수 있도록 화면 데이터를 내보냅니다."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Excalidraw+로 내보내기",
"description": "화면을 당신의 Excalidraw+ 작업 공간으로 저장합니다."
}
},
"modal": {
"loadFromFile": {
"title": "파일에서 불러오기",
"button": "파일에서 불러오기",
"description": "파일을 불러오면 <bold>현재 작성된 데이터를 덮어쓰게 됩니다</bold>.<br></br>다음 옵션 중 하나를 선택하여 작업물을 백업해 둘 수 있습니다."
},
"shareableLink": {
"title": "주소에서 불러오기",
"button": "컨텐츠를 덮어쓰기",
"description": "외부 작업물을 불러오면 <bold>현재 작성된 데이터를 덮어쓰게 됩니다</bold>.<br></br>다음 옵션 중 하나를 선택하여 작업물을 백업해 둘 수 있습니다."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "دەق لە چوارچێوەیەکدا بپێچە",
"link": {
"edit": "دەستکاریکردنی بەستەر",
"editEmbed": "",
"create": "دروستکردنی بەستەر",
"label": "بەستەر"
"createEmbed": "",
"label": "بەستەر",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "دەستکاری کردنی دێڕ",
@ -160,13 +164,16 @@
"darkMode": "دۆخی تاریک",
"lightMode": "دۆخی ڕووناک",
"zenMode": "دۆخی زێن",
"objectsSnapMode": "",
"exitZenMode": "بەجێهێشتنی دۆخی زێن",
"cancel": "هەڵوەشاندنەوە",
"clear": "خاوێنکردنەوە",
"remove": "لابردن",
"embed": "",
"publishLibrary": "بڵاوکردنەوە",
"submit": "پێشکەشکردن",
"confirm": "دوپاتکردنەوە"
"confirm": "دوپاتکردنەوە",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "ئەمە هەموو تابلۆکە خاوێن دەکاتەوە، دڵنیایت؟",
@ -196,6 +203,7 @@
"imageInsertError": "نەیتوانی وێنە داخڵ بکات. دواتر هەوڵ بدە",
"fileTooBig": "فایلەکە زۆر گەورەیە. زۆرترین قەبارەی ڕێگەپێدراو {{maxSize}}}.",
"svgImageInsertError": "نەیتوانی وێنەی SVG داخڵ بکات. نیشانەی ئێس ڤی جی نادروست دیارە.",
"failedToFetchImage": "",
"invalidSVGString": "ئێس ڤی جی نادروستە.",
"cannotResolveCollabServer": "ناتوانێت پەیوەندی بکات بە سێرڤەری کۆلاب. تکایە لاپەڕەکە دووبارە باربکەوە و دووبارە هەوڵ بدەوە.",
"importLibraryError": "نەیتوانی کتێبخانە بار بکات",
@ -206,6 +214,10 @@
"line2": "ئەمە ئەکرێ ببێتە هۆی تێکدانی <bold>دانە دەقییەکان</bold> لە وێنەکێشانەکانتدا.",
"line3": "ئێمە بە توندی پێشنیاری لەکارخستنی ئەم ڕێکخستنە دەکەین. بۆ لە کارخستنی دەتوانیت بەم <link>هەنگاوانە</link>دا بڕۆیت.",
"line4": "ئەگەر لەکارخستنی ئەم ڕێکخستنە نەبوە هۆی چاککردنەوەی پێشاندانی دانە دەقییەکان، تکایە <issueLink>کێشە</issueLink>یەک بکەرەوە لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لەسەر <discordLink>دیسکۆرد</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
"eraser": "سڕەر",
"frame": "ئامرازی چوارچێوە",
"embeddable": "",
"laser": "",
"hand": "دەست (ئامرازی پانکردن)",
"extraTools": "ئامرازی زیاتر"
},
@ -237,6 +251,7 @@
"linearElement": "کرتە بکە بۆ دەستپێکردنی چەند خاڵێک، ڕایبکێشە بۆ یەک هێڵ",
"freeDraw": "کرتە بکە و ڕایبکێشە، کاتێک تەواو بوویت دەست هەڵگرە",
"text": "زانیاری: هەروەها دەتوانیت دەق زیادبکەیت بە دوو کرتەکردن لە هەر شوێنێک لەگەڵ ئامڕازی دەستنیشانکردن",
"embeddable": "",
"text_selected": "دووجار کلیک بکە یان ENTER بکە بۆ دەستکاریکردنی دەق",
"text_editing": "بۆ تەواوکردنی دەستکاریکردنەکە Escape یان Ctrl/Cmd+ENTER بکە",
"linearElementMulti": "کلیک لەسەر کۆتا خاڵ بکە یان Escape یان Enter بکە بۆ تەواوکردن",
@ -252,7 +267,8 @@
"bindTextToElement": "بۆ زیادکردنی دەق enter بکە",
"deepBoxSelect": "CtrlOrCmd ڕابگرە بۆ هەڵبژاردنی قووڵ، و بۆ ڕێگریکردن لە ڕاکێشان",
"eraserRevert": "بۆ گەڕاندنەوەی ئەو توخمانەی کە بۆ سڕینەوە نیشانە کراون، Alt ڕابگرە",
"firefox_clipboard_write": "ئەم تایبەتمەندییە بە ئەگەرێکی زۆرەوە دەتوانرێت چالاک بکرێت بە ڕێکخستنی ئاڵای \"dom.events.asyncClipboard.clipboardItem\" بۆ \"true\". بۆ گۆڕینی ئاڵاکانی وێبگەڕ لە فایەرفۆکسدا، سەردانی لاپەڕەی \"about:config\" بکە."
"firefox_clipboard_write": "ئەم تایبەتمەندییە بە ئەگەرێکی زۆرەوە دەتوانرێت چالاک بکرێت بە ڕێکخستنی ئاڵای \"dom.events.asyncClipboard.clipboardItem\" بۆ \"true\". بۆ گۆڕینی ئاڵاکانی وێبگەڕ لە فایەرفۆکسدا، سەردانی لاپەڕەی \"about:config\" بکە.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "ناتوانرێ پێشبینین پیشان بدرێت",
@ -411,7 +427,9 @@
"fileSavedToFilename": "هەڵگیراوە بۆ {filename}",
"canvas": "تابلۆ",
"selection": "دەستنیشانکراوەکان",
"pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە"
"pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "ڕوون",
@ -449,5 +467,36 @@
"shades": "سێبەرەکان",
"hexCode": "کۆدی هێکس",
"noShades": "هیچ سێبەرێک بۆ ئەم ڕەنگە بەردەست نییە"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Redeguoti nuorodą",
"editEmbed": "",
"create": "Sukurti nuorodą",
"label": "Nuoroda"
"createEmbed": "",
"label": "Nuoroda",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "Tamsus režimas",
"lightMode": "Šviesus režimas",
"zenMode": "„Zen“ režimas",
"objectsSnapMode": "",
"exitZenMode": "Išeiti iš „Zen“ režimo",
"cancel": "Atšaukti",
"clear": "Išvalyti",
"remove": "Pašalinti",
"embed": "",
"publishLibrary": "Paskelbti",
"submit": "Pateikti",
"confirm": "Patvirtinti"
"confirm": "Patvirtinti",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "",
@ -196,6 +203,7 @@
"imageInsertError": "Nepyko įkelti paveiksliuko. Pabandyk vėliau...",
"fileTooBig": "Per didelis failas. Didžiausias leidžiamas dydis yra {{maxSize}}.",
"svgImageInsertError": "Nepavyko įtraukti SVG paveiksliuko. Panašu, jog SVG yra nevalidus.",
"failedToFetchImage": "",
"invalidSVGString": "Nevalidus SVG.",
"cannotResolveCollabServer": "Nepavyko prisijungti prie serverio bendradarbiavimui. Perkrauk puslapį ir pabandyk prisijungti dar kartą.",
"importLibraryError": "Nepavyko įkelti bibliotekos",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą",
"eraser": "Trintukas",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Paspaudimai sukurs papildomus taškus, nepertraukiamas tempimas sukurs liniją",
"freeDraw": "Spausk ir tempk, paleisk kai norėsi pabaigti",
"text": "Užuomina: tekstą taip pat galima pridėti bet kur su dvigubu pelės paspaudimu, kol parinkas žymėjimo įrankis",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Išsaugota į {filename}",
"canvas": "drobė",
"selection": "",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Permatoma",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "Ietilpināt tekstu figurā",
"link": {
"edit": "Rediģēt saiti",
"editEmbed": "",
"create": "Izveidot saiti",
"label": "Saite"
"createEmbed": "",
"label": "Saite",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Rediģēt līniju",
@ -160,13 +164,16 @@
"darkMode": "Tumšais režīms",
"lightMode": "Gaišais režīms",
"zenMode": "Zen režīms",
"objectsSnapMode": "",
"exitZenMode": "Pamest Zen režīmu",
"cancel": "Atcelt",
"clear": "Notīrīt",
"remove": "Noņemt",
"embed": "",
"publishLibrary": "Publicēt",
"submit": "Iesniegt",
"confirm": "Apstiprināt"
"confirm": "Apstiprināt",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Šī funkcija notīrīs visu tāfeli. Vai turpināt?",
@ -196,6 +203,7 @@
"imageInsertError": "Nevarēja ievietot attēlu. Mēģiniet vēlāk...",
"fileTooBig": "Datne ir par lielu. Lielākais atļautais izmērs ir {{maxSize}}.",
"svgImageInsertError": "Nevarēja ievietot SVG attēlu. Šķiet, ka SVG marķējums nav derīgs.",
"failedToFetchImage": "",
"invalidSVGString": "Nederīgs SVG.",
"cannotResolveCollabServer": "Nevarēja savienoties ar sadarbošanās serveri. Lūdzu, pārlādējiet lapu un mēģiniet vēlreiz.",
"importLibraryError": "Nevarēja ielādēt bibliotēku",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Pievienot/rediģēt atlasītās figūras saiti",
"eraser": "Dzēšgumija",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Roka (panoramēšanas rīks)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"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",
"embeddable": "",
"text_selected": "Dubultklikšķiniet vai spiediet ievades taustiņu, lai rediģētu tekstu",
"text_editing": "Spiediet iziešanas taustiņu vai CtrlOrCmd+ENTER, lai beigtu rediģēt",
"linearElementMulti": "Klikšķiniet uz pēdējā punkta vai spiediet izejas vai ievades taustiņu, lai pabeigtu",
@ -252,7 +267,8 @@
"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": "Š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."
"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.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Nevar rādīt priekšskatījumu",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Saglabāts kā {filename}",
"canvas": "tāfeli",
"selection": "atlasi",
"pasteAsSingleElement": "Izmantojiet {{shortcut}}, lai ielīmētu kā jaunu elementu, vai ielīmētu esošā teksta lauciņā"
"pasteAsSingleElement": "Izmantojiet {{shortcut}}, lai ielīmētu kā jaunu elementu, vai ielīmētu esošā teksta lauciņā",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Caurspīdīgs",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "मजकूर कंटेनर मधे मोडून दाखवा",
"link": {
"edit": "दुवा संपादन",
"editEmbed": "",
"create": "दुवा तयार करा",
"label": "दुवा"
"createEmbed": "",
"label": "दुवा",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "रेघ संपादन",
@ -160,13 +164,16 @@
"darkMode": "अंधार स्थिथि",
"lightMode": "उजेड स्थिति",
"zenMode": "ध्यानग्र स्थिति",
"objectsSnapMode": "वस्तूंपासून पकड़ा",
"exitZenMode": "ध्यानग्र स्थितितून बाहेर",
"cancel": "रद्द",
"clear": "स्वछ",
"remove": "हटवा",
"embed": "",
"publishLibrary": "प्रकाशित करा",
"submit": "जमा करा",
"confirm": "पुष्टि करा"
"confirm": "पुष्टि करा",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "पटल स्वच्छ होणार, तुम्हाला खात्री आहे का?",
@ -196,6 +203,7 @@
"imageInsertError": "प्रतिमा आत घालता येत नाही. नंतर पुन्हा प्रयत्न करा...",
"fileTooBig": "फाइल फार मोठी आहे. आकाराची कमाल परवानगी {{maxSize}} आहे.",
"svgImageInsertError": "एस-वी-जी प्रतिमा आत घालवू शकलो नाही. एस-वी-जी-मार्क-अप यंत्र अयोग्य आहे.",
"failedToFetchImage": "",
"invalidSVGString": "अयोग्य एस-वी-जी.",
"cannotResolveCollabServer": "कॉलेब-सर्वर हे पोहोचत नाही आहे. पान परत लोड करायचा प्रयत्न करावे.",
"importLibraryError": "संग्रह प्रतिस्थापित नाही करता आला",
@ -206,6 +214,10 @@
"line2": "हे तुमच्या चित्रांच्या <bold>पाठ तत्वांनां </bold> खंडित करू शकतात.",
"line3": "तुम्हाला आमच्या कड़ून खूप आग्रह आहे की हे सेटिंग्स मधले चयन नका करु. <link>हे अनुक्रम </link> हे कसे करावे हे दाखवु शकते.",
"line4": "ही सेटिंग अक्षम करूनही पृष्ठ योग्यरित्या प्रदर्शित होत नसल्यास, आमच्या GitHub वर <issueLink>समस्या</issueLink> सबमिट करा, किव्हा <discordLink>डिस्कॉर्ड</discordLink> वर आम्हाला लिहा"
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा",
"eraser": "खोड रबर",
"frame": "",
"embeddable": "",
"laser": "लेसर टॉर्च",
"hand": "हात ( सरकवण्या चे उपकरण)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "अनेक बिंदु साठी क्लिक करा, रेघे साठी ड्रैग करा",
"freeDraw": "क्लिक आणि ड्रैग करा, झालं तेव्हा सोडा",
"text": "टीप: तुम्हीं निवड यंत्रानी कोठेही दुहेरी क्लिक करून टेक्स्ट जोडू शकता",
"embeddable": "",
"text_selected": "लेखन संपादन साठी दुहेरी क्लिक करा किव्हा एंटर दाबा",
"text_editing": "संपादन संपवायचं असल्यास एस्केप दाबा किव्हा कंट्रोल या कम्मांड बरोबार एंटर दाबा",
"linearElementMulti": "शेवटच्या बिंदु वर क्लिक करा किव्हा एस्केप या एंटर दाबा",
@ -252,7 +267,8 @@
"bindTextToElement": "मजकूर जोडण्यासाठी एंटर की दाबा",
"deepBoxSelect": "खोल निवड ह्या साठी कंट्रोल किव्हा कमांड दाबून ठेवा, आणि बाहेर खेचणे वाचवण्या साठी पण",
"eraserRevert": "खोडण्या साठी घेतलेल्या वस्तु ना घेण्या साठी Alt दाबून ठेवावे",
"firefox_clipboard_write": "हे वैशिष्ट्य \"dom.events.asyncClipboard.clipboardItem\" फ्लॅग \"सत्य\" वर सेट करून शक्यतो सक्षम केले जाऊ शकते. Firefox मध्ये ब्राउझर फ्लॅग बदलण्यासाठी, \"about:config\" पृष्ठावर जा."
"firefox_clipboard_write": "हे वैशिष्ट्य \"dom.events.asyncClipboard.clipboardItem\" फ्लॅग \"सत्य\" वर सेट करून शक्यतो सक्षम केले जाऊ शकते. Firefox मध्ये ब्राउझर फ्लॅग बदलण्यासाठी, \"about:config\" पृष्ठावर जा.",
"disableSnapping": "स्नैपिंग अक्षम करण्या साठी CtrlOrCmd दाबून ठेवा"
},
"canvasError": {
"cannotShowPreview": "पूर्वावलोकन दाखवू शकत नाही",
@ -411,7 +427,9 @@
"fileSavedToFilename": "{filename} मधे जतन झाली",
"canvas": "पटल",
"selection": "निवड",
"pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा"
"pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "पारदर्शक",
@ -449,5 +467,36 @@
"shades": "रंगछटा",
"hexCode": "हेक्स कोड",
"noShades": "ह्या रंगाच्या छटा उपलब्ध नाहित"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "छवि स्वरूपे निर्यात करा",
"button": "छवि स्वरूपे निर्यात करा",
"description": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुन तो डेटा नंतर परत आणु शकता."
},
"saveToDisk": {
"title": "डिस्क मधे जतन करा",
"button": "डिस्क मधे जतन करा",
"description": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुन तो डेटा नंतर परत आणु शकता."
},
"excalidrawPlus": {
"title": "एक्षकालीड्रॉ +",
"button": "एक्षकाली ड्रॉ+ मधे निर्यात करा",
"description": "दृष्य तुमच्या एक्षकालीड्रॉ+ चा कार्यस्थल मधे जतन करा."
}
},
"modal": {
"loadFromFile": {
"title": "फ़ाइल मधुन लोड करा",
"button": "फ़ाइल मधुन लोड करा",
"description": "फ़ाइल मधुन लोड केल्या वर ते <bold>तुमचा सध्याचा कामा ठिकाणि एईल </bold><br></br> तुम्हीं तुमचं चित्र एकाधं खाली दिलेलं विकल्प निवडुन पहले सुरक्षीत करु शकता."
},
"shareableLink": {
"title": "लिंक पासून लोड करा",
"button": "माझ सध्याचे कार्य बदला",
"description": "बाहरी चित्र लोड केल्या वर ते <bold>तुमचा सध्याचा कामा ठिकाणि एईल </bold><br></br> तुम्हीं तुमचं चित्र एकाधं खाली दिलेलं विकल्प निवडुन पहले सुरक्षीत करु शकता."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "",
"editEmbed": "",
"create": "",
"label": ""
"createEmbed": "",
"label": "",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "",
"lightMode": "",
"zenMode": "",
"objectsSnapMode": "",
"exitZenMode": "ဇင်မြင်ကွင်းမှထွက်",
"cancel": "",
"clear": "",
"remove": "",
"embed": "",
"publishLibrary": "",
"submit": "",
"confirm": ""
"confirm": "",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "ကားချပ်တစ်ခုလုံးရှင်းလင်းပါတော့မည်။ အတည်ပြုပါ။",
@ -196,6 +203,7 @@
"imageInsertError": "",
"fileTooBig": "",
"svgImageInsertError": "",
"failedToFetchImage": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "အမှတ်များချမှတ်ရေးဆွဲရန်ကလစ်နှိပ်ပါ၊ မျဉ်းတစ်ကြောင်းတည်းအတွက် တရွတ်ဆွဲပါ။",
"freeDraw": "ကလစ်နှိပ်၍ တရွတ်ဆွဲပါ၊ ပြီးလျှင်လွှတ်ပါ။",
"text": "မှတ်ချက်။ ။မည်သည့်ကိရိယာရွေးထားသည်ဖြစ်စေ ကလစ်နှစ်ချက်နှိပ်၍စာသားထည့်နိုင်သည်",
"embeddable": "",
"text_selected": "",
"text_editing": "",
"linearElementMulti": "နောက်ဆုံးအမှတ်ပေါ်တွင်ကလစ်နှိပ်ခြင်း၊ Escape (သို့) Enter နှိပ်ခြင်းတို့ဖြင့်အဆုံးသတ်နိုင်",
@ -252,7 +267,8 @@
"bindTextToElement": "",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "နမူနာမပြသနိုင်ပါ",
@ -411,7 +427,9 @@
"fileSavedToFilename": "",
"canvas": "",
"selection": "",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "La tekst flyte i en beholder",
"link": {
"edit": "Rediger lenke",
"editEmbed": "Rediger lenke og bygg inn",
"create": "Opprett lenke",
"label": "Lenke"
"createEmbed": "Opprett lenke og bygg inn",
"label": "Lenke",
"labelEmbed": "Lenk & bygg inn",
"empty": "Ingen lenke er valgt"
},
"lineEditor": {
"edit": "Rediger linje",
@ -160,13 +164,16 @@
"darkMode": "Mørk modus",
"lightMode": "Lys modus",
"zenMode": "Zen-modus",
"objectsSnapMode": "",
"exitZenMode": "Avslutt zen-modus",
"cancel": "Avbryt",
"clear": "Tøm",
"remove": "Fjern",
"embed": "Slå av/på innebygging",
"publishLibrary": "Publiser",
"submit": "Send inn",
"confirm": "Bekreft"
"confirm": "Bekreft",
"embeddableInteractionButton": "Klikk for å samhandle"
},
"alerts": {
"clearReset": "Dette vil tømme lerretet. Er du sikker?",
@ -196,6 +203,7 @@
"imageInsertError": "Kunne ikke sette inn bildet. Prøv igjen senere...",
"fileTooBig": "Filen er for stor. Maksimal tillatt størrelse er {{maxSize}}.",
"svgImageInsertError": "Kunne ikke sette inn SVG-bilde. SVG-koden ser ugyldig ut.",
"failedToFetchImage": "",
"invalidSVGString": "Ugyldig SVG.",
"cannotResolveCollabServer": "Kunne ikke koble til samarbeidsserveren. Vennligst oppdater siden og prøv på nytt.",
"importLibraryError": "Kunne ikke laste bibliotek",
@ -206,6 +214,10 @@
"line2": "Dette kan resultere i å bryte <bold>tekst-elementene</bold> i tegningene.",
"line3": "Vi anbefaler på det sterkeste å deaktivere denne innstillingen. Du kan følge <link>disse trinnene</link> om hvordan du gjør det.",
"line4": "Hvis deaktivering av denne innstillingen ikke fikser visningen av tekstelementer, vennligst åpne en <issueLink>sak</issueLink> på vår GitHub, eller skriv oss på <discordLink>Discord</discordLink>"
},
"libraryElementTypeError": {
"embeddable": "Innebygde elementer kan ikke legges til i biblioteket.",
"image": "Støtte for å legge til bilder i biblioteket kommer snart!"
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Legg til / oppdater link for en valgt figur",
"eraser": "Viskelær",
"frame": "Rammeverktøy",
"embeddable": "Nettinnbygging",
"laser": "",
"hand": "Hånd (panoreringsverktøy)",
"extraTools": "Flere verktøy"
},
@ -237,6 +251,7 @@
"linearElement": "Klikk for å starte linje med flere punkter, eller dra for en enkel linje",
"freeDraw": "Klikk og dra, slipp når du er ferdig",
"text": "Tips: du kan også legge til tekst ved å dobbeltklikke hvor som helst med utvalgsverktøyet",
"embeddable": "Klikk og dra for å opprette en nettside innebygd",
"text_selected": "Dobbeltklikk eller trykk ENTER for å redigere tekst",
"text_editing": "Trykk Escape eller Ctrl/Cmd+Enter for å fullføre redigering",
"linearElementMulti": "Klikk på siste punkt eller trykk Escape eller Enter for å fullføre",
@ -252,7 +267,8 @@
"bindTextToElement": "Trykk Enter for å legge til tekst",
"deepBoxSelect": "Hold CTRL/CMD for å markere dypt og forhindre flytting",
"eraserRevert": "Hold Alt for å reversere elementene merket for sletting",
"firefox_clipboard_write": "Denne funksjonen kan sannsynligvis aktiveres ved å sette \"dom.events.asyncClipboard.clipboardItem\" flagget til \"true\". For å endre nettleserens flagg i Firefox, besøk \"about:config\"-siden."
"firefox_clipboard_write": "Denne funksjonen kan sannsynligvis aktiveres ved å sette \"dom.events.asyncClipboard.clipboardItem\" flagget til \"true\". For å endre nettleserens flagg i Firefox, besøk \"about:config\"-siden.",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Kan ikke vise forhåndsvisning",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Lagret til {filename}",
"canvas": "lerret",
"selection": "utvalg",
"pasteAsSingleElement": "Bruk {{shortcut}} for å lime inn som ett enkelt element,\neller lim inn i en eksisterende tekstbehandler"
"pasteAsSingleElement": "Bruk {{shortcut}} for å lime inn som ett enkelt element,\neller lim inn i en eksisterende tekstbehandler",
"unableToEmbed": "Innbygging av denne nettadressen er ikke tillatt. Oppret en sak på GitHub for å be om url-hvitelisting",
"unrecognizedLinkFormat": "Linken du bygget inn samsvarer ikke med det forventede formatet. Prøv å lime inn \"bygg inn\"-strengen fra kildesiden"
},
"colors": {
"transparent": "Gjennomsiktig",
@ -449,5 +467,36 @@
"shades": "Toner",
"hexCode": "Heksadesimal kode",
"noShades": "Ingen toner tilgjengelig for denne fargen"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "Eksporter som bilde",
"button": "Eksporter som bilde",
"description": "Eksporter scene-dataene til en fil som du kan importere fra senere."
},
"saveToDisk": {
"title": "Lagre til disk",
"button": "Lagre til disk",
"description": "Eksporter scene-dataene til en fil som du kan importere fra senere."
},
"excalidrawPlus": {
"title": "Excalidraw+",
"button": "Eksporter til Excalidraw+",
"description": "Lagre scenen til ditt Excalidraw+-arbeidsområde."
}
},
"modal": {
"loadFromFile": {
"title": "Last inn fra fil",
"button": "Last inn fra fil",
"description": "Å laste fra en fil vil <bold>erstatte ditt eksisterende innhold</bold>.<br></br>Du kan sikkerhetskopiere tegningen din først ved å bruke en av valgene under."
},
"shareableLink": {
"title": "Last inn fra lenke",
"button": "Erstatt innholdet mitt",
"description": "Lasting av ekstern tegning vil <bold>erstatte ditt eksisterende innhold</bold>.<br></br>Du kan sikkerhetskopiere tegningen din først ved å bruke en av valgene nedenfor."
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Wijzig link",
"editEmbed": "Link bewerken & insluiten",
"create": "Maak link",
"label": "Link"
"createEmbed": "Link maken en insluiten",
"label": "Link",
"labelEmbed": "Link toevoegen & insluiten",
"empty": "Er is geen link ingesteld"
},
"lineEditor": {
"edit": "Bewerk regel",
@ -160,13 +164,16 @@
"darkMode": "Donkere modus",
"lightMode": "Lichte modus",
"zenMode": "Zen modus",
"objectsSnapMode": "",
"exitZenMode": "Verlaat zen modus",
"cancel": "Annuleren",
"clear": "Wissen",
"remove": "Verwijderen",
"embed": "Insluiten in-/uitschakelen",
"publishLibrary": "Publiceren",
"submit": "Versturen",
"confirm": "Bevestigen"
"confirm": "Bevestigen",
"embeddableInteractionButton": "Klik voor interactie"
},
"alerts": {
"clearReset": "Dit zal het hele canvas verwijderen. Weet je het zeker?",
@ -196,6 +203,7 @@
"imageInsertError": "Afbeelding invoegen mislukt. Probeer het later opnieuw...",
"fileTooBig": "Bestand is te groot. Maximale grootte is {{maxSize}}.",
"svgImageInsertError": "Kon geen SVG-afbeelding invoegen. De SVG-opmaak ziet er niet geldig uit.",
"failedToFetchImage": "",
"invalidSVGString": "Ongeldige SVG.",
"cannotResolveCollabServer": "Kan geen verbinding maken met de collab server. Herlaad de pagina en probeer het opnieuw.",
"importLibraryError": "Kon bibliotheek niet laden",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "Ingesloten elementen kunnen niet worden toegevoegd aan de bibliotheek.",
"image": "Ondersteuning voor het toevoegen van afbeeldingen aan de bibliotheek komt binnenkort!"
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Link toevoegen / bijwerken voor een geselecteerde vorm",
"eraser": "Gum",
"frame": "",
"embeddable": "Web insluiten",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Klik om meerdere punten te starten, sleep voor één lijn",
"freeDraw": "Klik en sleep, laat los als je klaar bent",
"text": "Tip: je kunt tekst toevoegen door ergens dubbel te klikken met de selectietool",
"embeddable": "Klink-sleep om een website-insluiting te maken",
"text_selected": "Dubbelklik of druk op ENTER om tekst te bewerken",
"text_editing": "Druk op Escape of CtrlOrCmd+ENTER om het bewerken te voltooien",
"linearElementMulti": "Klik op het laatste punt of druk op Escape of Enter om te stoppen",
@ -252,7 +267,8 @@
"bindTextToElement": "Druk op enter om tekst toe te voegen",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Kan voorbeeld niet tonen",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Opgeslagen als {filename}",
"canvas": "canvas",
"selection": "selectie",
"pasteAsSingleElement": "Gebruik {{shortcut}} om te plakken als een enkel element,\nof plak in een bestaande teksteditor"
"pasteAsSingleElement": "Gebruik {{shortcut}} om te plakken als een enkel element,\nof plak in een bestaande teksteditor",
"unableToEmbed": "Het insluiten van deze url is momenteel niet toegestaan. Zet een probleem op GitHub om de URL op de whitelist te zetten",
"unrecognizedLinkFormat": "De link die u hebt ingesloten komt niet overeen met het verwachte formaat. Probeer de 'embed' string van de bronsite te plakken"
},
"colors": {
"transparent": "Transparant",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Rediger lenke",
"editEmbed": "",
"create": "Lag lenke",
"label": "Lenke"
"createEmbed": "",
"label": "Lenke",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "",
@ -160,13 +164,16 @@
"darkMode": "Mørk modus",
"lightMode": "Lys modus",
"zenMode": "Zen-modus",
"objectsSnapMode": "",
"exitZenMode": "Avslutt zen-modus",
"cancel": "Avbryt",
"clear": "Tøm",
"remove": "Fjern",
"embed": "",
"publishLibrary": "Publiser",
"submit": "Send inn",
"confirm": "Stadfest"
"confirm": "Stadfest",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Dette vil tømme lerretet. Er du sikker?",
@ -196,6 +203,7 @@
"imageInsertError": "Kunne ikkje sette inn biletet. Prøv igjen seinare...",
"fileTooBig": "Fila er for stor. Maksimal tillate storleik er {{maxSize}}.",
"svgImageInsertError": "Kunne ikkje sette inn SVG-biletet. SVG-koden ser ugyldig ut.",
"failedToFetchImage": "",
"invalidSVGString": "Ugyldig SVG.",
"cannotResolveCollabServer": "Kunne ikkje kople til samarbeidsserveren. Ver vennleg å oppdatere inn sida og prøv på nytt.",
"importLibraryError": "",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Legg til/ oppdater lenke til valt figur",
"eraser": "Viskelêr",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Klikk for å starte linje med fleire punkt, eller drag for ei enkel linje",
"freeDraw": "Klikk og drag, slepp når du er ferdig",
"text": "Tips: du kan òg leggje til tekst ved å dobbeltklikke kor som helst med utvalgsverktyet",
"embeddable": "",
"text_selected": "Dobbelklikk eller trykk ENTER for å redigere teksta",
"text_editing": "Trykk Escape eller CtrlOrCmd+ENTER for å fullføre redigeringa",
"linearElementMulti": "Klikk på siste punkt eller trykk Escape eller Enter for å fullføre",
@ -252,7 +267,8 @@
"bindTextToElement": "Trykk på enter for å legge til tekst",
"deepBoxSelect": "Hald inne Ctrl / Cmd for å velje djupt, og forhindre flytting",
"eraserRevert": "Hald inne Alt for å reversere markering av element for sletting",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Kan ikkje vise førehandsvising",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Lagra som {filename}",
"canvas": "lerret",
"selection": "val",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Gjennomsiktig",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "",
"link": {
"edit": "Modificar lo ligam",
"editEmbed": "",
"create": "Crear un ligam",
"label": "Ligam"
"createEmbed": "",
"label": "Ligam",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "Modificar la linha",
@ -160,13 +164,16 @@
"darkMode": "Mòde escur",
"lightMode": "Mòde clar",
"zenMode": "Mòde escur",
"objectsSnapMode": "",
"exitZenMode": "Sortir del mòde zen",
"cancel": "Anullar",
"clear": "Escafar",
"remove": "Tirar",
"embed": "",
"publishLibrary": "Publicar",
"submit": "Enviar",
"confirm": "Confirmar"
"confirm": "Confirmar",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "Aquò suprimirà lo canabàs complèt. O volètz vertadièrament?",
@ -196,6 +203,7 @@
"imageInsertError": "Insercion dimatge impossibla. Tornatz ensajar mai tard...",
"fileTooBig": "Fichièr tròp pesuc. La talha maximala autorizada es {{maxSize}}.",
"svgImageInsertError": "Insercion dimatge SVG impossibla. Las balisas SVG semblan invalidas.",
"failedToFetchImage": "",
"invalidSVGString": "SVG invalid.",
"cannotResolveCollabServer": "Connexion impossibla al servidor collab. Mercés de recargar la pagina e tornar ensajar.",
"importLibraryError": "Impossible de cargar la bibliotèca",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "Apondre/Actualizar lo ligam per una fòrma seleccionada",
"eraser": "Goma",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "Man (aisina de desplaçament de la vista)",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "Clicatz per començar mantun punt, lisatz per una sola linha",
"freeDraw": "Clicatz e lisatz, relargatz un còp acabat",
"text": "Astúcia: podètz tanben apondre de tèxt en doble clicant ont que siá amb laisina de seleccion",
"embeddable": "",
"text_selected": "Clicatz dos còps o quichatz ENTRADA per modificar lo tèxt",
"text_editing": "Quichatz ESCAPAR o CtrlOrCmd+ENTRADA per acabar la modificacion",
"linearElementMulti": "Clicatz sul darrièr punt o quichatz Ecap o Entrada per acabar",
@ -252,7 +267,8 @@
"bindTextToElement": "Quichatz Entrada per apondre de tèxte",
"deepBoxSelect": "Gardar CtrlOCmd per una seleccion gropada e empachar lo desplaçament",
"eraserRevert": "Tenètz quichat Alt per anullar los elements marcats per supression",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "Afichatge impossible de lapercebut",
@ -411,7 +427,9 @@
"fileSavedToFilename": "Enregistrat jos {filename}",
"canvas": "canabàs",
"selection": "seleccion",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "Transparéncia",
@ -449,5 +467,36 @@
"shades": "",
"hexCode": "",
"noShades": ""
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

View File

@ -109,8 +109,12 @@
"createContainerFromText": "ਪਾਠ ਨੂੰ ਕੰਟੇਨਰ ਵਿੱਚ ਇਕੱਠਾ ਕਰੋ",
"link": {
"edit": "ਕੜੀ ਸੋਧੋ",
"editEmbed": "",
"create": "ਕੜੀ ਬਣਾਓ",
"label": "ਕੜੀ"
"createEmbed": "",
"label": "ਕੜੀ",
"labelEmbed": "",
"empty": ""
},
"lineEditor": {
"edit": "ਪੰਕਤੀ ਸੋਧੋ",
@ -160,13 +164,16 @@
"darkMode": "ਡਾਰਕ ਮੋਡ",
"lightMode": "ਲਾਇਟ ਮੋਡ",
"zenMode": "ਜ਼ੈੱਨ ਮੋਡ",
"objectsSnapMode": "",
"exitZenMode": "ਜ਼ੈੱਨ ਮੋਡ 'ਚੋਂ ਬਾਹਰ ਨਿਕਲੋ",
"cancel": "ਰੱਦ ਕਰੋ",
"clear": "ਸਾਫ਼ ਕਰੋ",
"remove": "ਹਟਾਓ",
"embed": "",
"publishLibrary": "ਪ੍ਰਕਾਸ਼ਤ ਕਰੋ",
"submit": "ਜਮ੍ਹਾ ਕਰਵਾਓ",
"confirm": "ਪੁਸ਼ਟੀ ਕਰੋ"
"confirm": "ਪੁਸ਼ਟੀ ਕਰੋ",
"embeddableInteractionButton": ""
},
"alerts": {
"clearReset": "ਇਹ ਸਾਰਾ ਕੈਨਵਸ ਸਾਫ ਕਰ ਦੇਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਪੱਕਾ ਇੰਝ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
@ -196,6 +203,7 @@
"imageInsertError": "ਚਿੱਤਰ ਸ਼ਾਮਲ ਨਹੀਂ ਜਾ ਸਕਿਆ, ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ...",
"fileTooBig": "ਫਾਈਲ ਬਹੁਤ ਜ਼ਿਆਦਾ ਵੱਡੀ ਹੈ। ਵੱਧ-ਤੋਂ-ਵੱਧ ਪ੍ਰਵਾਨਤ ਅਕਾਰ {{maxSize}} ਹੈ।",
"svgImageInsertError": "SVG ਤਸਵੀਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕਰ ਸਕੇ। SVG ਮਾਰਕ-ਅੱਪ ਨਜਾਇਜ਼ ਲੱਗ ਰਿਹਾ ਹੈ।",
"failedToFetchImage": "",
"invalidSVGString": "SVG ਨਜਾਇਜ਼ ਹੈ।",
"cannotResolveCollabServer": "",
"importLibraryError": "ਲਾਇਬ੍ਰੇਰੀ ਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ",
@ -206,6 +214,10 @@
"line2": "",
"line3": "",
"line4": ""
},
"libraryElementTypeError": {
"embeddable": "",
"image": ""
}
},
"toolBar": {
@ -224,6 +236,8 @@
"link": "",
"eraser": "ਰਬੜ",
"frame": "",
"embeddable": "",
"laser": "",
"hand": "",
"extraTools": ""
},
@ -237,6 +251,7 @@
"linearElement": "ਇੱਕ ਤੋਂ ਜ਼ਿਆਦਾ ਬਿੰਦੂਆਂ ਲਈ ਕਲਿੱਕ ਕਰਕੇ ਸ਼ੁਰੂਆਤ ਕਰੋ, ਇਕਹਿਰੀ ਲਕੀਰ ਲਈ ਘਸੀਟੋ",
"freeDraw": "ਕਲਿੱਕ ਕਰਕੇ ਘਸੀਟੋ, ਪੂਰਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿਉ",
"text": "ਨੁਸਖਾ: ਤੁਸੀਂ ਚੋਣਕਾਰ ਸੰਦ ਰਾਹੀਂ ਕਿਤੇ ਵੀ ਡਬਲ-ਕਲਿੱਕ ਕਰਕੇ ਵੀ ਪਾਠ ਜੋੜ ਸਕਦੇ ਹੋ",
"embeddable": "",
"text_selected": "ਪਾਠ ਨੂੰ ਸੋਧਣ ਲਈ ਡਬਲ-ਕਲਿੱਕ ਕਰੋ ਜਾਂ ਐਂਟਰ ਦਬਾਓ",
"text_editing": "ਸੋਧ ਮੁਕੰਮਲ ਕਰਨ ਲਈ ਐਸਕੇਪ (Esc) ਜਾਂ Ctrl-ਜਾਂ-Cmd+ਐਂਟਰ (enter) ਦਬਾਓ",
"linearElementMulti": "ਮੁਕੰਮਲ ਕਰਨ ਲਈ ਆਖਰੀ ਬਿੰਦੂ 'ਤੇ ਕਲਿੱਕ ਕਰੋ ਜਾਂ ਇਸਕੇਪ ਜਾਂ ਐਂਟਰ ਦਬਾਓ",
@ -252,7 +267,8 @@
"bindTextToElement": "ਪਾਠ ਜੋੜਨ ਲਈ ਐੰਟਰ ਦਬਾਓ",
"deepBoxSelect": "",
"eraserRevert": "",
"firefox_clipboard_write": ""
"firefox_clipboard_write": "",
"disableSnapping": ""
},
"canvasError": {
"cannotShowPreview": "ਝਲਕ ਨਹੀਂ ਦਿਖਾ ਸਕਦੇ",
@ -411,7 +427,9 @@
"fileSavedToFilename": "{filename} ਵਿੱਚ ਸਾਂਭੀ",
"canvas": "ਕੈਨਵਸ",
"selection": "ਚੋਣ",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "",
"unableToEmbed": "",
"unrecognizedLinkFormat": ""
},
"colors": {
"transparent": "ਪਾਰਦਰਸ਼ੀ",
@ -449,5 +467,36 @@
"shades": "ਸ਼ੇਡਾਂ",
"hexCode": "ਹੈਕਸ ਕੋਡ",
"noShades": "ਇਸ ਰੰਗ ਦੀ ਕੋਈ ਸ਼ੇਡ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"
},
"overwriteConfirm": {
"action": {
"exportToImage": {
"title": "",
"button": "",
"description": ""
},
"saveToDisk": {
"title": "",
"button": "",
"description": ""
},
"excalidrawPlus": {
"title": "",
"button": "",
"description": ""
}
},
"modal": {
"loadFromFile": {
"title": "",
"button": "",
"description": ""
},
"shareableLink": {
"title": "",
"button": "",
"description": ""
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More