kritzel-stencil 0.3.16 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +50 -0
- package/dist/cjs/index-Xav9JFHg.js +2 -2
- package/dist/cjs/index.cjs.js +7 -1
- package/dist/cjs/{kritzel-active-users_42.cjs.entry.js → kritzel-active-users_44.cjs.entry.js} +710 -145
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{schema.constants-DJQTjcy7.js → schema.constants-DrHO_CYF.js} +1169 -171
- package/dist/cjs/stencil.cjs.js +1 -1
- package/dist/collection/classes/core/core.class.js +24 -0
- package/dist/collection/classes/handlers/context-menu.handler.js +24 -2
- package/dist/collection/classes/managers/license.manager.js +285 -0
- package/dist/collection/classes/managers/localization.manager.js +189 -0
- package/dist/collection/classes/objects/custom-element.class.js +2 -0
- package/dist/collection/classes/objects/group.class.js +7 -2
- package/dist/collection/classes/objects/image.class.js +10 -7
- package/dist/collection/classes/objects/line.class.js +3 -0
- package/dist/collection/classes/objects/path.class.js +13 -12
- package/dist/collection/classes/objects/selection-group.class.js +7 -2
- package/dist/collection/classes/objects/shape.class.js +3 -0
- package/dist/collection/classes/objects/text.class.js +4 -1
- package/dist/collection/classes/registries/icon-registry.class.js +1 -0
- package/dist/collection/classes/tools/brush-tool.class.js +1 -1
- package/dist/collection/collection-manifest.json +3 -1
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +16 -0
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +462 -60
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +446 -16
- package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.css +29 -0
- package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.js +83 -0
- package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
- package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
- package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
- package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
- package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
- package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +2 -1
- package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
- package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
- package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
- package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
- package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
- package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
- package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
- package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
- package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
- package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +2 -1
- package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
- package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
- package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +41 -6
- package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +36 -1
- package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +36 -1
- package/dist/collection/components/ui/kritzel-export/kritzel-export.js +44 -7
- package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
- package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +36 -1
- package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +108 -14
- package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +38 -3
- package/dist/collection/components/ui/kritzel-tool-config/kritzel-tool-config.js +38 -3
- package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +36 -1
- package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +38 -3
- package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.css +72 -0
- package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.js +173 -0
- package/dist/collection/constants/engine.constants.js +2 -0
- package/dist/collection/constants/license.constants.js +25 -0
- package/dist/collection/constants/version.js +1 -1
- package/dist/collection/helpers/localization.helper.js +25 -0
- package/dist/collection/helpers/math.helper.js +3 -0
- package/dist/collection/helpers/svg-export.helper.js +223 -26
- package/dist/collection/index.js +13 -0
- package/dist/collection/interfaces/localization.interface.js +1 -0
- package/dist/collection/locales/de-locale.js +119 -0
- package/dist/collection/locales/en-locale.js +120 -0
- package/dist/collection/locales/fr-locale.js +119 -0
- package/dist/collection/themes/dark-theme.js +18 -0
- package/dist/collection/themes/light-theme.js +18 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-active-users.js +1 -1
- package/dist/components/kritzel-avatar.js +1 -1
- package/dist/components/kritzel-awareness-cursors.js +1 -1
- package/dist/components/kritzel-back-to-content.js +1 -1
- package/dist/components/kritzel-brush-style.js +1 -1
- package/dist/components/kritzel-button.js +1 -1
- package/dist/components/kritzel-color-palette.js +1 -1
- package/dist/components/kritzel-color.js +1 -1
- package/dist/components/kritzel-context-menu.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-current-user-dialog.js +1 -1
- package/dist/components/kritzel-current-user.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-export.js +1 -1
- package/dist/components/kritzel-font-size.js +1 -1
- package/dist/components/kritzel-font.js +1 -1
- package/dist/components/kritzel-icon.js +1 -1
- package/dist/components/kritzel-input.js +1 -1
- package/dist/components/kritzel-login-dialog.js +1 -1
- package/dist/components/kritzel-master-detail.js +1 -1
- package/dist/components/kritzel-menu-item.js +1 -1
- package/dist/components/kritzel-menu.js +1 -1
- package/dist/components/kritzel-more-menu.js +1 -1
- package/dist/components/kritzel-numeric-input.js +1 -1
- package/dist/components/kritzel-opacity-slider.js +1 -1
- package/dist/components/kritzel-pill-tabs.js +1 -1
- package/dist/components/kritzel-portal.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-share-dialog.js +1 -1
- package/dist/components/kritzel-slide-toggle.js +1 -1
- package/dist/components/kritzel-split-button.js +1 -1
- package/dist/components/kritzel-stroke-size.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/kritzel-tooltip.js +1 -1
- package/dist/components/kritzel-utility-panel.js +1 -1
- package/dist/components/kritzel-watermark.d.ts +11 -0
- package/dist/components/kritzel-watermark.js +1 -0
- package/dist/components/kritzel-workspace-manager.js +1 -1
- package/dist/components/kritzel-zoom-panel.d.ts +11 -0
- package/dist/components/kritzel-zoom-panel.js +1 -0
- package/dist/components/{p-B5xxfwKF.js → p-3HxnBrCM.js} +1 -1
- package/dist/components/p-6RjeGuvH.js +1 -0
- package/dist/components/p-7NsK0uHu.js +1 -0
- package/dist/components/{p-dcAernE1.js → p-BCNyR5Sw.js} +1 -1
- package/dist/components/{p-C2SX-XRr.js → p-BG6hOSrm.js} +1 -1
- package/dist/components/p-BKJSh8qQ.js +1 -0
- package/dist/components/{p-SptaSMno.js → p-BKvHg9cv.js} +1 -1
- package/dist/components/p-Bc55X65h.js +1 -0
- package/dist/components/p-BpnIvNvq.js +1 -0
- package/dist/components/p-BvRrA4hN.js +1 -0
- package/dist/components/{p-B2w8X7vn.js → p-BxpKq94F.js} +1 -1
- package/dist/components/{p-BFoK4W--.js → p-Bzv9Px8v.js} +1 -1
- package/dist/components/{p-COLHjboZ.js → p-C9HGoDHE.js} +1 -1
- package/dist/components/p-CEnEDaix.js +1 -0
- package/dist/components/p-CIcLzcfA.js +1 -0
- package/dist/components/p-CPtDfadX.js +1 -0
- package/dist/components/p-C_fKgKHu.js +9 -0
- package/dist/components/p-CdR76C4L.js +1 -0
- package/dist/components/p-Cu9KYyoq.js +1 -0
- package/dist/components/p-CyqRcqsO.js +1 -0
- package/dist/components/{p-UoPj5QjH.js → p-DDkmsPpV.js} +1 -1
- package/dist/components/{p-D-sRVAbQ.js → p-DI4vQRE3.js} +1 -1
- package/dist/components/{p-CJOhfMU5.js → p-DNdXJp8F.js} +1 -1
- package/dist/components/p-DX5K8xnh.js +1 -0
- package/dist/components/{p-DEy7zJCe.js → p-DZdgXCAx.js} +1 -1
- package/dist/components/p-DdH1cKED.js +1 -0
- package/dist/components/p-DdsSSqFY.js +1 -0
- package/dist/components/p-DgmtCdnL.js +1 -0
- package/dist/components/{p-BzYU3-MJ.js → p-DmWSRsjK.js} +1 -1
- package/dist/components/{p-Bj2laX89.js → p-Dz-Ti24X.js} +1 -1
- package/dist/components/{p-BiG1dxPS.js → p-F5_X4dZG.js} +1 -1
- package/dist/components/{p-x6doYeiI.js → p-IpoC5EEY.js} +1 -1
- package/dist/components/p-Jn6TNdfe.js +1 -0
- package/dist/components/{p-BfNHpqQ8.js → p-NuLP1xHe.js} +1 -1
- package/dist/components/{p-skWUIStn.js → p-SDZNC8GF.js} +1 -1
- package/dist/components/{p-BYmp9Ovv.js → p-U4oawa1x.js} +1 -1
- package/dist/components/{p-DM11KXUT.js → p-f8aW1ye7.js} +1 -1
- package/dist/components/p-v7dxxrL5.js +1 -0
- package/dist/components/p-vAeiXe6c.js +1 -0
- package/dist/esm/index-Dhio9uis.js +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/{kritzel-active-users_42.entry.js → kritzel-active-users_44.entry.js} +709 -146
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{schema.constants-DiCnmIYK.js → schema.constants-DchTXG3V.js} +1163 -172
- package/dist/esm/stencil.js +1 -1
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-DchTXG3V.js +1 -0
- package/dist/stencil/p-c9a3807b.entry.js +9 -0
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/core/core.class.d.ts +16 -0
- package/dist/types/classes/handlers/context-menu.handler.d.ts +13 -0
- package/dist/types/classes/managers/license.manager.d.ts +141 -0
- package/dist/types/classes/managers/localization.manager.d.ts +121 -0
- package/dist/types/classes/objects/custom-element.class.d.ts +2 -0
- package/dist/types/classes/objects/group.class.d.ts +6 -1
- package/dist/types/classes/objects/image.class.d.ts +1 -1
- package/dist/types/classes/objects/path.class.d.ts +3 -2
- package/dist/types/classes/objects/selection-group.class.d.ts +6 -1
- package/dist/types/classes/objects/shape.class.d.ts +2 -0
- package/dist/types/classes/objects/text.class.d.ts +2 -1
- package/dist/types/classes/tools/brush-tool.class.d.ts +1 -1
- package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +53 -1
- package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +55 -3
- package/dist/types/components/core/kritzel-watermark/kritzel-watermark.d.ts +20 -0
- package/dist/types/components/ui/kritzel-controls/kritzel-controls.d.ts +3 -0
- package/dist/types/components/ui/kritzel-current-user/kritzel-current-user.d.ts +3 -0
- package/dist/types/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.d.ts +3 -0
- package/dist/types/components/ui/kritzel-export/kritzel-export.d.ts +4 -1
- package/dist/types/components/ui/kritzel-more-menu/kritzel-more-menu.d.ts +3 -0
- package/dist/types/components/ui/kritzel-settings/kritzel-settings.d.ts +16 -0
- package/dist/types/components/ui/kritzel-share-dialog/kritzel-share-dialog.d.ts +3 -0
- package/dist/types/components/ui/kritzel-tool-config/kritzel-tool-config.d.ts +3 -0
- package/dist/types/components/ui/kritzel-utility-panel/kritzel-utility-panel.d.ts +3 -0
- package/dist/types/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.d.ts +3 -0
- package/dist/types/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.d.ts +20 -0
- package/dist/types/components.d.ts +445 -26
- package/dist/types/constants/engine.constants.d.ts +2 -0
- package/dist/types/constants/license.constants.d.ts +25 -0
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/helpers/localization.helper.d.ts +18 -0
- package/dist/types/helpers/math.helper.d.ts +1 -0
- package/dist/types/helpers/svg-export.helper.d.ts +81 -7
- package/dist/types/index.d.ts +13 -0
- package/dist/types/interfaces/context-menu-item.interface.d.ts +7 -1
- package/dist/types/interfaces/line-options.interface.d.ts +2 -0
- package/dist/types/interfaces/localization.interface.d.ts +143 -0
- package/dist/types/interfaces/path-options.interface.d.ts +2 -0
- package/dist/types/interfaces/settings.interface.d.ts +3 -0
- package/dist/types/interfaces/theme.interface.d.ts +27 -2
- package/dist/types/locales/de-locale.d.ts +5 -0
- package/dist/types/locales/en-locale.d.ts +6 -0
- package/dist/types/locales/fr-locale.d.ts +5 -0
- package/package.json +4 -7
- package/dist/components/p-2xYAGd0I.js +0 -1
- package/dist/components/p-B2Os1ya_.js +0 -1
- package/dist/components/p-BTEV1WwT.js +0 -1
- package/dist/components/p-BbactVA0.js +0 -1
- package/dist/components/p-BqwqGFQY.js +0 -1
- package/dist/components/p-C0TN5IAi.js +0 -1
- package/dist/components/p-CFgkUYoO.js +0 -1
- package/dist/components/p-COgo9OWy.js +0 -1
- package/dist/components/p-CUPYGT8c.js +0 -1
- package/dist/components/p-CcyIAi9S.js +0 -1
- package/dist/components/p-Cj78L1Kk.js +0 -1
- package/dist/components/p-CkAVEdDw.js +0 -9
- package/dist/components/p-CmuNn1Tc.js +0 -1
- package/dist/components/p-DDYoDSrm.js +0 -1
- package/dist/components/p-DbB730vO.js +0 -1
- package/dist/components/p-DlwYHzSj.js +0 -1
- package/dist/components/p-FK7b3BGt.js +0 -1
- package/dist/components/p-J9_SwObO.js +0 -1
- package/dist/stencil/p-67775031.entry.js +0 -9
- package/dist/stencil/p-DiCnmIYK.js +0 -1
|
@@ -840,8 +840,11 @@ function findDiffStart(a, b, pos) {
|
|
|
840
840
|
if (!childA.sameMarkup(childB))
|
|
841
841
|
return pos;
|
|
842
842
|
if (childA.isText && childA.text != childB.text) {
|
|
843
|
-
|
|
843
|
+
let tA = childA.text, tB = childB.text, j = 0;
|
|
844
|
+
for (; tA[j] == tB[j]; j++)
|
|
844
845
|
pos++;
|
|
846
|
+
if (j && j < tA.length && j < tB.length && surrogateHigh(tA.charCodeAt(j - 1)) && surrogateLow(tA.charCodeAt(j)))
|
|
847
|
+
pos--;
|
|
845
848
|
return pos;
|
|
846
849
|
}
|
|
847
850
|
if (childA.content.size || childB.content.size) {
|
|
@@ -865,12 +868,17 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
865
868
|
if (!childA.sameMarkup(childB))
|
|
866
869
|
return { a: posA, b: posB };
|
|
867
870
|
if (childA.isText && childA.text != childB.text) {
|
|
868
|
-
let
|
|
869
|
-
while (
|
|
870
|
-
|
|
871
|
+
let tA = childA.text, tB = childB.text, iA = tA.length, iB = tB.length;
|
|
872
|
+
while (iA > 0 && iB > 0 && tA[iA - 1] == tB[iB - 1]) {
|
|
873
|
+
iA--;
|
|
874
|
+
iB--;
|
|
871
875
|
posA--;
|
|
872
876
|
posB--;
|
|
873
877
|
}
|
|
878
|
+
if (iA && iB && iA < tA.length && surrogateHigh(tA.charCodeAt(iA - 1)) && surrogateLow(tA.charCodeAt(iA))) {
|
|
879
|
+
posA++;
|
|
880
|
+
posB++;
|
|
881
|
+
}
|
|
874
882
|
return { a: posA, b: posB };
|
|
875
883
|
}
|
|
876
884
|
if (childA.content.size || childB.content.size) {
|
|
@@ -882,6 +890,8 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
882
890
|
posB -= size;
|
|
883
891
|
}
|
|
884
892
|
}
|
|
893
|
+
function surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; }
|
|
894
|
+
function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; }
|
|
885
895
|
|
|
886
896
|
/**
|
|
887
897
|
A fragment represents a node's collection of child nodes.
|
|
@@ -1562,7 +1572,8 @@ function addRange($start, $end, depth, target) {
|
|
|
1562
1572
|
addNode($end.nodeBefore, target);
|
|
1563
1573
|
}
|
|
1564
1574
|
function close(node, content) {
|
|
1565
|
-
node.type.
|
|
1575
|
+
if (!node.type.validContent(content))
|
|
1576
|
+
throw new ReplaceError("Invalid content for node " + node.type.name);
|
|
1566
1577
|
return node.copy(content);
|
|
1567
1578
|
}
|
|
1568
1579
|
function replaceThreeWay($from, $start, $end, $to, depth) {
|
|
@@ -2873,13 +2884,12 @@ function computeAttrs(attrs, value) {
|
|
|
2873
2884
|
return built;
|
|
2874
2885
|
}
|
|
2875
2886
|
function checkAttrs(attrs, values, type, name) {
|
|
2876
|
-
for (let
|
|
2877
|
-
if (!(
|
|
2878
|
-
throw new RangeError(`Unsupported attribute ${
|
|
2879
|
-
for (let
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
attr.validate(values[name]);
|
|
2887
|
+
for (let attr in values)
|
|
2888
|
+
if (!(attr in attrs))
|
|
2889
|
+
throw new RangeError(`Unsupported attribute ${attr} for ${type} of type ${name}`);
|
|
2890
|
+
for (let attr in attrs) {
|
|
2891
|
+
if (attrs[attr].validate)
|
|
2892
|
+
attrs[attr].validate(values[attr]);
|
|
2883
2893
|
}
|
|
2884
2894
|
}
|
|
2885
2895
|
function initAttrs(typeName, attrs) {
|
|
@@ -3056,7 +3066,7 @@ let NodeType$1 = class NodeType {
|
|
|
3056
3066
|
@internal
|
|
3057
3067
|
*/
|
|
3058
3068
|
checkAttrs(attrs) {
|
|
3059
|
-
checkAttrs(this.attrs, attrs, "node");
|
|
3069
|
+
checkAttrs(this.attrs, attrs, "node", this.name);
|
|
3060
3070
|
}
|
|
3061
3071
|
/**
|
|
3062
3072
|
Check whether the given mark type is allowed in this node.
|
|
@@ -3207,7 +3217,7 @@ class MarkType {
|
|
|
3207
3217
|
@internal
|
|
3208
3218
|
*/
|
|
3209
3219
|
checkAttrs(attrs) {
|
|
3210
|
-
checkAttrs(this.attrs, attrs, "mark");
|
|
3220
|
+
checkAttrs(this.attrs, attrs, "mark", this.name);
|
|
3211
3221
|
}
|
|
3212
3222
|
/**
|
|
3213
3223
|
Queries whether a given mark type is
|
|
@@ -9642,14 +9652,14 @@ function selectionToDOM(view, force = false) {
|
|
|
9642
9652
|
syncNodeSelection(view, sel);
|
|
9643
9653
|
if (!editorOwnsSelection(view))
|
|
9644
9654
|
return;
|
|
9645
|
-
//
|
|
9646
|
-
//
|
|
9647
|
-
|
|
9648
|
-
if (!force &&
|
|
9655
|
+
// Need to delay selection normalization during a native selection
|
|
9656
|
+
// drag on Chrome, or it will cause further dragging to glitch.
|
|
9657
|
+
let mouseDown = view.input.mouseDown;
|
|
9658
|
+
if (!force && chrome && mouseDown) {
|
|
9649
9659
|
let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection;
|
|
9650
9660
|
if (domSel.anchorNode && curSel.anchorNode &&
|
|
9651
|
-
isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)
|
|
9652
|
-
|
|
9661
|
+
isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset) &&
|
|
9662
|
+
mouseDown.delaySelUpdate()) {
|
|
9653
9663
|
view.domObserver.setCurSelection();
|
|
9654
9664
|
return;
|
|
9655
9665
|
}
|
|
@@ -10501,6 +10511,8 @@ function setSelectionOrigin(view, origin) {
|
|
|
10501
10511
|
view.input.lastSelectionTime = Date.now();
|
|
10502
10512
|
}
|
|
10503
10513
|
function destroyInput(view) {
|
|
10514
|
+
if (view.input.mouseDown)
|
|
10515
|
+
view.input.mouseDown.done();
|
|
10504
10516
|
view.domObserver.stop();
|
|
10505
10517
|
for (let type in view.input.eventHandlers)
|
|
10506
10518
|
view.dom.removeEventListener(type, view.input.eventHandlers[type]);
|
|
@@ -10539,7 +10551,7 @@ function dispatchEvent(view, event) {
|
|
|
10539
10551
|
editHandlers.keydown = (view, _event) => {
|
|
10540
10552
|
let event = _event;
|
|
10541
10553
|
view.input.shiftKey = event.keyCode == 16 || event.shiftKey;
|
|
10542
|
-
if (inOrNearComposition(view
|
|
10554
|
+
if (inOrNearComposition(view))
|
|
10543
10555
|
return;
|
|
10544
10556
|
view.input.lastKeyCode = event.keyCode;
|
|
10545
10557
|
view.input.lastKeyCodeTime = Date.now();
|
|
@@ -10577,7 +10589,7 @@ editHandlers.keyup = (view, event) => {
|
|
|
10577
10589
|
};
|
|
10578
10590
|
editHandlers.keypress = (view, _event) => {
|
|
10579
10591
|
let event = _event;
|
|
10580
|
-
if (inOrNearComposition(view
|
|
10592
|
+
if (inOrNearComposition(view) || !event.charCode ||
|
|
10581
10593
|
event.ctrlKey && !event.altKey || mac$3 && event.metaKey)
|
|
10582
10594
|
return;
|
|
10583
10595
|
if (view.someProp("handleKeyPress", f => f(view, event))) {
|
|
@@ -10671,26 +10683,28 @@ function handleTripleClick(view, pos, inside, event) {
|
|
|
10671
10683
|
function defaultTripleClick(view, inside, event) {
|
|
10672
10684
|
if (event.button != 0)
|
|
10673
10685
|
return false;
|
|
10674
|
-
let doc = view.state.doc;
|
|
10675
|
-
if (
|
|
10676
|
-
if (doc.inlineContent) {
|
|
10677
|
-
updateSelection(view, TextSelection.create(doc, 0, doc.content.size));
|
|
10678
|
-
return true;
|
|
10679
|
-
}
|
|
10686
|
+
let selection = selectionForTripleClick(view, inside, true), doc = view.state.doc;
|
|
10687
|
+
if (!selection)
|
|
10680
10688
|
return false;
|
|
10681
|
-
|
|
10689
|
+
updateSelection(view, selection);
|
|
10690
|
+
if (selection instanceof TextSelection && doc.eq(view.state.doc))
|
|
10691
|
+
view.input.mouseDown = new TripleClickDrag(view, selection);
|
|
10692
|
+
return true;
|
|
10693
|
+
}
|
|
10694
|
+
function selectionForTripleClick(view, inside, selectNodes) {
|
|
10695
|
+
let doc = view.state.doc;
|
|
10696
|
+
if (inside == -1)
|
|
10697
|
+
return doc.inlineContent ? TextSelection.create(doc, 0, doc.content.size) : null;
|
|
10682
10698
|
let $pos = doc.resolve(inside);
|
|
10683
10699
|
for (let i = $pos.depth + 1; i > 0; i--) {
|
|
10684
10700
|
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i);
|
|
10685
10701
|
let nodePos = $pos.before(i);
|
|
10686
10702
|
if (node.inlineContent)
|
|
10687
|
-
|
|
10688
|
-
else if (NodeSelection.isSelectable(node))
|
|
10689
|
-
|
|
10690
|
-
else
|
|
10691
|
-
continue;
|
|
10692
|
-
return true;
|
|
10703
|
+
return TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size);
|
|
10704
|
+
else if (selectNodes && NodeSelection.isSelectable(node))
|
|
10705
|
+
return NodeSelection.create(doc, nodePos);
|
|
10693
10706
|
}
|
|
10707
|
+
return null;
|
|
10694
10708
|
}
|
|
10695
10709
|
function forceDOMFlush(view) {
|
|
10696
10710
|
return endComposition(view);
|
|
@@ -10709,13 +10723,13 @@ handlers.mousedown = (view, _event) => {
|
|
|
10709
10723
|
type = "tripleClick";
|
|
10710
10724
|
}
|
|
10711
10725
|
view.input.lastClick = { time: now, x: event.clientX, y: event.clientY, type, button: event.button };
|
|
10726
|
+
if (view.input.mouseDown)
|
|
10727
|
+
view.input.mouseDown.done();
|
|
10712
10728
|
let pos = view.posAtCoords(eventCoords(event));
|
|
10713
10729
|
if (!pos)
|
|
10714
10730
|
return;
|
|
10715
10731
|
if (type == "singleClick") {
|
|
10716
|
-
|
|
10717
|
-
view.input.mouseDown.done();
|
|
10718
|
-
view.input.mouseDown = new MouseDown(view, pos, event, !!flushed);
|
|
10732
|
+
view.input.mouseDown = new LeftMouseDown(view, pos, event, !!flushed);
|
|
10719
10733
|
}
|
|
10720
10734
|
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
|
|
10721
10735
|
event.preventDefault();
|
|
@@ -10725,13 +10739,34 @@ handlers.mousedown = (view, _event) => {
|
|
|
10725
10739
|
}
|
|
10726
10740
|
};
|
|
10727
10741
|
class MouseDown {
|
|
10728
|
-
constructor(view
|
|
10742
|
+
constructor(view) {
|
|
10729
10743
|
this.view = view;
|
|
10744
|
+
this.mightDrag = null;
|
|
10745
|
+
view.root.addEventListener("mouseup", this.up = this.up.bind(this));
|
|
10746
|
+
view.root.addEventListener("mousemove", this.move = this.move.bind(this));
|
|
10747
|
+
}
|
|
10748
|
+
up(event) {
|
|
10749
|
+
this.done();
|
|
10750
|
+
}
|
|
10751
|
+
move(event) {
|
|
10752
|
+
if (event.buttons == 0)
|
|
10753
|
+
this.done();
|
|
10754
|
+
}
|
|
10755
|
+
done() {
|
|
10756
|
+
this.view.root.removeEventListener("mouseup", this.up);
|
|
10757
|
+
this.view.root.removeEventListener("mousemove", this.move);
|
|
10758
|
+
if (this.view.input.mouseDown == this)
|
|
10759
|
+
this.view.input.mouseDown = null;
|
|
10760
|
+
}
|
|
10761
|
+
delaySelUpdate() { return false; }
|
|
10762
|
+
}
|
|
10763
|
+
class LeftMouseDown extends MouseDown {
|
|
10764
|
+
constructor(view, pos, event, flushed) {
|
|
10765
|
+
super(view);
|
|
10730
10766
|
this.pos = pos;
|
|
10731
10767
|
this.event = event;
|
|
10732
10768
|
this.flushed = flushed;
|
|
10733
10769
|
this.delayedSelectionSync = false;
|
|
10734
|
-
this.mightDrag = null;
|
|
10735
10770
|
this.startDoc = view.state.doc;
|
|
10736
10771
|
this.selectNode = !!event[selectNodeModifier];
|
|
10737
10772
|
this.allowDefault = event.shiftKey;
|
|
@@ -10769,13 +10804,10 @@ class MouseDown {
|
|
|
10769
10804
|
}, 20);
|
|
10770
10805
|
this.view.domObserver.start();
|
|
10771
10806
|
}
|
|
10772
|
-
view.root.addEventListener("mouseup", this.up = this.up.bind(this));
|
|
10773
|
-
view.root.addEventListener("mousemove", this.move = this.move.bind(this));
|
|
10774
10807
|
setSelectionOrigin(view, "pointer");
|
|
10775
10808
|
}
|
|
10776
10809
|
done() {
|
|
10777
|
-
|
|
10778
|
-
this.view.root.removeEventListener("mousemove", this.move);
|
|
10810
|
+
super.done();
|
|
10779
10811
|
if (this.mightDrag && this.target) {
|
|
10780
10812
|
this.view.domObserver.stop();
|
|
10781
10813
|
if (this.mightDrag.addAttr)
|
|
@@ -10785,8 +10817,10 @@ class MouseDown {
|
|
|
10785
10817
|
this.view.domObserver.start();
|
|
10786
10818
|
}
|
|
10787
10819
|
if (this.delayedSelectionSync)
|
|
10788
|
-
setTimeout(() =>
|
|
10789
|
-
|
|
10820
|
+
setTimeout(() => {
|
|
10821
|
+
if (!this.view.isDestroyed)
|
|
10822
|
+
selectionToDOM(this.view);
|
|
10823
|
+
});
|
|
10790
10824
|
}
|
|
10791
10825
|
up(event) {
|
|
10792
10826
|
this.done();
|
|
@@ -10825,14 +10859,41 @@ class MouseDown {
|
|
|
10825
10859
|
move(event) {
|
|
10826
10860
|
this.updateAllowDefault(event);
|
|
10827
10861
|
setSelectionOrigin(this.view, "pointer");
|
|
10828
|
-
|
|
10829
|
-
this.done();
|
|
10862
|
+
super.move(event);
|
|
10830
10863
|
}
|
|
10831
10864
|
updateAllowDefault(event) {
|
|
10832
10865
|
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
10833
10866
|
Math.abs(this.event.y - event.clientY) > 4))
|
|
10834
10867
|
this.allowDefault = true;
|
|
10835
10868
|
}
|
|
10869
|
+
delaySelUpdate() {
|
|
10870
|
+
if (!this.allowDefault)
|
|
10871
|
+
return false;
|
|
10872
|
+
this.delayedSelectionSync = true;
|
|
10873
|
+
return true;
|
|
10874
|
+
}
|
|
10875
|
+
}
|
|
10876
|
+
class TripleClickDrag extends MouseDown {
|
|
10877
|
+
constructor(view, startSelection) {
|
|
10878
|
+
super(view);
|
|
10879
|
+
this.startSelection = startSelection;
|
|
10880
|
+
this.startDoc = view.state.doc;
|
|
10881
|
+
}
|
|
10882
|
+
move(event) {
|
|
10883
|
+
if (event.buttons == 0 || this.view.isDestroyed || !this.view.state.doc.eq(this.startDoc)) {
|
|
10884
|
+
this.done();
|
|
10885
|
+
return;
|
|
10886
|
+
}
|
|
10887
|
+
event.preventDefault();
|
|
10888
|
+
setSelectionOrigin(this.view, "pointer");
|
|
10889
|
+
let pos = this.view.posAtCoords(eventCoords(event));
|
|
10890
|
+
let target = pos && selectionForTripleClick(this.view, pos.inside, false);
|
|
10891
|
+
if (!target)
|
|
10892
|
+
return;
|
|
10893
|
+
let { doc } = this.view.state, start = this.startSelection;
|
|
10894
|
+
let [anchor, head] = target.from < start.from ? [start.to, target.from] : [start.from, target.to];
|
|
10895
|
+
updateSelection(this.view, TextSelection.create(doc, anchor, head));
|
|
10896
|
+
}
|
|
10836
10897
|
}
|
|
10837
10898
|
handlers.touchstart = view => {
|
|
10838
10899
|
view.input.lastTouch = Date.now();
|
|
@@ -10857,7 +10918,7 @@ function inOrNearComposition(view, event) {
|
|
|
10857
10918
|
// This guards against the case where compositionend is triggered without the keyboard
|
|
10858
10919
|
// (e.g. character confirmation may be done with the mouse), and keydown is triggered
|
|
10859
10920
|
// afterwards- we wouldn't want to ignore the keydown event in this case.
|
|
10860
|
-
if (safari && Math.abs(
|
|
10921
|
+
if (safari && Math.abs(Date.now() - view.input.compositionEndedAt) < 500) {
|
|
10861
10922
|
view.input.compositionEndedAt = -2e8;
|
|
10862
10923
|
return true;
|
|
10863
10924
|
}
|
|
@@ -10916,7 +10977,7 @@ function selectionBeforeUneditable(view) {
|
|
|
10916
10977
|
editHandlers.compositionend = (view, event) => {
|
|
10917
10978
|
if (view.composing) {
|
|
10918
10979
|
view.input.composing = false;
|
|
10919
|
-
view.input.compositionEndedAt =
|
|
10980
|
+
view.input.compositionEndedAt = Date.now();
|
|
10920
10981
|
view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
|
|
10921
10982
|
view.input.compositionNode = null;
|
|
10922
10983
|
if (view.input.badSafariComposition)
|
|
@@ -10935,7 +10996,7 @@ function scheduleComposeEnd(view, delay) {
|
|
|
10935
10996
|
function clearComposition(view) {
|
|
10936
10997
|
if (view.composing) {
|
|
10937
10998
|
view.input.composing = false;
|
|
10938
|
-
view.input.compositionEndedAt =
|
|
10999
|
+
view.input.compositionEndedAt = Date.now();
|
|
10939
11000
|
}
|
|
10940
11001
|
while (view.input.compositionNodes.length > 0)
|
|
10941
11002
|
view.input.compositionNodes.pop().markParentsDirty();
|
|
@@ -10961,11 +11022,6 @@ function findCompositionNode(view) {
|
|
|
10961
11022
|
}
|
|
10962
11023
|
return textBefore || textAfter;
|
|
10963
11024
|
}
|
|
10964
|
-
function timestampFromCustomEvent() {
|
|
10965
|
-
let event = document.createEvent("Event");
|
|
10966
|
-
event.initEvent("event", true, true);
|
|
10967
|
-
return event.timeStamp;
|
|
10968
|
-
}
|
|
10969
11025
|
/**
|
|
10970
11026
|
@internal
|
|
10971
11027
|
*/
|
|
@@ -12122,7 +12178,10 @@ class DOMObserver {
|
|
|
12122
12178
|
}
|
|
12123
12179
|
}
|
|
12124
12180
|
}
|
|
12125
|
-
if (added.some(n => n.nodeName == "BR") &&
|
|
12181
|
+
if (added.some(n => n.nodeName == "BR") &&
|
|
12182
|
+
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46 ||
|
|
12183
|
+
chrome && (view.composing || view.input.compositionEndedAt > Date.now() - 50) &&
|
|
12184
|
+
mutations.some(m => m.type == "childList" && m.removedNodes.length))) {
|
|
12126
12185
|
// Browsers sometimes insert a bogus break node if you
|
|
12127
12186
|
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
12128
12187
|
for (let node of added)
|
|
@@ -12678,38 +12737,28 @@ function skipClosingAndOpening($pos, fromEnd, mayOpen) {
|
|
|
12678
12737
|
return end;
|
|
12679
12738
|
}
|
|
12680
12739
|
function findDiff(a, b, pos, preferredPos, preferredSide) {
|
|
12681
|
-
let start = a.findDiffStart(b, pos);
|
|
12740
|
+
let start = a.findDiffStart(b, pos), lenA = pos + a.size, lenB = pos + b.size;
|
|
12682
12741
|
if (start == null)
|
|
12683
12742
|
return null;
|
|
12684
|
-
let { a: endA, b: endB } = a.findDiffEnd(b,
|
|
12743
|
+
let { a: endA, b: endB } = a.findDiffEnd(b, lenA, lenB);
|
|
12685
12744
|
if (preferredSide == "end") {
|
|
12686
12745
|
let adjust = Math.max(0, start - Math.min(endA, endB));
|
|
12687
12746
|
preferredPos -= endA + adjust - start;
|
|
12688
12747
|
}
|
|
12689
|
-
if (endA < start &&
|
|
12748
|
+
if (endA < start && lenA < lenB) {
|
|
12690
12749
|
let move = preferredPos <= start && preferredPos >= endA ? start - preferredPos : 0;
|
|
12691
12750
|
start -= move;
|
|
12692
|
-
if (start && start < b.size && isSurrogatePair(b.textBetween(start - 1, start + 1)))
|
|
12693
|
-
start += move ? 1 : -1;
|
|
12694
12751
|
endB = start + (endB - endA);
|
|
12695
12752
|
endA = start;
|
|
12696
12753
|
}
|
|
12697
12754
|
else if (endB < start) {
|
|
12698
12755
|
let move = preferredPos <= start && preferredPos >= endB ? start - preferredPos : 0;
|
|
12699
12756
|
start -= move;
|
|
12700
|
-
if (start && start < a.size && isSurrogatePair(a.textBetween(start - 1, start + 1)))
|
|
12701
|
-
start += move ? 1 : -1;
|
|
12702
12757
|
endA = start + (endA - endB);
|
|
12703
12758
|
endB = start;
|
|
12704
12759
|
}
|
|
12705
12760
|
return { start, endA, endB };
|
|
12706
12761
|
}
|
|
12707
|
-
function isSurrogatePair(str) {
|
|
12708
|
-
if (str.length != 2)
|
|
12709
|
-
return false;
|
|
12710
|
-
let a = str.charCodeAt(0), b = str.charCodeAt(1);
|
|
12711
|
-
return a >= 0xDC00 && a <= 0xDFFF && b >= 0xD800 && b <= 0xDBFF;
|
|
12712
|
-
}
|
|
12713
12762
|
/**
|
|
12714
12763
|
An editor view manages the DOM structure that represents an
|
|
12715
12764
|
editable document. Its state and behavior are determined by its
|
|
@@ -12901,9 +12950,10 @@ class EditorView {
|
|
|
12901
12950
|
// a DOM selection change and the "selectionchange" event for it
|
|
12902
12951
|
// can cause a spurious DOM selection update, disrupting mouse
|
|
12903
12952
|
// drag selection.
|
|
12953
|
+
let mouseDown = this.input.mouseDown;
|
|
12904
12954
|
if (forceSelUpdate ||
|
|
12905
|
-
!(
|
|
12906
|
-
anchorInRightPlace(this))) {
|
|
12955
|
+
!(mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
|
|
12956
|
+
anchorInRightPlace(this) && mouseDown.delaySelUpdate())) {
|
|
12907
12957
|
selectionToDOM(this, forceSelUpdate);
|
|
12908
12958
|
}
|
|
12909
12959
|
else {
|
|
@@ -14600,6 +14650,24 @@ const lightTheme = {
|
|
|
14600
14650
|
buttonHoverBackgroundColor: 'hsl(0, 0%, 0%, 4.3%)',
|
|
14601
14651
|
separatorColor: 'hsl(0, 0%, 0%, 8%)',
|
|
14602
14652
|
},
|
|
14653
|
+
watermark: {
|
|
14654
|
+
background: 'rgba(255, 255, 255, 0.6)',
|
|
14655
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.12)',
|
|
14656
|
+
color: 'rgba(60, 60, 60, 0.75)',
|
|
14657
|
+
},
|
|
14658
|
+
zoomPanel: {
|
|
14659
|
+
backgroundColor: '#ffffff',
|
|
14660
|
+
border: '1px solid #ebebeb',
|
|
14661
|
+
borderRadius: '12px',
|
|
14662
|
+
boxShadow: '0 0 3px rgba(0, 0, 0, 0.08)',
|
|
14663
|
+
buttonActiveBackgroundColor: 'hsl(0, 0%, 0%, 8.6%)',
|
|
14664
|
+
buttonBorderRadius: '8px',
|
|
14665
|
+
buttonHoverBackgroundColor: 'hsl(0, 0%, 0%, 4.3%)',
|
|
14666
|
+
buttonSize: '32px',
|
|
14667
|
+
gap: '4px',
|
|
14668
|
+
iconColor: '#000000',
|
|
14669
|
+
padding: '4px',
|
|
14670
|
+
},
|
|
14603
14671
|
};
|
|
14604
14672
|
|
|
14605
14673
|
/**
|
|
@@ -14877,6 +14945,24 @@ const darkTheme = {
|
|
|
14877
14945
|
buttonHoverBackgroundColor: 'hsl(0, 0%, 100%, 8%)',
|
|
14878
14946
|
separatorColor: 'hsl(0, 0%, 100%, 12%)',
|
|
14879
14947
|
},
|
|
14948
|
+
watermark: {
|
|
14949
|
+
background: 'rgba(40, 40, 40, 0.6)',
|
|
14950
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.35)',
|
|
14951
|
+
color: 'rgba(220, 220, 220, 0.8)',
|
|
14952
|
+
},
|
|
14953
|
+
zoomPanel: {
|
|
14954
|
+
backgroundColor: '#2a2a2a',
|
|
14955
|
+
border: '1px solid #3a3a3a',
|
|
14956
|
+
borderRadius: '12px',
|
|
14957
|
+
boxShadow: '0 0 3px rgba(0, 0, 0, 0.4)',
|
|
14958
|
+
buttonActiveBackgroundColor: 'hsl(0, 0%, 100%, 12%)',
|
|
14959
|
+
buttonBorderRadius: '8px',
|
|
14960
|
+
buttonHoverBackgroundColor: 'hsl(0, 0%, 100%, 8%)',
|
|
14961
|
+
buttonSize: '32px',
|
|
14962
|
+
gap: '4px',
|
|
14963
|
+
iconColor: '#e0e0e0',
|
|
14964
|
+
padding: '4px',
|
|
14965
|
+
},
|
|
14880
14966
|
};
|
|
14881
14967
|
|
|
14882
14968
|
class ThemeHelper {
|
|
@@ -14950,7 +15036,7 @@ class ThemeHelper {
|
|
|
14950
15036
|
}
|
|
14951
15037
|
|
|
14952
15038
|
/** Key used to store the settings object in localStorage. */
|
|
14953
|
-
const SETTINGS_STORAGE_KEY = 'kritzel-settings';
|
|
15039
|
+
const SETTINGS_STORAGE_KEY$1 = 'kritzel-settings';
|
|
14954
15040
|
/** Default theme used when no stored preference exists. */
|
|
14955
15041
|
const DEFAULT_THEME = 'light';
|
|
14956
15042
|
/**
|
|
@@ -14977,7 +15063,7 @@ class KritzelThemeManager {
|
|
|
14977
15063
|
*/
|
|
14978
15064
|
constructor(core) {
|
|
14979
15065
|
this._core = core;
|
|
14980
|
-
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY}-${core.editorId}` : SETTINGS_STORAGE_KEY;
|
|
15066
|
+
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY$1}-${core.editorId}` : SETTINGS_STORAGE_KEY$1;
|
|
14981
15067
|
this._themeRegistry.set('light', lightTheme);
|
|
14982
15068
|
this._themeRegistry.set('dark', darkTheme);
|
|
14983
15069
|
this._currentTheme = this.getStoredTheme();
|
|
@@ -15093,7 +15179,7 @@ class KritzelThemeManager {
|
|
|
15093
15179
|
if (typeof localStorage === 'undefined') {
|
|
15094
15180
|
return DEFAULT_THEME;
|
|
15095
15181
|
}
|
|
15096
|
-
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
|
15182
|
+
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY$1);
|
|
15097
15183
|
if (!stored) {
|
|
15098
15184
|
return DEFAULT_THEME;
|
|
15099
15185
|
}
|
|
@@ -15257,6 +15343,15 @@ class KritzelColorHelper {
|
|
|
15257
15343
|
}
|
|
15258
15344
|
}
|
|
15259
15345
|
|
|
15346
|
+
class KritzelMathHelper {
|
|
15347
|
+
static average(a, b) {
|
|
15348
|
+
return (a + b) / 2;
|
|
15349
|
+
}
|
|
15350
|
+
static degreesToRadians(degrees) {
|
|
15351
|
+
return degrees * (Math.PI / 180);
|
|
15352
|
+
}
|
|
15353
|
+
}
|
|
15354
|
+
|
|
15260
15355
|
/**
|
|
15261
15356
|
* Represents a text object on the canvas that supports rich text editing via ProseMirror.
|
|
15262
15357
|
* Extends the base object class to inherit common object properties and behaviors.
|
|
@@ -15321,6 +15416,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15321
15416
|
*/
|
|
15322
15417
|
constructor(config) {
|
|
15323
15418
|
super();
|
|
15419
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
15324
15420
|
// Always create the editor so setContent() works immediately
|
|
15325
15421
|
this.editor = this.createEditor();
|
|
15326
15422
|
if (config) {
|
|
@@ -15359,7 +15455,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15359
15455
|
* @param scale - Optional scale factor (defaults to current viewport scale).
|
|
15360
15456
|
* @returns A new, fully initialized KritzelText instance.
|
|
15361
15457
|
*/
|
|
15362
|
-
static create(core, fontSize, fontFamily, scale) {
|
|
15458
|
+
static create(core, fontSize, fontFamily, scale, rotation) {
|
|
15363
15459
|
const object = new KritzelText();
|
|
15364
15460
|
object._core = core;
|
|
15365
15461
|
object.id = object.generateId();
|
|
@@ -15369,6 +15465,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15369
15465
|
object.fontFamily = fontFamily || 'Arial';
|
|
15370
15466
|
object.translateX = 0;
|
|
15371
15467
|
object.translateY = 0;
|
|
15468
|
+
object.rotation = KritzelMathHelper.degreesToRadians(rotation ?? 0);
|
|
15372
15469
|
const coreScale = core.store.state.scale;
|
|
15373
15470
|
const effectiveScale = coreScale < 0 ? coreScale : 1;
|
|
15374
15471
|
object.width = object.initialWidth / effectiveScale;
|
|
@@ -15720,12 +15817,6 @@ function requireCjs () {
|
|
|
15720
15817
|
|
|
15721
15818
|
var cjsExports = requireCjs();
|
|
15722
15819
|
|
|
15723
|
-
class KritzelMathHelper {
|
|
15724
|
-
static average(a, b) {
|
|
15725
|
-
return (a + b) / 2;
|
|
15726
|
-
}
|
|
15727
|
-
}
|
|
15728
|
-
|
|
15729
15820
|
class KritzelPath extends KritzelBaseObject {
|
|
15730
15821
|
__class__ = 'KritzelPath';
|
|
15731
15822
|
points;
|
|
@@ -15768,6 +15859,7 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15768
15859
|
this.points = config?.points ?? [];
|
|
15769
15860
|
this.translateX = config?.translateX ?? 0;
|
|
15770
15861
|
this.translateY = config?.translateY ?? 0;
|
|
15862
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
15771
15863
|
this.scale = config?.scale ?? 1;
|
|
15772
15864
|
this.strokeWidth = config?.strokeWidth ?? 8;
|
|
15773
15865
|
this.fill = config?.fill ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -15791,6 +15883,7 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15791
15883
|
object.points = options?.points ?? [];
|
|
15792
15884
|
object.translateX = options?.translateX ?? 0;
|
|
15793
15885
|
object.translateY = options?.translateY ?? 0;
|
|
15886
|
+
object.rotation = KritzelMathHelper.degreesToRadians(options?.rotation ?? 0);
|
|
15794
15887
|
object.scale = options?.scale ?? 1;
|
|
15795
15888
|
object.strokeWidth = options?.strokeWidth ?? 8;
|
|
15796
15889
|
object.fill = options?.fill ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -15840,8 +15933,12 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15840
15933
|
this.height = Math.max(...this.points.map(p => p[1])) - Math.min(...this.points.map(p => p[1])) + this.strokeWidth;
|
|
15841
15934
|
this.x = Math.min(...this.points.map(p => p[0])) - this.strokeWidth / 2;
|
|
15842
15935
|
this.y = Math.min(...this.points.map(p => p[1])) - this.strokeWidth / 2;
|
|
15843
|
-
|
|
15844
|
-
|
|
15936
|
+
if (x !== null) {
|
|
15937
|
+
this.translateX = x;
|
|
15938
|
+
}
|
|
15939
|
+
if (y !== null) {
|
|
15940
|
+
this.translateY = y;
|
|
15941
|
+
}
|
|
15845
15942
|
this._adjustedPoints = null;
|
|
15846
15943
|
this._core.store.objects.update(this);
|
|
15847
15944
|
}
|
|
@@ -16047,19 +16144,14 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
16047
16144
|
}
|
|
16048
16145
|
/**
|
|
16049
16146
|
* Updates width, height, x, y, translateX, and translateY based on current points.
|
|
16050
|
-
*
|
|
16147
|
+
* Uses the unrotated local points and stroke width for base dimensions.
|
|
16051
16148
|
* Called during initial setup to establish the path's dimensions and position.
|
|
16052
16149
|
*/
|
|
16053
16150
|
updateDimensions() {
|
|
16054
|
-
const
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
});
|
|
16059
|
-
const minX = Math.min(...rotatedPoints.map(p => p[0] - this.strokeWidth / 2));
|
|
16060
|
-
const minY = Math.min(...rotatedPoints.map(p => p[1] - this.strokeWidth / 2));
|
|
16061
|
-
const maxX = Math.max(...rotatedPoints.map(p => p[0] + this.strokeWidth / 2));
|
|
16062
|
-
const maxY = Math.max(...rotatedPoints.map(p => p[1] + this.strokeWidth / 2));
|
|
16151
|
+
const minX = Math.min(...this.points.map(p => p[0])) - this.strokeWidth / 2;
|
|
16152
|
+
const minY = Math.min(...this.points.map(p => p[1])) - this.strokeWidth / 2;
|
|
16153
|
+
const maxX = Math.max(...this.points.map(p => p[0])) + this.strokeWidth / 2;
|
|
16154
|
+
const maxY = Math.max(...this.points.map(p => p[1])) + this.strokeWidth / 2;
|
|
16063
16155
|
this.width = maxX - minX + this.lineSlack;
|
|
16064
16156
|
this.height = maxY - minY + this.lineSlack;
|
|
16065
16157
|
this.x = minX;
|
|
@@ -17126,6 +17218,7 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
17126
17218
|
this.y = config?.y || 0;
|
|
17127
17219
|
this.translateX = config?.translateX || 0;
|
|
17128
17220
|
this.translateY = config?.translateY || 0;
|
|
17221
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17129
17222
|
this.scale = config?.scale || 1;
|
|
17130
17223
|
this.width = config?.width || 0;
|
|
17131
17224
|
this.height = config?.height || 0;
|
|
@@ -17139,17 +17232,18 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
17139
17232
|
* @param core - The KritzelCore instance providing access to store and state management.
|
|
17140
17233
|
* @returns A new KritzelImage instance configured with the core context.
|
|
17141
17234
|
*/
|
|
17142
|
-
static create(core) {
|
|
17143
|
-
const object = new KritzelImage();
|
|
17235
|
+
static create(core, config) {
|
|
17236
|
+
const object = new KritzelImage(config);
|
|
17144
17237
|
object._core = core;
|
|
17145
17238
|
object.id = object.generateId();
|
|
17146
17239
|
object.workspaceId = core.store.state.activeWorkspace.id;
|
|
17147
17240
|
object.userId = core.user?.id;
|
|
17148
|
-
object.x = 0;
|
|
17149
|
-
object.y = 0;
|
|
17150
|
-
object.translateX = 0;
|
|
17151
|
-
object.translateY = 0;
|
|
17152
|
-
object.scale = object._core.store.state.scale;
|
|
17241
|
+
object.x = config?.x ?? 0;
|
|
17242
|
+
object.y = config?.y ?? 0;
|
|
17243
|
+
object.translateX = config?.translateX ?? 0;
|
|
17244
|
+
object.translateY = config?.translateY ?? 0;
|
|
17245
|
+
object.scale = config?.scale ?? object._core.store.state.scale;
|
|
17246
|
+
object.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17153
17247
|
object.zIndex = core.store.currentZIndex;
|
|
17154
17248
|
return object;
|
|
17155
17249
|
}
|
|
@@ -17528,6 +17622,7 @@ class KritzelLine extends KritzelBaseObject {
|
|
|
17528
17622
|
this.controlY = config?.controlY;
|
|
17529
17623
|
this.translateX = config?.translateX ?? 0;
|
|
17530
17624
|
this.translateY = config?.translateY ?? 0;
|
|
17625
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17531
17626
|
this.scale = config?.scale ?? 1;
|
|
17532
17627
|
this.strokeWidth = config?.strokeWidth ?? 4;
|
|
17533
17628
|
this.stroke = config?.stroke ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -17558,6 +17653,7 @@ class KritzelLine extends KritzelBaseObject {
|
|
|
17558
17653
|
object.controlY = options?.controlY;
|
|
17559
17654
|
object.translateX = options?.translateX ?? 0;
|
|
17560
17655
|
object.translateY = options?.translateY ?? 0;
|
|
17656
|
+
object.rotation = KritzelMathHelper.degreesToRadians(options?.rotation ?? 0);
|
|
17561
17657
|
object.scale = options?.scale ?? 1;
|
|
17562
17658
|
object.strokeWidth = options?.strokeWidth ?? 4;
|
|
17563
17659
|
object.stroke = options?.stroke ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -18252,6 +18348,10 @@ class KritzelClassHelper {
|
|
|
18252
18348
|
*/
|
|
18253
18349
|
class KritzelGroup extends KritzelBaseObject {
|
|
18254
18350
|
__class__ = 'KritzelGroup';
|
|
18351
|
+
constructor(config) {
|
|
18352
|
+
super();
|
|
18353
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
18354
|
+
}
|
|
18255
18355
|
/**
|
|
18256
18356
|
* IDs of child objects within this group.
|
|
18257
18357
|
* Children can be any KritzelBaseObject, including other KritzelGroups for nesting.
|
|
@@ -18303,8 +18403,8 @@ class KritzelGroup extends KritzelBaseObject {
|
|
|
18303
18403
|
* @param core - The KritzelCore instance providing access to store and state management.
|
|
18304
18404
|
* @returns A new KritzelGroup instance configured with the core context.
|
|
18305
18405
|
*/
|
|
18306
|
-
static create(core) {
|
|
18307
|
-
const group = new KritzelGroup();
|
|
18406
|
+
static create(core, config) {
|
|
18407
|
+
const group = new KritzelGroup(config);
|
|
18308
18408
|
group._core = core;
|
|
18309
18409
|
group.id = group.generateId();
|
|
18310
18410
|
group.workspaceId = core.store.state.activeWorkspace.id;
|
|
@@ -18893,6 +18993,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
18893
18993
|
this.y = config.y ?? 0;
|
|
18894
18994
|
this.translateX = config.translateX ?? 0;
|
|
18895
18995
|
this.translateY = config.translateY ?? 0;
|
|
18996
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
|
|
18896
18997
|
this.width = config.width ?? 100;
|
|
18897
18998
|
this.height = config.height ?? 100;
|
|
18898
18999
|
this.shapeType = config.shapeType ?? ShapeType.Rectangle;
|
|
@@ -18924,6 +19025,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
18924
19025
|
object.y = config?.y ?? 0;
|
|
18925
19026
|
object.translateX = config?.translateX ?? 0;
|
|
18926
19027
|
object.translateY = config?.translateY ?? 0;
|
|
19028
|
+
object.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
18927
19029
|
object.width = config?.width ?? 100;
|
|
18928
19030
|
object.height = config?.height ?? 100;
|
|
18929
19031
|
object.shapeType = config?.shapeType ?? ShapeType.Rectangle;
|
|
@@ -19422,71 +19524,6 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
19422
19524
|
}
|
|
19423
19525
|
}
|
|
19424
19526
|
|
|
19425
|
-
var KritzelMouseButton;
|
|
19426
|
-
(function (KritzelMouseButton) {
|
|
19427
|
-
KritzelMouseButton[KritzelMouseButton["Left"] = 0] = "Left";
|
|
19428
|
-
KritzelMouseButton[KritzelMouseButton["Middle"] = 1] = "Middle";
|
|
19429
|
-
KritzelMouseButton[KritzelMouseButton["Right"] = 2] = "Right";
|
|
19430
|
-
})(KritzelMouseButton || (KritzelMouseButton = {}));
|
|
19431
|
-
|
|
19432
|
-
class KritzelEventHelper {
|
|
19433
|
-
static isRightClick(ev) {
|
|
19434
|
-
return ev.button === KritzelMouseButton.Right;
|
|
19435
|
-
}
|
|
19436
|
-
static isLeftClick(ev) {
|
|
19437
|
-
return ev.button === KritzelMouseButton.Left;
|
|
19438
|
-
}
|
|
19439
|
-
static isPointerEventOnContextMenu(event) {
|
|
19440
|
-
const path = event.composedPath();
|
|
19441
|
-
const contextMenu = path.find(element => element.classList && element.classList.contains('context-menu'));
|
|
19442
|
-
return !!contextMenu;
|
|
19443
|
-
}
|
|
19444
|
-
static onLongPress(event, onSuccess, onCancel) {
|
|
19445
|
-
if (event.pointerType !== 'touch') {
|
|
19446
|
-
onCancel?.();
|
|
19447
|
-
return () => { };
|
|
19448
|
-
}
|
|
19449
|
-
const startX = event.clientX;
|
|
19450
|
-
const startY = event.clientY;
|
|
19451
|
-
const target = event.target;
|
|
19452
|
-
if (!target) {
|
|
19453
|
-
onCancel?.();
|
|
19454
|
-
return () => { };
|
|
19455
|
-
}
|
|
19456
|
-
const longPressTimeout = 400;
|
|
19457
|
-
const moveThreshold = 10;
|
|
19458
|
-
const timer = setTimeout(() => {
|
|
19459
|
-
removeListeners();
|
|
19460
|
-
onSuccess(event);
|
|
19461
|
-
}, longPressTimeout);
|
|
19462
|
-
const cancel = () => {
|
|
19463
|
-
clearTimeout(timer);
|
|
19464
|
-
removeListeners();
|
|
19465
|
-
onCancel?.();
|
|
19466
|
-
};
|
|
19467
|
-
const onPointerMove = (e) => {
|
|
19468
|
-
if (Math.abs(e.clientX - startX) > moveThreshold || Math.abs(e.clientY - startY) > moveThreshold) {
|
|
19469
|
-
cancel();
|
|
19470
|
-
}
|
|
19471
|
-
};
|
|
19472
|
-
const onPointerUp = () => {
|
|
19473
|
-
cancel();
|
|
19474
|
-
};
|
|
19475
|
-
const onPointerCancel = () => {
|
|
19476
|
-
cancel();
|
|
19477
|
-
};
|
|
19478
|
-
const removeListeners = () => {
|
|
19479
|
-
target.removeEventListener('pointermove', onPointerMove);
|
|
19480
|
-
target.removeEventListener('pointerup', onPointerUp);
|
|
19481
|
-
target.removeEventListener('pointercancel', onPointerCancel);
|
|
19482
|
-
};
|
|
19483
|
-
target.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
19484
|
-
target.addEventListener('pointerup', onPointerUp, { once: true });
|
|
19485
|
-
target.addEventListener('pointercancel', onPointerCancel, { once: true });
|
|
19486
|
-
return cancel;
|
|
19487
|
-
}
|
|
19488
|
-
}
|
|
19489
|
-
|
|
19490
19527
|
/**
|
|
19491
19528
|
* Abstract base class for all drawing tools in Kritzel.
|
|
19492
19529
|
* Provides common functionality and defines the interface that all tools must implement.
|
|
@@ -19575,6 +19612,71 @@ class KritzelBaseTool {
|
|
|
19575
19612
|
}
|
|
19576
19613
|
}
|
|
19577
19614
|
|
|
19615
|
+
var KritzelMouseButton;
|
|
19616
|
+
(function (KritzelMouseButton) {
|
|
19617
|
+
KritzelMouseButton[KritzelMouseButton["Left"] = 0] = "Left";
|
|
19618
|
+
KritzelMouseButton[KritzelMouseButton["Middle"] = 1] = "Middle";
|
|
19619
|
+
KritzelMouseButton[KritzelMouseButton["Right"] = 2] = "Right";
|
|
19620
|
+
})(KritzelMouseButton || (KritzelMouseButton = {}));
|
|
19621
|
+
|
|
19622
|
+
class KritzelEventHelper {
|
|
19623
|
+
static isRightClick(ev) {
|
|
19624
|
+
return ev.button === KritzelMouseButton.Right;
|
|
19625
|
+
}
|
|
19626
|
+
static isLeftClick(ev) {
|
|
19627
|
+
return ev.button === KritzelMouseButton.Left;
|
|
19628
|
+
}
|
|
19629
|
+
static isPointerEventOnContextMenu(event) {
|
|
19630
|
+
const path = event.composedPath();
|
|
19631
|
+
const contextMenu = path.find(element => element.classList && element.classList.contains('context-menu'));
|
|
19632
|
+
return !!contextMenu;
|
|
19633
|
+
}
|
|
19634
|
+
static onLongPress(event, onSuccess, onCancel) {
|
|
19635
|
+
if (event.pointerType !== 'touch') {
|
|
19636
|
+
onCancel?.();
|
|
19637
|
+
return () => { };
|
|
19638
|
+
}
|
|
19639
|
+
const startX = event.clientX;
|
|
19640
|
+
const startY = event.clientY;
|
|
19641
|
+
const target = event.target;
|
|
19642
|
+
if (!target) {
|
|
19643
|
+
onCancel?.();
|
|
19644
|
+
return () => { };
|
|
19645
|
+
}
|
|
19646
|
+
const longPressTimeout = 400;
|
|
19647
|
+
const moveThreshold = 10;
|
|
19648
|
+
const timer = setTimeout(() => {
|
|
19649
|
+
removeListeners();
|
|
19650
|
+
onSuccess(event);
|
|
19651
|
+
}, longPressTimeout);
|
|
19652
|
+
const cancel = () => {
|
|
19653
|
+
clearTimeout(timer);
|
|
19654
|
+
removeListeners();
|
|
19655
|
+
onCancel?.();
|
|
19656
|
+
};
|
|
19657
|
+
const onPointerMove = (e) => {
|
|
19658
|
+
if (Math.abs(e.clientX - startX) > moveThreshold || Math.abs(e.clientY - startY) > moveThreshold) {
|
|
19659
|
+
cancel();
|
|
19660
|
+
}
|
|
19661
|
+
};
|
|
19662
|
+
const onPointerUp = () => {
|
|
19663
|
+
cancel();
|
|
19664
|
+
};
|
|
19665
|
+
const onPointerCancel = () => {
|
|
19666
|
+
cancel();
|
|
19667
|
+
};
|
|
19668
|
+
const removeListeners = () => {
|
|
19669
|
+
target.removeEventListener('pointermove', onPointerMove);
|
|
19670
|
+
target.removeEventListener('pointerup', onPointerUp);
|
|
19671
|
+
target.removeEventListener('pointercancel', onPointerCancel);
|
|
19672
|
+
};
|
|
19673
|
+
target.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
19674
|
+
target.addEventListener('pointerup', onPointerUp, { once: true });
|
|
19675
|
+
target.addEventListener('pointercancel', onPointerCancel, { once: true });
|
|
19676
|
+
return cancel;
|
|
19677
|
+
}
|
|
19678
|
+
}
|
|
19679
|
+
|
|
19578
19680
|
const DEFAULT_STROKE_SIZES = [4, 6, 8, 12, 16, 24];
|
|
19579
19681
|
const DEFAULT_FONT_SIZES = [8, 10, 12, 16, 20, 24];
|
|
19580
19682
|
|
|
@@ -19603,7 +19705,7 @@ class KritzelBrushTool extends KritzelBaseTool {
|
|
|
19603
19705
|
* websocket traffic without visible quality loss — `perfect-freehand`
|
|
19604
19706
|
* already smooths the rendered stroke.
|
|
19605
19707
|
*/
|
|
19606
|
-
static MIN_POINT_DISTANCE_PX =
|
|
19708
|
+
static MIN_POINT_DISTANCE_PX = 3;
|
|
19607
19709
|
/** Tracks the ID of the path currently being drawn */
|
|
19608
19710
|
_currentPathId = null;
|
|
19609
19711
|
/**
|
|
@@ -19817,6 +19919,10 @@ class KritzelBrushTool extends KritzelBaseTool {
|
|
|
19817
19919
|
*/
|
|
19818
19920
|
class KritzelSelectionGroup extends KritzelBaseObject {
|
|
19819
19921
|
__class__ = 'KritzelSelectionGroup';
|
|
19922
|
+
constructor(config) {
|
|
19923
|
+
super();
|
|
19924
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
19925
|
+
}
|
|
19820
19926
|
// Store only object IDs instead of full objects
|
|
19821
19927
|
_objectIds = [];
|
|
19822
19928
|
// Cached objects array - invalidated when objectIds changes
|
|
@@ -19900,8 +20006,8 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
19900
20006
|
* @param core - The KritzelCore instance to associate with this selection group
|
|
19901
20007
|
* @returns A new KritzelSelectionGroup instance configured with default settings
|
|
19902
20008
|
*/
|
|
19903
|
-
static create(core) {
|
|
19904
|
-
const object = new KritzelSelectionGroup();
|
|
20009
|
+
static create(core, config) {
|
|
20010
|
+
const object = new KritzelSelectionGroup(config);
|
|
19905
20011
|
object._core = core;
|
|
19906
20012
|
object.id = object.generateId();
|
|
19907
20013
|
object.workspaceId = core.store.state.activeWorkspace.id;
|
|
@@ -21518,6 +21624,7 @@ KritzelIconRegistry.registerIcons({
|
|
|
21518
21624
|
'undo': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-icon lucide-undo"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>',
|
|
21519
21625
|
'redo': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-redo-icon lucide-redo"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/></svg>',
|
|
21520
21626
|
'plus': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>',
|
|
21627
|
+
'minus': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-minus-icon lucide-minus"><path d="M5 12h14"/></svg>',
|
|
21521
21628
|
'ellipsis-vertical': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis-vertical-icon lucide-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>',
|
|
21522
21629
|
'x': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
|
|
21523
21630
|
'check': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>',
|
|
@@ -28291,6 +28398,890 @@ class KritzelAnchorManager {
|
|
|
28291
28398
|
}
|
|
28292
28399
|
}
|
|
28293
28400
|
|
|
28401
|
+
/**
|
|
28402
|
+
* Pure static utilities for localization term resolution.
|
|
28403
|
+
*/
|
|
28404
|
+
class LocalizationHelper {
|
|
28405
|
+
/**
|
|
28406
|
+
* Interpolates `{placeholder}` tokens in a term template with the provided
|
|
28407
|
+
* variables. Tokens without a matching variable are left untouched.
|
|
28408
|
+
*
|
|
28409
|
+
* @param template - The raw term string, e.g. `'Expires {date}'`
|
|
28410
|
+
* @param vars - Map of placeholder names to replacement values
|
|
28411
|
+
* @returns The interpolated string
|
|
28412
|
+
*
|
|
28413
|
+
* @example
|
|
28414
|
+
* LocalizationHelper.interpolate('Hello {name}', { name: 'Ada' }); // 'Hello Ada'
|
|
28415
|
+
*/
|
|
28416
|
+
static interpolate(template, vars) {
|
|
28417
|
+
if (!vars) {
|
|
28418
|
+
return template;
|
|
28419
|
+
}
|
|
28420
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
28421
|
+
const value = vars[key];
|
|
28422
|
+
return value === undefined || value === null ? match : String(value);
|
|
28423
|
+
});
|
|
28424
|
+
}
|
|
28425
|
+
}
|
|
28426
|
+
|
|
28427
|
+
/**
|
|
28428
|
+
* Built-in English locale (default). Provides a complete set of term values
|
|
28429
|
+
* that act as the ultimate fallback when other locales omit a key.
|
|
28430
|
+
*/
|
|
28431
|
+
const EN_LOCALE = {
|
|
28432
|
+
code: 'en',
|
|
28433
|
+
label: 'English',
|
|
28434
|
+
terms: {
|
|
28435
|
+
// Context menu
|
|
28436
|
+
'menu.copy': 'Copy',
|
|
28437
|
+
'menu.cut': 'Cut',
|
|
28438
|
+
'menu.paste': 'Paste',
|
|
28439
|
+
'menu.selectAll': 'Select All',
|
|
28440
|
+
'menu.order': 'Order',
|
|
28441
|
+
'menu.bringToFront': 'Bring to Front',
|
|
28442
|
+
'menu.sendToBack': 'Send to Back',
|
|
28443
|
+
'menu.moveUp': 'Move Up',
|
|
28444
|
+
'menu.moveDown': 'Move Down',
|
|
28445
|
+
'menu.align': 'Align',
|
|
28446
|
+
'menu.alignLeft': 'Align Left',
|
|
28447
|
+
'menu.alignCenterHorizontal': 'Align Center Horizontally',
|
|
28448
|
+
'menu.alignRight': 'Align Right',
|
|
28449
|
+
'menu.alignTop': 'Align Top',
|
|
28450
|
+
'menu.alignCenterVertical': 'Align Center Vertically',
|
|
28451
|
+
'menu.alignBottom': 'Align Bottom',
|
|
28452
|
+
'menu.group': 'Group',
|
|
28453
|
+
'menu.ungroup': 'Ungroup',
|
|
28454
|
+
'menu.export': 'Export',
|
|
28455
|
+
'menu.exportAsSvg': 'Export as SVG',
|
|
28456
|
+
'menu.exportAsPng': 'Export as PNG',
|
|
28457
|
+
'menu.delete': 'Delete',
|
|
28458
|
+
// More menu
|
|
28459
|
+
'menu.share': 'Share',
|
|
28460
|
+
'menu.import': 'Import',
|
|
28461
|
+
'menu.settings': 'Settings',
|
|
28462
|
+
'menu.logout': 'Logout',
|
|
28463
|
+
// Settings dialog
|
|
28464
|
+
'settings.dialogTitle': 'Settings',
|
|
28465
|
+
'settings.categories.general': 'General',
|
|
28466
|
+
'settings.categories.viewport': 'Viewport',
|
|
28467
|
+
'settings.categories.shortcuts': 'Keyboard Shortcuts',
|
|
28468
|
+
'settings.categories.developer': 'Developer Options',
|
|
28469
|
+
'settings.categories.about': 'About',
|
|
28470
|
+
'settings.general.title': 'General Settings',
|
|
28471
|
+
'settings.general.theme.label': 'Theme',
|
|
28472
|
+
'settings.general.theme.description': 'Select a registered color theme for the editor interface.',
|
|
28473
|
+
'settings.general.language.label': 'Language',
|
|
28474
|
+
'settings.general.language.description': 'Select the display language for the editor interface.',
|
|
28475
|
+
'settings.general.lockDrawingScale.label': 'Lock Drawing Scale',
|
|
28476
|
+
'settings.general.lockDrawingScale.description': 'When enabled, drawn objects maintain a fixed visual size regardless of the current zoom level.',
|
|
28477
|
+
'settings.viewport.title': 'Viewport Settings',
|
|
28478
|
+
'settings.viewport.minZoom.label': 'Minimum Zoom Level',
|
|
28479
|
+
'settings.viewport.minZoom.description': 'Sets the minimum zoom level. Lower values allow zooming out further to see more of the canvas.',
|
|
28480
|
+
'settings.viewport.maxZoom.label': 'Maximum Zoom Level',
|
|
28481
|
+
'settings.viewport.maxZoom.description': 'Sets the maximum zoom level. Higher values allow zooming in closer for detailed work.',
|
|
28482
|
+
'settings.viewport.boundaryLeft.label': 'Viewport Boundary Left',
|
|
28483
|
+
'settings.viewport.boundaryLeft.description': 'Left boundary in world coordinates. Set to limit how far left the viewport can pan.',
|
|
28484
|
+
'settings.viewport.boundaryRight.label': 'Viewport Boundary Right',
|
|
28485
|
+
'settings.viewport.boundaryRight.description': 'Right boundary in world coordinates. Set to limit how far right the viewport can pan.',
|
|
28486
|
+
'settings.viewport.boundaryTop.label': 'Viewport Boundary Top',
|
|
28487
|
+
'settings.viewport.boundaryTop.description': 'Top boundary in world coordinates. Set to limit how far up the viewport can pan.',
|
|
28488
|
+
'settings.viewport.boundaryBottom.label': 'Viewport Boundary Bottom',
|
|
28489
|
+
'settings.viewport.boundaryBottom.description': 'Bottom boundary in world coordinates. Set to limit how far down the viewport can pan.',
|
|
28490
|
+
'settings.viewport.boundaryPlaceholder': 'Infinite',
|
|
28491
|
+
'settings.shortcuts.title': 'Keyboard Shortcuts',
|
|
28492
|
+
'settings.developer.title': 'Developer Options',
|
|
28493
|
+
'settings.developer.showViewportInfo.label': 'Show Viewport Info',
|
|
28494
|
+
'settings.developer.showViewportInfo.description': 'Display viewport debug information such as position, zoom level, and boundaries.',
|
|
28495
|
+
'settings.developer.showObjectInfo.label': 'Show Object Info',
|
|
28496
|
+
'settings.developer.showObjectInfo.description': 'Display debug information about objects on the canvas.',
|
|
28497
|
+
'settings.developer.showSyncProviderInfo.label': 'Show Sync Provider Info',
|
|
28498
|
+
'settings.developer.showSyncProviderInfo.description': 'Display debug information about the sync provider connection status.',
|
|
28499
|
+
'settings.developer.showMigrationInfo.label': 'Show Migration Info',
|
|
28500
|
+
'settings.developer.showMigrationInfo.description': 'Display debug information about data migrations.',
|
|
28501
|
+
'settings.about.title': 'About',
|
|
28502
|
+
'settings.about.description': 'Kritzel - A drawing application',
|
|
28503
|
+
// Export dialog
|
|
28504
|
+
'export.dialogTitle': 'Export',
|
|
28505
|
+
'export.tabs.viewport': 'Export Viewport',
|
|
28506
|
+
'export.tabs.workspace': 'Export Workspace',
|
|
28507
|
+
'export.format.label': 'Format',
|
|
28508
|
+
'export.filename.label': 'Filename',
|
|
28509
|
+
'export.filename.placeholder': 'Enter filename',
|
|
28510
|
+
'export.exportButton': 'Export',
|
|
28511
|
+
// Workspace manager
|
|
28512
|
+
'workspace.sharedTooltip': 'Shared workspace',
|
|
28513
|
+
'workspace.rename': 'Rename',
|
|
28514
|
+
'workspace.delete': 'Delete',
|
|
28515
|
+
// Zoom panel
|
|
28516
|
+
'zoom.zoomIn': 'Zoom in',
|
|
28517
|
+
'zoom.zoomOut': 'Zoom out',
|
|
28518
|
+
// Utility panel
|
|
28519
|
+
'utility.undo': 'Undo',
|
|
28520
|
+
'utility.redo': 'Redo',
|
|
28521
|
+
'utility.delete': 'Delete selected items',
|
|
28522
|
+
// Share dialog
|
|
28523
|
+
'share.dialogTitle': 'Share Workspace',
|
|
28524
|
+
'share.linkSharing.label': 'Link sharing',
|
|
28525
|
+
'share.linkSharing.enabledDescription': 'Anyone with the link can access this workspace.',
|
|
28526
|
+
'share.linkSharing.disabledDescription': 'Link sharing is disabled. Only you can access this workspace.',
|
|
28527
|
+
'share.linkSharing.toggleLabel': 'Enable link sharing',
|
|
28528
|
+
'share.copyLink.title': 'Copy link',
|
|
28529
|
+
'share.copyLink.copied': 'Copied!',
|
|
28530
|
+
// Login dialog
|
|
28531
|
+
'login.dialogTitle': 'Sign in',
|
|
28532
|
+
// Current user dialog
|
|
28533
|
+
'currentUser.dialogTitle': 'Account',
|
|
28534
|
+
// Back to content
|
|
28535
|
+
'backToContent.label': 'Back to content',
|
|
28536
|
+
// Tool config
|
|
28537
|
+
'toolConfig.collapse': 'Collapse',
|
|
28538
|
+
'toolConfig.expand': 'Expand',
|
|
28539
|
+
// More menu button
|
|
28540
|
+
'moreMenu.ariaLabel': 'More options',
|
|
28541
|
+
// Engine
|
|
28542
|
+
'engine.loading': 'Loading...',
|
|
28543
|
+
// Watermark
|
|
28544
|
+
'watermark.poweredBy': 'Powered by Kritzel',
|
|
28545
|
+
},
|
|
28546
|
+
};
|
|
28547
|
+
|
|
28548
|
+
/**
|
|
28549
|
+
* Built-in German locale.
|
|
28550
|
+
*/
|
|
28551
|
+
const DE_LOCALE = {
|
|
28552
|
+
code: 'de',
|
|
28553
|
+
label: 'Deutsch',
|
|
28554
|
+
terms: {
|
|
28555
|
+
// Context menu
|
|
28556
|
+
'menu.copy': 'Kopieren',
|
|
28557
|
+
'menu.cut': 'Ausschneiden',
|
|
28558
|
+
'menu.paste': 'Einfügen',
|
|
28559
|
+
'menu.selectAll': 'Alles auswählen',
|
|
28560
|
+
'menu.order': 'Anordnen',
|
|
28561
|
+
'menu.bringToFront': 'In den Vordergrund',
|
|
28562
|
+
'menu.sendToBack': 'In den Hintergrund',
|
|
28563
|
+
'menu.moveUp': 'Nach vorne',
|
|
28564
|
+
'menu.moveDown': 'Nach hinten',
|
|
28565
|
+
'menu.align': 'Ausrichten',
|
|
28566
|
+
'menu.alignLeft': 'Linksbündig ausrichten',
|
|
28567
|
+
'menu.alignCenterHorizontal': 'Horizontal zentrieren',
|
|
28568
|
+
'menu.alignRight': 'Rechtsbündig ausrichten',
|
|
28569
|
+
'menu.alignTop': 'Oben ausrichten',
|
|
28570
|
+
'menu.alignCenterVertical': 'Vertikal zentrieren',
|
|
28571
|
+
'menu.alignBottom': 'Unten ausrichten',
|
|
28572
|
+
'menu.group': 'Gruppieren',
|
|
28573
|
+
'menu.ungroup': 'Gruppierung aufheben',
|
|
28574
|
+
'menu.export': 'Exportieren',
|
|
28575
|
+
'menu.exportAsSvg': 'Als SVG exportieren',
|
|
28576
|
+
'menu.exportAsPng': 'Als PNG exportieren',
|
|
28577
|
+
'menu.delete': 'Löschen',
|
|
28578
|
+
// More menu
|
|
28579
|
+
'menu.share': 'Teilen',
|
|
28580
|
+
'menu.import': 'Importieren',
|
|
28581
|
+
'menu.settings': 'Einstellungen',
|
|
28582
|
+
'menu.logout': 'Abmelden',
|
|
28583
|
+
// Settings dialog
|
|
28584
|
+
'settings.dialogTitle': 'Einstellungen',
|
|
28585
|
+
'settings.categories.general': 'Allgemein',
|
|
28586
|
+
'settings.categories.viewport': 'Ansichtsfenster',
|
|
28587
|
+
'settings.categories.shortcuts': 'Tastenkürzel',
|
|
28588
|
+
'settings.categories.developer': 'Entwickleroptionen',
|
|
28589
|
+
'settings.categories.about': 'Über',
|
|
28590
|
+
'settings.general.title': 'Allgemeine Einstellungen',
|
|
28591
|
+
'settings.general.theme.label': 'Design',
|
|
28592
|
+
'settings.general.theme.description': 'Wählen Sie ein registriertes Farbdesign für die Editor-Oberfläche.',
|
|
28593
|
+
'settings.general.language.label': 'Sprache',
|
|
28594
|
+
'settings.general.language.description': 'Wählen Sie die Anzeigesprache für die Editor-Oberfläche.',
|
|
28595
|
+
'settings.general.lockDrawingScale.label': 'Zeichenskalierung sperren',
|
|
28596
|
+
'settings.general.lockDrawingScale.description': 'Wenn aktiviert, behalten gezeichnete Objekte unabhängig von der aktuellen Zoomstufe eine feste visuelle Größe.',
|
|
28597
|
+
'settings.viewport.title': 'Ansichtsfenster-Einstellungen',
|
|
28598
|
+
'settings.viewport.minZoom.label': 'Minimale Zoomstufe',
|
|
28599
|
+
'settings.viewport.minZoom.description': 'Legt die minimale Zoomstufe fest. Niedrigere Werte ermöglichen weiteres Herauszoomen, um mehr von der Zeichenfläche zu sehen.',
|
|
28600
|
+
'settings.viewport.maxZoom.label': 'Maximale Zoomstufe',
|
|
28601
|
+
'settings.viewport.maxZoom.description': 'Legt die maximale Zoomstufe fest. Höhere Werte ermöglichen näheres Heranzoomen für Detailarbeit.',
|
|
28602
|
+
'settings.viewport.boundaryLeft.label': 'Ansichtsfenster-Grenze links',
|
|
28603
|
+
'settings.viewport.boundaryLeft.description': 'Linke Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach links verschoben werden kann.',
|
|
28604
|
+
'settings.viewport.boundaryRight.label': 'Ansichtsfenster-Grenze rechts',
|
|
28605
|
+
'settings.viewport.boundaryRight.description': 'Rechte Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach rechts verschoben werden kann.',
|
|
28606
|
+
'settings.viewport.boundaryTop.label': 'Ansichtsfenster-Grenze oben',
|
|
28607
|
+
'settings.viewport.boundaryTop.description': 'Obere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach oben verschoben werden kann.',
|
|
28608
|
+
'settings.viewport.boundaryBottom.label': 'Ansichtsfenster-Grenze unten',
|
|
28609
|
+
'settings.viewport.boundaryBottom.description': 'Untere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach unten verschoben werden kann.',
|
|
28610
|
+
'settings.viewport.boundaryPlaceholder': 'Unendlich',
|
|
28611
|
+
'settings.shortcuts.title': 'Tastenkürzel',
|
|
28612
|
+
'settings.developer.title': 'Entwickleroptionen',
|
|
28613
|
+
'settings.developer.showViewportInfo.label': 'Ansichtsfenster-Infos anzeigen',
|
|
28614
|
+
'settings.developer.showViewportInfo.description': 'Zeigt Debug-Informationen zum Ansichtsfenster an, z. B. Position, Zoomstufe und Grenzen.',
|
|
28615
|
+
'settings.developer.showObjectInfo.label': 'Objekt-Infos anzeigen',
|
|
28616
|
+
'settings.developer.showObjectInfo.description': 'Zeigt Debug-Informationen zu Objekten auf der Zeichenfläche an.',
|
|
28617
|
+
'settings.developer.showSyncProviderInfo.label': 'Sync-Provider-Infos anzeigen',
|
|
28618
|
+
'settings.developer.showSyncProviderInfo.description': 'Zeigt Debug-Informationen zum Verbindungsstatus des Sync-Providers an.',
|
|
28619
|
+
'settings.developer.showMigrationInfo.label': 'Migrations-Infos anzeigen',
|
|
28620
|
+
'settings.developer.showMigrationInfo.description': 'Zeigt Debug-Informationen zu Datenmigrationen an.',
|
|
28621
|
+
'settings.about.title': 'Über',
|
|
28622
|
+
'settings.about.description': 'Kritzel – Eine Zeichenanwendung',
|
|
28623
|
+
// Export dialog
|
|
28624
|
+
'export.dialogTitle': 'Exportieren',
|
|
28625
|
+
'export.tabs.viewport': 'Ansichtsfenster exportieren',
|
|
28626
|
+
'export.tabs.workspace': 'Arbeitsbereich exportieren',
|
|
28627
|
+
'export.format.label': 'Format',
|
|
28628
|
+
'export.filename.label': 'Dateiname',
|
|
28629
|
+
'export.filename.placeholder': 'Dateiname eingeben',
|
|
28630
|
+
'export.exportButton': 'Exportieren',
|
|
28631
|
+
// Workspace manager
|
|
28632
|
+
'workspace.sharedTooltip': 'Geteilter Arbeitsbereich',
|
|
28633
|
+
'workspace.rename': 'Umbenennen',
|
|
28634
|
+
'workspace.delete': 'Löschen',
|
|
28635
|
+
// Zoom panel
|
|
28636
|
+
'zoom.zoomIn': 'Vergrößern',
|
|
28637
|
+
'zoom.zoomOut': 'Verkleinern',
|
|
28638
|
+
// Utility panel
|
|
28639
|
+
'utility.undo': 'Rückgängig',
|
|
28640
|
+
'utility.redo': 'Wiederholen',
|
|
28641
|
+
'utility.delete': 'Ausgewählte Elemente löschen',
|
|
28642
|
+
// Share dialog
|
|
28643
|
+
'share.dialogTitle': 'Arbeitsbereich teilen',
|
|
28644
|
+
'share.linkSharing.label': 'Link-Freigabe',
|
|
28645
|
+
'share.linkSharing.enabledDescription': 'Jeder mit dem Link kann auf diesen Arbeitsbereich zugreifen.',
|
|
28646
|
+
'share.linkSharing.disabledDescription': 'Die Link-Freigabe ist deaktiviert. Nur Sie können auf diesen Arbeitsbereich zugreifen.',
|
|
28647
|
+
'share.linkSharing.toggleLabel': 'Link-Freigabe aktivieren',
|
|
28648
|
+
'share.copyLink.title': 'Link kopieren',
|
|
28649
|
+
'share.copyLink.copied': 'Kopiert!',
|
|
28650
|
+
// Login dialog
|
|
28651
|
+
'login.dialogTitle': 'Anmelden',
|
|
28652
|
+
// Current user dialog
|
|
28653
|
+
'currentUser.dialogTitle': 'Konto',
|
|
28654
|
+
// Back to content
|
|
28655
|
+
'backToContent.label': 'Zurück zum Inhalt',
|
|
28656
|
+
// Tool config
|
|
28657
|
+
'toolConfig.collapse': 'Einklappen',
|
|
28658
|
+
'toolConfig.expand': 'Ausklappen',
|
|
28659
|
+
// More menu button
|
|
28660
|
+
'moreMenu.ariaLabel': 'Weitere Optionen',
|
|
28661
|
+
// Engine
|
|
28662
|
+
'engine.loading': 'Wird geladen...',
|
|
28663
|
+
// Watermark
|
|
28664
|
+
'watermark.poweredBy': 'Bereitgestellt von Kritzel',
|
|
28665
|
+
},
|
|
28666
|
+
};
|
|
28667
|
+
|
|
28668
|
+
/**
|
|
28669
|
+
* Built-in French locale.
|
|
28670
|
+
*/
|
|
28671
|
+
const FR_LOCALE = {
|
|
28672
|
+
code: 'fr',
|
|
28673
|
+
label: 'Français',
|
|
28674
|
+
terms: {
|
|
28675
|
+
// Context menu
|
|
28676
|
+
'menu.copy': 'Copier',
|
|
28677
|
+
'menu.cut': 'Couper',
|
|
28678
|
+
'menu.paste': 'Coller',
|
|
28679
|
+
'menu.selectAll': 'Tout sélectionner',
|
|
28680
|
+
'menu.order': 'Ordre',
|
|
28681
|
+
'menu.bringToFront': 'Mettre au premier plan',
|
|
28682
|
+
'menu.sendToBack': "Mettre à l'arrière-plan",
|
|
28683
|
+
'menu.moveUp': 'Avancer',
|
|
28684
|
+
'menu.moveDown': 'Reculer',
|
|
28685
|
+
'menu.align': 'Aligner',
|
|
28686
|
+
'menu.alignLeft': 'Aligner à gauche',
|
|
28687
|
+
'menu.alignCenterHorizontal': 'Centrer horizontalement',
|
|
28688
|
+
'menu.alignRight': 'Aligner à droite',
|
|
28689
|
+
'menu.alignTop': 'Aligner en haut',
|
|
28690
|
+
'menu.alignCenterVertical': 'Centrer verticalement',
|
|
28691
|
+
'menu.alignBottom': 'Aligner en bas',
|
|
28692
|
+
'menu.group': 'Grouper',
|
|
28693
|
+
'menu.ungroup': 'Dégrouper',
|
|
28694
|
+
'menu.export': 'Exporter',
|
|
28695
|
+
'menu.exportAsSvg': 'Exporter en SVG',
|
|
28696
|
+
'menu.exportAsPng': 'Exporter en PNG',
|
|
28697
|
+
'menu.delete': 'Supprimer',
|
|
28698
|
+
// More menu
|
|
28699
|
+
'menu.share': 'Partager',
|
|
28700
|
+
'menu.import': 'Importer',
|
|
28701
|
+
'menu.settings': 'Paramètres',
|
|
28702
|
+
'menu.logout': 'Déconnexion',
|
|
28703
|
+
// Settings dialog
|
|
28704
|
+
'settings.dialogTitle': 'Paramètres',
|
|
28705
|
+
'settings.categories.general': 'Général',
|
|
28706
|
+
'settings.categories.viewport': "Fenêtre d'affichage",
|
|
28707
|
+
'settings.categories.shortcuts': 'Raccourcis clavier',
|
|
28708
|
+
'settings.categories.developer': 'Options développeur',
|
|
28709
|
+
'settings.categories.about': 'À propos',
|
|
28710
|
+
'settings.general.title': 'Paramètres généraux',
|
|
28711
|
+
'settings.general.theme.label': 'Thème',
|
|
28712
|
+
'settings.general.theme.description': "Sélectionnez un thème de couleurs enregistré pour l'interface de l'éditeur.",
|
|
28713
|
+
'settings.general.language.label': 'Langue',
|
|
28714
|
+
'settings.general.language.description': "Sélectionnez la langue d'affichage de l'interface de l'éditeur.",
|
|
28715
|
+
'settings.general.lockDrawingScale.label': "Verrouiller l'échelle de dessin",
|
|
28716
|
+
'settings.general.lockDrawingScale.description': "Lorsque cette option est activée, les objets dessinés conservent une taille visuelle fixe quel que soit le niveau de zoom actuel.",
|
|
28717
|
+
'settings.viewport.title': "Paramètres de la fenêtre d'affichage",
|
|
28718
|
+
'settings.viewport.minZoom.label': 'Niveau de zoom minimal',
|
|
28719
|
+
'settings.viewport.minZoom.description': 'Définit le niveau de zoom minimal. Des valeurs plus faibles permettent de dézoomer davantage pour voir une plus grande partie du canevas.',
|
|
28720
|
+
'settings.viewport.maxZoom.label': 'Niveau de zoom maximal',
|
|
28721
|
+
'settings.viewport.maxZoom.description': 'Définit le niveau de zoom maximal. Des valeurs plus élevées permettent de zoomer davantage pour un travail détaillé.',
|
|
28722
|
+
'settings.viewport.boundaryLeft.label': "Limite gauche de la fenêtre d'affichage",
|
|
28723
|
+
'settings.viewport.boundaryLeft.description': "Limite gauche en coordonnées du monde. Définissez jusqu'où la fenêtre d'affichage peut se déplacer vers la gauche.",
|
|
28724
|
+
'settings.viewport.boundaryRight.label': "Limite droite de la fenêtre d'affichage",
|
|
28725
|
+
'settings.viewport.boundaryRight.description': "Limite droite en coordonnées du monde. Définissez jusqu'où la fenêtre d'affichage peut se déplacer vers la droite.",
|
|
28726
|
+
'settings.viewport.boundaryTop.label': "Limite supérieure de la fenêtre d'affichage",
|
|
28727
|
+
'settings.viewport.boundaryTop.description': "Limite supérieure en coordonnées du monde. Définissez jusqu'où la fenêtre d'affichage peut se déplacer vers le haut.",
|
|
28728
|
+
'settings.viewport.boundaryBottom.label': "Limite inférieure de la fenêtre d'affichage",
|
|
28729
|
+
'settings.viewport.boundaryBottom.description': "Limite inférieure en coordonnées du monde. Définissez jusqu'où la fenêtre d'affichage peut se déplacer vers le bas.",
|
|
28730
|
+
'settings.viewport.boundaryPlaceholder': 'Infini',
|
|
28731
|
+
'settings.shortcuts.title': 'Raccourcis clavier',
|
|
28732
|
+
'settings.developer.title': 'Options développeur',
|
|
28733
|
+
'settings.developer.showViewportInfo.label': "Afficher les infos de la fenêtre d'affichage",
|
|
28734
|
+
'settings.developer.showViewportInfo.description': "Affiche les informations de débogage de la fenêtre d'affichage telles que la position, le niveau de zoom et les limites.",
|
|
28735
|
+
'settings.developer.showObjectInfo.label': 'Afficher les infos des objets',
|
|
28736
|
+
'settings.developer.showObjectInfo.description': 'Affiche les informations de débogage des objets sur le canevas.',
|
|
28737
|
+
'settings.developer.showSyncProviderInfo.label': 'Afficher les infos du fournisseur de synchronisation',
|
|
28738
|
+
'settings.developer.showSyncProviderInfo.description': "Affiche les informations de débogage sur l'état de connexion du fournisseur de synchronisation.",
|
|
28739
|
+
'settings.developer.showMigrationInfo.label': 'Afficher les infos de migration',
|
|
28740
|
+
'settings.developer.showMigrationInfo.description': 'Affiche les informations de débogage sur les migrations de données.',
|
|
28741
|
+
'settings.about.title': 'À propos',
|
|
28742
|
+
'settings.about.description': 'Kritzel - Une application de dessin',
|
|
28743
|
+
// Export dialog
|
|
28744
|
+
'export.dialogTitle': 'Exporter',
|
|
28745
|
+
'export.tabs.viewport': "Exporter la fenêtre d'affichage",
|
|
28746
|
+
'export.tabs.workspace': "Exporter l'espace de travail",
|
|
28747
|
+
'export.format.label': 'Format',
|
|
28748
|
+
'export.filename.label': 'Nom du fichier',
|
|
28749
|
+
'export.filename.placeholder': 'Saisir le nom du fichier',
|
|
28750
|
+
'export.exportButton': 'Exporter',
|
|
28751
|
+
// Workspace manager
|
|
28752
|
+
'workspace.sharedTooltip': 'Espace de travail partagé',
|
|
28753
|
+
'workspace.rename': 'Renommer',
|
|
28754
|
+
'workspace.delete': 'Supprimer',
|
|
28755
|
+
// Zoom panel
|
|
28756
|
+
'zoom.zoomIn': 'Zoom avant',
|
|
28757
|
+
'zoom.zoomOut': 'Zoom arrière',
|
|
28758
|
+
// Utility panel
|
|
28759
|
+
'utility.undo': 'Annuler',
|
|
28760
|
+
'utility.redo': 'Rétablir',
|
|
28761
|
+
'utility.delete': 'Supprimer les éléments sélectionnés',
|
|
28762
|
+
// Share dialog
|
|
28763
|
+
'share.dialogTitle': "Partager l'espace de travail",
|
|
28764
|
+
'share.linkSharing.label': 'Partage par lien',
|
|
28765
|
+
'share.linkSharing.enabledDescription': 'Toute personne disposant du lien peut accéder à cet espace de travail.',
|
|
28766
|
+
'share.linkSharing.disabledDescription': 'Le partage par lien est désactivé. Vous seul pouvez accéder à cet espace de travail.',
|
|
28767
|
+
'share.linkSharing.toggleLabel': 'Activer le partage par lien',
|
|
28768
|
+
'share.copyLink.title': 'Copier le lien',
|
|
28769
|
+
'share.copyLink.copied': 'Copié !',
|
|
28770
|
+
// Login dialog
|
|
28771
|
+
'login.dialogTitle': 'Se connecter',
|
|
28772
|
+
// Current user dialog
|
|
28773
|
+
'currentUser.dialogTitle': 'Compte',
|
|
28774
|
+
// Back to content
|
|
28775
|
+
'backToContent.label': 'Retour au contenu',
|
|
28776
|
+
// Tool config
|
|
28777
|
+
'toolConfig.collapse': 'Réduire',
|
|
28778
|
+
'toolConfig.expand': 'Développer',
|
|
28779
|
+
// More menu button
|
|
28780
|
+
'moreMenu.ariaLabel': "Plus d'options",
|
|
28781
|
+
// Engine
|
|
28782
|
+
'engine.loading': 'Chargement...',
|
|
28783
|
+
// Watermark
|
|
28784
|
+
'watermark.poweredBy': 'Propulsé par Kritzel',
|
|
28785
|
+
},
|
|
28786
|
+
};
|
|
28787
|
+
|
|
28788
|
+
/** Key used to store the settings object in localStorage. */
|
|
28789
|
+
const SETTINGS_STORAGE_KEY = 'kritzel-settings';
|
|
28790
|
+
/** Default locale used when no stored preference exists. */
|
|
28791
|
+
const DEFAULT_LOCALE = 'en';
|
|
28792
|
+
/**
|
|
28793
|
+
* Manages localization state and term resolution across the Kritzel editor.
|
|
28794
|
+
*
|
|
28795
|
+
* The manager is owned per {@link KritzelCore} instance (like the theme manager)
|
|
28796
|
+
* so that multiple editors on the same page can use different languages in
|
|
28797
|
+
* isolation.
|
|
28798
|
+
*
|
|
28799
|
+
* Term resolution follows a deterministic fallback chain for each key:
|
|
28800
|
+
* 1. active locale's custom terms
|
|
28801
|
+
* 2. active locale's built-in terms
|
|
28802
|
+
* 3. fallback locale's custom terms
|
|
28803
|
+
* 4. fallback locale's built-in terms
|
|
28804
|
+
* 5. the key itself (last resort)
|
|
28805
|
+
*
|
|
28806
|
+
* Persistence of the selected locale is handled by the KritzelSettings
|
|
28807
|
+
* component; this manager only reads the stored value.
|
|
28808
|
+
*/
|
|
28809
|
+
class KritzelLocalizationManager {
|
|
28810
|
+
_core;
|
|
28811
|
+
_storageKey;
|
|
28812
|
+
/** Built-in locale definitions, keyed by locale code. */
|
|
28813
|
+
_builtinLocales = new Map();
|
|
28814
|
+
/** Consumer-registered locale definitions, keyed by locale code. */
|
|
28815
|
+
_customLocales = new Map();
|
|
28816
|
+
_currentLocale = DEFAULT_LOCALE;
|
|
28817
|
+
_fallbackLocale = DEFAULT_LOCALE;
|
|
28818
|
+
/**
|
|
28819
|
+
* Creates a new KritzelLocalizationManager instance.
|
|
28820
|
+
* Seeds the built-in locales and initializes the current locale from the
|
|
28821
|
+
* settings object in localStorage (or the default locale).
|
|
28822
|
+
*
|
|
28823
|
+
* @param core - The KritzelCore instance this manager belongs to
|
|
28824
|
+
*/
|
|
28825
|
+
constructor(core) {
|
|
28826
|
+
this._core = core;
|
|
28827
|
+
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY}-${core.editorId}` : SETTINGS_STORAGE_KEY;
|
|
28828
|
+
this._builtinLocales.set(EN_LOCALE.code, EN_LOCALE);
|
|
28829
|
+
this._builtinLocales.set(DE_LOCALE.code, DE_LOCALE);
|
|
28830
|
+
this._builtinLocales.set(FR_LOCALE.code, FR_LOCALE);
|
|
28831
|
+
this._currentLocale = this.getStoredLocale();
|
|
28832
|
+
}
|
|
28833
|
+
/**
|
|
28834
|
+
* Gets the currently active locale code.
|
|
28835
|
+
*/
|
|
28836
|
+
get currentLocale() {
|
|
28837
|
+
return this._currentLocale;
|
|
28838
|
+
}
|
|
28839
|
+
/**
|
|
28840
|
+
* Gets the fallback locale code used when a term is missing from the active locale.
|
|
28841
|
+
*/
|
|
28842
|
+
get fallbackLocale() {
|
|
28843
|
+
return this._fallbackLocale;
|
|
28844
|
+
}
|
|
28845
|
+
/**
|
|
28846
|
+
* Sets the fallback locale code used when a term is missing from the active locale.
|
|
28847
|
+
*
|
|
28848
|
+
* @param code - The fallback locale code
|
|
28849
|
+
*/
|
|
28850
|
+
setFallbackLocale(code) {
|
|
28851
|
+
this._fallbackLocale = code;
|
|
28852
|
+
}
|
|
28853
|
+
/**
|
|
28854
|
+
* Registers consumer-provided locale definitions. Definitions are merged by
|
|
28855
|
+
* code; registering the same code again replaces the previous definition.
|
|
28856
|
+
* A registered locale may provide a partial set of terms — missing terms are
|
|
28857
|
+
* resolved through the fallback chain.
|
|
28858
|
+
*
|
|
28859
|
+
* @param locales - The locale definitions to register
|
|
28860
|
+
*/
|
|
28861
|
+
registerLocales(locales) {
|
|
28862
|
+
for (const locale of locales) {
|
|
28863
|
+
this._customLocales.set(locale.code, locale);
|
|
28864
|
+
}
|
|
28865
|
+
}
|
|
28866
|
+
/**
|
|
28867
|
+
* Sets the active locale and triggers a re-render so UI strings update.
|
|
28868
|
+
*
|
|
28869
|
+
* @param code - The locale code to activate
|
|
28870
|
+
*/
|
|
28871
|
+
setLocale(code) {
|
|
28872
|
+
this._currentLocale = code;
|
|
28873
|
+
this._core.rerender();
|
|
28874
|
+
}
|
|
28875
|
+
/**
|
|
28876
|
+
* Gets the list of available locale codes (built-in and registered).
|
|
28877
|
+
*/
|
|
28878
|
+
getAvailableLocales() {
|
|
28879
|
+
return Array.from(new Set([...this._builtinLocales.keys(), ...this._customLocales.keys()]));
|
|
28880
|
+
}
|
|
28881
|
+
/**
|
|
28882
|
+
* Gets the available locales as `{ code, label }` pairs for use in a selector.
|
|
28883
|
+
* A registered locale's label takes precedence over the built-in label; the
|
|
28884
|
+
* code is used as the label when none is provided.
|
|
28885
|
+
*/
|
|
28886
|
+
getAvailableLocaleOptions() {
|
|
28887
|
+
return this.getAvailableLocales().map(code => ({ code, label: this.getLocaleLabel(code) }));
|
|
28888
|
+
}
|
|
28889
|
+
/**
|
|
28890
|
+
* Gets the display label for a locale code, falling back to the code itself.
|
|
28891
|
+
*
|
|
28892
|
+
* @param code - The locale code
|
|
28893
|
+
*/
|
|
28894
|
+
getLocaleLabel(code) {
|
|
28895
|
+
return this._customLocales.get(code)?.label ?? this._builtinLocales.get(code)?.label ?? code;
|
|
28896
|
+
}
|
|
28897
|
+
/**
|
|
28898
|
+
* Resolves a term key to its translated string for the active locale and
|
|
28899
|
+
* interpolates any `{placeholder}` tokens.
|
|
28900
|
+
*
|
|
28901
|
+
* @param key - The term key to resolve
|
|
28902
|
+
* @param vars - Optional values for `{placeholder}` interpolation
|
|
28903
|
+
* @returns The translated, interpolated string (or the key if unresolved)
|
|
28904
|
+
*
|
|
28905
|
+
* @example
|
|
28906
|
+
* manager.translate('menu.copy'); // 'Copy'
|
|
28907
|
+
* manager.translate('share.expires', { date: '5/1' }); // 'Expires 5/1'
|
|
28908
|
+
*/
|
|
28909
|
+
translate(key, vars) {
|
|
28910
|
+
const resolved = this._customLocales.get(this._currentLocale)?.terms[key] ??
|
|
28911
|
+
this._builtinLocales.get(this._currentLocale)?.terms[key] ??
|
|
28912
|
+
this._customLocales.get(this._fallbackLocale)?.terms[key] ??
|
|
28913
|
+
this._builtinLocales.get(this._fallbackLocale)?.terms[key] ??
|
|
28914
|
+
key;
|
|
28915
|
+
return LocalizationHelper.interpolate(resolved, vars);
|
|
28916
|
+
}
|
|
28917
|
+
/**
|
|
28918
|
+
* Resolves every known term key for the active locale into a flat map.
|
|
28919
|
+
*
|
|
28920
|
+
* The key set is the union of all keys defined across the built-in and
|
|
28921
|
+
* registered locales, so consumer-added keys are included. Each value is
|
|
28922
|
+
* resolved through the same fallback chain as {@link translate} (without
|
|
28923
|
+
* interpolation, since these are shared, variable-free UI labels).
|
|
28924
|
+
*
|
|
28925
|
+
* @returns A map of every term key to its resolved string for the active locale
|
|
28926
|
+
*/
|
|
28927
|
+
getAllTerms() {
|
|
28928
|
+
const keys = new Set();
|
|
28929
|
+
for (const locale of [...this._builtinLocales.values(), ...this._customLocales.values()]) {
|
|
28930
|
+
for (const key of Object.keys(locale.terms)) {
|
|
28931
|
+
keys.add(key);
|
|
28932
|
+
}
|
|
28933
|
+
}
|
|
28934
|
+
const result = {};
|
|
28935
|
+
for (const key of keys) {
|
|
28936
|
+
result[key] = this.translate(key);
|
|
28937
|
+
}
|
|
28938
|
+
return result;
|
|
28939
|
+
}
|
|
28940
|
+
/**
|
|
28941
|
+
* Reads the stored locale from the settings object in localStorage using this
|
|
28942
|
+
* instance's namespaced key.
|
|
28943
|
+
*
|
|
28944
|
+
* @returns The stored locale code if valid, or the default locale
|
|
28945
|
+
*/
|
|
28946
|
+
getStoredLocale() {
|
|
28947
|
+
if (typeof localStorage === 'undefined') {
|
|
28948
|
+
return DEFAULT_LOCALE;
|
|
28949
|
+
}
|
|
28950
|
+
const stored = localStorage.getItem(this._storageKey);
|
|
28951
|
+
if (!stored) {
|
|
28952
|
+
return DEFAULT_LOCALE;
|
|
28953
|
+
}
|
|
28954
|
+
try {
|
|
28955
|
+
const parsed = JSON.parse(stored);
|
|
28956
|
+
if (typeof parsed?.locale === 'string') {
|
|
28957
|
+
return parsed.locale;
|
|
28958
|
+
}
|
|
28959
|
+
}
|
|
28960
|
+
catch {
|
|
28961
|
+
// Invalid JSON, use default
|
|
28962
|
+
}
|
|
28963
|
+
return DEFAULT_LOCALE;
|
|
28964
|
+
}
|
|
28965
|
+
/**
|
|
28966
|
+
* Cleans up the localization manager state.
|
|
28967
|
+
* Provided for lifecycle parity with other managers.
|
|
28968
|
+
*/
|
|
28969
|
+
cleanup() {
|
|
28970
|
+
this._customLocales.clear();
|
|
28971
|
+
}
|
|
28972
|
+
}
|
|
28973
|
+
|
|
28974
|
+
/**
|
|
28975
|
+
* License verification constants.
|
|
28976
|
+
*
|
|
28977
|
+
* Kritzel licenses are offline Ed25519 signed tokens. The owner signs each
|
|
28978
|
+
* customer's token with a private key that never leaves their machine
|
|
28979
|
+
* (see scripts/license/); the library verifies tokens against the public key
|
|
28980
|
+
* below. The public key is safe to ship — it can only *verify* signatures,
|
|
28981
|
+
* never forge them.
|
|
28982
|
+
*/
|
|
28983
|
+
/**
|
|
28984
|
+
* Token format version prefix. A valid license key looks like:
|
|
28985
|
+
*
|
|
28986
|
+
* KRTZL1.<base64url(payload JSON)>.<base64url(ed25519 signature)>
|
|
28987
|
+
*
|
|
28988
|
+
* Bump the version (and this prefix) if the token scheme ever changes.
|
|
28989
|
+
*/
|
|
28990
|
+
const KRITZEL_LICENSE_TOKEN_PREFIX = 'KRTZL1.';
|
|
28991
|
+
/**
|
|
28992
|
+
* Raw 32-byte Ed25519 public key (base64url) used to verify license tokens.
|
|
28993
|
+
*
|
|
28994
|
+
* Generate the matching key pair with `npm run license:keygen` and paste the
|
|
28995
|
+
* printed public key here. Replacing this value invalidates every license
|
|
28996
|
+
* signed with the previous key.
|
|
28997
|
+
*/
|
|
28998
|
+
const KRITZEL_LICENSE_PUBLIC_KEY = 'I-pYJlxDEjT94rwSaqhXW5Sv__fMKS-JKKnV3pg0PaM';
|
|
28999
|
+
|
|
29000
|
+
/** Minimum delay (ms) between periodic license re-validations. */
|
|
29001
|
+
const MIN_REVALIDATION_DELAY_MS = 30_000;
|
|
29002
|
+
/** Maximum delay (ms) between periodic license re-validations. */
|
|
29003
|
+
const MAX_REVALIDATION_DELAY_MS = 60_000;
|
|
29004
|
+
/**
|
|
29005
|
+
* Manages license validation state for the Kritzel editor.
|
|
29006
|
+
*
|
|
29007
|
+
* The manager is owned per {@link KritzelCore} instance (like the theme and
|
|
29008
|
+
* localization managers) so that multiple editors on the same page validate
|
|
29009
|
+
* independently.
|
|
29010
|
+
*
|
|
29011
|
+
* A license key is an offline Ed25519 signed token of the form
|
|
29012
|
+
* `KRTZL1.<base64url(payload)>.<base64url(signature)>`. The library verifies it
|
|
29013
|
+
* against the embedded {@link KRITZEL_LICENSE_PUBLIC_KEY}; only the owner can
|
|
29014
|
+
* mint tokens, with the matching private key (see scripts/license/).
|
|
29015
|
+
*
|
|
29016
|
+
* Responsibilities:
|
|
29017
|
+
* - Cheaply reject absent/malformed/expired keys synchronously so the watermark
|
|
29018
|
+
* never flashes off for an obviously invalid key.
|
|
29019
|
+
* - Verify the signature asynchronously via WebCrypto, then drive a rerender if
|
|
29020
|
+
* the licensed state changed so the watermark is re-asserted on the reactive
|
|
29021
|
+
* render path.
|
|
29022
|
+
* - Periodically re-check on a randomized interval so the check is harder to
|
|
29023
|
+
* stub out programmatically than a fixed `setInterval`. Re-checks reuse the
|
|
29024
|
+
* cached verified token and only re-evaluate expiry, so crypto is not re-run
|
|
29025
|
+
* on an unchanged key every tick.
|
|
29026
|
+
*
|
|
29027
|
+
* Failure semantics (fail open):
|
|
29028
|
+
* - No key provided -> unlicensed (free tier, watermark shown). The normal
|
|
29029
|
+
* default, not an error.
|
|
29030
|
+
* - Key present but cleanly invalid (malformed, bad signature, expired) ->
|
|
29031
|
+
* unlicensed.
|
|
29032
|
+
* - Key present but validation throws an *unexpected* internal error, or the
|
|
29033
|
+
* runtime lacks Ed25519 support in WebCrypto -> fail open and treat as
|
|
29034
|
+
* licensed so a library defect or old browser never breaks a paying
|
|
29035
|
+
* customer's app.
|
|
29036
|
+
*/
|
|
29037
|
+
class KritzelLicenseManager {
|
|
29038
|
+
_core;
|
|
29039
|
+
/** The most recently provided license key, re-checked on each periodic tick. */
|
|
29040
|
+
_licenseKey;
|
|
29041
|
+
/** Cached result of the latest validation. */
|
|
29042
|
+
_isLicensed = false;
|
|
29043
|
+
/**
|
|
29044
|
+
* The token whose signature most recently verified successfully. Lets the
|
|
29045
|
+
* periodic re-check skip crypto and only re-evaluate expiry while the key is
|
|
29046
|
+
* unchanged.
|
|
29047
|
+
*/
|
|
29048
|
+
_verifiedToken;
|
|
29049
|
+
/**
|
|
29050
|
+
* Monotonic id incremented on every {@link validate} call. An in-flight async
|
|
29051
|
+
* verification only commits its result if its id still matches, so a stale
|
|
29052
|
+
* verification resolving after a newer call can never clobber fresh state.
|
|
29053
|
+
*/
|
|
29054
|
+
_validationId = 0;
|
|
29055
|
+
/** Cached imported public key, lazily created on first signature check. */
|
|
29056
|
+
_publicKeyPromise;
|
|
29057
|
+
/** Handle for the self-rescheduling re-validation timer. */
|
|
29058
|
+
_timer = null;
|
|
29059
|
+
/**
|
|
29060
|
+
* Creates a new KritzelLicenseManager instance.
|
|
29061
|
+
* @param core - The KritzelCore instance this manager belongs to
|
|
29062
|
+
*/
|
|
29063
|
+
constructor(core) {
|
|
29064
|
+
this._core = core;
|
|
29065
|
+
}
|
|
29066
|
+
/**
|
|
29067
|
+
* Whether the editor is currently considered licensed (watermark hidden).
|
|
29068
|
+
*/
|
|
29069
|
+
get isLicensed() {
|
|
29070
|
+
return this._isLicensed;
|
|
29071
|
+
}
|
|
29072
|
+
/**
|
|
29073
|
+
* Validates the given license key.
|
|
29074
|
+
*
|
|
29075
|
+
* Runs a synchronous structural pre-check first: an absent, malformed, or
|
|
29076
|
+
* already-expired key resolves the state immediately (no async work). A
|
|
29077
|
+
* structurally valid key then has its signature verified asynchronously; the
|
|
29078
|
+
* licensed state is committed (and a rerender triggered) only if it changed.
|
|
29079
|
+
*
|
|
29080
|
+
* The return value reflects the state known *synchronously* at call time. The
|
|
29081
|
+
* authoritative result for a structurally valid key arrives shortly after via
|
|
29082
|
+
* the async verification and the reactive rerender.
|
|
29083
|
+
*
|
|
29084
|
+
* @param key - The license key to validate, or undefined to clear the license
|
|
29085
|
+
* @returns The licensed state known synchronously at call time
|
|
29086
|
+
*/
|
|
29087
|
+
validate(key) {
|
|
29088
|
+
this._licenseKey = key;
|
|
29089
|
+
const validationId = ++this._validationId;
|
|
29090
|
+
let pre;
|
|
29091
|
+
try {
|
|
29092
|
+
pre = this.preCheck(key);
|
|
29093
|
+
}
|
|
29094
|
+
catch {
|
|
29095
|
+
// Fail open: an unexpected internal error in the cheap pre-check must
|
|
29096
|
+
// never break a paying customer's app.
|
|
29097
|
+
this.commit(true, validationId);
|
|
29098
|
+
return this._isLicensed;
|
|
29099
|
+
}
|
|
29100
|
+
if (pre.status === 'rejected') {
|
|
29101
|
+
this._verifiedToken = undefined;
|
|
29102
|
+
this.commit(false, validationId);
|
|
29103
|
+
return this._isLicensed;
|
|
29104
|
+
}
|
|
29105
|
+
// Structurally valid and unexpired. If we already verified this exact token,
|
|
29106
|
+
// the signature is still good, so license it without re-running crypto.
|
|
29107
|
+
if (this._verifiedToken === key) {
|
|
29108
|
+
this.commit(true, validationId);
|
|
29109
|
+
return this._isLicensed;
|
|
29110
|
+
}
|
|
29111
|
+
// Otherwise verify the signature asynchronously. Leave the current state
|
|
29112
|
+
// untouched until it settles so a forged token cannot even briefly hide the
|
|
29113
|
+
// watermark (default state is unlicensed), and a valid renewal does not flap.
|
|
29114
|
+
this.verifySignature(pre.payloadSegment, pre.signatureSegment)
|
|
29115
|
+
.then(valid => {
|
|
29116
|
+
if (validationId !== this._validationId) {
|
|
29117
|
+
return; // A newer validate() superseded this one.
|
|
29118
|
+
}
|
|
29119
|
+
if (valid) {
|
|
29120
|
+
this._verifiedToken = key;
|
|
29121
|
+
this.commit(true, validationId);
|
|
29122
|
+
}
|
|
29123
|
+
else {
|
|
29124
|
+
this._verifiedToken = undefined;
|
|
29125
|
+
this.commit(false, validationId);
|
|
29126
|
+
}
|
|
29127
|
+
})
|
|
29128
|
+
.catch(() => {
|
|
29129
|
+
if (validationId !== this._validationId) {
|
|
29130
|
+
return;
|
|
29131
|
+
}
|
|
29132
|
+
// Fail open on unexpected crypto errors / missing Ed25519 support.
|
|
29133
|
+
this.commit(true, validationId);
|
|
29134
|
+
});
|
|
29135
|
+
return this._isLicensed;
|
|
29136
|
+
}
|
|
29137
|
+
/**
|
|
29138
|
+
* Starts periodic re-validation on a randomized interval between
|
|
29139
|
+
* {@link MIN_REVALIDATION_DELAY_MS} and {@link MAX_REVALIDATION_DELAY_MS}.
|
|
29140
|
+
*
|
|
29141
|
+
* Uses a self-rescheduling `setTimeout` (a fresh random delay each cycle)
|
|
29142
|
+
* rather than a fixed `setInterval`, making the check fractionally harder to
|
|
29143
|
+
* predict and no-op programmatically. Any existing timer is cleared first so
|
|
29144
|
+
* the method is idempotent.
|
|
29145
|
+
*/
|
|
29146
|
+
startPeriodicValidation() {
|
|
29147
|
+
this.stopPeriodicValidation();
|
|
29148
|
+
this.scheduleNextValidation();
|
|
29149
|
+
}
|
|
29150
|
+
/**
|
|
29151
|
+
* Stops periodic re-validation and releases the timer.
|
|
29152
|
+
*/
|
|
29153
|
+
stopPeriodicValidation() {
|
|
29154
|
+
if (this._timer !== null) {
|
|
29155
|
+
clearTimeout(this._timer);
|
|
29156
|
+
this._timer = null;
|
|
29157
|
+
}
|
|
29158
|
+
}
|
|
29159
|
+
/**
|
|
29160
|
+
* Cleanup hook called when the editor is torn down.
|
|
29161
|
+
*/
|
|
29162
|
+
destroy() {
|
|
29163
|
+
this.stopPeriodicValidation();
|
|
29164
|
+
}
|
|
29165
|
+
/**
|
|
29166
|
+
* Commits a new licensed state, triggering a rerender only when it changed.
|
|
29167
|
+
* The {@link validationId} guard is checked by async callers; synchronous
|
|
29168
|
+
* callers pass the current id.
|
|
29169
|
+
*/
|
|
29170
|
+
commit(nextLicensed, validationId) {
|
|
29171
|
+
if (validationId !== this._validationId) {
|
|
29172
|
+
return;
|
|
29173
|
+
}
|
|
29174
|
+
if (nextLicensed !== this._isLicensed) {
|
|
29175
|
+
this._isLicensed = nextLicensed;
|
|
29176
|
+
this._core.rerender();
|
|
29177
|
+
}
|
|
29178
|
+
}
|
|
29179
|
+
/**
|
|
29180
|
+
* Schedules the next re-validation tick at a randomized delay.
|
|
29181
|
+
*/
|
|
29182
|
+
scheduleNextValidation() {
|
|
29183
|
+
const delay = this.getRandomRevalidationDelay();
|
|
29184
|
+
this._timer = setTimeout(() => {
|
|
29185
|
+
this.validate(this._licenseKey);
|
|
29186
|
+
this.scheduleNextValidation();
|
|
29187
|
+
}, delay);
|
|
29188
|
+
}
|
|
29189
|
+
/**
|
|
29190
|
+
* Returns a random delay within the configured re-validation window.
|
|
29191
|
+
*/
|
|
29192
|
+
getRandomRevalidationDelay() {
|
|
29193
|
+
const span = MAX_REVALIDATION_DELAY_MS - MIN_REVALIDATION_DELAY_MS;
|
|
29194
|
+
return MIN_REVALIDATION_DELAY_MS + Math.floor(Math.random() * (span + 1));
|
|
29195
|
+
}
|
|
29196
|
+
/**
|
|
29197
|
+
* Synchronous structural pre-check of a license key: verifies the format
|
|
29198
|
+
* prefix, three-part structure, JSON payload, and (if present) that the token
|
|
29199
|
+
* has not expired. Does not verify the signature.
|
|
29200
|
+
*
|
|
29201
|
+
* @returns `rejected` for absent/malformed/expired keys, or `pass` with the
|
|
29202
|
+
* parsed segments and claims for a structurally valid, unexpired token.
|
|
29203
|
+
*/
|
|
29204
|
+
preCheck(key) {
|
|
29205
|
+
if (key === undefined || key === null) {
|
|
29206
|
+
return { status: 'rejected' };
|
|
29207
|
+
}
|
|
29208
|
+
const token = key.trim();
|
|
29209
|
+
if (token.length === 0 || !token.startsWith(KRITZEL_LICENSE_TOKEN_PREFIX)) {
|
|
29210
|
+
return { status: 'rejected' };
|
|
29211
|
+
}
|
|
29212
|
+
const body = token.slice(KRITZEL_LICENSE_TOKEN_PREFIX.length);
|
|
29213
|
+
const parts = body.split('.');
|
|
29214
|
+
if (parts.length !== 2 || parts[0].length === 0 || parts[1].length === 0) {
|
|
29215
|
+
return { status: 'rejected' };
|
|
29216
|
+
}
|
|
29217
|
+
const [payloadSegment, signatureSegment] = parts;
|
|
29218
|
+
let claims;
|
|
29219
|
+
try {
|
|
29220
|
+
claims = JSON.parse(this.base64UrlToString(payloadSegment));
|
|
29221
|
+
}
|
|
29222
|
+
catch {
|
|
29223
|
+
return { status: 'rejected' };
|
|
29224
|
+
}
|
|
29225
|
+
if (claims === null || typeof claims !== 'object') {
|
|
29226
|
+
return { status: 'rejected' };
|
|
29227
|
+
}
|
|
29228
|
+
if (typeof claims.exp === 'number' && claims.exp * 1000 <= Date.now()) {
|
|
29229
|
+
return { status: 'rejected' }; // Cleanly expired.
|
|
29230
|
+
}
|
|
29231
|
+
return { status: 'pass', payloadSegment, signatureSegment, claims };
|
|
29232
|
+
}
|
|
29233
|
+
/**
|
|
29234
|
+
* Verifies the Ed25519 signature of the token against the embedded public
|
|
29235
|
+
* key. Resolves true/false for a definitive valid/invalid signature.
|
|
29236
|
+
*
|
|
29237
|
+
* Rejects (rather than resolving false) on unexpected crypto failures or when
|
|
29238
|
+
* the runtime lacks Ed25519 support, so the caller fails open.
|
|
29239
|
+
*/
|
|
29240
|
+
async verifySignature(payloadSegment, signatureSegment) {
|
|
29241
|
+
const publicKey = await this.getPublicKey();
|
|
29242
|
+
const data = this.utf8Bytes(payloadSegment);
|
|
29243
|
+
const signature = this.base64UrlToBytes(signatureSegment);
|
|
29244
|
+
return crypto.subtle.verify('Ed25519', publicKey, signature, data);
|
|
29245
|
+
}
|
|
29246
|
+
/**
|
|
29247
|
+
* Imports and caches the embedded Ed25519 public key for signature checks.
|
|
29248
|
+
*/
|
|
29249
|
+
getPublicKey() {
|
|
29250
|
+
if (!this._publicKeyPromise) {
|
|
29251
|
+
const raw = this.base64UrlToBytes(KRITZEL_LICENSE_PUBLIC_KEY);
|
|
29252
|
+
this._publicKeyPromise = Promise.resolve().then(() => crypto.subtle.importKey('raw', raw, { name: 'Ed25519' }, false, ['verify']));
|
|
29253
|
+
// If import fails (e.g. no Ed25519 support), drop the cache so a later
|
|
29254
|
+
// tick can retry, and let the rejection propagate to fail open.
|
|
29255
|
+
this._publicKeyPromise.catch(() => {
|
|
29256
|
+
this._publicKeyPromise = undefined;
|
|
29257
|
+
});
|
|
29258
|
+
}
|
|
29259
|
+
return this._publicKeyPromise;
|
|
29260
|
+
}
|
|
29261
|
+
/** Decodes a base64url string to its UTF-8 text. */
|
|
29262
|
+
base64UrlToString(value) {
|
|
29263
|
+
return new TextDecoder().decode(this.base64UrlToBytes(value));
|
|
29264
|
+
}
|
|
29265
|
+
/** Decodes a base64url string to raw bytes. */
|
|
29266
|
+
base64UrlToBytes(value) {
|
|
29267
|
+
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
29268
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
|
|
29269
|
+
const binary = atob(padded);
|
|
29270
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
29271
|
+
for (let i = 0; i < binary.length; i++) {
|
|
29272
|
+
bytes[i] = binary.charCodeAt(i);
|
|
29273
|
+
}
|
|
29274
|
+
return bytes;
|
|
29275
|
+
}
|
|
29276
|
+
/** Encodes a string to its UTF-8 bytes. */
|
|
29277
|
+
utf8Bytes(value) {
|
|
29278
|
+
const encoded = new TextEncoder().encode(value);
|
|
29279
|
+
const bytes = new Uint8Array(new ArrayBuffer(encoded.length));
|
|
29280
|
+
bytes.set(encoded);
|
|
29281
|
+
return bytes;
|
|
29282
|
+
}
|
|
29283
|
+
}
|
|
29284
|
+
|
|
28294
29285
|
const DEFAULT_BRUSH_CONFIG = {
|
|
28295
29286
|
type: 'pen',
|
|
28296
29287
|
color: DEFAULT_COLOR_PALETTE[0],
|
|
@@ -28463,4 +29454,4 @@ const CURRENT_APP_STATE_SCHEMA_VERSION = 2;
|
|
|
28463
29454
|
/** Current schema version for workspace Y.Docs (drawable objects). */
|
|
28464
29455
|
const CURRENT_WORKSPACE_SCHEMA_VERSION = 2;
|
|
28465
29456
|
|
|
28466
|
-
export { AssetNotFoundError as A,
|
|
29457
|
+
export { KritzelBaseHandler as $, AssetNotFoundError as A, darkTheme as B, DE_LOCALE as C, DEFAULT_BRUSH_CONFIG as D, EN_LOCALE as E, FR_LOCALE as F, KritzelAlignment as G, HocuspocusProvider as H, IndexedDBAssetProvider as I, runMigrations as J, KritzelBaseObject as K, APP_STATE_MIGRATIONS as L, WORKSPACE_MIGRATIONS as M, CURRENT_APP_STATE_SCHEMA_VERSION as N, CURRENT_WORKSPACE_SCHEMA_VERSION as O, KritzelColorHelper as P, KritzelDevicesHelper as Q, KritzelMouseButton as R, ShapeType as S, DEFAULT_STROKE_SIZES as T, DEFAULT_COLOR_PALETTE as U, ThemeHelper as V, WORKSPACE_EXPORT_VERSION as W, KritzelSelectionGroup as X, KritzelSelectionBox as Y, KritzelIconRegistry as Z, KritzelKeyboardHelper as _, HocuspocusProviderWebsocket as a, KritzelMathHelper as a0, ObjectHelper as a1, KritzelClassHelper as a2, KritzelEventHelper as a3, KritzelText as b, KritzelPath as c, KritzelImage as d, KritzelLine as e, KritzelGroup as f, KritzelShape as g, KritzelBaseTool as h, KritzelBrushTool as i, KritzelLineTool as j, KritzelEraserTool as k, KritzelImageTool as l, KritzelTextTool as m, KritzelShapeTool as n, KritzelCursorHelper as o, KritzelSelectionTool as p, KritzelAssetResolver as q, KritzelWorkspace as r, KritzelAnchorManager as s, KritzelThemeManager as t, KritzelLocalizationManager as u, KritzelLicenseManager as v, DEFAULT_TEXT_CONFIG as w, DEFAULT_LINE_TOOL_CONFIG as x, DEFAULT_ASSET_STORAGE_CONFIG as y, lightTheme as z };
|