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
|
@@ -862,8 +862,11 @@ function findDiffStart(a, b, pos) {
|
|
|
862
862
|
if (!childA.sameMarkup(childB))
|
|
863
863
|
return pos;
|
|
864
864
|
if (childA.isText && childA.text != childB.text) {
|
|
865
|
-
|
|
865
|
+
let tA = childA.text, tB = childB.text, j = 0;
|
|
866
|
+
for (; tA[j] == tB[j]; j++)
|
|
866
867
|
pos++;
|
|
868
|
+
if (j && j < tA.length && j < tB.length && surrogateHigh(tA.charCodeAt(j - 1)) && surrogateLow(tA.charCodeAt(j)))
|
|
869
|
+
pos--;
|
|
867
870
|
return pos;
|
|
868
871
|
}
|
|
869
872
|
if (childA.content.size || childB.content.size) {
|
|
@@ -887,12 +890,17 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
887
890
|
if (!childA.sameMarkup(childB))
|
|
888
891
|
return { a: posA, b: posB };
|
|
889
892
|
if (childA.isText && childA.text != childB.text) {
|
|
890
|
-
let
|
|
891
|
-
while (
|
|
892
|
-
|
|
893
|
+
let tA = childA.text, tB = childB.text, iA = tA.length, iB = tB.length;
|
|
894
|
+
while (iA > 0 && iB > 0 && tA[iA - 1] == tB[iB - 1]) {
|
|
895
|
+
iA--;
|
|
896
|
+
iB--;
|
|
893
897
|
posA--;
|
|
894
898
|
posB--;
|
|
895
899
|
}
|
|
900
|
+
if (iA && iB && iA < tA.length && surrogateHigh(tA.charCodeAt(iA - 1)) && surrogateLow(tA.charCodeAt(iA))) {
|
|
901
|
+
posA++;
|
|
902
|
+
posB++;
|
|
903
|
+
}
|
|
896
904
|
return { a: posA, b: posB };
|
|
897
905
|
}
|
|
898
906
|
if (childA.content.size || childB.content.size) {
|
|
@@ -904,6 +912,8 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
904
912
|
posB -= size;
|
|
905
913
|
}
|
|
906
914
|
}
|
|
915
|
+
function surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; }
|
|
916
|
+
function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; }
|
|
907
917
|
|
|
908
918
|
/**
|
|
909
919
|
A fragment represents a node's collection of child nodes.
|
|
@@ -1584,7 +1594,8 @@ function addRange($start, $end, depth, target) {
|
|
|
1584
1594
|
addNode($end.nodeBefore, target);
|
|
1585
1595
|
}
|
|
1586
1596
|
function close(node, content) {
|
|
1587
|
-
node.type.
|
|
1597
|
+
if (!node.type.validContent(content))
|
|
1598
|
+
throw new ReplaceError("Invalid content for node " + node.type.name);
|
|
1588
1599
|
return node.copy(content);
|
|
1589
1600
|
}
|
|
1590
1601
|
function replaceThreeWay($from, $start, $end, $to, depth) {
|
|
@@ -2895,13 +2906,12 @@ function computeAttrs(attrs, value) {
|
|
|
2895
2906
|
return built;
|
|
2896
2907
|
}
|
|
2897
2908
|
function checkAttrs(attrs, values, type, name) {
|
|
2898
|
-
for (let
|
|
2899
|
-
if (!(
|
|
2900
|
-
throw new RangeError(`Unsupported attribute ${
|
|
2901
|
-
for (let
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
attr.validate(values[name]);
|
|
2909
|
+
for (let attr in values)
|
|
2910
|
+
if (!(attr in attrs))
|
|
2911
|
+
throw new RangeError(`Unsupported attribute ${attr} for ${type} of type ${name}`);
|
|
2912
|
+
for (let attr in attrs) {
|
|
2913
|
+
if (attrs[attr].validate)
|
|
2914
|
+
attrs[attr].validate(values[attr]);
|
|
2905
2915
|
}
|
|
2906
2916
|
}
|
|
2907
2917
|
function initAttrs(typeName, attrs) {
|
|
@@ -3078,7 +3088,7 @@ let NodeType$1 = class NodeType {
|
|
|
3078
3088
|
@internal
|
|
3079
3089
|
*/
|
|
3080
3090
|
checkAttrs(attrs) {
|
|
3081
|
-
checkAttrs(this.attrs, attrs, "node");
|
|
3091
|
+
checkAttrs(this.attrs, attrs, "node", this.name);
|
|
3082
3092
|
}
|
|
3083
3093
|
/**
|
|
3084
3094
|
Check whether the given mark type is allowed in this node.
|
|
@@ -3229,7 +3239,7 @@ class MarkType {
|
|
|
3229
3239
|
@internal
|
|
3230
3240
|
*/
|
|
3231
3241
|
checkAttrs(attrs) {
|
|
3232
|
-
checkAttrs(this.attrs, attrs, "mark");
|
|
3242
|
+
checkAttrs(this.attrs, attrs, "mark", this.name);
|
|
3233
3243
|
}
|
|
3234
3244
|
/**
|
|
3235
3245
|
Queries whether a given mark type is
|
|
@@ -9664,14 +9674,14 @@ function selectionToDOM(view, force = false) {
|
|
|
9664
9674
|
syncNodeSelection(view, sel);
|
|
9665
9675
|
if (!editorOwnsSelection(view))
|
|
9666
9676
|
return;
|
|
9667
|
-
//
|
|
9668
|
-
//
|
|
9669
|
-
|
|
9670
|
-
if (!force &&
|
|
9677
|
+
// Need to delay selection normalization during a native selection
|
|
9678
|
+
// drag on Chrome, or it will cause further dragging to glitch.
|
|
9679
|
+
let mouseDown = view.input.mouseDown;
|
|
9680
|
+
if (!force && chrome && mouseDown) {
|
|
9671
9681
|
let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection;
|
|
9672
9682
|
if (domSel.anchorNode && curSel.anchorNode &&
|
|
9673
|
-
isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)
|
|
9674
|
-
|
|
9683
|
+
isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset) &&
|
|
9684
|
+
mouseDown.delaySelUpdate()) {
|
|
9675
9685
|
view.domObserver.setCurSelection();
|
|
9676
9686
|
return;
|
|
9677
9687
|
}
|
|
@@ -10523,6 +10533,8 @@ function setSelectionOrigin(view, origin) {
|
|
|
10523
10533
|
view.input.lastSelectionTime = Date.now();
|
|
10524
10534
|
}
|
|
10525
10535
|
function destroyInput(view) {
|
|
10536
|
+
if (view.input.mouseDown)
|
|
10537
|
+
view.input.mouseDown.done();
|
|
10526
10538
|
view.domObserver.stop();
|
|
10527
10539
|
for (let type in view.input.eventHandlers)
|
|
10528
10540
|
view.dom.removeEventListener(type, view.input.eventHandlers[type]);
|
|
@@ -10561,7 +10573,7 @@ function dispatchEvent(view, event) {
|
|
|
10561
10573
|
editHandlers.keydown = (view, _event) => {
|
|
10562
10574
|
let event = _event;
|
|
10563
10575
|
view.input.shiftKey = event.keyCode == 16 || event.shiftKey;
|
|
10564
|
-
if (inOrNearComposition(view
|
|
10576
|
+
if (inOrNearComposition(view))
|
|
10565
10577
|
return;
|
|
10566
10578
|
view.input.lastKeyCode = event.keyCode;
|
|
10567
10579
|
view.input.lastKeyCodeTime = Date.now();
|
|
@@ -10599,7 +10611,7 @@ editHandlers.keyup = (view, event) => {
|
|
|
10599
10611
|
};
|
|
10600
10612
|
editHandlers.keypress = (view, _event) => {
|
|
10601
10613
|
let event = _event;
|
|
10602
|
-
if (inOrNearComposition(view
|
|
10614
|
+
if (inOrNearComposition(view) || !event.charCode ||
|
|
10603
10615
|
event.ctrlKey && !event.altKey || mac$3 && event.metaKey)
|
|
10604
10616
|
return;
|
|
10605
10617
|
if (view.someProp("handleKeyPress", f => f(view, event))) {
|
|
@@ -10693,26 +10705,28 @@ function handleTripleClick(view, pos, inside, event) {
|
|
|
10693
10705
|
function defaultTripleClick(view, inside, event) {
|
|
10694
10706
|
if (event.button != 0)
|
|
10695
10707
|
return false;
|
|
10696
|
-
let doc = view.state.doc;
|
|
10697
|
-
if (
|
|
10698
|
-
if (doc.inlineContent) {
|
|
10699
|
-
updateSelection(view, TextSelection.create(doc, 0, doc.content.size));
|
|
10700
|
-
return true;
|
|
10701
|
-
}
|
|
10708
|
+
let selection = selectionForTripleClick(view, inside, true), doc = view.state.doc;
|
|
10709
|
+
if (!selection)
|
|
10702
10710
|
return false;
|
|
10703
|
-
|
|
10711
|
+
updateSelection(view, selection);
|
|
10712
|
+
if (selection instanceof TextSelection && doc.eq(view.state.doc))
|
|
10713
|
+
view.input.mouseDown = new TripleClickDrag(view, selection);
|
|
10714
|
+
return true;
|
|
10715
|
+
}
|
|
10716
|
+
function selectionForTripleClick(view, inside, selectNodes) {
|
|
10717
|
+
let doc = view.state.doc;
|
|
10718
|
+
if (inside == -1)
|
|
10719
|
+
return doc.inlineContent ? TextSelection.create(doc, 0, doc.content.size) : null;
|
|
10704
10720
|
let $pos = doc.resolve(inside);
|
|
10705
10721
|
for (let i = $pos.depth + 1; i > 0; i--) {
|
|
10706
10722
|
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i);
|
|
10707
10723
|
let nodePos = $pos.before(i);
|
|
10708
10724
|
if (node.inlineContent)
|
|
10709
|
-
|
|
10710
|
-
else if (NodeSelection.isSelectable(node))
|
|
10711
|
-
|
|
10712
|
-
else
|
|
10713
|
-
continue;
|
|
10714
|
-
return true;
|
|
10725
|
+
return TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size);
|
|
10726
|
+
else if (selectNodes && NodeSelection.isSelectable(node))
|
|
10727
|
+
return NodeSelection.create(doc, nodePos);
|
|
10715
10728
|
}
|
|
10729
|
+
return null;
|
|
10716
10730
|
}
|
|
10717
10731
|
function forceDOMFlush(view) {
|
|
10718
10732
|
return endComposition(view);
|
|
@@ -10731,13 +10745,13 @@ handlers.mousedown = (view, _event) => {
|
|
|
10731
10745
|
type = "tripleClick";
|
|
10732
10746
|
}
|
|
10733
10747
|
view.input.lastClick = { time: now, x: event.clientX, y: event.clientY, type, button: event.button };
|
|
10748
|
+
if (view.input.mouseDown)
|
|
10749
|
+
view.input.mouseDown.done();
|
|
10734
10750
|
let pos = view.posAtCoords(eventCoords(event));
|
|
10735
10751
|
if (!pos)
|
|
10736
10752
|
return;
|
|
10737
10753
|
if (type == "singleClick") {
|
|
10738
|
-
|
|
10739
|
-
view.input.mouseDown.done();
|
|
10740
|
-
view.input.mouseDown = new MouseDown(view, pos, event, !!flushed);
|
|
10754
|
+
view.input.mouseDown = new LeftMouseDown(view, pos, event, !!flushed);
|
|
10741
10755
|
}
|
|
10742
10756
|
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
|
|
10743
10757
|
event.preventDefault();
|
|
@@ -10747,13 +10761,34 @@ handlers.mousedown = (view, _event) => {
|
|
|
10747
10761
|
}
|
|
10748
10762
|
};
|
|
10749
10763
|
class MouseDown {
|
|
10750
|
-
constructor(view
|
|
10764
|
+
constructor(view) {
|
|
10751
10765
|
this.view = view;
|
|
10766
|
+
this.mightDrag = null;
|
|
10767
|
+
view.root.addEventListener("mouseup", this.up = this.up.bind(this));
|
|
10768
|
+
view.root.addEventListener("mousemove", this.move = this.move.bind(this));
|
|
10769
|
+
}
|
|
10770
|
+
up(event) {
|
|
10771
|
+
this.done();
|
|
10772
|
+
}
|
|
10773
|
+
move(event) {
|
|
10774
|
+
if (event.buttons == 0)
|
|
10775
|
+
this.done();
|
|
10776
|
+
}
|
|
10777
|
+
done() {
|
|
10778
|
+
this.view.root.removeEventListener("mouseup", this.up);
|
|
10779
|
+
this.view.root.removeEventListener("mousemove", this.move);
|
|
10780
|
+
if (this.view.input.mouseDown == this)
|
|
10781
|
+
this.view.input.mouseDown = null;
|
|
10782
|
+
}
|
|
10783
|
+
delaySelUpdate() { return false; }
|
|
10784
|
+
}
|
|
10785
|
+
class LeftMouseDown extends MouseDown {
|
|
10786
|
+
constructor(view, pos, event, flushed) {
|
|
10787
|
+
super(view);
|
|
10752
10788
|
this.pos = pos;
|
|
10753
10789
|
this.event = event;
|
|
10754
10790
|
this.flushed = flushed;
|
|
10755
10791
|
this.delayedSelectionSync = false;
|
|
10756
|
-
this.mightDrag = null;
|
|
10757
10792
|
this.startDoc = view.state.doc;
|
|
10758
10793
|
this.selectNode = !!event[selectNodeModifier];
|
|
10759
10794
|
this.allowDefault = event.shiftKey;
|
|
@@ -10791,13 +10826,10 @@ class MouseDown {
|
|
|
10791
10826
|
}, 20);
|
|
10792
10827
|
this.view.domObserver.start();
|
|
10793
10828
|
}
|
|
10794
|
-
view.root.addEventListener("mouseup", this.up = this.up.bind(this));
|
|
10795
|
-
view.root.addEventListener("mousemove", this.move = this.move.bind(this));
|
|
10796
10829
|
setSelectionOrigin(view, "pointer");
|
|
10797
10830
|
}
|
|
10798
10831
|
done() {
|
|
10799
|
-
|
|
10800
|
-
this.view.root.removeEventListener("mousemove", this.move);
|
|
10832
|
+
super.done();
|
|
10801
10833
|
if (this.mightDrag && this.target) {
|
|
10802
10834
|
this.view.domObserver.stop();
|
|
10803
10835
|
if (this.mightDrag.addAttr)
|
|
@@ -10807,8 +10839,10 @@ class MouseDown {
|
|
|
10807
10839
|
this.view.domObserver.start();
|
|
10808
10840
|
}
|
|
10809
10841
|
if (this.delayedSelectionSync)
|
|
10810
|
-
setTimeout(() =>
|
|
10811
|
-
|
|
10842
|
+
setTimeout(() => {
|
|
10843
|
+
if (!this.view.isDestroyed)
|
|
10844
|
+
selectionToDOM(this.view);
|
|
10845
|
+
});
|
|
10812
10846
|
}
|
|
10813
10847
|
up(event) {
|
|
10814
10848
|
this.done();
|
|
@@ -10847,14 +10881,41 @@ class MouseDown {
|
|
|
10847
10881
|
move(event) {
|
|
10848
10882
|
this.updateAllowDefault(event);
|
|
10849
10883
|
setSelectionOrigin(this.view, "pointer");
|
|
10850
|
-
|
|
10851
|
-
this.done();
|
|
10884
|
+
super.move(event);
|
|
10852
10885
|
}
|
|
10853
10886
|
updateAllowDefault(event) {
|
|
10854
10887
|
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
10855
10888
|
Math.abs(this.event.y - event.clientY) > 4))
|
|
10856
10889
|
this.allowDefault = true;
|
|
10857
10890
|
}
|
|
10891
|
+
delaySelUpdate() {
|
|
10892
|
+
if (!this.allowDefault)
|
|
10893
|
+
return false;
|
|
10894
|
+
this.delayedSelectionSync = true;
|
|
10895
|
+
return true;
|
|
10896
|
+
}
|
|
10897
|
+
}
|
|
10898
|
+
class TripleClickDrag extends MouseDown {
|
|
10899
|
+
constructor(view, startSelection) {
|
|
10900
|
+
super(view);
|
|
10901
|
+
this.startSelection = startSelection;
|
|
10902
|
+
this.startDoc = view.state.doc;
|
|
10903
|
+
}
|
|
10904
|
+
move(event) {
|
|
10905
|
+
if (event.buttons == 0 || this.view.isDestroyed || !this.view.state.doc.eq(this.startDoc)) {
|
|
10906
|
+
this.done();
|
|
10907
|
+
return;
|
|
10908
|
+
}
|
|
10909
|
+
event.preventDefault();
|
|
10910
|
+
setSelectionOrigin(this.view, "pointer");
|
|
10911
|
+
let pos = this.view.posAtCoords(eventCoords(event));
|
|
10912
|
+
let target = pos && selectionForTripleClick(this.view, pos.inside, false);
|
|
10913
|
+
if (!target)
|
|
10914
|
+
return;
|
|
10915
|
+
let { doc } = this.view.state, start = this.startSelection;
|
|
10916
|
+
let [anchor, head] = target.from < start.from ? [start.to, target.from] : [start.from, target.to];
|
|
10917
|
+
updateSelection(this.view, TextSelection.create(doc, anchor, head));
|
|
10918
|
+
}
|
|
10858
10919
|
}
|
|
10859
10920
|
handlers.touchstart = view => {
|
|
10860
10921
|
view.input.lastTouch = Date.now();
|
|
@@ -10879,7 +10940,7 @@ function inOrNearComposition(view, event) {
|
|
|
10879
10940
|
// This guards against the case where compositionend is triggered without the keyboard
|
|
10880
10941
|
// (e.g. character confirmation may be done with the mouse), and keydown is triggered
|
|
10881
10942
|
// afterwards- we wouldn't want to ignore the keydown event in this case.
|
|
10882
|
-
if (safari && Math.abs(
|
|
10943
|
+
if (safari && Math.abs(Date.now() - view.input.compositionEndedAt) < 500) {
|
|
10883
10944
|
view.input.compositionEndedAt = -2e8;
|
|
10884
10945
|
return true;
|
|
10885
10946
|
}
|
|
@@ -10938,7 +10999,7 @@ function selectionBeforeUneditable(view) {
|
|
|
10938
10999
|
editHandlers.compositionend = (view, event) => {
|
|
10939
11000
|
if (view.composing) {
|
|
10940
11001
|
view.input.composing = false;
|
|
10941
|
-
view.input.compositionEndedAt =
|
|
11002
|
+
view.input.compositionEndedAt = Date.now();
|
|
10942
11003
|
view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
|
|
10943
11004
|
view.input.compositionNode = null;
|
|
10944
11005
|
if (view.input.badSafariComposition)
|
|
@@ -10957,7 +11018,7 @@ function scheduleComposeEnd(view, delay) {
|
|
|
10957
11018
|
function clearComposition(view) {
|
|
10958
11019
|
if (view.composing) {
|
|
10959
11020
|
view.input.composing = false;
|
|
10960
|
-
view.input.compositionEndedAt =
|
|
11021
|
+
view.input.compositionEndedAt = Date.now();
|
|
10961
11022
|
}
|
|
10962
11023
|
while (view.input.compositionNodes.length > 0)
|
|
10963
11024
|
view.input.compositionNodes.pop().markParentsDirty();
|
|
@@ -10983,11 +11044,6 @@ function findCompositionNode(view) {
|
|
|
10983
11044
|
}
|
|
10984
11045
|
return textBefore || textAfter;
|
|
10985
11046
|
}
|
|
10986
|
-
function timestampFromCustomEvent() {
|
|
10987
|
-
let event = document.createEvent("Event");
|
|
10988
|
-
event.initEvent("event", true, true);
|
|
10989
|
-
return event.timeStamp;
|
|
10990
|
-
}
|
|
10991
11047
|
/**
|
|
10992
11048
|
@internal
|
|
10993
11049
|
*/
|
|
@@ -12144,7 +12200,10 @@ class DOMObserver {
|
|
|
12144
12200
|
}
|
|
12145
12201
|
}
|
|
12146
12202
|
}
|
|
12147
|
-
if (added.some(n => n.nodeName == "BR") &&
|
|
12203
|
+
if (added.some(n => n.nodeName == "BR") &&
|
|
12204
|
+
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46 ||
|
|
12205
|
+
chrome && (view.composing || view.input.compositionEndedAt > Date.now() - 50) &&
|
|
12206
|
+
mutations.some(m => m.type == "childList" && m.removedNodes.length))) {
|
|
12148
12207
|
// Browsers sometimes insert a bogus break node if you
|
|
12149
12208
|
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
12150
12209
|
for (let node of added)
|
|
@@ -12700,38 +12759,28 @@ function skipClosingAndOpening($pos, fromEnd, mayOpen) {
|
|
|
12700
12759
|
return end;
|
|
12701
12760
|
}
|
|
12702
12761
|
function findDiff(a, b, pos, preferredPos, preferredSide) {
|
|
12703
|
-
let start = a.findDiffStart(b, pos);
|
|
12762
|
+
let start = a.findDiffStart(b, pos), lenA = pos + a.size, lenB = pos + b.size;
|
|
12704
12763
|
if (start == null)
|
|
12705
12764
|
return null;
|
|
12706
|
-
let { a: endA, b: endB } = a.findDiffEnd(b,
|
|
12765
|
+
let { a: endA, b: endB } = a.findDiffEnd(b, lenA, lenB);
|
|
12707
12766
|
if (preferredSide == "end") {
|
|
12708
12767
|
let adjust = Math.max(0, start - Math.min(endA, endB));
|
|
12709
12768
|
preferredPos -= endA + adjust - start;
|
|
12710
12769
|
}
|
|
12711
|
-
if (endA < start &&
|
|
12770
|
+
if (endA < start && lenA < lenB) {
|
|
12712
12771
|
let move = preferredPos <= start && preferredPos >= endA ? start - preferredPos : 0;
|
|
12713
12772
|
start -= move;
|
|
12714
|
-
if (start && start < b.size && isSurrogatePair(b.textBetween(start - 1, start + 1)))
|
|
12715
|
-
start += move ? 1 : -1;
|
|
12716
12773
|
endB = start + (endB - endA);
|
|
12717
12774
|
endA = start;
|
|
12718
12775
|
}
|
|
12719
12776
|
else if (endB < start) {
|
|
12720
12777
|
let move = preferredPos <= start && preferredPos >= endB ? start - preferredPos : 0;
|
|
12721
12778
|
start -= move;
|
|
12722
|
-
if (start && start < a.size && isSurrogatePair(a.textBetween(start - 1, start + 1)))
|
|
12723
|
-
start += move ? 1 : -1;
|
|
12724
12779
|
endA = start + (endA - endB);
|
|
12725
12780
|
endB = start;
|
|
12726
12781
|
}
|
|
12727
12782
|
return { start, endA, endB };
|
|
12728
12783
|
}
|
|
12729
|
-
function isSurrogatePair(str) {
|
|
12730
|
-
if (str.length != 2)
|
|
12731
|
-
return false;
|
|
12732
|
-
let a = str.charCodeAt(0), b = str.charCodeAt(1);
|
|
12733
|
-
return a >= 0xDC00 && a <= 0xDFFF && b >= 0xD800 && b <= 0xDBFF;
|
|
12734
|
-
}
|
|
12735
12784
|
/**
|
|
12736
12785
|
An editor view manages the DOM structure that represents an
|
|
12737
12786
|
editable document. Its state and behavior are determined by its
|
|
@@ -12923,9 +12972,10 @@ class EditorView {
|
|
|
12923
12972
|
// a DOM selection change and the "selectionchange" event for it
|
|
12924
12973
|
// can cause a spurious DOM selection update, disrupting mouse
|
|
12925
12974
|
// drag selection.
|
|
12975
|
+
let mouseDown = this.input.mouseDown;
|
|
12926
12976
|
if (forceSelUpdate ||
|
|
12927
|
-
!(
|
|
12928
|
-
anchorInRightPlace(this))) {
|
|
12977
|
+
!(mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
|
|
12978
|
+
anchorInRightPlace(this) && mouseDown.delaySelUpdate())) {
|
|
12929
12979
|
selectionToDOM(this, forceSelUpdate);
|
|
12930
12980
|
}
|
|
12931
12981
|
else {
|
|
@@ -14622,6 +14672,24 @@ const lightTheme = {
|
|
|
14622
14672
|
buttonHoverBackgroundColor: 'hsl(0, 0%, 0%, 4.3%)',
|
|
14623
14673
|
separatorColor: 'hsl(0, 0%, 0%, 8%)',
|
|
14624
14674
|
},
|
|
14675
|
+
watermark: {
|
|
14676
|
+
background: 'rgba(255, 255, 255, 0.6)',
|
|
14677
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.12)',
|
|
14678
|
+
color: 'rgba(60, 60, 60, 0.75)',
|
|
14679
|
+
},
|
|
14680
|
+
zoomPanel: {
|
|
14681
|
+
backgroundColor: '#ffffff',
|
|
14682
|
+
border: '1px solid #ebebeb',
|
|
14683
|
+
borderRadius: '12px',
|
|
14684
|
+
boxShadow: '0 0 3px rgba(0, 0, 0, 0.08)',
|
|
14685
|
+
buttonActiveBackgroundColor: 'hsl(0, 0%, 0%, 8.6%)',
|
|
14686
|
+
buttonBorderRadius: '8px',
|
|
14687
|
+
buttonHoverBackgroundColor: 'hsl(0, 0%, 0%, 4.3%)',
|
|
14688
|
+
buttonSize: '32px',
|
|
14689
|
+
gap: '4px',
|
|
14690
|
+
iconColor: '#000000',
|
|
14691
|
+
padding: '4px',
|
|
14692
|
+
},
|
|
14625
14693
|
};
|
|
14626
14694
|
|
|
14627
14695
|
/**
|
|
@@ -14899,6 +14967,24 @@ const darkTheme = {
|
|
|
14899
14967
|
buttonHoverBackgroundColor: 'hsl(0, 0%, 100%, 8%)',
|
|
14900
14968
|
separatorColor: 'hsl(0, 0%, 100%, 12%)',
|
|
14901
14969
|
},
|
|
14970
|
+
watermark: {
|
|
14971
|
+
background: 'rgba(40, 40, 40, 0.6)',
|
|
14972
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.35)',
|
|
14973
|
+
color: 'rgba(220, 220, 220, 0.8)',
|
|
14974
|
+
},
|
|
14975
|
+
zoomPanel: {
|
|
14976
|
+
backgroundColor: '#2a2a2a',
|
|
14977
|
+
border: '1px solid #3a3a3a',
|
|
14978
|
+
borderRadius: '12px',
|
|
14979
|
+
boxShadow: '0 0 3px rgba(0, 0, 0, 0.4)',
|
|
14980
|
+
buttonActiveBackgroundColor: 'hsl(0, 0%, 100%, 12%)',
|
|
14981
|
+
buttonBorderRadius: '8px',
|
|
14982
|
+
buttonHoverBackgroundColor: 'hsl(0, 0%, 100%, 8%)',
|
|
14983
|
+
buttonSize: '32px',
|
|
14984
|
+
gap: '4px',
|
|
14985
|
+
iconColor: '#e0e0e0',
|
|
14986
|
+
padding: '4px',
|
|
14987
|
+
},
|
|
14902
14988
|
};
|
|
14903
14989
|
|
|
14904
14990
|
class ThemeHelper {
|
|
@@ -14972,7 +15058,7 @@ class ThemeHelper {
|
|
|
14972
15058
|
}
|
|
14973
15059
|
|
|
14974
15060
|
/** Key used to store the settings object in localStorage. */
|
|
14975
|
-
const SETTINGS_STORAGE_KEY = 'kritzel-settings';
|
|
15061
|
+
const SETTINGS_STORAGE_KEY$1 = 'kritzel-settings';
|
|
14976
15062
|
/** Default theme used when no stored preference exists. */
|
|
14977
15063
|
const DEFAULT_THEME = 'light';
|
|
14978
15064
|
/**
|
|
@@ -14999,7 +15085,7 @@ class KritzelThemeManager {
|
|
|
14999
15085
|
*/
|
|
15000
15086
|
constructor(core) {
|
|
15001
15087
|
this._core = core;
|
|
15002
|
-
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY}-${core.editorId}` : SETTINGS_STORAGE_KEY;
|
|
15088
|
+
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY$1}-${core.editorId}` : SETTINGS_STORAGE_KEY$1;
|
|
15003
15089
|
this._themeRegistry.set('light', lightTheme);
|
|
15004
15090
|
this._themeRegistry.set('dark', darkTheme);
|
|
15005
15091
|
this._currentTheme = this.getStoredTheme();
|
|
@@ -15115,7 +15201,7 @@ class KritzelThemeManager {
|
|
|
15115
15201
|
if (typeof localStorage === 'undefined') {
|
|
15116
15202
|
return DEFAULT_THEME;
|
|
15117
15203
|
}
|
|
15118
|
-
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
|
15204
|
+
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY$1);
|
|
15119
15205
|
if (!stored) {
|
|
15120
15206
|
return DEFAULT_THEME;
|
|
15121
15207
|
}
|
|
@@ -15279,6 +15365,15 @@ class KritzelColorHelper {
|
|
|
15279
15365
|
}
|
|
15280
15366
|
}
|
|
15281
15367
|
|
|
15368
|
+
class KritzelMathHelper {
|
|
15369
|
+
static average(a, b) {
|
|
15370
|
+
return (a + b) / 2;
|
|
15371
|
+
}
|
|
15372
|
+
static degreesToRadians(degrees) {
|
|
15373
|
+
return degrees * (Math.PI / 180);
|
|
15374
|
+
}
|
|
15375
|
+
}
|
|
15376
|
+
|
|
15282
15377
|
/**
|
|
15283
15378
|
* Represents a text object on the canvas that supports rich text editing via ProseMirror.
|
|
15284
15379
|
* Extends the base object class to inherit common object properties and behaviors.
|
|
@@ -15343,6 +15438,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15343
15438
|
*/
|
|
15344
15439
|
constructor(config) {
|
|
15345
15440
|
super();
|
|
15441
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
15346
15442
|
// Always create the editor so setContent() works immediately
|
|
15347
15443
|
this.editor = this.createEditor();
|
|
15348
15444
|
if (config) {
|
|
@@ -15381,7 +15477,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15381
15477
|
* @param scale - Optional scale factor (defaults to current viewport scale).
|
|
15382
15478
|
* @returns A new, fully initialized KritzelText instance.
|
|
15383
15479
|
*/
|
|
15384
|
-
static create(core, fontSize, fontFamily, scale) {
|
|
15480
|
+
static create(core, fontSize, fontFamily, scale, rotation) {
|
|
15385
15481
|
const object = new KritzelText();
|
|
15386
15482
|
object._core = core;
|
|
15387
15483
|
object.id = object.generateId();
|
|
@@ -15391,6 +15487,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15391
15487
|
object.fontFamily = fontFamily || 'Arial';
|
|
15392
15488
|
object.translateX = 0;
|
|
15393
15489
|
object.translateY = 0;
|
|
15490
|
+
object.rotation = KritzelMathHelper.degreesToRadians(rotation ?? 0);
|
|
15394
15491
|
const coreScale = core.store.state.scale;
|
|
15395
15492
|
const effectiveScale = coreScale < 0 ? coreScale : 1;
|
|
15396
15493
|
object.width = object.initialWidth / effectiveScale;
|
|
@@ -15742,12 +15839,6 @@ function requireCjs () {
|
|
|
15742
15839
|
|
|
15743
15840
|
var cjsExports = requireCjs();
|
|
15744
15841
|
|
|
15745
|
-
class KritzelMathHelper {
|
|
15746
|
-
static average(a, b) {
|
|
15747
|
-
return (a + b) / 2;
|
|
15748
|
-
}
|
|
15749
|
-
}
|
|
15750
|
-
|
|
15751
15842
|
class KritzelPath extends KritzelBaseObject {
|
|
15752
15843
|
__class__ = 'KritzelPath';
|
|
15753
15844
|
points;
|
|
@@ -15790,6 +15881,7 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15790
15881
|
this.points = config?.points ?? [];
|
|
15791
15882
|
this.translateX = config?.translateX ?? 0;
|
|
15792
15883
|
this.translateY = config?.translateY ?? 0;
|
|
15884
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
15793
15885
|
this.scale = config?.scale ?? 1;
|
|
15794
15886
|
this.strokeWidth = config?.strokeWidth ?? 8;
|
|
15795
15887
|
this.fill = config?.fill ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -15813,6 +15905,7 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15813
15905
|
object.points = options?.points ?? [];
|
|
15814
15906
|
object.translateX = options?.translateX ?? 0;
|
|
15815
15907
|
object.translateY = options?.translateY ?? 0;
|
|
15908
|
+
object.rotation = KritzelMathHelper.degreesToRadians(options?.rotation ?? 0);
|
|
15816
15909
|
object.scale = options?.scale ?? 1;
|
|
15817
15910
|
object.strokeWidth = options?.strokeWidth ?? 8;
|
|
15818
15911
|
object.fill = options?.fill ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -15862,8 +15955,12 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
15862
15955
|
this.height = Math.max(...this.points.map(p => p[1])) - Math.min(...this.points.map(p => p[1])) + this.strokeWidth;
|
|
15863
15956
|
this.x = Math.min(...this.points.map(p => p[0])) - this.strokeWidth / 2;
|
|
15864
15957
|
this.y = Math.min(...this.points.map(p => p[1])) - this.strokeWidth / 2;
|
|
15865
|
-
|
|
15866
|
-
|
|
15958
|
+
if (x !== null) {
|
|
15959
|
+
this.translateX = x;
|
|
15960
|
+
}
|
|
15961
|
+
if (y !== null) {
|
|
15962
|
+
this.translateY = y;
|
|
15963
|
+
}
|
|
15867
15964
|
this._adjustedPoints = null;
|
|
15868
15965
|
this._core.store.objects.update(this);
|
|
15869
15966
|
}
|
|
@@ -16069,19 +16166,14 @@ class KritzelPath extends KritzelBaseObject {
|
|
|
16069
16166
|
}
|
|
16070
16167
|
/**
|
|
16071
16168
|
* Updates width, height, x, y, translateX, and translateY based on current points.
|
|
16072
|
-
*
|
|
16169
|
+
* Uses the unrotated local points and stroke width for base dimensions.
|
|
16073
16170
|
* Called during initial setup to establish the path's dimensions and position.
|
|
16074
16171
|
*/
|
|
16075
16172
|
updateDimensions() {
|
|
16076
|
-
const
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
});
|
|
16081
|
-
const minX = Math.min(...rotatedPoints.map(p => p[0] - this.strokeWidth / 2));
|
|
16082
|
-
const minY = Math.min(...rotatedPoints.map(p => p[1] - this.strokeWidth / 2));
|
|
16083
|
-
const maxX = Math.max(...rotatedPoints.map(p => p[0] + this.strokeWidth / 2));
|
|
16084
|
-
const maxY = Math.max(...rotatedPoints.map(p => p[1] + this.strokeWidth / 2));
|
|
16173
|
+
const minX = Math.min(...this.points.map(p => p[0])) - this.strokeWidth / 2;
|
|
16174
|
+
const minY = Math.min(...this.points.map(p => p[1])) - this.strokeWidth / 2;
|
|
16175
|
+
const maxX = Math.max(...this.points.map(p => p[0])) + this.strokeWidth / 2;
|
|
16176
|
+
const maxY = Math.max(...this.points.map(p => p[1])) + this.strokeWidth / 2;
|
|
16085
16177
|
this.width = maxX - minX + this.lineSlack;
|
|
16086
16178
|
this.height = maxY - minY + this.lineSlack;
|
|
16087
16179
|
this.x = minX;
|
|
@@ -17148,6 +17240,7 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
17148
17240
|
this.y = config?.y || 0;
|
|
17149
17241
|
this.translateX = config?.translateX || 0;
|
|
17150
17242
|
this.translateY = config?.translateY || 0;
|
|
17243
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17151
17244
|
this.scale = config?.scale || 1;
|
|
17152
17245
|
this.width = config?.width || 0;
|
|
17153
17246
|
this.height = config?.height || 0;
|
|
@@ -17161,17 +17254,18 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
17161
17254
|
* @param core - The KritzelCore instance providing access to store and state management.
|
|
17162
17255
|
* @returns A new KritzelImage instance configured with the core context.
|
|
17163
17256
|
*/
|
|
17164
|
-
static create(core) {
|
|
17165
|
-
const object = new KritzelImage();
|
|
17257
|
+
static create(core, config) {
|
|
17258
|
+
const object = new KritzelImage(config);
|
|
17166
17259
|
object._core = core;
|
|
17167
17260
|
object.id = object.generateId();
|
|
17168
17261
|
object.workspaceId = core.store.state.activeWorkspace.id;
|
|
17169
17262
|
object.userId = core.user?.id;
|
|
17170
|
-
object.x = 0;
|
|
17171
|
-
object.y = 0;
|
|
17172
|
-
object.translateX = 0;
|
|
17173
|
-
object.translateY = 0;
|
|
17174
|
-
object.scale = object._core.store.state.scale;
|
|
17263
|
+
object.x = config?.x ?? 0;
|
|
17264
|
+
object.y = config?.y ?? 0;
|
|
17265
|
+
object.translateX = config?.translateX ?? 0;
|
|
17266
|
+
object.translateY = config?.translateY ?? 0;
|
|
17267
|
+
object.scale = config?.scale ?? object._core.store.state.scale;
|
|
17268
|
+
object.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17175
17269
|
object.zIndex = core.store.currentZIndex;
|
|
17176
17270
|
return object;
|
|
17177
17271
|
}
|
|
@@ -17550,6 +17644,7 @@ class KritzelLine extends KritzelBaseObject {
|
|
|
17550
17644
|
this.controlY = config?.controlY;
|
|
17551
17645
|
this.translateX = config?.translateX ?? 0;
|
|
17552
17646
|
this.translateY = config?.translateY ?? 0;
|
|
17647
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
17553
17648
|
this.scale = config?.scale ?? 1;
|
|
17554
17649
|
this.strokeWidth = config?.strokeWidth ?? 4;
|
|
17555
17650
|
this.stroke = config?.stroke ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -17580,6 +17675,7 @@ class KritzelLine extends KritzelBaseObject {
|
|
|
17580
17675
|
object.controlY = options?.controlY;
|
|
17581
17676
|
object.translateX = options?.translateX ?? 0;
|
|
17582
17677
|
object.translateY = options?.translateY ?? 0;
|
|
17678
|
+
object.rotation = KritzelMathHelper.degreesToRadians(options?.rotation ?? 0);
|
|
17583
17679
|
object.scale = options?.scale ?? 1;
|
|
17584
17680
|
object.strokeWidth = options?.strokeWidth ?? 4;
|
|
17585
17681
|
object.stroke = options?.stroke ?? { light: '#000000', dark: '#ffffff' };
|
|
@@ -18274,6 +18370,10 @@ class KritzelClassHelper {
|
|
|
18274
18370
|
*/
|
|
18275
18371
|
class KritzelGroup extends KritzelBaseObject {
|
|
18276
18372
|
__class__ = 'KritzelGroup';
|
|
18373
|
+
constructor(config) {
|
|
18374
|
+
super();
|
|
18375
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
18376
|
+
}
|
|
18277
18377
|
/**
|
|
18278
18378
|
* IDs of child objects within this group.
|
|
18279
18379
|
* Children can be any KritzelBaseObject, including other KritzelGroups for nesting.
|
|
@@ -18325,8 +18425,8 @@ class KritzelGroup extends KritzelBaseObject {
|
|
|
18325
18425
|
* @param core - The KritzelCore instance providing access to store and state management.
|
|
18326
18426
|
* @returns A new KritzelGroup instance configured with the core context.
|
|
18327
18427
|
*/
|
|
18328
|
-
static create(core) {
|
|
18329
|
-
const group = new KritzelGroup();
|
|
18428
|
+
static create(core, config) {
|
|
18429
|
+
const group = new KritzelGroup(config);
|
|
18330
18430
|
group._core = core;
|
|
18331
18431
|
group.id = group.generateId();
|
|
18332
18432
|
group.workspaceId = core.store.state.activeWorkspace.id;
|
|
@@ -18915,6 +19015,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
18915
19015
|
this.y = config.y ?? 0;
|
|
18916
19016
|
this.translateX = config.translateX ?? 0;
|
|
18917
19017
|
this.translateY = config.translateY ?? 0;
|
|
19018
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
|
|
18918
19019
|
this.width = config.width ?? 100;
|
|
18919
19020
|
this.height = config.height ?? 100;
|
|
18920
19021
|
this.shapeType = config.shapeType ?? exports.ShapeType.Rectangle;
|
|
@@ -18946,6 +19047,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
18946
19047
|
object.y = config?.y ?? 0;
|
|
18947
19048
|
object.translateX = config?.translateX ?? 0;
|
|
18948
19049
|
object.translateY = config?.translateY ?? 0;
|
|
19050
|
+
object.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
18949
19051
|
object.width = config?.width ?? 100;
|
|
18950
19052
|
object.height = config?.height ?? 100;
|
|
18951
19053
|
object.shapeType = config?.shapeType ?? exports.ShapeType.Rectangle;
|
|
@@ -19444,71 +19546,6 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
19444
19546
|
}
|
|
19445
19547
|
}
|
|
19446
19548
|
|
|
19447
|
-
exports.KritzelMouseButton = void 0;
|
|
19448
|
-
(function (KritzelMouseButton) {
|
|
19449
|
-
KritzelMouseButton[KritzelMouseButton["Left"] = 0] = "Left";
|
|
19450
|
-
KritzelMouseButton[KritzelMouseButton["Middle"] = 1] = "Middle";
|
|
19451
|
-
KritzelMouseButton[KritzelMouseButton["Right"] = 2] = "Right";
|
|
19452
|
-
})(exports.KritzelMouseButton || (exports.KritzelMouseButton = {}));
|
|
19453
|
-
|
|
19454
|
-
class KritzelEventHelper {
|
|
19455
|
-
static isRightClick(ev) {
|
|
19456
|
-
return ev.button === exports.KritzelMouseButton.Right;
|
|
19457
|
-
}
|
|
19458
|
-
static isLeftClick(ev) {
|
|
19459
|
-
return ev.button === exports.KritzelMouseButton.Left;
|
|
19460
|
-
}
|
|
19461
|
-
static isPointerEventOnContextMenu(event) {
|
|
19462
|
-
const path = event.composedPath();
|
|
19463
|
-
const contextMenu = path.find(element => element.classList && element.classList.contains('context-menu'));
|
|
19464
|
-
return !!contextMenu;
|
|
19465
|
-
}
|
|
19466
|
-
static onLongPress(event, onSuccess, onCancel) {
|
|
19467
|
-
if (event.pointerType !== 'touch') {
|
|
19468
|
-
onCancel?.();
|
|
19469
|
-
return () => { };
|
|
19470
|
-
}
|
|
19471
|
-
const startX = event.clientX;
|
|
19472
|
-
const startY = event.clientY;
|
|
19473
|
-
const target = event.target;
|
|
19474
|
-
if (!target) {
|
|
19475
|
-
onCancel?.();
|
|
19476
|
-
return () => { };
|
|
19477
|
-
}
|
|
19478
|
-
const longPressTimeout = 400;
|
|
19479
|
-
const moveThreshold = 10;
|
|
19480
|
-
const timer = setTimeout(() => {
|
|
19481
|
-
removeListeners();
|
|
19482
|
-
onSuccess(event);
|
|
19483
|
-
}, longPressTimeout);
|
|
19484
|
-
const cancel = () => {
|
|
19485
|
-
clearTimeout(timer);
|
|
19486
|
-
removeListeners();
|
|
19487
|
-
onCancel?.();
|
|
19488
|
-
};
|
|
19489
|
-
const onPointerMove = (e) => {
|
|
19490
|
-
if (Math.abs(e.clientX - startX) > moveThreshold || Math.abs(e.clientY - startY) > moveThreshold) {
|
|
19491
|
-
cancel();
|
|
19492
|
-
}
|
|
19493
|
-
};
|
|
19494
|
-
const onPointerUp = () => {
|
|
19495
|
-
cancel();
|
|
19496
|
-
};
|
|
19497
|
-
const onPointerCancel = () => {
|
|
19498
|
-
cancel();
|
|
19499
|
-
};
|
|
19500
|
-
const removeListeners = () => {
|
|
19501
|
-
target.removeEventListener('pointermove', onPointerMove);
|
|
19502
|
-
target.removeEventListener('pointerup', onPointerUp);
|
|
19503
|
-
target.removeEventListener('pointercancel', onPointerCancel);
|
|
19504
|
-
};
|
|
19505
|
-
target.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
19506
|
-
target.addEventListener('pointerup', onPointerUp, { once: true });
|
|
19507
|
-
target.addEventListener('pointercancel', onPointerCancel, { once: true });
|
|
19508
|
-
return cancel;
|
|
19509
|
-
}
|
|
19510
|
-
}
|
|
19511
|
-
|
|
19512
19549
|
/**
|
|
19513
19550
|
* Abstract base class for all drawing tools in Kritzel.
|
|
19514
19551
|
* Provides common functionality and defines the interface that all tools must implement.
|
|
@@ -19597,6 +19634,71 @@ class KritzelBaseTool {
|
|
|
19597
19634
|
}
|
|
19598
19635
|
}
|
|
19599
19636
|
|
|
19637
|
+
exports.KritzelMouseButton = void 0;
|
|
19638
|
+
(function (KritzelMouseButton) {
|
|
19639
|
+
KritzelMouseButton[KritzelMouseButton["Left"] = 0] = "Left";
|
|
19640
|
+
KritzelMouseButton[KritzelMouseButton["Middle"] = 1] = "Middle";
|
|
19641
|
+
KritzelMouseButton[KritzelMouseButton["Right"] = 2] = "Right";
|
|
19642
|
+
})(exports.KritzelMouseButton || (exports.KritzelMouseButton = {}));
|
|
19643
|
+
|
|
19644
|
+
class KritzelEventHelper {
|
|
19645
|
+
static isRightClick(ev) {
|
|
19646
|
+
return ev.button === exports.KritzelMouseButton.Right;
|
|
19647
|
+
}
|
|
19648
|
+
static isLeftClick(ev) {
|
|
19649
|
+
return ev.button === exports.KritzelMouseButton.Left;
|
|
19650
|
+
}
|
|
19651
|
+
static isPointerEventOnContextMenu(event) {
|
|
19652
|
+
const path = event.composedPath();
|
|
19653
|
+
const contextMenu = path.find(element => element.classList && element.classList.contains('context-menu'));
|
|
19654
|
+
return !!contextMenu;
|
|
19655
|
+
}
|
|
19656
|
+
static onLongPress(event, onSuccess, onCancel) {
|
|
19657
|
+
if (event.pointerType !== 'touch') {
|
|
19658
|
+
onCancel?.();
|
|
19659
|
+
return () => { };
|
|
19660
|
+
}
|
|
19661
|
+
const startX = event.clientX;
|
|
19662
|
+
const startY = event.clientY;
|
|
19663
|
+
const target = event.target;
|
|
19664
|
+
if (!target) {
|
|
19665
|
+
onCancel?.();
|
|
19666
|
+
return () => { };
|
|
19667
|
+
}
|
|
19668
|
+
const longPressTimeout = 400;
|
|
19669
|
+
const moveThreshold = 10;
|
|
19670
|
+
const timer = setTimeout(() => {
|
|
19671
|
+
removeListeners();
|
|
19672
|
+
onSuccess(event);
|
|
19673
|
+
}, longPressTimeout);
|
|
19674
|
+
const cancel = () => {
|
|
19675
|
+
clearTimeout(timer);
|
|
19676
|
+
removeListeners();
|
|
19677
|
+
onCancel?.();
|
|
19678
|
+
};
|
|
19679
|
+
const onPointerMove = (e) => {
|
|
19680
|
+
if (Math.abs(e.clientX - startX) > moveThreshold || Math.abs(e.clientY - startY) > moveThreshold) {
|
|
19681
|
+
cancel();
|
|
19682
|
+
}
|
|
19683
|
+
};
|
|
19684
|
+
const onPointerUp = () => {
|
|
19685
|
+
cancel();
|
|
19686
|
+
};
|
|
19687
|
+
const onPointerCancel = () => {
|
|
19688
|
+
cancel();
|
|
19689
|
+
};
|
|
19690
|
+
const removeListeners = () => {
|
|
19691
|
+
target.removeEventListener('pointermove', onPointerMove);
|
|
19692
|
+
target.removeEventListener('pointerup', onPointerUp);
|
|
19693
|
+
target.removeEventListener('pointercancel', onPointerCancel);
|
|
19694
|
+
};
|
|
19695
|
+
target.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
19696
|
+
target.addEventListener('pointerup', onPointerUp, { once: true });
|
|
19697
|
+
target.addEventListener('pointercancel', onPointerCancel, { once: true });
|
|
19698
|
+
return cancel;
|
|
19699
|
+
}
|
|
19700
|
+
}
|
|
19701
|
+
|
|
19600
19702
|
const DEFAULT_STROKE_SIZES = [4, 6, 8, 12, 16, 24];
|
|
19601
19703
|
const DEFAULT_FONT_SIZES = [8, 10, 12, 16, 20, 24];
|
|
19602
19704
|
|
|
@@ -19625,7 +19727,7 @@ class KritzelBrushTool extends KritzelBaseTool {
|
|
|
19625
19727
|
* websocket traffic without visible quality loss — `perfect-freehand`
|
|
19626
19728
|
* already smooths the rendered stroke.
|
|
19627
19729
|
*/
|
|
19628
|
-
static MIN_POINT_DISTANCE_PX =
|
|
19730
|
+
static MIN_POINT_DISTANCE_PX = 3;
|
|
19629
19731
|
/** Tracks the ID of the path currently being drawn */
|
|
19630
19732
|
_currentPathId = null;
|
|
19631
19733
|
/**
|
|
@@ -19839,6 +19941,10 @@ class KritzelBrushTool extends KritzelBaseTool {
|
|
|
19839
19941
|
*/
|
|
19840
19942
|
class KritzelSelectionGroup extends KritzelBaseObject {
|
|
19841
19943
|
__class__ = 'KritzelSelectionGroup';
|
|
19944
|
+
constructor(config) {
|
|
19945
|
+
super();
|
|
19946
|
+
this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
|
|
19947
|
+
}
|
|
19842
19948
|
// Store only object IDs instead of full objects
|
|
19843
19949
|
_objectIds = [];
|
|
19844
19950
|
// Cached objects array - invalidated when objectIds changes
|
|
@@ -19922,8 +20028,8 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
19922
20028
|
* @param core - The KritzelCore instance to associate with this selection group
|
|
19923
20029
|
* @returns A new KritzelSelectionGroup instance configured with default settings
|
|
19924
20030
|
*/
|
|
19925
|
-
static create(core) {
|
|
19926
|
-
const object = new KritzelSelectionGroup();
|
|
20031
|
+
static create(core, config) {
|
|
20032
|
+
const object = new KritzelSelectionGroup(config);
|
|
19927
20033
|
object._core = core;
|
|
19928
20034
|
object.id = object.generateId();
|
|
19929
20035
|
object.workspaceId = core.store.state.activeWorkspace.id;
|
|
@@ -21540,6 +21646,7 @@ KritzelIconRegistry.registerIcons({
|
|
|
21540
21646
|
'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>',
|
|
21541
21647
|
'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>',
|
|
21542
21648
|
'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>',
|
|
21649
|
+
'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>',
|
|
21543
21650
|
'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>',
|
|
21544
21651
|
'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>',
|
|
21545
21652
|
'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>',
|
|
@@ -28313,6 +28420,890 @@ class KritzelAnchorManager {
|
|
|
28313
28420
|
}
|
|
28314
28421
|
}
|
|
28315
28422
|
|
|
28423
|
+
/**
|
|
28424
|
+
* Pure static utilities for localization term resolution.
|
|
28425
|
+
*/
|
|
28426
|
+
class LocalizationHelper {
|
|
28427
|
+
/**
|
|
28428
|
+
* Interpolates `{placeholder}` tokens in a term template with the provided
|
|
28429
|
+
* variables. Tokens without a matching variable are left untouched.
|
|
28430
|
+
*
|
|
28431
|
+
* @param template - The raw term string, e.g. `'Expires {date}'`
|
|
28432
|
+
* @param vars - Map of placeholder names to replacement values
|
|
28433
|
+
* @returns The interpolated string
|
|
28434
|
+
*
|
|
28435
|
+
* @example
|
|
28436
|
+
* LocalizationHelper.interpolate('Hello {name}', { name: 'Ada' }); // 'Hello Ada'
|
|
28437
|
+
*/
|
|
28438
|
+
static interpolate(template, vars) {
|
|
28439
|
+
if (!vars) {
|
|
28440
|
+
return template;
|
|
28441
|
+
}
|
|
28442
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
28443
|
+
const value = vars[key];
|
|
28444
|
+
return value === undefined || value === null ? match : String(value);
|
|
28445
|
+
});
|
|
28446
|
+
}
|
|
28447
|
+
}
|
|
28448
|
+
|
|
28449
|
+
/**
|
|
28450
|
+
* Built-in English locale (default). Provides a complete set of term values
|
|
28451
|
+
* that act as the ultimate fallback when other locales omit a key.
|
|
28452
|
+
*/
|
|
28453
|
+
const EN_LOCALE = {
|
|
28454
|
+
code: 'en',
|
|
28455
|
+
label: 'English',
|
|
28456
|
+
terms: {
|
|
28457
|
+
// Context menu
|
|
28458
|
+
'menu.copy': 'Copy',
|
|
28459
|
+
'menu.cut': 'Cut',
|
|
28460
|
+
'menu.paste': 'Paste',
|
|
28461
|
+
'menu.selectAll': 'Select All',
|
|
28462
|
+
'menu.order': 'Order',
|
|
28463
|
+
'menu.bringToFront': 'Bring to Front',
|
|
28464
|
+
'menu.sendToBack': 'Send to Back',
|
|
28465
|
+
'menu.moveUp': 'Move Up',
|
|
28466
|
+
'menu.moveDown': 'Move Down',
|
|
28467
|
+
'menu.align': 'Align',
|
|
28468
|
+
'menu.alignLeft': 'Align Left',
|
|
28469
|
+
'menu.alignCenterHorizontal': 'Align Center Horizontally',
|
|
28470
|
+
'menu.alignRight': 'Align Right',
|
|
28471
|
+
'menu.alignTop': 'Align Top',
|
|
28472
|
+
'menu.alignCenterVertical': 'Align Center Vertically',
|
|
28473
|
+
'menu.alignBottom': 'Align Bottom',
|
|
28474
|
+
'menu.group': 'Group',
|
|
28475
|
+
'menu.ungroup': 'Ungroup',
|
|
28476
|
+
'menu.export': 'Export',
|
|
28477
|
+
'menu.exportAsSvg': 'Export as SVG',
|
|
28478
|
+
'menu.exportAsPng': 'Export as PNG',
|
|
28479
|
+
'menu.delete': 'Delete',
|
|
28480
|
+
// More menu
|
|
28481
|
+
'menu.share': 'Share',
|
|
28482
|
+
'menu.import': 'Import',
|
|
28483
|
+
'menu.settings': 'Settings',
|
|
28484
|
+
'menu.logout': 'Logout',
|
|
28485
|
+
// Settings dialog
|
|
28486
|
+
'settings.dialogTitle': 'Settings',
|
|
28487
|
+
'settings.categories.general': 'General',
|
|
28488
|
+
'settings.categories.viewport': 'Viewport',
|
|
28489
|
+
'settings.categories.shortcuts': 'Keyboard Shortcuts',
|
|
28490
|
+
'settings.categories.developer': 'Developer Options',
|
|
28491
|
+
'settings.categories.about': 'About',
|
|
28492
|
+
'settings.general.title': 'General Settings',
|
|
28493
|
+
'settings.general.theme.label': 'Theme',
|
|
28494
|
+
'settings.general.theme.description': 'Select a registered color theme for the editor interface.',
|
|
28495
|
+
'settings.general.language.label': 'Language',
|
|
28496
|
+
'settings.general.language.description': 'Select the display language for the editor interface.',
|
|
28497
|
+
'settings.general.lockDrawingScale.label': 'Lock Drawing Scale',
|
|
28498
|
+
'settings.general.lockDrawingScale.description': 'When enabled, drawn objects maintain a fixed visual size regardless of the current zoom level.',
|
|
28499
|
+
'settings.viewport.title': 'Viewport Settings',
|
|
28500
|
+
'settings.viewport.minZoom.label': 'Minimum Zoom Level',
|
|
28501
|
+
'settings.viewport.minZoom.description': 'Sets the minimum zoom level. Lower values allow zooming out further to see more of the canvas.',
|
|
28502
|
+
'settings.viewport.maxZoom.label': 'Maximum Zoom Level',
|
|
28503
|
+
'settings.viewport.maxZoom.description': 'Sets the maximum zoom level. Higher values allow zooming in closer for detailed work.',
|
|
28504
|
+
'settings.viewport.boundaryLeft.label': 'Viewport Boundary Left',
|
|
28505
|
+
'settings.viewport.boundaryLeft.description': 'Left boundary in world coordinates. Set to limit how far left the viewport can pan.',
|
|
28506
|
+
'settings.viewport.boundaryRight.label': 'Viewport Boundary Right',
|
|
28507
|
+
'settings.viewport.boundaryRight.description': 'Right boundary in world coordinates. Set to limit how far right the viewport can pan.',
|
|
28508
|
+
'settings.viewport.boundaryTop.label': 'Viewport Boundary Top',
|
|
28509
|
+
'settings.viewport.boundaryTop.description': 'Top boundary in world coordinates. Set to limit how far up the viewport can pan.',
|
|
28510
|
+
'settings.viewport.boundaryBottom.label': 'Viewport Boundary Bottom',
|
|
28511
|
+
'settings.viewport.boundaryBottom.description': 'Bottom boundary in world coordinates. Set to limit how far down the viewport can pan.',
|
|
28512
|
+
'settings.viewport.boundaryPlaceholder': 'Infinite',
|
|
28513
|
+
'settings.shortcuts.title': 'Keyboard Shortcuts',
|
|
28514
|
+
'settings.developer.title': 'Developer Options',
|
|
28515
|
+
'settings.developer.showViewportInfo.label': 'Show Viewport Info',
|
|
28516
|
+
'settings.developer.showViewportInfo.description': 'Display viewport debug information such as position, zoom level, and boundaries.',
|
|
28517
|
+
'settings.developer.showObjectInfo.label': 'Show Object Info',
|
|
28518
|
+
'settings.developer.showObjectInfo.description': 'Display debug information about objects on the canvas.',
|
|
28519
|
+
'settings.developer.showSyncProviderInfo.label': 'Show Sync Provider Info',
|
|
28520
|
+
'settings.developer.showSyncProviderInfo.description': 'Display debug information about the sync provider connection status.',
|
|
28521
|
+
'settings.developer.showMigrationInfo.label': 'Show Migration Info',
|
|
28522
|
+
'settings.developer.showMigrationInfo.description': 'Display debug information about data migrations.',
|
|
28523
|
+
'settings.about.title': 'About',
|
|
28524
|
+
'settings.about.description': 'Kritzel - A drawing application',
|
|
28525
|
+
// Export dialog
|
|
28526
|
+
'export.dialogTitle': 'Export',
|
|
28527
|
+
'export.tabs.viewport': 'Export Viewport',
|
|
28528
|
+
'export.tabs.workspace': 'Export Workspace',
|
|
28529
|
+
'export.format.label': 'Format',
|
|
28530
|
+
'export.filename.label': 'Filename',
|
|
28531
|
+
'export.filename.placeholder': 'Enter filename',
|
|
28532
|
+
'export.exportButton': 'Export',
|
|
28533
|
+
// Workspace manager
|
|
28534
|
+
'workspace.sharedTooltip': 'Shared workspace',
|
|
28535
|
+
'workspace.rename': 'Rename',
|
|
28536
|
+
'workspace.delete': 'Delete',
|
|
28537
|
+
// Zoom panel
|
|
28538
|
+
'zoom.zoomIn': 'Zoom in',
|
|
28539
|
+
'zoom.zoomOut': 'Zoom out',
|
|
28540
|
+
// Utility panel
|
|
28541
|
+
'utility.undo': 'Undo',
|
|
28542
|
+
'utility.redo': 'Redo',
|
|
28543
|
+
'utility.delete': 'Delete selected items',
|
|
28544
|
+
// Share dialog
|
|
28545
|
+
'share.dialogTitle': 'Share Workspace',
|
|
28546
|
+
'share.linkSharing.label': 'Link sharing',
|
|
28547
|
+
'share.linkSharing.enabledDescription': 'Anyone with the link can access this workspace.',
|
|
28548
|
+
'share.linkSharing.disabledDescription': 'Link sharing is disabled. Only you can access this workspace.',
|
|
28549
|
+
'share.linkSharing.toggleLabel': 'Enable link sharing',
|
|
28550
|
+
'share.copyLink.title': 'Copy link',
|
|
28551
|
+
'share.copyLink.copied': 'Copied!',
|
|
28552
|
+
// Login dialog
|
|
28553
|
+
'login.dialogTitle': 'Sign in',
|
|
28554
|
+
// Current user dialog
|
|
28555
|
+
'currentUser.dialogTitle': 'Account',
|
|
28556
|
+
// Back to content
|
|
28557
|
+
'backToContent.label': 'Back to content',
|
|
28558
|
+
// Tool config
|
|
28559
|
+
'toolConfig.collapse': 'Collapse',
|
|
28560
|
+
'toolConfig.expand': 'Expand',
|
|
28561
|
+
// More menu button
|
|
28562
|
+
'moreMenu.ariaLabel': 'More options',
|
|
28563
|
+
// Engine
|
|
28564
|
+
'engine.loading': 'Loading...',
|
|
28565
|
+
// Watermark
|
|
28566
|
+
'watermark.poweredBy': 'Powered by Kritzel',
|
|
28567
|
+
},
|
|
28568
|
+
};
|
|
28569
|
+
|
|
28570
|
+
/**
|
|
28571
|
+
* Built-in German locale.
|
|
28572
|
+
*/
|
|
28573
|
+
const DE_LOCALE = {
|
|
28574
|
+
code: 'de',
|
|
28575
|
+
label: 'Deutsch',
|
|
28576
|
+
terms: {
|
|
28577
|
+
// Context menu
|
|
28578
|
+
'menu.copy': 'Kopieren',
|
|
28579
|
+
'menu.cut': 'Ausschneiden',
|
|
28580
|
+
'menu.paste': 'Einfügen',
|
|
28581
|
+
'menu.selectAll': 'Alles auswählen',
|
|
28582
|
+
'menu.order': 'Anordnen',
|
|
28583
|
+
'menu.bringToFront': 'In den Vordergrund',
|
|
28584
|
+
'menu.sendToBack': 'In den Hintergrund',
|
|
28585
|
+
'menu.moveUp': 'Nach vorne',
|
|
28586
|
+
'menu.moveDown': 'Nach hinten',
|
|
28587
|
+
'menu.align': 'Ausrichten',
|
|
28588
|
+
'menu.alignLeft': 'Linksbündig ausrichten',
|
|
28589
|
+
'menu.alignCenterHorizontal': 'Horizontal zentrieren',
|
|
28590
|
+
'menu.alignRight': 'Rechtsbündig ausrichten',
|
|
28591
|
+
'menu.alignTop': 'Oben ausrichten',
|
|
28592
|
+
'menu.alignCenterVertical': 'Vertikal zentrieren',
|
|
28593
|
+
'menu.alignBottom': 'Unten ausrichten',
|
|
28594
|
+
'menu.group': 'Gruppieren',
|
|
28595
|
+
'menu.ungroup': 'Gruppierung aufheben',
|
|
28596
|
+
'menu.export': 'Exportieren',
|
|
28597
|
+
'menu.exportAsSvg': 'Als SVG exportieren',
|
|
28598
|
+
'menu.exportAsPng': 'Als PNG exportieren',
|
|
28599
|
+
'menu.delete': 'Löschen',
|
|
28600
|
+
// More menu
|
|
28601
|
+
'menu.share': 'Teilen',
|
|
28602
|
+
'menu.import': 'Importieren',
|
|
28603
|
+
'menu.settings': 'Einstellungen',
|
|
28604
|
+
'menu.logout': 'Abmelden',
|
|
28605
|
+
// Settings dialog
|
|
28606
|
+
'settings.dialogTitle': 'Einstellungen',
|
|
28607
|
+
'settings.categories.general': 'Allgemein',
|
|
28608
|
+
'settings.categories.viewport': 'Ansichtsfenster',
|
|
28609
|
+
'settings.categories.shortcuts': 'Tastenkürzel',
|
|
28610
|
+
'settings.categories.developer': 'Entwickleroptionen',
|
|
28611
|
+
'settings.categories.about': 'Über',
|
|
28612
|
+
'settings.general.title': 'Allgemeine Einstellungen',
|
|
28613
|
+
'settings.general.theme.label': 'Design',
|
|
28614
|
+
'settings.general.theme.description': 'Wählen Sie ein registriertes Farbdesign für die Editor-Oberfläche.',
|
|
28615
|
+
'settings.general.language.label': 'Sprache',
|
|
28616
|
+
'settings.general.language.description': 'Wählen Sie die Anzeigesprache für die Editor-Oberfläche.',
|
|
28617
|
+
'settings.general.lockDrawingScale.label': 'Zeichenskalierung sperren',
|
|
28618
|
+
'settings.general.lockDrawingScale.description': 'Wenn aktiviert, behalten gezeichnete Objekte unabhängig von der aktuellen Zoomstufe eine feste visuelle Größe.',
|
|
28619
|
+
'settings.viewport.title': 'Ansichtsfenster-Einstellungen',
|
|
28620
|
+
'settings.viewport.minZoom.label': 'Minimale Zoomstufe',
|
|
28621
|
+
'settings.viewport.minZoom.description': 'Legt die minimale Zoomstufe fest. Niedrigere Werte ermöglichen weiteres Herauszoomen, um mehr von der Zeichenfläche zu sehen.',
|
|
28622
|
+
'settings.viewport.maxZoom.label': 'Maximale Zoomstufe',
|
|
28623
|
+
'settings.viewport.maxZoom.description': 'Legt die maximale Zoomstufe fest. Höhere Werte ermöglichen näheres Heranzoomen für Detailarbeit.',
|
|
28624
|
+
'settings.viewport.boundaryLeft.label': 'Ansichtsfenster-Grenze links',
|
|
28625
|
+
'settings.viewport.boundaryLeft.description': 'Linke Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach links verschoben werden kann.',
|
|
28626
|
+
'settings.viewport.boundaryRight.label': 'Ansichtsfenster-Grenze rechts',
|
|
28627
|
+
'settings.viewport.boundaryRight.description': 'Rechte Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach rechts verschoben werden kann.',
|
|
28628
|
+
'settings.viewport.boundaryTop.label': 'Ansichtsfenster-Grenze oben',
|
|
28629
|
+
'settings.viewport.boundaryTop.description': 'Obere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach oben verschoben werden kann.',
|
|
28630
|
+
'settings.viewport.boundaryBottom.label': 'Ansichtsfenster-Grenze unten',
|
|
28631
|
+
'settings.viewport.boundaryBottom.description': 'Untere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach unten verschoben werden kann.',
|
|
28632
|
+
'settings.viewport.boundaryPlaceholder': 'Unendlich',
|
|
28633
|
+
'settings.shortcuts.title': 'Tastenkürzel',
|
|
28634
|
+
'settings.developer.title': 'Entwickleroptionen',
|
|
28635
|
+
'settings.developer.showViewportInfo.label': 'Ansichtsfenster-Infos anzeigen',
|
|
28636
|
+
'settings.developer.showViewportInfo.description': 'Zeigt Debug-Informationen zum Ansichtsfenster an, z. B. Position, Zoomstufe und Grenzen.',
|
|
28637
|
+
'settings.developer.showObjectInfo.label': 'Objekt-Infos anzeigen',
|
|
28638
|
+
'settings.developer.showObjectInfo.description': 'Zeigt Debug-Informationen zu Objekten auf der Zeichenfläche an.',
|
|
28639
|
+
'settings.developer.showSyncProviderInfo.label': 'Sync-Provider-Infos anzeigen',
|
|
28640
|
+
'settings.developer.showSyncProviderInfo.description': 'Zeigt Debug-Informationen zum Verbindungsstatus des Sync-Providers an.',
|
|
28641
|
+
'settings.developer.showMigrationInfo.label': 'Migrations-Infos anzeigen',
|
|
28642
|
+
'settings.developer.showMigrationInfo.description': 'Zeigt Debug-Informationen zu Datenmigrationen an.',
|
|
28643
|
+
'settings.about.title': 'Über',
|
|
28644
|
+
'settings.about.description': 'Kritzel – Eine Zeichenanwendung',
|
|
28645
|
+
// Export dialog
|
|
28646
|
+
'export.dialogTitle': 'Exportieren',
|
|
28647
|
+
'export.tabs.viewport': 'Ansichtsfenster exportieren',
|
|
28648
|
+
'export.tabs.workspace': 'Arbeitsbereich exportieren',
|
|
28649
|
+
'export.format.label': 'Format',
|
|
28650
|
+
'export.filename.label': 'Dateiname',
|
|
28651
|
+
'export.filename.placeholder': 'Dateiname eingeben',
|
|
28652
|
+
'export.exportButton': 'Exportieren',
|
|
28653
|
+
// Workspace manager
|
|
28654
|
+
'workspace.sharedTooltip': 'Geteilter Arbeitsbereich',
|
|
28655
|
+
'workspace.rename': 'Umbenennen',
|
|
28656
|
+
'workspace.delete': 'Löschen',
|
|
28657
|
+
// Zoom panel
|
|
28658
|
+
'zoom.zoomIn': 'Vergrößern',
|
|
28659
|
+
'zoom.zoomOut': 'Verkleinern',
|
|
28660
|
+
// Utility panel
|
|
28661
|
+
'utility.undo': 'Rückgängig',
|
|
28662
|
+
'utility.redo': 'Wiederholen',
|
|
28663
|
+
'utility.delete': 'Ausgewählte Elemente löschen',
|
|
28664
|
+
// Share dialog
|
|
28665
|
+
'share.dialogTitle': 'Arbeitsbereich teilen',
|
|
28666
|
+
'share.linkSharing.label': 'Link-Freigabe',
|
|
28667
|
+
'share.linkSharing.enabledDescription': 'Jeder mit dem Link kann auf diesen Arbeitsbereich zugreifen.',
|
|
28668
|
+
'share.linkSharing.disabledDescription': 'Die Link-Freigabe ist deaktiviert. Nur Sie können auf diesen Arbeitsbereich zugreifen.',
|
|
28669
|
+
'share.linkSharing.toggleLabel': 'Link-Freigabe aktivieren',
|
|
28670
|
+
'share.copyLink.title': 'Link kopieren',
|
|
28671
|
+
'share.copyLink.copied': 'Kopiert!',
|
|
28672
|
+
// Login dialog
|
|
28673
|
+
'login.dialogTitle': 'Anmelden',
|
|
28674
|
+
// Current user dialog
|
|
28675
|
+
'currentUser.dialogTitle': 'Konto',
|
|
28676
|
+
// Back to content
|
|
28677
|
+
'backToContent.label': 'Zurück zum Inhalt',
|
|
28678
|
+
// Tool config
|
|
28679
|
+
'toolConfig.collapse': 'Einklappen',
|
|
28680
|
+
'toolConfig.expand': 'Ausklappen',
|
|
28681
|
+
// More menu button
|
|
28682
|
+
'moreMenu.ariaLabel': 'Weitere Optionen',
|
|
28683
|
+
// Engine
|
|
28684
|
+
'engine.loading': 'Wird geladen...',
|
|
28685
|
+
// Watermark
|
|
28686
|
+
'watermark.poweredBy': 'Bereitgestellt von Kritzel',
|
|
28687
|
+
},
|
|
28688
|
+
};
|
|
28689
|
+
|
|
28690
|
+
/**
|
|
28691
|
+
* Built-in French locale.
|
|
28692
|
+
*/
|
|
28693
|
+
const FR_LOCALE = {
|
|
28694
|
+
code: 'fr',
|
|
28695
|
+
label: 'Français',
|
|
28696
|
+
terms: {
|
|
28697
|
+
// Context menu
|
|
28698
|
+
'menu.copy': 'Copier',
|
|
28699
|
+
'menu.cut': 'Couper',
|
|
28700
|
+
'menu.paste': 'Coller',
|
|
28701
|
+
'menu.selectAll': 'Tout sélectionner',
|
|
28702
|
+
'menu.order': 'Ordre',
|
|
28703
|
+
'menu.bringToFront': 'Mettre au premier plan',
|
|
28704
|
+
'menu.sendToBack': "Mettre à l'arrière-plan",
|
|
28705
|
+
'menu.moveUp': 'Avancer',
|
|
28706
|
+
'menu.moveDown': 'Reculer',
|
|
28707
|
+
'menu.align': 'Aligner',
|
|
28708
|
+
'menu.alignLeft': 'Aligner à gauche',
|
|
28709
|
+
'menu.alignCenterHorizontal': 'Centrer horizontalement',
|
|
28710
|
+
'menu.alignRight': 'Aligner à droite',
|
|
28711
|
+
'menu.alignTop': 'Aligner en haut',
|
|
28712
|
+
'menu.alignCenterVertical': 'Centrer verticalement',
|
|
28713
|
+
'menu.alignBottom': 'Aligner en bas',
|
|
28714
|
+
'menu.group': 'Grouper',
|
|
28715
|
+
'menu.ungroup': 'Dégrouper',
|
|
28716
|
+
'menu.export': 'Exporter',
|
|
28717
|
+
'menu.exportAsSvg': 'Exporter en SVG',
|
|
28718
|
+
'menu.exportAsPng': 'Exporter en PNG',
|
|
28719
|
+
'menu.delete': 'Supprimer',
|
|
28720
|
+
// More menu
|
|
28721
|
+
'menu.share': 'Partager',
|
|
28722
|
+
'menu.import': 'Importer',
|
|
28723
|
+
'menu.settings': 'Paramètres',
|
|
28724
|
+
'menu.logout': 'Déconnexion',
|
|
28725
|
+
// Settings dialog
|
|
28726
|
+
'settings.dialogTitle': 'Paramètres',
|
|
28727
|
+
'settings.categories.general': 'Général',
|
|
28728
|
+
'settings.categories.viewport': "Fenêtre d'affichage",
|
|
28729
|
+
'settings.categories.shortcuts': 'Raccourcis clavier',
|
|
28730
|
+
'settings.categories.developer': 'Options développeur',
|
|
28731
|
+
'settings.categories.about': 'À propos',
|
|
28732
|
+
'settings.general.title': 'Paramètres généraux',
|
|
28733
|
+
'settings.general.theme.label': 'Thème',
|
|
28734
|
+
'settings.general.theme.description': "Sélectionnez un thème de couleurs enregistré pour l'interface de l'éditeur.",
|
|
28735
|
+
'settings.general.language.label': 'Langue',
|
|
28736
|
+
'settings.general.language.description': "Sélectionnez la langue d'affichage de l'interface de l'éditeur.",
|
|
28737
|
+
'settings.general.lockDrawingScale.label': "Verrouiller l'échelle de dessin",
|
|
28738
|
+
'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.",
|
|
28739
|
+
'settings.viewport.title': "Paramètres de la fenêtre d'affichage",
|
|
28740
|
+
'settings.viewport.minZoom.label': 'Niveau de zoom minimal',
|
|
28741
|
+
'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.',
|
|
28742
|
+
'settings.viewport.maxZoom.label': 'Niveau de zoom maximal',
|
|
28743
|
+
'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é.',
|
|
28744
|
+
'settings.viewport.boundaryLeft.label': "Limite gauche de la fenêtre d'affichage",
|
|
28745
|
+
'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.",
|
|
28746
|
+
'settings.viewport.boundaryRight.label': "Limite droite de la fenêtre d'affichage",
|
|
28747
|
+
'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.",
|
|
28748
|
+
'settings.viewport.boundaryTop.label': "Limite supérieure de la fenêtre d'affichage",
|
|
28749
|
+
'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.",
|
|
28750
|
+
'settings.viewport.boundaryBottom.label': "Limite inférieure de la fenêtre d'affichage",
|
|
28751
|
+
'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.",
|
|
28752
|
+
'settings.viewport.boundaryPlaceholder': 'Infini',
|
|
28753
|
+
'settings.shortcuts.title': 'Raccourcis clavier',
|
|
28754
|
+
'settings.developer.title': 'Options développeur',
|
|
28755
|
+
'settings.developer.showViewportInfo.label': "Afficher les infos de la fenêtre d'affichage",
|
|
28756
|
+
'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.",
|
|
28757
|
+
'settings.developer.showObjectInfo.label': 'Afficher les infos des objets',
|
|
28758
|
+
'settings.developer.showObjectInfo.description': 'Affiche les informations de débogage des objets sur le canevas.',
|
|
28759
|
+
'settings.developer.showSyncProviderInfo.label': 'Afficher les infos du fournisseur de synchronisation',
|
|
28760
|
+
'settings.developer.showSyncProviderInfo.description': "Affiche les informations de débogage sur l'état de connexion du fournisseur de synchronisation.",
|
|
28761
|
+
'settings.developer.showMigrationInfo.label': 'Afficher les infos de migration',
|
|
28762
|
+
'settings.developer.showMigrationInfo.description': 'Affiche les informations de débogage sur les migrations de données.',
|
|
28763
|
+
'settings.about.title': 'À propos',
|
|
28764
|
+
'settings.about.description': 'Kritzel - Une application de dessin',
|
|
28765
|
+
// Export dialog
|
|
28766
|
+
'export.dialogTitle': 'Exporter',
|
|
28767
|
+
'export.tabs.viewport': "Exporter la fenêtre d'affichage",
|
|
28768
|
+
'export.tabs.workspace': "Exporter l'espace de travail",
|
|
28769
|
+
'export.format.label': 'Format',
|
|
28770
|
+
'export.filename.label': 'Nom du fichier',
|
|
28771
|
+
'export.filename.placeholder': 'Saisir le nom du fichier',
|
|
28772
|
+
'export.exportButton': 'Exporter',
|
|
28773
|
+
// Workspace manager
|
|
28774
|
+
'workspace.sharedTooltip': 'Espace de travail partagé',
|
|
28775
|
+
'workspace.rename': 'Renommer',
|
|
28776
|
+
'workspace.delete': 'Supprimer',
|
|
28777
|
+
// Zoom panel
|
|
28778
|
+
'zoom.zoomIn': 'Zoom avant',
|
|
28779
|
+
'zoom.zoomOut': 'Zoom arrière',
|
|
28780
|
+
// Utility panel
|
|
28781
|
+
'utility.undo': 'Annuler',
|
|
28782
|
+
'utility.redo': 'Rétablir',
|
|
28783
|
+
'utility.delete': 'Supprimer les éléments sélectionnés',
|
|
28784
|
+
// Share dialog
|
|
28785
|
+
'share.dialogTitle': "Partager l'espace de travail",
|
|
28786
|
+
'share.linkSharing.label': 'Partage par lien',
|
|
28787
|
+
'share.linkSharing.enabledDescription': 'Toute personne disposant du lien peut accéder à cet espace de travail.',
|
|
28788
|
+
'share.linkSharing.disabledDescription': 'Le partage par lien est désactivé. Vous seul pouvez accéder à cet espace de travail.',
|
|
28789
|
+
'share.linkSharing.toggleLabel': 'Activer le partage par lien',
|
|
28790
|
+
'share.copyLink.title': 'Copier le lien',
|
|
28791
|
+
'share.copyLink.copied': 'Copié !',
|
|
28792
|
+
// Login dialog
|
|
28793
|
+
'login.dialogTitle': 'Se connecter',
|
|
28794
|
+
// Current user dialog
|
|
28795
|
+
'currentUser.dialogTitle': 'Compte',
|
|
28796
|
+
// Back to content
|
|
28797
|
+
'backToContent.label': 'Retour au contenu',
|
|
28798
|
+
// Tool config
|
|
28799
|
+
'toolConfig.collapse': 'Réduire',
|
|
28800
|
+
'toolConfig.expand': 'Développer',
|
|
28801
|
+
// More menu button
|
|
28802
|
+
'moreMenu.ariaLabel': "Plus d'options",
|
|
28803
|
+
// Engine
|
|
28804
|
+
'engine.loading': 'Chargement...',
|
|
28805
|
+
// Watermark
|
|
28806
|
+
'watermark.poweredBy': 'Propulsé par Kritzel',
|
|
28807
|
+
},
|
|
28808
|
+
};
|
|
28809
|
+
|
|
28810
|
+
/** Key used to store the settings object in localStorage. */
|
|
28811
|
+
const SETTINGS_STORAGE_KEY = 'kritzel-settings';
|
|
28812
|
+
/** Default locale used when no stored preference exists. */
|
|
28813
|
+
const DEFAULT_LOCALE = 'en';
|
|
28814
|
+
/**
|
|
28815
|
+
* Manages localization state and term resolution across the Kritzel editor.
|
|
28816
|
+
*
|
|
28817
|
+
* The manager is owned per {@link KritzelCore} instance (like the theme manager)
|
|
28818
|
+
* so that multiple editors on the same page can use different languages in
|
|
28819
|
+
* isolation.
|
|
28820
|
+
*
|
|
28821
|
+
* Term resolution follows a deterministic fallback chain for each key:
|
|
28822
|
+
* 1. active locale's custom terms
|
|
28823
|
+
* 2. active locale's built-in terms
|
|
28824
|
+
* 3. fallback locale's custom terms
|
|
28825
|
+
* 4. fallback locale's built-in terms
|
|
28826
|
+
* 5. the key itself (last resort)
|
|
28827
|
+
*
|
|
28828
|
+
* Persistence of the selected locale is handled by the KritzelSettings
|
|
28829
|
+
* component; this manager only reads the stored value.
|
|
28830
|
+
*/
|
|
28831
|
+
class KritzelLocalizationManager {
|
|
28832
|
+
_core;
|
|
28833
|
+
_storageKey;
|
|
28834
|
+
/** Built-in locale definitions, keyed by locale code. */
|
|
28835
|
+
_builtinLocales = new Map();
|
|
28836
|
+
/** Consumer-registered locale definitions, keyed by locale code. */
|
|
28837
|
+
_customLocales = new Map();
|
|
28838
|
+
_currentLocale = DEFAULT_LOCALE;
|
|
28839
|
+
_fallbackLocale = DEFAULT_LOCALE;
|
|
28840
|
+
/**
|
|
28841
|
+
* Creates a new KritzelLocalizationManager instance.
|
|
28842
|
+
* Seeds the built-in locales and initializes the current locale from the
|
|
28843
|
+
* settings object in localStorage (or the default locale).
|
|
28844
|
+
*
|
|
28845
|
+
* @param core - The KritzelCore instance this manager belongs to
|
|
28846
|
+
*/
|
|
28847
|
+
constructor(core) {
|
|
28848
|
+
this._core = core;
|
|
28849
|
+
this._storageKey = core.editorId ? `${SETTINGS_STORAGE_KEY}-${core.editorId}` : SETTINGS_STORAGE_KEY;
|
|
28850
|
+
this._builtinLocales.set(EN_LOCALE.code, EN_LOCALE);
|
|
28851
|
+
this._builtinLocales.set(DE_LOCALE.code, DE_LOCALE);
|
|
28852
|
+
this._builtinLocales.set(FR_LOCALE.code, FR_LOCALE);
|
|
28853
|
+
this._currentLocale = this.getStoredLocale();
|
|
28854
|
+
}
|
|
28855
|
+
/**
|
|
28856
|
+
* Gets the currently active locale code.
|
|
28857
|
+
*/
|
|
28858
|
+
get currentLocale() {
|
|
28859
|
+
return this._currentLocale;
|
|
28860
|
+
}
|
|
28861
|
+
/**
|
|
28862
|
+
* Gets the fallback locale code used when a term is missing from the active locale.
|
|
28863
|
+
*/
|
|
28864
|
+
get fallbackLocale() {
|
|
28865
|
+
return this._fallbackLocale;
|
|
28866
|
+
}
|
|
28867
|
+
/**
|
|
28868
|
+
* Sets the fallback locale code used when a term is missing from the active locale.
|
|
28869
|
+
*
|
|
28870
|
+
* @param code - The fallback locale code
|
|
28871
|
+
*/
|
|
28872
|
+
setFallbackLocale(code) {
|
|
28873
|
+
this._fallbackLocale = code;
|
|
28874
|
+
}
|
|
28875
|
+
/**
|
|
28876
|
+
* Registers consumer-provided locale definitions. Definitions are merged by
|
|
28877
|
+
* code; registering the same code again replaces the previous definition.
|
|
28878
|
+
* A registered locale may provide a partial set of terms — missing terms are
|
|
28879
|
+
* resolved through the fallback chain.
|
|
28880
|
+
*
|
|
28881
|
+
* @param locales - The locale definitions to register
|
|
28882
|
+
*/
|
|
28883
|
+
registerLocales(locales) {
|
|
28884
|
+
for (const locale of locales) {
|
|
28885
|
+
this._customLocales.set(locale.code, locale);
|
|
28886
|
+
}
|
|
28887
|
+
}
|
|
28888
|
+
/**
|
|
28889
|
+
* Sets the active locale and triggers a re-render so UI strings update.
|
|
28890
|
+
*
|
|
28891
|
+
* @param code - The locale code to activate
|
|
28892
|
+
*/
|
|
28893
|
+
setLocale(code) {
|
|
28894
|
+
this._currentLocale = code;
|
|
28895
|
+
this._core.rerender();
|
|
28896
|
+
}
|
|
28897
|
+
/**
|
|
28898
|
+
* Gets the list of available locale codes (built-in and registered).
|
|
28899
|
+
*/
|
|
28900
|
+
getAvailableLocales() {
|
|
28901
|
+
return Array.from(new Set([...this._builtinLocales.keys(), ...this._customLocales.keys()]));
|
|
28902
|
+
}
|
|
28903
|
+
/**
|
|
28904
|
+
* Gets the available locales as `{ code, label }` pairs for use in a selector.
|
|
28905
|
+
* A registered locale's label takes precedence over the built-in label; the
|
|
28906
|
+
* code is used as the label when none is provided.
|
|
28907
|
+
*/
|
|
28908
|
+
getAvailableLocaleOptions() {
|
|
28909
|
+
return this.getAvailableLocales().map(code => ({ code, label: this.getLocaleLabel(code) }));
|
|
28910
|
+
}
|
|
28911
|
+
/**
|
|
28912
|
+
* Gets the display label for a locale code, falling back to the code itself.
|
|
28913
|
+
*
|
|
28914
|
+
* @param code - The locale code
|
|
28915
|
+
*/
|
|
28916
|
+
getLocaleLabel(code) {
|
|
28917
|
+
return this._customLocales.get(code)?.label ?? this._builtinLocales.get(code)?.label ?? code;
|
|
28918
|
+
}
|
|
28919
|
+
/**
|
|
28920
|
+
* Resolves a term key to its translated string for the active locale and
|
|
28921
|
+
* interpolates any `{placeholder}` tokens.
|
|
28922
|
+
*
|
|
28923
|
+
* @param key - The term key to resolve
|
|
28924
|
+
* @param vars - Optional values for `{placeholder}` interpolation
|
|
28925
|
+
* @returns The translated, interpolated string (or the key if unresolved)
|
|
28926
|
+
*
|
|
28927
|
+
* @example
|
|
28928
|
+
* manager.translate('menu.copy'); // 'Copy'
|
|
28929
|
+
* manager.translate('share.expires', { date: '5/1' }); // 'Expires 5/1'
|
|
28930
|
+
*/
|
|
28931
|
+
translate(key, vars) {
|
|
28932
|
+
const resolved = this._customLocales.get(this._currentLocale)?.terms[key] ??
|
|
28933
|
+
this._builtinLocales.get(this._currentLocale)?.terms[key] ??
|
|
28934
|
+
this._customLocales.get(this._fallbackLocale)?.terms[key] ??
|
|
28935
|
+
this._builtinLocales.get(this._fallbackLocale)?.terms[key] ??
|
|
28936
|
+
key;
|
|
28937
|
+
return LocalizationHelper.interpolate(resolved, vars);
|
|
28938
|
+
}
|
|
28939
|
+
/**
|
|
28940
|
+
* Resolves every known term key for the active locale into a flat map.
|
|
28941
|
+
*
|
|
28942
|
+
* The key set is the union of all keys defined across the built-in and
|
|
28943
|
+
* registered locales, so consumer-added keys are included. Each value is
|
|
28944
|
+
* resolved through the same fallback chain as {@link translate} (without
|
|
28945
|
+
* interpolation, since these are shared, variable-free UI labels).
|
|
28946
|
+
*
|
|
28947
|
+
* @returns A map of every term key to its resolved string for the active locale
|
|
28948
|
+
*/
|
|
28949
|
+
getAllTerms() {
|
|
28950
|
+
const keys = new Set();
|
|
28951
|
+
for (const locale of [...this._builtinLocales.values(), ...this._customLocales.values()]) {
|
|
28952
|
+
for (const key of Object.keys(locale.terms)) {
|
|
28953
|
+
keys.add(key);
|
|
28954
|
+
}
|
|
28955
|
+
}
|
|
28956
|
+
const result = {};
|
|
28957
|
+
for (const key of keys) {
|
|
28958
|
+
result[key] = this.translate(key);
|
|
28959
|
+
}
|
|
28960
|
+
return result;
|
|
28961
|
+
}
|
|
28962
|
+
/**
|
|
28963
|
+
* Reads the stored locale from the settings object in localStorage using this
|
|
28964
|
+
* instance's namespaced key.
|
|
28965
|
+
*
|
|
28966
|
+
* @returns The stored locale code if valid, or the default locale
|
|
28967
|
+
*/
|
|
28968
|
+
getStoredLocale() {
|
|
28969
|
+
if (typeof localStorage === 'undefined') {
|
|
28970
|
+
return DEFAULT_LOCALE;
|
|
28971
|
+
}
|
|
28972
|
+
const stored = localStorage.getItem(this._storageKey);
|
|
28973
|
+
if (!stored) {
|
|
28974
|
+
return DEFAULT_LOCALE;
|
|
28975
|
+
}
|
|
28976
|
+
try {
|
|
28977
|
+
const parsed = JSON.parse(stored);
|
|
28978
|
+
if (typeof parsed?.locale === 'string') {
|
|
28979
|
+
return parsed.locale;
|
|
28980
|
+
}
|
|
28981
|
+
}
|
|
28982
|
+
catch {
|
|
28983
|
+
// Invalid JSON, use default
|
|
28984
|
+
}
|
|
28985
|
+
return DEFAULT_LOCALE;
|
|
28986
|
+
}
|
|
28987
|
+
/**
|
|
28988
|
+
* Cleans up the localization manager state.
|
|
28989
|
+
* Provided for lifecycle parity with other managers.
|
|
28990
|
+
*/
|
|
28991
|
+
cleanup() {
|
|
28992
|
+
this._customLocales.clear();
|
|
28993
|
+
}
|
|
28994
|
+
}
|
|
28995
|
+
|
|
28996
|
+
/**
|
|
28997
|
+
* License verification constants.
|
|
28998
|
+
*
|
|
28999
|
+
* Kritzel licenses are offline Ed25519 signed tokens. The owner signs each
|
|
29000
|
+
* customer's token with a private key that never leaves their machine
|
|
29001
|
+
* (see scripts/license/); the library verifies tokens against the public key
|
|
29002
|
+
* below. The public key is safe to ship — it can only *verify* signatures,
|
|
29003
|
+
* never forge them.
|
|
29004
|
+
*/
|
|
29005
|
+
/**
|
|
29006
|
+
* Token format version prefix. A valid license key looks like:
|
|
29007
|
+
*
|
|
29008
|
+
* KRTZL1.<base64url(payload JSON)>.<base64url(ed25519 signature)>
|
|
29009
|
+
*
|
|
29010
|
+
* Bump the version (and this prefix) if the token scheme ever changes.
|
|
29011
|
+
*/
|
|
29012
|
+
const KRITZEL_LICENSE_TOKEN_PREFIX = 'KRTZL1.';
|
|
29013
|
+
/**
|
|
29014
|
+
* Raw 32-byte Ed25519 public key (base64url) used to verify license tokens.
|
|
29015
|
+
*
|
|
29016
|
+
* Generate the matching key pair with `npm run license:keygen` and paste the
|
|
29017
|
+
* printed public key here. Replacing this value invalidates every license
|
|
29018
|
+
* signed with the previous key.
|
|
29019
|
+
*/
|
|
29020
|
+
const KRITZEL_LICENSE_PUBLIC_KEY = 'I-pYJlxDEjT94rwSaqhXW5Sv__fMKS-JKKnV3pg0PaM';
|
|
29021
|
+
|
|
29022
|
+
/** Minimum delay (ms) between periodic license re-validations. */
|
|
29023
|
+
const MIN_REVALIDATION_DELAY_MS = 30_000;
|
|
29024
|
+
/** Maximum delay (ms) between periodic license re-validations. */
|
|
29025
|
+
const MAX_REVALIDATION_DELAY_MS = 60_000;
|
|
29026
|
+
/**
|
|
29027
|
+
* Manages license validation state for the Kritzel editor.
|
|
29028
|
+
*
|
|
29029
|
+
* The manager is owned per {@link KritzelCore} instance (like the theme and
|
|
29030
|
+
* localization managers) so that multiple editors on the same page validate
|
|
29031
|
+
* independently.
|
|
29032
|
+
*
|
|
29033
|
+
* A license key is an offline Ed25519 signed token of the form
|
|
29034
|
+
* `KRTZL1.<base64url(payload)>.<base64url(signature)>`. The library verifies it
|
|
29035
|
+
* against the embedded {@link KRITZEL_LICENSE_PUBLIC_KEY}; only the owner can
|
|
29036
|
+
* mint tokens, with the matching private key (see scripts/license/).
|
|
29037
|
+
*
|
|
29038
|
+
* Responsibilities:
|
|
29039
|
+
* - Cheaply reject absent/malformed/expired keys synchronously so the watermark
|
|
29040
|
+
* never flashes off for an obviously invalid key.
|
|
29041
|
+
* - Verify the signature asynchronously via WebCrypto, then drive a rerender if
|
|
29042
|
+
* the licensed state changed so the watermark is re-asserted on the reactive
|
|
29043
|
+
* render path.
|
|
29044
|
+
* - Periodically re-check on a randomized interval so the check is harder to
|
|
29045
|
+
* stub out programmatically than a fixed `setInterval`. Re-checks reuse the
|
|
29046
|
+
* cached verified token and only re-evaluate expiry, so crypto is not re-run
|
|
29047
|
+
* on an unchanged key every tick.
|
|
29048
|
+
*
|
|
29049
|
+
* Failure semantics (fail open):
|
|
29050
|
+
* - No key provided -> unlicensed (free tier, watermark shown). The normal
|
|
29051
|
+
* default, not an error.
|
|
29052
|
+
* - Key present but cleanly invalid (malformed, bad signature, expired) ->
|
|
29053
|
+
* unlicensed.
|
|
29054
|
+
* - Key present but validation throws an *unexpected* internal error, or the
|
|
29055
|
+
* runtime lacks Ed25519 support in WebCrypto -> fail open and treat as
|
|
29056
|
+
* licensed so a library defect or old browser never breaks a paying
|
|
29057
|
+
* customer's app.
|
|
29058
|
+
*/
|
|
29059
|
+
class KritzelLicenseManager {
|
|
29060
|
+
_core;
|
|
29061
|
+
/** The most recently provided license key, re-checked on each periodic tick. */
|
|
29062
|
+
_licenseKey;
|
|
29063
|
+
/** Cached result of the latest validation. */
|
|
29064
|
+
_isLicensed = false;
|
|
29065
|
+
/**
|
|
29066
|
+
* The token whose signature most recently verified successfully. Lets the
|
|
29067
|
+
* periodic re-check skip crypto and only re-evaluate expiry while the key is
|
|
29068
|
+
* unchanged.
|
|
29069
|
+
*/
|
|
29070
|
+
_verifiedToken;
|
|
29071
|
+
/**
|
|
29072
|
+
* Monotonic id incremented on every {@link validate} call. An in-flight async
|
|
29073
|
+
* verification only commits its result if its id still matches, so a stale
|
|
29074
|
+
* verification resolving after a newer call can never clobber fresh state.
|
|
29075
|
+
*/
|
|
29076
|
+
_validationId = 0;
|
|
29077
|
+
/** Cached imported public key, lazily created on first signature check. */
|
|
29078
|
+
_publicKeyPromise;
|
|
29079
|
+
/** Handle for the self-rescheduling re-validation timer. */
|
|
29080
|
+
_timer = null;
|
|
29081
|
+
/**
|
|
29082
|
+
* Creates a new KritzelLicenseManager instance.
|
|
29083
|
+
* @param core - The KritzelCore instance this manager belongs to
|
|
29084
|
+
*/
|
|
29085
|
+
constructor(core) {
|
|
29086
|
+
this._core = core;
|
|
29087
|
+
}
|
|
29088
|
+
/**
|
|
29089
|
+
* Whether the editor is currently considered licensed (watermark hidden).
|
|
29090
|
+
*/
|
|
29091
|
+
get isLicensed() {
|
|
29092
|
+
return this._isLicensed;
|
|
29093
|
+
}
|
|
29094
|
+
/**
|
|
29095
|
+
* Validates the given license key.
|
|
29096
|
+
*
|
|
29097
|
+
* Runs a synchronous structural pre-check first: an absent, malformed, or
|
|
29098
|
+
* already-expired key resolves the state immediately (no async work). A
|
|
29099
|
+
* structurally valid key then has its signature verified asynchronously; the
|
|
29100
|
+
* licensed state is committed (and a rerender triggered) only if it changed.
|
|
29101
|
+
*
|
|
29102
|
+
* The return value reflects the state known *synchronously* at call time. The
|
|
29103
|
+
* authoritative result for a structurally valid key arrives shortly after via
|
|
29104
|
+
* the async verification and the reactive rerender.
|
|
29105
|
+
*
|
|
29106
|
+
* @param key - The license key to validate, or undefined to clear the license
|
|
29107
|
+
* @returns The licensed state known synchronously at call time
|
|
29108
|
+
*/
|
|
29109
|
+
validate(key) {
|
|
29110
|
+
this._licenseKey = key;
|
|
29111
|
+
const validationId = ++this._validationId;
|
|
29112
|
+
let pre;
|
|
29113
|
+
try {
|
|
29114
|
+
pre = this.preCheck(key);
|
|
29115
|
+
}
|
|
29116
|
+
catch {
|
|
29117
|
+
// Fail open: an unexpected internal error in the cheap pre-check must
|
|
29118
|
+
// never break a paying customer's app.
|
|
29119
|
+
this.commit(true, validationId);
|
|
29120
|
+
return this._isLicensed;
|
|
29121
|
+
}
|
|
29122
|
+
if (pre.status === 'rejected') {
|
|
29123
|
+
this._verifiedToken = undefined;
|
|
29124
|
+
this.commit(false, validationId);
|
|
29125
|
+
return this._isLicensed;
|
|
29126
|
+
}
|
|
29127
|
+
// Structurally valid and unexpired. If we already verified this exact token,
|
|
29128
|
+
// the signature is still good, so license it without re-running crypto.
|
|
29129
|
+
if (this._verifiedToken === key) {
|
|
29130
|
+
this.commit(true, validationId);
|
|
29131
|
+
return this._isLicensed;
|
|
29132
|
+
}
|
|
29133
|
+
// Otherwise verify the signature asynchronously. Leave the current state
|
|
29134
|
+
// untouched until it settles so a forged token cannot even briefly hide the
|
|
29135
|
+
// watermark (default state is unlicensed), and a valid renewal does not flap.
|
|
29136
|
+
this.verifySignature(pre.payloadSegment, pre.signatureSegment)
|
|
29137
|
+
.then(valid => {
|
|
29138
|
+
if (validationId !== this._validationId) {
|
|
29139
|
+
return; // A newer validate() superseded this one.
|
|
29140
|
+
}
|
|
29141
|
+
if (valid) {
|
|
29142
|
+
this._verifiedToken = key;
|
|
29143
|
+
this.commit(true, validationId);
|
|
29144
|
+
}
|
|
29145
|
+
else {
|
|
29146
|
+
this._verifiedToken = undefined;
|
|
29147
|
+
this.commit(false, validationId);
|
|
29148
|
+
}
|
|
29149
|
+
})
|
|
29150
|
+
.catch(() => {
|
|
29151
|
+
if (validationId !== this._validationId) {
|
|
29152
|
+
return;
|
|
29153
|
+
}
|
|
29154
|
+
// Fail open on unexpected crypto errors / missing Ed25519 support.
|
|
29155
|
+
this.commit(true, validationId);
|
|
29156
|
+
});
|
|
29157
|
+
return this._isLicensed;
|
|
29158
|
+
}
|
|
29159
|
+
/**
|
|
29160
|
+
* Starts periodic re-validation on a randomized interval between
|
|
29161
|
+
* {@link MIN_REVALIDATION_DELAY_MS} and {@link MAX_REVALIDATION_DELAY_MS}.
|
|
29162
|
+
*
|
|
29163
|
+
* Uses a self-rescheduling `setTimeout` (a fresh random delay each cycle)
|
|
29164
|
+
* rather than a fixed `setInterval`, making the check fractionally harder to
|
|
29165
|
+
* predict and no-op programmatically. Any existing timer is cleared first so
|
|
29166
|
+
* the method is idempotent.
|
|
29167
|
+
*/
|
|
29168
|
+
startPeriodicValidation() {
|
|
29169
|
+
this.stopPeriodicValidation();
|
|
29170
|
+
this.scheduleNextValidation();
|
|
29171
|
+
}
|
|
29172
|
+
/**
|
|
29173
|
+
* Stops periodic re-validation and releases the timer.
|
|
29174
|
+
*/
|
|
29175
|
+
stopPeriodicValidation() {
|
|
29176
|
+
if (this._timer !== null) {
|
|
29177
|
+
clearTimeout(this._timer);
|
|
29178
|
+
this._timer = null;
|
|
29179
|
+
}
|
|
29180
|
+
}
|
|
29181
|
+
/**
|
|
29182
|
+
* Cleanup hook called when the editor is torn down.
|
|
29183
|
+
*/
|
|
29184
|
+
destroy() {
|
|
29185
|
+
this.stopPeriodicValidation();
|
|
29186
|
+
}
|
|
29187
|
+
/**
|
|
29188
|
+
* Commits a new licensed state, triggering a rerender only when it changed.
|
|
29189
|
+
* The {@link validationId} guard is checked by async callers; synchronous
|
|
29190
|
+
* callers pass the current id.
|
|
29191
|
+
*/
|
|
29192
|
+
commit(nextLicensed, validationId) {
|
|
29193
|
+
if (validationId !== this._validationId) {
|
|
29194
|
+
return;
|
|
29195
|
+
}
|
|
29196
|
+
if (nextLicensed !== this._isLicensed) {
|
|
29197
|
+
this._isLicensed = nextLicensed;
|
|
29198
|
+
this._core.rerender();
|
|
29199
|
+
}
|
|
29200
|
+
}
|
|
29201
|
+
/**
|
|
29202
|
+
* Schedules the next re-validation tick at a randomized delay.
|
|
29203
|
+
*/
|
|
29204
|
+
scheduleNextValidation() {
|
|
29205
|
+
const delay = this.getRandomRevalidationDelay();
|
|
29206
|
+
this._timer = setTimeout(() => {
|
|
29207
|
+
this.validate(this._licenseKey);
|
|
29208
|
+
this.scheduleNextValidation();
|
|
29209
|
+
}, delay);
|
|
29210
|
+
}
|
|
29211
|
+
/**
|
|
29212
|
+
* Returns a random delay within the configured re-validation window.
|
|
29213
|
+
*/
|
|
29214
|
+
getRandomRevalidationDelay() {
|
|
29215
|
+
const span = MAX_REVALIDATION_DELAY_MS - MIN_REVALIDATION_DELAY_MS;
|
|
29216
|
+
return MIN_REVALIDATION_DELAY_MS + Math.floor(Math.random() * (span + 1));
|
|
29217
|
+
}
|
|
29218
|
+
/**
|
|
29219
|
+
* Synchronous structural pre-check of a license key: verifies the format
|
|
29220
|
+
* prefix, three-part structure, JSON payload, and (if present) that the token
|
|
29221
|
+
* has not expired. Does not verify the signature.
|
|
29222
|
+
*
|
|
29223
|
+
* @returns `rejected` for absent/malformed/expired keys, or `pass` with the
|
|
29224
|
+
* parsed segments and claims for a structurally valid, unexpired token.
|
|
29225
|
+
*/
|
|
29226
|
+
preCheck(key) {
|
|
29227
|
+
if (key === undefined || key === null) {
|
|
29228
|
+
return { status: 'rejected' };
|
|
29229
|
+
}
|
|
29230
|
+
const token = key.trim();
|
|
29231
|
+
if (token.length === 0 || !token.startsWith(KRITZEL_LICENSE_TOKEN_PREFIX)) {
|
|
29232
|
+
return { status: 'rejected' };
|
|
29233
|
+
}
|
|
29234
|
+
const body = token.slice(KRITZEL_LICENSE_TOKEN_PREFIX.length);
|
|
29235
|
+
const parts = body.split('.');
|
|
29236
|
+
if (parts.length !== 2 || parts[0].length === 0 || parts[1].length === 0) {
|
|
29237
|
+
return { status: 'rejected' };
|
|
29238
|
+
}
|
|
29239
|
+
const [payloadSegment, signatureSegment] = parts;
|
|
29240
|
+
let claims;
|
|
29241
|
+
try {
|
|
29242
|
+
claims = JSON.parse(this.base64UrlToString(payloadSegment));
|
|
29243
|
+
}
|
|
29244
|
+
catch {
|
|
29245
|
+
return { status: 'rejected' };
|
|
29246
|
+
}
|
|
29247
|
+
if (claims === null || typeof claims !== 'object') {
|
|
29248
|
+
return { status: 'rejected' };
|
|
29249
|
+
}
|
|
29250
|
+
if (typeof claims.exp === 'number' && claims.exp * 1000 <= Date.now()) {
|
|
29251
|
+
return { status: 'rejected' }; // Cleanly expired.
|
|
29252
|
+
}
|
|
29253
|
+
return { status: 'pass', payloadSegment, signatureSegment, claims };
|
|
29254
|
+
}
|
|
29255
|
+
/**
|
|
29256
|
+
* Verifies the Ed25519 signature of the token against the embedded public
|
|
29257
|
+
* key. Resolves true/false for a definitive valid/invalid signature.
|
|
29258
|
+
*
|
|
29259
|
+
* Rejects (rather than resolving false) on unexpected crypto failures or when
|
|
29260
|
+
* the runtime lacks Ed25519 support, so the caller fails open.
|
|
29261
|
+
*/
|
|
29262
|
+
async verifySignature(payloadSegment, signatureSegment) {
|
|
29263
|
+
const publicKey = await this.getPublicKey();
|
|
29264
|
+
const data = this.utf8Bytes(payloadSegment);
|
|
29265
|
+
const signature = this.base64UrlToBytes(signatureSegment);
|
|
29266
|
+
return crypto.subtle.verify('Ed25519', publicKey, signature, data);
|
|
29267
|
+
}
|
|
29268
|
+
/**
|
|
29269
|
+
* Imports and caches the embedded Ed25519 public key for signature checks.
|
|
29270
|
+
*/
|
|
29271
|
+
getPublicKey() {
|
|
29272
|
+
if (!this._publicKeyPromise) {
|
|
29273
|
+
const raw = this.base64UrlToBytes(KRITZEL_LICENSE_PUBLIC_KEY);
|
|
29274
|
+
this._publicKeyPromise = Promise.resolve().then(() => crypto.subtle.importKey('raw', raw, { name: 'Ed25519' }, false, ['verify']));
|
|
29275
|
+
// If import fails (e.g. no Ed25519 support), drop the cache so a later
|
|
29276
|
+
// tick can retry, and let the rejection propagate to fail open.
|
|
29277
|
+
this._publicKeyPromise.catch(() => {
|
|
29278
|
+
this._publicKeyPromise = undefined;
|
|
29279
|
+
});
|
|
29280
|
+
}
|
|
29281
|
+
return this._publicKeyPromise;
|
|
29282
|
+
}
|
|
29283
|
+
/** Decodes a base64url string to its UTF-8 text. */
|
|
29284
|
+
base64UrlToString(value) {
|
|
29285
|
+
return new TextDecoder().decode(this.base64UrlToBytes(value));
|
|
29286
|
+
}
|
|
29287
|
+
/** Decodes a base64url string to raw bytes. */
|
|
29288
|
+
base64UrlToBytes(value) {
|
|
29289
|
+
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
29290
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
|
|
29291
|
+
const binary = atob(padded);
|
|
29292
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
29293
|
+
for (let i = 0; i < binary.length; i++) {
|
|
29294
|
+
bytes[i] = binary.charCodeAt(i);
|
|
29295
|
+
}
|
|
29296
|
+
return bytes;
|
|
29297
|
+
}
|
|
29298
|
+
/** Encodes a string to its UTF-8 bytes. */
|
|
29299
|
+
utf8Bytes(value) {
|
|
29300
|
+
const encoded = new TextEncoder().encode(value);
|
|
29301
|
+
const bytes = new Uint8Array(new ArrayBuffer(encoded.length));
|
|
29302
|
+
bytes.set(encoded);
|
|
29303
|
+
return bytes;
|
|
29304
|
+
}
|
|
29305
|
+
}
|
|
29306
|
+
|
|
28316
29307
|
const DEFAULT_BRUSH_CONFIG = {
|
|
28317
29308
|
type: 'pen',
|
|
28318
29309
|
color: DEFAULT_COLOR_PALETTE[0],
|
|
@@ -28495,6 +29486,9 @@ exports.DEFAULT_COLOR_PALETTE = DEFAULT_COLOR_PALETTE;
|
|
|
28495
29486
|
exports.DEFAULT_LINE_TOOL_CONFIG = DEFAULT_LINE_TOOL_CONFIG;
|
|
28496
29487
|
exports.DEFAULT_STROKE_SIZES = DEFAULT_STROKE_SIZES;
|
|
28497
29488
|
exports.DEFAULT_TEXT_CONFIG = DEFAULT_TEXT_CONFIG;
|
|
29489
|
+
exports.DE_LOCALE = DE_LOCALE;
|
|
29490
|
+
exports.EN_LOCALE = EN_LOCALE;
|
|
29491
|
+
exports.FR_LOCALE = FR_LOCALE;
|
|
28498
29492
|
exports.HocuspocusProvider = HocuspocusProvider;
|
|
28499
29493
|
exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
|
|
28500
29494
|
exports.IndexedDBAssetProvider = IndexedDBAssetProvider;
|
|
@@ -28502,6 +29496,7 @@ exports.KritzelAnchorManager = KritzelAnchorManager;
|
|
|
28502
29496
|
exports.KritzelAssetResolver = KritzelAssetResolver;
|
|
28503
29497
|
exports.KritzelBaseHandler = KritzelBaseHandler;
|
|
28504
29498
|
exports.KritzelBaseObject = KritzelBaseObject;
|
|
29499
|
+
exports.KritzelBaseTool = KritzelBaseTool;
|
|
28505
29500
|
exports.KritzelBrushTool = KritzelBrushTool;
|
|
28506
29501
|
exports.KritzelClassHelper = KritzelClassHelper;
|
|
28507
29502
|
exports.KritzelColorHelper = KritzelColorHelper;
|
|
@@ -28514,8 +29509,11 @@ exports.KritzelIconRegistry = KritzelIconRegistry;
|
|
|
28514
29509
|
exports.KritzelImage = KritzelImage;
|
|
28515
29510
|
exports.KritzelImageTool = KritzelImageTool;
|
|
28516
29511
|
exports.KritzelKeyboardHelper = KritzelKeyboardHelper;
|
|
29512
|
+
exports.KritzelLicenseManager = KritzelLicenseManager;
|
|
28517
29513
|
exports.KritzelLine = KritzelLine;
|
|
28518
29514
|
exports.KritzelLineTool = KritzelLineTool;
|
|
29515
|
+
exports.KritzelLocalizationManager = KritzelLocalizationManager;
|
|
29516
|
+
exports.KritzelMathHelper = KritzelMathHelper;
|
|
28519
29517
|
exports.KritzelPath = KritzelPath;
|
|
28520
29518
|
exports.KritzelSelectionBox = KritzelSelectionBox;
|
|
28521
29519
|
exports.KritzelSelectionGroup = KritzelSelectionGroup;
|