superdoc 2.18.1-next.5 → 2.19.0-next.1
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/dist/chunks/{create-super-doc-ui-C3HDE1IE.es.js → create-super-doc-ui-BFJ9Y0YV.cjs} +1396 -1
- package/dist/chunks/{create-super-doc-ui-Gxk1m05U.cjs → create-super-doc-ui-Dfw1b6h_.es.js} +1318 -66
- package/dist/collaboration-upgrade-engine.cjs +1 -1
- package/dist/collaboration-upgrade-engine.es.js +1 -1
- package/dist/document-api/src/contract/command-catalog.d.ts +1 -1
- package/dist/document-api/src/contract/operation-definitions.d.ts +9 -0
- package/dist/document-api/src/index.d.ts +1 -0
- package/dist/document-api/src/validation-primitives.d.ts +1 -1
- package/dist/document-api/src/watermarks/watermarks.d.ts +4 -1
- package/dist/document-api/src/watermarks/watermarks.types.d.ts +34 -0
- package/dist/layout-engine/painters/dom/src/index.d.ts +2 -0
- package/dist/public/ui-react.cjs +1 -1
- package/dist/public/ui-react.es.js +1 -1
- package/dist/public/ui-vue.cjs +1 -1
- package/dist/public/ui-vue.es.js +1 -1
- package/dist/public/ui.cjs +1 -1
- package/dist/public/ui.es.js +1 -1
- package/dist/style.css +596 -80
- package/dist/style.layered.css +597 -81
- package/dist/superdoc/src/components/surfaces/WatermarkSurface.vue.d.ts +16 -0
- package/dist/superdoc/src/components/surfaces/watermark-surface-runtime.d.ts +8 -0
- package/dist/superdoc/src/core/types/index.d.ts +4 -4
- package/dist/superdoc/src/internal/toolbar/built-in/OverflowMenu.vue.d.ts +93 -1
- package/dist/superdoc/src/internal/toolbar/built-in/Toolbar.vue.d.ts +3 -1
- package/dist/superdoc/src/internal/toolbar/built-in/ToolbarDropdown.vue.d.ts +1 -0
- package/dist/superdoc/src/internal/toolbar/built-in/toolbarIcons.d.ts +1 -0
- package/dist/superdoc/src/internal/toolbar/built-in/toolbarTexts.d.ts +1 -0
- package/dist/superdoc/src/internal/toolbar/built-in-toolbar.d.ts +3 -0
- package/dist/superdoc/src/public/ui/types.d.ts +10 -1
- package/dist/superdoc/src/public/ui/watermark-controller.d.ts +32 -0
- package/dist/superdoc/src/public/ui/watermark-dialog-state.d.ts +66 -0
- package/dist/superdoc/src/public/ui/watermark-draft.d.ts +6 -0
- package/dist/superdoc/src/public/ui.d.cts +12 -0
- package/dist/superdoc/src/public/ui.d.ts +1 -1
- package/dist/superdoc.cjs +107 -171
- package/dist/superdoc.es.js +105 -169
- package/dist-cdn/style.layered.css +1 -1
- package/dist-cdn/superdoc.min.css +1 -1
- package/dist-cdn/superdoc.min.js +38 -37
- package/package.json +2 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const require__plugin_vue_export_helper = require("./_plugin-vue_export-helper-SDR04tiH.cjs");
|
|
2
|
+
let vue = require("vue");
|
|
1
3
|
//#region src/internal/diagnostics/interaction-history.ts
|
|
2
4
|
var recorders = /* @__PURE__ */ new WeakMap();
|
|
3
5
|
var nextSession = 0;
|
|
@@ -534,6 +536,1311 @@ function shallowEqual(a, b) {
|
|
|
534
536
|
return true;
|
|
535
537
|
}
|
|
536
538
|
//#endregion
|
|
539
|
+
//#region src/internal/toolbar/image-upload.js
|
|
540
|
+
/**
|
|
541
|
+
* Interactive image acquisition for the built-in toolbar (SD-3567).
|
|
542
|
+
*
|
|
543
|
+
* The toolbar image button routes through the shared controller command
|
|
544
|
+
* `image` → `create.image`, which requires a base64 PNG/JPEG data-URI `src`.
|
|
545
|
+
* This module owns the browser file picker and the File → data-URI
|
|
546
|
+
* conversion; the toolbar dispatches the resulting payload through the
|
|
547
|
+
* normal controller command path.
|
|
548
|
+
*/
|
|
549
|
+
/** Accept filter for the picker: PNG and JPEG only (V1 parity; OOXML-native formats). */
|
|
550
|
+
var IMAGE_PICKER_ACCEPT = ".png,.jpg,.jpeg,image/png,image/jpeg";
|
|
551
|
+
var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
552
|
+
"image/png",
|
|
553
|
+
"image/jpeg",
|
|
554
|
+
"image/jpg"
|
|
555
|
+
]);
|
|
556
|
+
var SUPPORTED_IMAGE_EXTENSIONS = /\.(png|jpe?g)$/i;
|
|
557
|
+
/**
|
|
558
|
+
* Whether a picked/dropped file is an insertable image (PNG/JPEG). Falls back
|
|
559
|
+
* to the file extension when the browser reports no MIME type.
|
|
560
|
+
* @param {File | null | undefined} file
|
|
561
|
+
* @returns {boolean}
|
|
562
|
+
*/
|
|
563
|
+
function isSupportedImageFile(file) {
|
|
564
|
+
if (!file) return false;
|
|
565
|
+
const type = typeof file.type === "string" ? file.type.toLowerCase() : "";
|
|
566
|
+
if (type) return SUPPORTED_IMAGE_MIME_TYPES.has(type);
|
|
567
|
+
return SUPPORTED_IMAGE_EXTENSIONS.test(String(file.name ?? ""));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Read a File/Blob into a base64 data URI.
|
|
571
|
+
*
|
|
572
|
+
* Some browsers/OSes deliver picked files with an empty `type`; the picker
|
|
573
|
+
* accepts those by extension, but `readAsDataURL` would then produce an
|
|
574
|
+
* `application/octet-stream` data URI that `create.image` rejects. Rewrite
|
|
575
|
+
* the media type from the filename so extension-accepted files still insert.
|
|
576
|
+
* @param {Blob & { name?: string }} file
|
|
577
|
+
* @returns {Promise<string>}
|
|
578
|
+
*/
|
|
579
|
+
function fileToDataUri(file) {
|
|
580
|
+
return new Promise((resolve, reject) => {
|
|
581
|
+
const reader = new FileReader();
|
|
582
|
+
reader.onload = () => resolve(normalizeImageDataUri(String(reader.result), file));
|
|
583
|
+
reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("image-read-failed"));
|
|
584
|
+
reader.readAsDataURL(file);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Ensure a data URI read from a supported file carries a PNG/JPEG media type.
|
|
589
|
+
* Leaves URIs already typed as a supported image (or uninferable ones) alone.
|
|
590
|
+
* @param {string} dataUri
|
|
591
|
+
* @param {(Blob & { name?: string }) | null | undefined} file
|
|
592
|
+
* @returns {string}
|
|
593
|
+
*/
|
|
594
|
+
function normalizeImageDataUri(dataUri, file) {
|
|
595
|
+
const match = /^data:([^;,]*);base64,/.exec(dataUri);
|
|
596
|
+
if (!match) return dataUri;
|
|
597
|
+
const mime = match[1].toLowerCase();
|
|
598
|
+
if (SUPPORTED_IMAGE_MIME_TYPES.has(mime)) return dataUri;
|
|
599
|
+
const name = String(file?.name ?? "");
|
|
600
|
+
const inferred = /\.jpe?g$/i.test(name) ? "image/jpeg" : /\.png$/i.test(name) ? "image/png" : null;
|
|
601
|
+
if (!inferred) return dataUri;
|
|
602
|
+
return `data:${inferred};base64,${dataUri.slice(match[0].length)}`;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Resolve the `create.image` src for a picked file. Uses the consumer's
|
|
606
|
+
* `handleImageUpload` callback when configured (V1 contract:
|
|
607
|
+
* `(file: File) => Promise<string>`). A returned non-data URL is fetched and
|
|
608
|
+
* embedded, because `create.image` requires an embedded base64 data URI.
|
|
609
|
+
* @param {File} file
|
|
610
|
+
* @param {((file: File) => Promise<string>) | null | undefined} handleImageUpload
|
|
611
|
+
* @returns {Promise<string>}
|
|
612
|
+
*/
|
|
613
|
+
async function resolveImageSrc(file, handleImageUpload) {
|
|
614
|
+
if (typeof handleImageUpload === "function") {
|
|
615
|
+
const result = await handleImageUpload(file);
|
|
616
|
+
if (typeof result !== "string" || result.trim() === "") throw new Error("handleImageUpload returned an empty result");
|
|
617
|
+
if (result.startsWith("data:")) return result;
|
|
618
|
+
return embedRemoteImage(result, file);
|
|
619
|
+
}
|
|
620
|
+
return fileToDataUri(file);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Fetch a remote image URL and embed it as a data URI. Object-store /
|
|
624
|
+
* presigned URLs often respond `application/octet-stream`, so keep a filename
|
|
625
|
+
* hint (URL path, falling back to the originally picked file) for the media
|
|
626
|
+
* type normalization in `fileToDataUri`.
|
|
627
|
+
* @param {string} url
|
|
628
|
+
* @param {File | null | undefined} pickedFile
|
|
629
|
+
* @returns {Promise<string>}
|
|
630
|
+
*/
|
|
631
|
+
async function embedRemoteImage(url, pickedFile) {
|
|
632
|
+
const response = await fetch(url);
|
|
633
|
+
if (!response.ok) throw new Error(`image fetch failed: ${response.status}`);
|
|
634
|
+
const blob = await response.blob();
|
|
635
|
+
const name = (/\.(?:png|jpe?g)(?:$|[?#])/i.test(url) ? url.split(/[?#]/)[0] : "") || pickedFile?.name || "";
|
|
636
|
+
return fileToDataUri(name ? Object.assign(blob, { name }) : blob);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Hidden `<input type=file>` picker bound to one toolbar instance. The input
|
|
640
|
+
* stays attached (hidden) so automation and tests can drive it; `destroy()`
|
|
641
|
+
* removes it. Picking a file calls `onPick(file)`; an unsupported type or a
|
|
642
|
+
* failed pick routes through `onError(error)`.
|
|
643
|
+
*
|
|
644
|
+
* @param {{
|
|
645
|
+
* ownerDocument?: Document,
|
|
646
|
+
* onPick?: (file: File) => unknown,
|
|
647
|
+
* onError?: (error: Error) => void,
|
|
648
|
+
* }} options
|
|
649
|
+
* @returns {{ input: HTMLInputElement, open: () => void, destroy: () => void }}
|
|
650
|
+
*/
|
|
651
|
+
function createImageFilePicker({ ownerDocument = document, onPick, onError } = {}) {
|
|
652
|
+
const input = ownerDocument.createElement("input");
|
|
653
|
+
input.type = "file";
|
|
654
|
+
input.accept = IMAGE_PICKER_ACCEPT;
|
|
655
|
+
input.hidden = true;
|
|
656
|
+
input.tabIndex = -1;
|
|
657
|
+
input.setAttribute("data-superdoc-image-picker", "true");
|
|
658
|
+
input.setAttribute("aria-hidden", "true");
|
|
659
|
+
input.addEventListener("change", () => {
|
|
660
|
+
const file = input.files && input.files[0];
|
|
661
|
+
input.value = "";
|
|
662
|
+
if (!file) return;
|
|
663
|
+
if (!isSupportedImageFile(file)) {
|
|
664
|
+
onError?.(/* @__PURE__ */ new Error(`unsupported image type: ${file.type || file.name}`));
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const result = onPick?.(file);
|
|
669
|
+
if (result && typeof result.then === "function") result.catch((error) => onError?.(error));
|
|
670
|
+
} catch (error) {
|
|
671
|
+
onError?.(error);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
ownerDocument.body?.appendChild(input);
|
|
675
|
+
return {
|
|
676
|
+
input,
|
|
677
|
+
open() {
|
|
678
|
+
input.click();
|
|
679
|
+
},
|
|
680
|
+
destroy() {
|
|
681
|
+
input.remove();
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region src/components/surfaces/WatermarkSurface.vue?vue&type=script&setup=true&lang.ts
|
|
687
|
+
var _hoisted_1 = { class: "sd-watermark__body" };
|
|
688
|
+
var _hoisted_2 = { class: "sd-watermark__settings" };
|
|
689
|
+
var _hoisted_3 = ["disabled"];
|
|
690
|
+
var _hoisted_4 = { class: "sd-watermark__field" };
|
|
691
|
+
var _hoisted_5 = ["value"];
|
|
692
|
+
var _hoisted_6 = ["value"];
|
|
693
|
+
var _hoisted_7 = { class: "sd-watermark__field" };
|
|
694
|
+
var _hoisted_8 = ["value"];
|
|
695
|
+
var _hoisted_9 = ["disabled"];
|
|
696
|
+
var _hoisted_10 = { class: "sd-watermark__selection-list" };
|
|
697
|
+
var _hoisted_11 = [
|
|
698
|
+
"value",
|
|
699
|
+
"checked",
|
|
700
|
+
"onChange"
|
|
701
|
+
];
|
|
702
|
+
var _hoisted_12 = ["disabled"];
|
|
703
|
+
var _hoisted_13 = {
|
|
704
|
+
key: 0,
|
|
705
|
+
class: "sd-watermark__notice"
|
|
706
|
+
};
|
|
707
|
+
var _hoisted_14 = {
|
|
708
|
+
key: 1,
|
|
709
|
+
class: "sd-watermark__notice"
|
|
710
|
+
};
|
|
711
|
+
var _hoisted_15 = ["disabled"];
|
|
712
|
+
var _hoisted_16 = { class: "sd-watermark__types" };
|
|
713
|
+
var _hoisted_17 = [
|
|
714
|
+
"name",
|
|
715
|
+
"value",
|
|
716
|
+
"checked",
|
|
717
|
+
"onChange"
|
|
718
|
+
];
|
|
719
|
+
var _hoisted_18 = { class: "sd-watermark__field" };
|
|
720
|
+
var _hoisted_19 = ["value"];
|
|
721
|
+
var _hoisted_20 = {
|
|
722
|
+
class: "sd-watermark__presets",
|
|
723
|
+
"aria-label": "Text presets"
|
|
724
|
+
};
|
|
725
|
+
var _hoisted_21 = ["onClick"];
|
|
726
|
+
var _hoisted_22 = { class: "sd-watermark__font-row" };
|
|
727
|
+
var _hoisted_23 = { class: "sd-watermark__field" };
|
|
728
|
+
var _hoisted_24 = ["value"];
|
|
729
|
+
var _hoisted_25 = ["value"];
|
|
730
|
+
var _hoisted_26 = { class: "sd-watermark__field sd-watermark__size" };
|
|
731
|
+
var _hoisted_27 = ["list", "value"];
|
|
732
|
+
var _hoisted_28 = ["id"];
|
|
733
|
+
var _hoisted_29 = ["value"];
|
|
734
|
+
var _hoisted_30 = { class: "sd-watermark__styles" };
|
|
735
|
+
var _hoisted_31 = ["aria-pressed"];
|
|
736
|
+
var _hoisted_32 = ["aria-pressed"];
|
|
737
|
+
var _hoisted_33 = { class: "sd-watermark__field sd-watermark__font-color" };
|
|
738
|
+
var _hoisted_34 = { class: "sd-watermark__color" };
|
|
739
|
+
var _hoisted_35 = ["value"];
|
|
740
|
+
var _hoisted_36 = ["value"];
|
|
741
|
+
var _hoisted_37 = { class: "sd-watermark__picture" };
|
|
742
|
+
var _hoisted_38 = ["src"];
|
|
743
|
+
var _hoisted_39 = ["accept"];
|
|
744
|
+
var _hoisted_40 = { class: "sd-watermark__field" };
|
|
745
|
+
var _hoisted_41 = ["value"];
|
|
746
|
+
var _hoisted_42 = {
|
|
747
|
+
key: 0,
|
|
748
|
+
value: "custom",
|
|
749
|
+
disabled: ""
|
|
750
|
+
};
|
|
751
|
+
var _hoisted_43 = ["value"];
|
|
752
|
+
var _hoisted_44 = { class: "sd-watermark__check" };
|
|
753
|
+
var _hoisted_45 = ["checked"];
|
|
754
|
+
var _hoisted_46 = { class: "sd-watermark__check" };
|
|
755
|
+
var _hoisted_47 = ["checked"];
|
|
756
|
+
var _hoisted_48 = {
|
|
757
|
+
key: 2,
|
|
758
|
+
class: "sd-watermark__field sd-watermark__transparency"
|
|
759
|
+
};
|
|
760
|
+
var _hoisted_49 = ["for"];
|
|
761
|
+
var _hoisted_50 = { class: "sd-watermark__range" };
|
|
762
|
+
var _hoisted_51 = ["id", "value"];
|
|
763
|
+
var _hoisted_52 = ["value"];
|
|
764
|
+
var _hoisted_53 = {
|
|
765
|
+
key: 3,
|
|
766
|
+
class: "sd-watermark__orientation"
|
|
767
|
+
};
|
|
768
|
+
var _hoisted_54 = [
|
|
769
|
+
"name",
|
|
770
|
+
"value",
|
|
771
|
+
"checked",
|
|
772
|
+
"onChange"
|
|
773
|
+
];
|
|
774
|
+
var _hoisted_55 = {
|
|
775
|
+
key: 4,
|
|
776
|
+
class: "sd-watermark__notice"
|
|
777
|
+
};
|
|
778
|
+
var _hoisted_56 = {
|
|
779
|
+
key: 5,
|
|
780
|
+
class: "sd-watermark__notice"
|
|
781
|
+
};
|
|
782
|
+
var _hoisted_57 = ["aria-busy"];
|
|
783
|
+
var _hoisted_58 = { class: "sd-watermark__page" };
|
|
784
|
+
var _hoisted_59 = [
|
|
785
|
+
"src",
|
|
786
|
+
"width",
|
|
787
|
+
"height"
|
|
788
|
+
];
|
|
789
|
+
var _hoisted_60 = { key: 1 };
|
|
790
|
+
var _hoisted_61 = { class: "sd-watermark__impact" };
|
|
791
|
+
var _hoisted_62 = {
|
|
792
|
+
key: 0,
|
|
793
|
+
class: "sd-watermark__impact-details"
|
|
794
|
+
};
|
|
795
|
+
var _hoisted_63 = {
|
|
796
|
+
key: 0,
|
|
797
|
+
class: "sd-watermark__error",
|
|
798
|
+
role: "alert"
|
|
799
|
+
};
|
|
800
|
+
var _hoisted_64 = ["disabled"];
|
|
801
|
+
var _hoisted_65 = { class: "sd-watermark__footer sd-dialog-footer" };
|
|
802
|
+
var _hoisted_66 = {
|
|
803
|
+
role: "status",
|
|
804
|
+
"aria-live": "polite"
|
|
805
|
+
};
|
|
806
|
+
var _hoisted_67 = { class: "sd-watermark__actions" };
|
|
807
|
+
var _hoisted_68 = ["disabled"];
|
|
808
|
+
var WatermarkSurface_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.defineComponent)({
|
|
809
|
+
__name: "WatermarkSurface",
|
|
810
|
+
props: {
|
|
811
|
+
model: {},
|
|
812
|
+
surfaceId: { default: "" },
|
|
813
|
+
mode: {},
|
|
814
|
+
request: {},
|
|
815
|
+
resolve: {},
|
|
816
|
+
close: {
|
|
817
|
+
type: Function,
|
|
818
|
+
default: () => {}
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
setup(__props) {
|
|
822
|
+
const props = __props;
|
|
823
|
+
const snapshot = (0, vue.shallowRef)(props.model.getSnapshot());
|
|
824
|
+
const fileInput = (0, vue.ref)(null);
|
|
825
|
+
const filePending = (0, vue.ref)(false);
|
|
826
|
+
const localError = (0, vue.ref)("");
|
|
827
|
+
let fileGeneration = 0;
|
|
828
|
+
let unsubscribe;
|
|
829
|
+
const presets = [
|
|
830
|
+
"DRAFT",
|
|
831
|
+
"CONFIDENTIAL",
|
|
832
|
+
"DO NOT COPY"
|
|
833
|
+
];
|
|
834
|
+
const types = [
|
|
835
|
+
{
|
|
836
|
+
value: "none",
|
|
837
|
+
label: "No watermark"
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
value: "text",
|
|
841
|
+
label: "Text"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
value: "picture",
|
|
845
|
+
label: "Picture"
|
|
846
|
+
}
|
|
847
|
+
];
|
|
848
|
+
const busy = (0, vue.computed)(() => snapshot.value.phase === "loading" || snapshot.value.phase === "applying");
|
|
849
|
+
const editable = (0, vue.computed)(() => !busy.value && !snapshot.value.readonlyReason);
|
|
850
|
+
const canApply = (0, vue.computed)(() => snapshot.value.canApply && !busy.value && !filePending.value && !snapshot.value.stale);
|
|
851
|
+
const error = (0, vue.computed)(() => localError.value || snapshot.value.error);
|
|
852
|
+
const status = (0, vue.computed)(() => {
|
|
853
|
+
if (filePending.value) return "Preparing picture…";
|
|
854
|
+
if (snapshot.value.phase === "loading") return "Loading watermarks…";
|
|
855
|
+
if (snapshot.value.phase === "applying") return "Applying changes; closing will not cancel them.";
|
|
856
|
+
if (snapshot.value.phase === "previewing") return "Updating preview…";
|
|
857
|
+
return snapshot.value.dirty ? "Changes apply when you choose Apply" : "No changes";
|
|
858
|
+
});
|
|
859
|
+
const fontOptions = (0, vue.computed)(() => {
|
|
860
|
+
const { fonts, draft } = snapshot.value;
|
|
861
|
+
return fonts.some((font) => font.value === draft.fontFamily) ? fonts : [{
|
|
862
|
+
value: draft.fontFamily,
|
|
863
|
+
label: draft.fontFamily
|
|
864
|
+
}, ...fonts];
|
|
865
|
+
});
|
|
866
|
+
function cancelPreparation() {
|
|
867
|
+
fileGeneration += 1;
|
|
868
|
+
filePending.value = false;
|
|
869
|
+
}
|
|
870
|
+
function patch(patch) {
|
|
871
|
+
if (!editable.value) return;
|
|
872
|
+
localError.value = "";
|
|
873
|
+
props.model.patchDraft(patch);
|
|
874
|
+
}
|
|
875
|
+
function changeScope(event, field) {
|
|
876
|
+
const value = event.target.value;
|
|
877
|
+
cancelPreparation();
|
|
878
|
+
props.model.setScope(field === "section" ? value || null : snapshot.value.sectionId, field === "variant" ? value : snapshot.value.variant);
|
|
879
|
+
}
|
|
880
|
+
function selectWatermark(id, event) {
|
|
881
|
+
cancelPreparation();
|
|
882
|
+
const selected = new Set(snapshot.value.selectedIds);
|
|
883
|
+
if (event.target.checked) selected.add(id);
|
|
884
|
+
else selected.delete(id);
|
|
885
|
+
props.model.selectWatermarks([...selected]);
|
|
886
|
+
}
|
|
887
|
+
async function choosePicture(event) {
|
|
888
|
+
const input = event.target;
|
|
889
|
+
const file = input.files?.[0];
|
|
890
|
+
input.value = "";
|
|
891
|
+
if (!file || !editable.value) return;
|
|
892
|
+
cancelPreparation();
|
|
893
|
+
localError.value = "";
|
|
894
|
+
if (!isSupportedImageFile(file)) {
|
|
895
|
+
localError.value = "Choose a PNG or JPEG picture.";
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const generation = fileGeneration;
|
|
899
|
+
filePending.value = true;
|
|
900
|
+
try {
|
|
901
|
+
const src = await fileToDataUri(file);
|
|
902
|
+
if (generation !== fileGeneration) return;
|
|
903
|
+
patch({
|
|
904
|
+
src,
|
|
905
|
+
pictureName: file.name,
|
|
906
|
+
kind: "picture"
|
|
907
|
+
});
|
|
908
|
+
} catch {
|
|
909
|
+
if (generation === fileGeneration) localError.value = "The picture could not be read. Choose it again.";
|
|
910
|
+
} finally {
|
|
911
|
+
if (generation === fileGeneration) filePending.value = false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function apply() {
|
|
915
|
+
if (!canApply.value) return;
|
|
916
|
+
try {
|
|
917
|
+
await props.model.apply();
|
|
918
|
+
} catch {
|
|
919
|
+
localError.value = "The watermark could not be applied. Try again.";
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async function reload() {
|
|
923
|
+
cancelPreparation();
|
|
924
|
+
localError.value = "";
|
|
925
|
+
try {
|
|
926
|
+
await props.model.reload();
|
|
927
|
+
} catch {
|
|
928
|
+
localError.value = "Watermarks could not be loaded. Try again.";
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function close() {
|
|
932
|
+
cancelPreparation();
|
|
933
|
+
props.close("user-cancelled");
|
|
934
|
+
}
|
|
935
|
+
(0, vue.watch)([
|
|
936
|
+
() => snapshot.value.draft.kind,
|
|
937
|
+
() => snapshot.value.sectionId,
|
|
938
|
+
() => snapshot.value.variant,
|
|
939
|
+
() => snapshot.value.selectedIds.join("\0")
|
|
940
|
+
], cancelPreparation);
|
|
941
|
+
(0, vue.onMounted)(() => {
|
|
942
|
+
unsubscribe = props.model.subscribe((value) => {
|
|
943
|
+
snapshot.value = value;
|
|
944
|
+
});
|
|
945
|
+
snapshot.value = props.model.getSnapshot();
|
|
946
|
+
});
|
|
947
|
+
(0, vue.onBeforeUnmount)(() => {
|
|
948
|
+
cancelPreparation();
|
|
949
|
+
unsubscribe?.();
|
|
950
|
+
});
|
|
951
|
+
return (_ctx, _cache) => {
|
|
952
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("form", {
|
|
953
|
+
class: "sd-watermark sd-dialog-content",
|
|
954
|
+
onSubmit: (0, vue.withModifiers)(apply, ["prevent"])
|
|
955
|
+
}, [
|
|
956
|
+
(0, vue.createElementVNode)("header", { class: "sd-watermark__header sd-dialog-header" }, [_cache[17] || (_cache[17] = (0, vue.createElementVNode)("h2", null, "Watermark", -1)), (0, vue.createElementVNode)("button", {
|
|
957
|
+
type: "button",
|
|
958
|
+
class: "sd-dialog-close",
|
|
959
|
+
"aria-label": "Close watermark dialog",
|
|
960
|
+
onClick: close
|
|
961
|
+
}, [..._cache[16] || (_cache[16] = [(0, vue.createElementVNode)("svg", {
|
|
962
|
+
width: "16",
|
|
963
|
+
height: "16",
|
|
964
|
+
viewBox: "0 0 16 16",
|
|
965
|
+
"aria-hidden": "true"
|
|
966
|
+
}, [(0, vue.createElementVNode)("path", {
|
|
967
|
+
d: "m4 4 8 8m0-8-8 8",
|
|
968
|
+
fill: "none",
|
|
969
|
+
stroke: "currentColor",
|
|
970
|
+
"stroke-width": "1.5",
|
|
971
|
+
"stroke-linecap": "round"
|
|
972
|
+
})], -1)])])]),
|
|
973
|
+
(0, vue.createElementVNode)("div", _hoisted_1, [(0, vue.createElementVNode)("div", _hoisted_2, [
|
|
974
|
+
(0, vue.createElementVNode)("fieldset", {
|
|
975
|
+
class: "sd-watermark__scope",
|
|
976
|
+
disabled: busy.value
|
|
977
|
+
}, [
|
|
978
|
+
_cache[22] || (_cache[22] = (0, vue.createElementVNode)("legend", { class: "sd-watermark__sr-only" }, "Watermark scope", -1)),
|
|
979
|
+
(0, vue.createElementVNode)("label", _hoisted_4, [_cache[19] || (_cache[19] = (0, vue.createElementVNode)("span", null, "Apply to", -1)), (0, vue.createElementVNode)("select", {
|
|
980
|
+
"aria-label": "Apply to",
|
|
981
|
+
value: snapshot.value.sectionId ?? "",
|
|
982
|
+
onChange: _cache[0] || (_cache[0] = ($event) => changeScope($event, "section"))
|
|
983
|
+
}, [_cache[18] || (_cache[18] = (0, vue.createElementVNode)("option", { value: "" }, "Entire document", -1)), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(snapshot.value.sections, (section) => {
|
|
984
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
|
|
985
|
+
key: section.id,
|
|
986
|
+
value: section.id
|
|
987
|
+
}, (0, vue.toDisplayString)(section.label), 9, _hoisted_6);
|
|
988
|
+
}), 128))], 40, _hoisted_5)]),
|
|
989
|
+
(0, vue.createElementVNode)("label", _hoisted_7, [_cache[21] || (_cache[21] = (0, vue.createElementVNode)("span", null, "Page type", -1)), (0, vue.createElementVNode)("select", {
|
|
990
|
+
"aria-label": "Page type",
|
|
991
|
+
value: snapshot.value.variant,
|
|
992
|
+
onChange: _cache[1] || (_cache[1] = ($event) => changeScope($event, "variant"))
|
|
993
|
+
}, [..._cache[20] || (_cache[20] = [
|
|
994
|
+
(0, vue.createElementVNode)("option", { value: "all" }, "All applicable", -1),
|
|
995
|
+
(0, vue.createElementVNode)("option", { value: "default" }, "Default", -1),
|
|
996
|
+
(0, vue.createElementVNode)("option", { value: "first" }, "First", -1),
|
|
997
|
+
(0, vue.createElementVNode)("option", { value: "even" }, "Even", -1)
|
|
998
|
+
])], 40, _hoisted_8)])
|
|
999
|
+
], 8, _hoisted_3),
|
|
1000
|
+
snapshot.value.items.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("fieldset", {
|
|
1001
|
+
key: 0,
|
|
1002
|
+
class: "sd-watermark__selection",
|
|
1003
|
+
disabled: busy.value
|
|
1004
|
+
}, [
|
|
1005
|
+
_cache[23] || (_cache[23] = (0, vue.createElementVNode)("legend", null, "Watermarks to edit", -1)),
|
|
1006
|
+
(0, vue.createElementVNode)("div", _hoisted_10, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(snapshot.value.items, (item) => {
|
|
1007
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("label", {
|
|
1008
|
+
key: item.id,
|
|
1009
|
+
class: "sd-watermark__check"
|
|
1010
|
+
}, [(0, vue.createElementVNode)("input", {
|
|
1011
|
+
type: "checkbox",
|
|
1012
|
+
value: item.id,
|
|
1013
|
+
checked: snapshot.value.selectedIds.includes(item.id),
|
|
1014
|
+
onChange: ($event) => selectWatermark(item.id, $event)
|
|
1015
|
+
}, null, 40, _hoisted_11), (0, vue.createElementVNode)("span", null, [(0, vue.createTextVNode)((0, vue.toDisplayString)(item.label), 1), (0, vue.createElementVNode)("small", null, (0, vue.toDisplayString)(item.locations), 1)])]);
|
|
1016
|
+
}), 128))]),
|
|
1017
|
+
(0, vue.createElementVNode)("button", {
|
|
1018
|
+
type: "button",
|
|
1019
|
+
disabled: !editable.value,
|
|
1020
|
+
onClick: _cache[2] || (_cache[2] = ($event) => __props.model.selectWatermarks([]))
|
|
1021
|
+
}, "Add watermark", 8, _hoisted_12),
|
|
1022
|
+
snapshot.value.selectedIds.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_13, " Applying uses the settings below for all " + (0, vue.toDisplayString)(snapshot.value.selectedIds.length) + " selected copies. ", 1)) : (0, vue.createCommentVNode)("", true)
|
|
1023
|
+
], 8, _hoisted_9)) : (0, vue.createCommentVNode)("", true),
|
|
1024
|
+
snapshot.value.readonlyReason ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_14, (0, vue.toDisplayString)(snapshot.value.readonlyReason), 1)) : (0, vue.createCommentVNode)("", true),
|
|
1025
|
+
(0, vue.createElementVNode)("fieldset", {
|
|
1026
|
+
class: (0, vue.normalizeClass)(["sd-watermark__draft", { "sd-watermark__draft--text": snapshot.value.draft.kind === "text" }]),
|
|
1027
|
+
disabled: !editable.value
|
|
1028
|
+
}, [
|
|
1029
|
+
_cache[40] || (_cache[40] = (0, vue.createElementVNode)("legend", { class: "sd-watermark__sr-only" }, "Watermark settings", -1)),
|
|
1030
|
+
(0, vue.createElementVNode)("fieldset", _hoisted_16, [_cache[24] || (_cache[24] = (0, vue.createElementVNode)("legend", { class: "sd-watermark__sr-only" }, "Watermark type", -1)), ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(types, (type) => {
|
|
1031
|
+
return (0, vue.createElementVNode)("label", {
|
|
1032
|
+
key: type.value,
|
|
1033
|
+
class: (0, vue.normalizeClass)({ "is-selected": snapshot.value.draft.kind === type.value })
|
|
1034
|
+
}, [(0, vue.createElementVNode)("input", {
|
|
1035
|
+
type: "radio",
|
|
1036
|
+
name: `watermark-type-${__props.surfaceId}`,
|
|
1037
|
+
value: type.value,
|
|
1038
|
+
checked: snapshot.value.draft.kind === type.value,
|
|
1039
|
+
onChange: ($event) => patch({ kind: type.value })
|
|
1040
|
+
}, null, 40, _hoisted_17), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(type.value === "none" && snapshot.value.items.length > 1 && snapshot.value.selectedIds.length ? "Remove selected" : type.label), 1)], 2);
|
|
1041
|
+
}), 64))]),
|
|
1042
|
+
snapshot.value.draft.kind === "text" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [
|
|
1043
|
+
(0, vue.createElementVNode)("label", _hoisted_18, [_cache[25] || (_cache[25] = (0, vue.createElementVNode)("span", null, "Text", -1)), (0, vue.createElementVNode)("input", {
|
|
1044
|
+
"aria-label": "Watermark text",
|
|
1045
|
+
type: "text",
|
|
1046
|
+
value: snapshot.value.draft.text,
|
|
1047
|
+
onInput: _cache[3] || (_cache[3] = ($event) => patch({ text: $event.target.value }))
|
|
1048
|
+
}, null, 40, _hoisted_19)]),
|
|
1049
|
+
(0, vue.createElementVNode)("div", _hoisted_20, [((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(presets, (preset) => {
|
|
1050
|
+
return (0, vue.createElementVNode)("button", {
|
|
1051
|
+
key: preset,
|
|
1052
|
+
type: "button",
|
|
1053
|
+
onClick: ($event) => patch({ text: preset })
|
|
1054
|
+
}, (0, vue.toDisplayString)(preset), 9, _hoisted_21);
|
|
1055
|
+
}), 64))]),
|
|
1056
|
+
(0, vue.createElementVNode)("div", _hoisted_22, [
|
|
1057
|
+
(0, vue.createElementVNode)("label", _hoisted_23, [_cache[26] || (_cache[26] = (0, vue.createElementVNode)("span", null, "Font", -1)), (0, vue.createElementVNode)("select", {
|
|
1058
|
+
"aria-label": "Font",
|
|
1059
|
+
value: snapshot.value.draft.fontFamily,
|
|
1060
|
+
onChange: _cache[4] || (_cache[4] = ($event) => patch({ fontFamily: $event.target.value }))
|
|
1061
|
+
}, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(fontOptions.value, (font) => {
|
|
1062
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
|
|
1063
|
+
key: font.value,
|
|
1064
|
+
value: font.value
|
|
1065
|
+
}, (0, vue.toDisplayString)(font.label), 9, _hoisted_25);
|
|
1066
|
+
}), 128))], 40, _hoisted_24)]),
|
|
1067
|
+
(0, vue.createElementVNode)("label", _hoisted_26, [
|
|
1068
|
+
_cache[28] || (_cache[28] = (0, vue.createElementVNode)("span", null, "Size (pt)", -1)),
|
|
1069
|
+
(0, vue.createElementVNode)("input", {
|
|
1070
|
+
"aria-label": "Font size",
|
|
1071
|
+
list: `watermark-sizes-${__props.surfaceId}`,
|
|
1072
|
+
value: snapshot.value.draft.fontSize === "auto" ? "Auto" : snapshot.value.draft.fontSize,
|
|
1073
|
+
onChange: _cache[5] || (_cache[5] = ($event) => patch({ fontSize: $event.target.value.toLowerCase() === "auto" ? "auto" : Number($event.target.value) }))
|
|
1074
|
+
}, null, 40, _hoisted_27),
|
|
1075
|
+
(0, vue.createElementVNode)("datalist", { id: `watermark-sizes-${__props.surfaceId}` }, [_cache[27] || (_cache[27] = (0, vue.createElementVNode)("option", { value: "Auto" }, null, -1)), ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)([
|
|
1076
|
+
24,
|
|
1077
|
+
36,
|
|
1078
|
+
48,
|
|
1079
|
+
72,
|
|
1080
|
+
96
|
|
1081
|
+
], (size) => {
|
|
1082
|
+
return (0, vue.createElementVNode)("option", {
|
|
1083
|
+
key: size,
|
|
1084
|
+
value: size
|
|
1085
|
+
}, null, 8, _hoisted_29);
|
|
1086
|
+
}), 64))], 8, _hoisted_28)
|
|
1087
|
+
]),
|
|
1088
|
+
(0, vue.createElementVNode)("div", _hoisted_30, [(0, vue.createElementVNode)("button", {
|
|
1089
|
+
type: "button",
|
|
1090
|
+
"aria-label": "Bold",
|
|
1091
|
+
"aria-pressed": snapshot.value.draft.bold,
|
|
1092
|
+
onClick: _cache[6] || (_cache[6] = ($event) => patch({ bold: !snapshot.value.draft.bold }))
|
|
1093
|
+
}, [..._cache[29] || (_cache[29] = [(0, vue.createElementVNode)("b", null, "B", -1)])], 8, _hoisted_31), (0, vue.createElementVNode)("button", {
|
|
1094
|
+
type: "button",
|
|
1095
|
+
"aria-label": "Italic",
|
|
1096
|
+
"aria-pressed": snapshot.value.draft.italic,
|
|
1097
|
+
onClick: _cache[7] || (_cache[7] = ($event) => patch({ italic: !snapshot.value.draft.italic }))
|
|
1098
|
+
}, [..._cache[30] || (_cache[30] = [(0, vue.createElementVNode)("i", null, "I", -1)])], 8, _hoisted_32)])
|
|
1099
|
+
]),
|
|
1100
|
+
(0, vue.createElementVNode)("label", _hoisted_33, [_cache[31] || (_cache[31] = (0, vue.createElementVNode)("span", null, "Font color", -1)), (0, vue.createElementVNode)("span", _hoisted_34, [(0, vue.createElementVNode)("input", {
|
|
1101
|
+
type: "color",
|
|
1102
|
+
"aria-label": "Choose font color",
|
|
1103
|
+
value: snapshot.value.draft.color,
|
|
1104
|
+
onInput: _cache[8] || (_cache[8] = ($event) => patch({ color: $event.target.value }))
|
|
1105
|
+
}, null, 40, _hoisted_35), (0, vue.createElementVNode)("input", {
|
|
1106
|
+
type: "text",
|
|
1107
|
+
"aria-label": "Font color",
|
|
1108
|
+
value: snapshot.value.draft.color,
|
|
1109
|
+
spellcheck: "false",
|
|
1110
|
+
onInput: _cache[9] || (_cache[9] = ($event) => patch({ color: $event.target.value }))
|
|
1111
|
+
}, null, 40, _hoisted_36)])])
|
|
1112
|
+
], 64)) : (0, vue.createCommentVNode)("", true),
|
|
1113
|
+
snapshot.value.draft.kind === "picture" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
|
|
1114
|
+
(0, vue.createElementVNode)("div", _hoisted_37, [
|
|
1115
|
+
snapshot.value.draft.src ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
|
|
1116
|
+
key: 0,
|
|
1117
|
+
src: snapshot.value.draft.src,
|
|
1118
|
+
alt: "Selected picture"
|
|
1119
|
+
}, null, 8, _hoisted_38)) : (0, vue.createCommentVNode)("", true),
|
|
1120
|
+
(0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("strong", null, (0, vue.toDisplayString)(snapshot.value.draft.pictureName || "No picture selected"), 1), _cache[32] || (_cache[32] = (0, vue.createElementVNode)("small", null, "PNG or JPEG", -1))]),
|
|
1121
|
+
(0, vue.createElementVNode)("button", {
|
|
1122
|
+
type: "button",
|
|
1123
|
+
onClick: _cache[10] || (_cache[10] = ($event) => fileInput.value?.click())
|
|
1124
|
+
}, (0, vue.toDisplayString)(snapshot.value.draft.pictureName ? "Replace picture" : "Choose picture"), 1),
|
|
1125
|
+
(0, vue.createElementVNode)("input", {
|
|
1126
|
+
ref_key: "fileInput",
|
|
1127
|
+
ref: fileInput,
|
|
1128
|
+
type: "file",
|
|
1129
|
+
accept: (0, vue.unref)(IMAGE_PICKER_ACCEPT),
|
|
1130
|
+
"aria-label": "Choose watermark picture",
|
|
1131
|
+
hidden: "",
|
|
1132
|
+
onChange: choosePicture
|
|
1133
|
+
}, null, 40, _hoisted_39)
|
|
1134
|
+
]),
|
|
1135
|
+
(0, vue.createElementVNode)("label", _hoisted_40, [_cache[34] || (_cache[34] = (0, vue.createElementVNode)("span", null, "Scale", -1)), (0, vue.createElementVNode)("select", {
|
|
1136
|
+
"aria-label": "Picture scale",
|
|
1137
|
+
value: snapshot.value.draft.customSize ? "custom" : snapshot.value.draft.scalePercent,
|
|
1138
|
+
onChange: _cache[11] || (_cache[11] = ($event) => patch({ scalePercent: $event.target.value === "auto" ? "auto" : Number($event.target.value) }))
|
|
1139
|
+
}, [
|
|
1140
|
+
snapshot.value.draft.customSize ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("option", _hoisted_42, "Custom size retained")) : (0, vue.createCommentVNode)("", true),
|
|
1141
|
+
_cache[33] || (_cache[33] = (0, vue.createElementVNode)("option", { value: "auto" }, "Auto", -1)),
|
|
1142
|
+
((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)([
|
|
1143
|
+
50,
|
|
1144
|
+
100,
|
|
1145
|
+
150,
|
|
1146
|
+
200,
|
|
1147
|
+
500
|
|
1148
|
+
], (scale) => {
|
|
1149
|
+
return (0, vue.createElementVNode)("option", {
|
|
1150
|
+
key: scale,
|
|
1151
|
+
value: scale
|
|
1152
|
+
}, (0, vue.toDisplayString)(scale) + "%", 9, _hoisted_43);
|
|
1153
|
+
}), 64))
|
|
1154
|
+
], 40, _hoisted_41)]),
|
|
1155
|
+
(0, vue.createElementVNode)("label", _hoisted_44, [(0, vue.createElementVNode)("input", {
|
|
1156
|
+
type: "checkbox",
|
|
1157
|
+
checked: snapshot.value.draft.lockAspectRatio,
|
|
1158
|
+
onChange: _cache[12] || (_cache[12] = ($event) => patch({ lockAspectRatio: $event.target.checked }))
|
|
1159
|
+
}, null, 40, _hoisted_45), _cache[35] || (_cache[35] = (0, vue.createElementVNode)("span", null, "Preserve aspect ratio", -1))]),
|
|
1160
|
+
(0, vue.createElementVNode)("label", _hoisted_46, [(0, vue.createElementVNode)("input", {
|
|
1161
|
+
type: "checkbox",
|
|
1162
|
+
checked: snapshot.value.draft.washout,
|
|
1163
|
+
onChange: _cache[13] || (_cache[13] = ($event) => patch({ washout: $event.target.checked }))
|
|
1164
|
+
}, null, 40, _hoisted_47), _cache[36] || (_cache[36] = (0, vue.createElementVNode)("span", null, [(0, vue.createTextVNode)("Washout"), (0, vue.createElementVNode)("small", null, "Lighten the picture's colors.")], -1))])
|
|
1165
|
+
], 64)) : (0, vue.createCommentVNode)("", true),
|
|
1166
|
+
snapshot.value.draft.kind !== "none" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [
|
|
1167
|
+
(0, vue.createElementVNode)("label", { for: `watermark-transparency-${__props.surfaceId}` }, "Transparency", 8, _hoisted_49),
|
|
1168
|
+
(0, vue.createElementVNode)("div", _hoisted_50, [(0, vue.createElementVNode)("input", {
|
|
1169
|
+
id: `watermark-transparency-${__props.surfaceId}`,
|
|
1170
|
+
type: "range",
|
|
1171
|
+
min: "0",
|
|
1172
|
+
max: "100",
|
|
1173
|
+
step: "1",
|
|
1174
|
+
"aria-label": "Transparency",
|
|
1175
|
+
value: snapshot.value.draft.transparency,
|
|
1176
|
+
onInput: _cache[14] || (_cache[14] = ($event) => patch({ transparency: Number($event.target.value) }))
|
|
1177
|
+
}, null, 40, _hoisted_51), (0, vue.createElementVNode)("label", null, [(0, vue.createElementVNode)("input", {
|
|
1178
|
+
type: "number",
|
|
1179
|
+
min: "0",
|
|
1180
|
+
max: "100",
|
|
1181
|
+
step: "any",
|
|
1182
|
+
"aria-label": "Transparency percentage",
|
|
1183
|
+
value: snapshot.value.draft.transparency,
|
|
1184
|
+
onInput: _cache[15] || (_cache[15] = ($event) => patch({ transparency: Number($event.target.value) }))
|
|
1185
|
+
}, null, 40, _hoisted_52), _cache[37] || (_cache[37] = (0, vue.createElementVNode)("span", null, "%", -1))])]),
|
|
1186
|
+
_cache[38] || (_cache[38] = (0, vue.createElementVNode)("small", null, "0% opaque, 100% transparent", -1))
|
|
1187
|
+
])) : (0, vue.createCommentVNode)("", true),
|
|
1188
|
+
snapshot.value.draft.kind !== "none" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("fieldset", _hoisted_53, [_cache[39] || (_cache[39] = (0, vue.createElementVNode)("legend", null, "Orientation", -1)), ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(["horizontal", "diagonal"], (orientation) => {
|
|
1189
|
+
return (0, vue.createElementVNode)("label", { key: orientation }, [(0, vue.createElementVNode)("input", {
|
|
1190
|
+
type: "radio",
|
|
1191
|
+
name: `watermark-orientation-${__props.surfaceId}`,
|
|
1192
|
+
value: orientation,
|
|
1193
|
+
checked: !snapshot.value.draft.customOrientation && snapshot.value.draft.orientation === orientation,
|
|
1194
|
+
onChange: ($event) => patch({ orientation })
|
|
1195
|
+
}, null, 40, _hoisted_54), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(orientation === "horizontal" ? "Horizontal" : "Diagonal"), 1)]);
|
|
1196
|
+
}), 64))])) : (0, vue.createCommentVNode)("", true),
|
|
1197
|
+
snapshot.value.draft.customPlacement ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_55, "Custom placement retained.")) : (0, vue.createCommentVNode)("", true),
|
|
1198
|
+
snapshot.value.draft.kind === "none" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_56, (0, vue.toDisplayString)(snapshot.value.selectedIds.length ? "The selected watermark will be removed when you apply." : "Choose Text or Picture to add a watermark."), 1)) : (0, vue.createCommentVNode)("", true)
|
|
1199
|
+
], 10, _hoisted_15)
|
|
1200
|
+
]), (0, vue.createElementVNode)("aside", {
|
|
1201
|
+
class: "sd-watermark__preview",
|
|
1202
|
+
"aria-label": "Watermark preview",
|
|
1203
|
+
"aria-busy": snapshot.value.phase === "previewing"
|
|
1204
|
+
}, [
|
|
1205
|
+
_cache[41] || (_cache[41] = (0, vue.createElementVNode)("h3", null, "Preview", -1)),
|
|
1206
|
+
(0, vue.createElementVNode)("div", _hoisted_58, [snapshot.value.preview ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
|
|
1207
|
+
key: 0,
|
|
1208
|
+
src: snapshot.value.preview.url,
|
|
1209
|
+
width: snapshot.value.preview.width,
|
|
1210
|
+
height: snapshot.value.preview.height,
|
|
1211
|
+
alt: "Watermark on a sample page"
|
|
1212
|
+
}, null, 8, _hoisted_59)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_60, (0, vue.toDisplayString)(snapshot.value.phase === "loading" || snapshot.value.phase === "previewing" ? "Preparing preview…" : snapshot.value.draft.kind === "none" ? "No watermark in this preview" : "Preview unavailable"), 1))]),
|
|
1213
|
+
_cache[42] || (_cache[42] = (0, vue.createElementVNode)("p", { class: "sd-watermark__caption" }, "Preview at the selected page size", -1)),
|
|
1214
|
+
(0, vue.createElementVNode)("p", _hoisted_61, (0, vue.toDisplayString)(snapshot.value.impact), 1),
|
|
1215
|
+
snapshot.value.impactDetails?.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("details", _hoisted_62, [(0, vue.createElementVNode)("summary", null, "View " + (0, vue.toDisplayString)(snapshot.value.impactDetails.length) + " affected locations", 1), (0, vue.createElementVNode)("ul", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(snapshot.value.impactDetails, (location) => {
|
|
1216
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("li", { key: location }, (0, vue.toDisplayString)(location), 1);
|
|
1217
|
+
}), 128))])])) : (0, vue.createCommentVNode)("", true)
|
|
1218
|
+
], 8, _hoisted_57)]),
|
|
1219
|
+
error.value || snapshot.value.stale ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_63, [(0, vue.createElementVNode)("p", null, (0, vue.toDisplayString)(error.value || "The document changed. Review the latest state before applying."), 1), snapshot.value.stale || snapshot.value.error ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
|
|
1220
|
+
key: 0,
|
|
1221
|
+
type: "button",
|
|
1222
|
+
disabled: busy.value,
|
|
1223
|
+
onClick: reload
|
|
1224
|
+
}, " Review latest state ", 8, _hoisted_64)) : (0, vue.createCommentVNode)("", true)])) : (0, vue.createCommentVNode)("", true),
|
|
1225
|
+
(0, vue.createElementVNode)("footer", _hoisted_65, [(0, vue.createElementVNode)("p", _hoisted_66, (0, vue.toDisplayString)(status.value), 1), (0, vue.createElementVNode)("div", _hoisted_67, [(0, vue.createElementVNode)("button", {
|
|
1226
|
+
type: "button",
|
|
1227
|
+
class: "sd-dialog-secondary",
|
|
1228
|
+
onClick: close
|
|
1229
|
+
}, "Cancel"), (0, vue.createElementVNode)("button", {
|
|
1230
|
+
type: "submit",
|
|
1231
|
+
class: "sd-watermark__apply sd-dialog-primary",
|
|
1232
|
+
disabled: !canApply.value
|
|
1233
|
+
}, (0, vue.toDisplayString)(snapshot.value.phase === "applying" ? "Applying…" : snapshot.value.draft.kind === "none" ? snapshot.value.items.length > 1 ? "Remove selected watermark" : "Remove watermark" : "Apply watermark"), 9, _hoisted_68)])])
|
|
1234
|
+
], 32);
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region src/components/surfaces/WatermarkSurface.vue
|
|
1240
|
+
var WatermarkSurface_default = /* @__PURE__ */ require__plugin_vue_export_helper._plugin_vue_export_helper_default(WatermarkSurface_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-df56c82c"]]);
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/components/surfaces/watermark-surface-runtime.js
|
|
1243
|
+
/**
|
|
1244
|
+
* Keeps Vue rendering and post-dismissal announcements outside the public type graph.
|
|
1245
|
+
* @param {(() => HTMLElement | null) | undefined} getContainer
|
|
1246
|
+
*/
|
|
1247
|
+
function createWatermarkSurfaceRuntime(getContainer) {
|
|
1248
|
+
/** @type {HTMLDivElement | null} */
|
|
1249
|
+
let status = null;
|
|
1250
|
+
let disposed = false;
|
|
1251
|
+
return {
|
|
1252
|
+
component: WatermarkSurface_default,
|
|
1253
|
+
captureFocusReturn() {
|
|
1254
|
+
const document = getContainer?.()?.ownerDocument;
|
|
1255
|
+
const opener = document?.activeElement;
|
|
1256
|
+
const item = opener?.closest("[data-sd-part=\"toolbar-item\"]") ?? opener?.querySelector("[data-sd-part=\"toolbar-item\"]");
|
|
1257
|
+
const container = (item?.closest("[data-sd-part=\"toolbar\"]"))?.parentElement;
|
|
1258
|
+
const isWatermarkEntry = item?.querySelector("[data-item=\"btn-watermark\"], [data-item=\"btn-overflow\"]");
|
|
1259
|
+
/** @param {() => boolean} isCurrent */
|
|
1260
|
+
return async (isCurrent) => {
|
|
1261
|
+
await (0, vue.nextTick)();
|
|
1262
|
+
if (disposed || !isCurrent() || !isWatermarkEntry || opener?.isConnected || !container?.isConnected) return;
|
|
1263
|
+
if (document?.activeElement !== document?.body) return;
|
|
1264
|
+
const currentToolbar = container.querySelector("[data-sd-part=\"toolbar\"]");
|
|
1265
|
+
for (const name of ["watermark", "overflow"]) {
|
|
1266
|
+
const target = currentToolbar?.querySelector(`[data-item="btn-${name}"]`)?.closest("[data-sd-part=\"toolbar-item\"]");
|
|
1267
|
+
if (target instanceof HTMLElement && target.getClientRects().length && target.getAttribute("aria-disabled") !== "true") {
|
|
1268
|
+
target.focus({ preventScroll: true });
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
},
|
|
1274
|
+
mount() {
|
|
1275
|
+
const container = getContainer?.();
|
|
1276
|
+
if (disposed || status || !container?.ownerDocument) return;
|
|
1277
|
+
status = container.ownerDocument.createElement("div");
|
|
1278
|
+
status.setAttribute("role", "status");
|
|
1279
|
+
status.setAttribute("aria-live", "polite");
|
|
1280
|
+
status.setAttribute("aria-atomic", "true");
|
|
1281
|
+
Object.assign(status.style, {
|
|
1282
|
+
position: "absolute",
|
|
1283
|
+
width: "1px",
|
|
1284
|
+
height: "1px",
|
|
1285
|
+
overflow: "hidden",
|
|
1286
|
+
clipPath: "inset(50%)",
|
|
1287
|
+
whiteSpace: "nowrap"
|
|
1288
|
+
});
|
|
1289
|
+
container.appendChild(status);
|
|
1290
|
+
},
|
|
1291
|
+
/** @param {string} message */
|
|
1292
|
+
announce(message) {
|
|
1293
|
+
if (!disposed && status) status.textContent = message;
|
|
1294
|
+
},
|
|
1295
|
+
destroy() {
|
|
1296
|
+
disposed = true;
|
|
1297
|
+
status?.remove();
|
|
1298
|
+
status = null;
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
//#endregion
|
|
1303
|
+
//#region src/public/ui/watermark-draft.ts
|
|
1304
|
+
function placementState(placement) {
|
|
1305
|
+
const customOrientation = placement?.rotationDegrees != null && ![0, 315].includes(placement.rotationDegrees);
|
|
1306
|
+
return {
|
|
1307
|
+
customOrientation,
|
|
1308
|
+
customPlacement: Boolean(customOrientation || placement?.behindText === false || placement?.horizontal && (placement.horizontal.alignment !== "center" || placement.horizontal.offsetPt) || placement?.vertical && (placement.vertical.alignment !== "center" || placement.vertical.offsetPt))
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function createWatermarkDraft(item) {
|
|
1312
|
+
const value = item?.watermark;
|
|
1313
|
+
const placement = value?.placement ? structuredClone(value.placement) : void 0;
|
|
1314
|
+
const picture = value?.kind === "picture" ? value : void 0;
|
|
1315
|
+
const text = value?.kind === "text" ? value : void 0;
|
|
1316
|
+
const rotation = placement?.rotationDegrees ?? (picture ? 0 : text?.orientation === "horizontal" ? 0 : 315);
|
|
1317
|
+
return {
|
|
1318
|
+
kind: value?.kind ?? "none",
|
|
1319
|
+
geometryKind: value?.kind ?? null,
|
|
1320
|
+
text: text?.text ?? "DRAFT",
|
|
1321
|
+
fontFamily: text?.fontFamily ?? "Calibri",
|
|
1322
|
+
fontSize: text?.fontSizePt ?? "auto",
|
|
1323
|
+
bold: text?.bold ?? false,
|
|
1324
|
+
italic: text?.italic ?? false,
|
|
1325
|
+
color: text?.color ?? "#C0C0C0",
|
|
1326
|
+
transparency: (1 - (value?.opacity ?? .5)) * 100,
|
|
1327
|
+
orientation: rotation === 0 ? "horizontal" : "diagonal",
|
|
1328
|
+
scalePercent: picture?.scalePercent ?? "auto",
|
|
1329
|
+
washout: picture?.washout ?? true,
|
|
1330
|
+
lockAspectRatio: picture?.lockAspectRatio ?? true,
|
|
1331
|
+
pictureName: picture ? "Image in this document" : "",
|
|
1332
|
+
sourceWatermarkId: picture ? item?.watermarkId : void 0,
|
|
1333
|
+
widthPt: picture?.widthPt,
|
|
1334
|
+
heightPt: picture?.heightPt,
|
|
1335
|
+
placement,
|
|
1336
|
+
...placementState(placement),
|
|
1337
|
+
customSize: Boolean(picture && (placement?.widthPt != null || picture.widthPt != null))
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function patchWatermarkDraft(draft, patch) {
|
|
1341
|
+
const next = {
|
|
1342
|
+
...draft,
|
|
1343
|
+
...patch
|
|
1344
|
+
};
|
|
1345
|
+
if (patch.kind && patch.kind !== "none") {
|
|
1346
|
+
if (patch.kind !== draft.geometryKind) {
|
|
1347
|
+
next.placement = void 0;
|
|
1348
|
+
next.widthPt = void 0;
|
|
1349
|
+
next.heightPt = void 0;
|
|
1350
|
+
next.customSize = false;
|
|
1351
|
+
next.orientation = patch.kind === "picture" ? "horizontal" : "diagonal";
|
|
1352
|
+
}
|
|
1353
|
+
next.geometryKind = patch.kind;
|
|
1354
|
+
}
|
|
1355
|
+
if (patch.orientation !== void 0) next.placement = {
|
|
1356
|
+
...next.placement,
|
|
1357
|
+
rotationDegrees: patch.orientation === "diagonal" ? 315 : 0
|
|
1358
|
+
};
|
|
1359
|
+
if (patch.scalePercent !== void 0 || patch.src !== void 0) {
|
|
1360
|
+
const { widthPt: _width, heightPt: _height, ...placement } = next.placement ?? {};
|
|
1361
|
+
next.placement = Object.keys(placement).length ? placement : void 0;
|
|
1362
|
+
next.widthPt = void 0;
|
|
1363
|
+
next.heightPt = void 0;
|
|
1364
|
+
next.customSize = false;
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
...next,
|
|
1368
|
+
...placementState(next.placement)
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
function getWatermarkDraftError(draft) {
|
|
1372
|
+
if (draft.kind === "none") return null;
|
|
1373
|
+
if (!Number.isFinite(draft.transparency) || draft.transparency < 0 || draft.transparency > 100) return "Transparency must be between 0 and 100%.";
|
|
1374
|
+
if (draft.kind === "picture") return draft.src || draft.sourceWatermarkId ? null : "Choose a picture.";
|
|
1375
|
+
if (!draft.text.trim()) return "Enter watermark text.";
|
|
1376
|
+
if (draft.fontSize !== "auto" && (!Number.isFinite(draft.fontSize) || draft.fontSize <= 0)) return "Enter a positive font size or choose Auto.";
|
|
1377
|
+
if (!/^#[\da-f]{6}$/i.test(draft.color)) return "Enter a six-digit hex color.";
|
|
1378
|
+
if (!draft.fontFamily.trim()) return "Choose a font.";
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
function toWatermarkInput(draft) {
|
|
1382
|
+
const shared = {
|
|
1383
|
+
opacity: 1 - draft.transparency / 100,
|
|
1384
|
+
...draft.placement ? { placement: draft.placement } : {}
|
|
1385
|
+
};
|
|
1386
|
+
if (draft.kind === "text") return {
|
|
1387
|
+
kind: "text",
|
|
1388
|
+
text: draft.text,
|
|
1389
|
+
fontFamily: draft.fontFamily,
|
|
1390
|
+
fontSizePt: draft.fontSize,
|
|
1391
|
+
bold: draft.bold,
|
|
1392
|
+
italic: draft.italic,
|
|
1393
|
+
color: draft.color,
|
|
1394
|
+
orientation: draft.orientation,
|
|
1395
|
+
...shared
|
|
1396
|
+
};
|
|
1397
|
+
if (draft.kind !== "picture") throw new Error("A removal has no watermark input.");
|
|
1398
|
+
return {
|
|
1399
|
+
kind: "picture",
|
|
1400
|
+
...draft.src ? { src: draft.src } : { source: {
|
|
1401
|
+
kind: "existing",
|
|
1402
|
+
watermarkId: draft.sourceWatermarkId
|
|
1403
|
+
} },
|
|
1404
|
+
scalePercent: draft.scalePercent,
|
|
1405
|
+
washout: draft.washout,
|
|
1406
|
+
lockAspectRatio: draft.lockAspectRatio,
|
|
1407
|
+
...draft.widthPt != null ? { widthPt: draft.widthPt } : {},
|
|
1408
|
+
...draft.heightPt != null ? { heightPt: draft.heightPt } : {},
|
|
1409
|
+
...shared
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
//#endregion
|
|
1413
|
+
//#region src/public/ui/watermark-controller.ts
|
|
1414
|
+
var PAGE_TYPES = {
|
|
1415
|
+
default: "Default pages",
|
|
1416
|
+
first: "First page",
|
|
1417
|
+
even: "Even pages"
|
|
1418
|
+
};
|
|
1419
|
+
var STALE_MESSAGE = "The document changed while this dialog was open. Review the latest watermark before applying.";
|
|
1420
|
+
function scopeSlots(sections, sectionId, variant) {
|
|
1421
|
+
return sections.filter((section) => sectionId == null || section.address.sectionId === sectionId).flatMap((section) => {
|
|
1422
|
+
return (variant === "all" ? [
|
|
1423
|
+
"default",
|
|
1424
|
+
...section.titlePage ? ["first"] : [],
|
|
1425
|
+
...section.oddEvenHeadersFooters ? ["even"] : []
|
|
1426
|
+
] : [variant]).map((variant) => ({
|
|
1427
|
+
kind: "headerFooterSlot",
|
|
1428
|
+
section: section.address,
|
|
1429
|
+
headerFooterKind: "header",
|
|
1430
|
+
variant
|
|
1431
|
+
}));
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function slotKey(slot) {
|
|
1435
|
+
return `${slot.section.sectionId}:${slot.variant}`;
|
|
1436
|
+
}
|
|
1437
|
+
function failureMessage(error) {
|
|
1438
|
+
return error instanceof Error ? error.message : "The watermark could not be updated. Try again.";
|
|
1439
|
+
}
|
|
1440
|
+
function createWatermarkController(options) {
|
|
1441
|
+
const surface = createWatermarkSurfaceRuntime(options.getContainer);
|
|
1442
|
+
const workflowOptions = {
|
|
1443
|
+
...options,
|
|
1444
|
+
announce: (message) => {
|
|
1445
|
+
surface.announce(message);
|
|
1446
|
+
options.announce?.(message);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
let active = null;
|
|
1450
|
+
let destroyed = false;
|
|
1451
|
+
let openGeneration = 0;
|
|
1452
|
+
function close() {
|
|
1453
|
+
const previous = active;
|
|
1454
|
+
active = null;
|
|
1455
|
+
previous?.dispose();
|
|
1456
|
+
previous?.handle.close();
|
|
1457
|
+
}
|
|
1458
|
+
function open() {
|
|
1459
|
+
if (destroyed) return {
|
|
1460
|
+
ok: false,
|
|
1461
|
+
reason: "not-ready"
|
|
1462
|
+
};
|
|
1463
|
+
const editor = options.getEditor();
|
|
1464
|
+
if (!editor) return {
|
|
1465
|
+
ok: false,
|
|
1466
|
+
reason: "not-ready"
|
|
1467
|
+
};
|
|
1468
|
+
if (!editor.doc?.watermarks?.list || !editor.doc.sections?.list) return {
|
|
1469
|
+
ok: false,
|
|
1470
|
+
reason: "document-api-unavailable"
|
|
1471
|
+
};
|
|
1472
|
+
if (!options.openSurface || !editor.doc.watermarks.apply || !editor.host?.watermarkPreview) return {
|
|
1473
|
+
ok: false,
|
|
1474
|
+
reason: "operation-unavailable"
|
|
1475
|
+
};
|
|
1476
|
+
const generation = ++openGeneration;
|
|
1477
|
+
const restoreFocus = surface.captureFocusReturn();
|
|
1478
|
+
close();
|
|
1479
|
+
surface.mount();
|
|
1480
|
+
const workflow = createDialogWorkflow(editor, workflowOptions, () => {
|
|
1481
|
+
if (active?.handle === handle) close();
|
|
1482
|
+
});
|
|
1483
|
+
let handle;
|
|
1484
|
+
try {
|
|
1485
|
+
handle = options.openSurface({
|
|
1486
|
+
mode: "dialog",
|
|
1487
|
+
ariaLabel: "Watermark",
|
|
1488
|
+
component: surface.component,
|
|
1489
|
+
props: { model: workflow.model },
|
|
1490
|
+
dialog: { maxWidth: 700 }
|
|
1491
|
+
});
|
|
1492
|
+
} catch {
|
|
1493
|
+
workflow.dispose();
|
|
1494
|
+
return {
|
|
1495
|
+
ok: false,
|
|
1496
|
+
reason: "operation-unavailable"
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
active = {
|
|
1500
|
+
handle,
|
|
1501
|
+
dispose: workflow.dispose
|
|
1502
|
+
};
|
|
1503
|
+
handle.result.then((outcome) => {
|
|
1504
|
+
workflow.dispose();
|
|
1505
|
+
if (active?.handle === handle) active = null;
|
|
1506
|
+
if (outcome.status === "closed" || outcome.status === "submitted") restoreFocus(() => !destroyed && generation === openGeneration && active === null);
|
|
1507
|
+
});
|
|
1508
|
+
workflow.model.reload();
|
|
1509
|
+
return { ok: true };
|
|
1510
|
+
}
|
|
1511
|
+
return {
|
|
1512
|
+
open,
|
|
1513
|
+
close,
|
|
1514
|
+
destroy() {
|
|
1515
|
+
destroyed = true;
|
|
1516
|
+
close();
|
|
1517
|
+
surface.destroy();
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
function createDialogWorkflow(editor, options, onSuccess) {
|
|
1522
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1523
|
+
let alive = true;
|
|
1524
|
+
let generation = 0;
|
|
1525
|
+
let timer;
|
|
1526
|
+
let catalog = [];
|
|
1527
|
+
let sections = [];
|
|
1528
|
+
let revision = "";
|
|
1529
|
+
let originalDraft = JSON.stringify(createWatermarkDraft());
|
|
1530
|
+
let preparedInput = null;
|
|
1531
|
+
let snapshot = {
|
|
1532
|
+
phase: "loading",
|
|
1533
|
+
draft: createWatermarkDraft(),
|
|
1534
|
+
sections: [],
|
|
1535
|
+
sectionId: null,
|
|
1536
|
+
variant: "all",
|
|
1537
|
+
items: [],
|
|
1538
|
+
selectedIds: [],
|
|
1539
|
+
fonts: [...options.getFonts()],
|
|
1540
|
+
dirty: false,
|
|
1541
|
+
canApply: false,
|
|
1542
|
+
error: null,
|
|
1543
|
+
stale: false,
|
|
1544
|
+
readonlyReason: null,
|
|
1545
|
+
impact: "",
|
|
1546
|
+
impactDetails: [],
|
|
1547
|
+
preview: null
|
|
1548
|
+
};
|
|
1549
|
+
const current = (token) => alive && token === generation && options.getEditor() === editor;
|
|
1550
|
+
const editable = () => options.getMode() === "editing";
|
|
1551
|
+
function publish(patch) {
|
|
1552
|
+
if (!alive) return;
|
|
1553
|
+
snapshot = {
|
|
1554
|
+
...snapshot,
|
|
1555
|
+
...patch
|
|
1556
|
+
};
|
|
1557
|
+
for (const listener of listeners) listener(snapshot);
|
|
1558
|
+
}
|
|
1559
|
+
function invalidate() {
|
|
1560
|
+
clearTimeout(timer);
|
|
1561
|
+
preparedInput = null;
|
|
1562
|
+
return ++generation;
|
|
1563
|
+
}
|
|
1564
|
+
function location(slot) {
|
|
1565
|
+
const section = sections.find((item) => item.address.sectionId === slot.section.sectionId);
|
|
1566
|
+
const inactive = slot.variant === "first" ? !section?.titlePage : slot.variant === "even" ? !section?.oddEvenHeadersFooters : false;
|
|
1567
|
+
return `Section ${(section?.index ?? 0) + 1}, ${PAGE_TYPES[slot.variant].toLowerCase()}${inactive ? " (inactive)" : ""}`;
|
|
1568
|
+
}
|
|
1569
|
+
function scopedItems() {
|
|
1570
|
+
if (snapshot.sectionId == null && snapshot.variant === "all") return catalog;
|
|
1571
|
+
const keys = new Set(scopeSlots(sections, snapshot.sectionId, snapshot.variant).map(slotKey));
|
|
1572
|
+
return catalog.filter((item) => item.effectiveIn.some((slot) => keys.has(slotKey(slot))));
|
|
1573
|
+
}
|
|
1574
|
+
function publishCatalog() {
|
|
1575
|
+
publish({ items: scopedItems().map((item) => ({
|
|
1576
|
+
id: item.watermarkId,
|
|
1577
|
+
label: item.watermark.kind === "text" ? item.watermark.text : "Picture watermark",
|
|
1578
|
+
locations: item.effectiveIn.map(location).join("; ")
|
|
1579
|
+
})) });
|
|
1580
|
+
}
|
|
1581
|
+
function buildInput() {
|
|
1582
|
+
const target = snapshot.sectionId == null && snapshot.variant === "all" ? { kind: "document" } : {
|
|
1583
|
+
kind: "headerFooterSlots",
|
|
1584
|
+
slots: scopeSlots(sections, snapshot.sectionId, snapshot.variant)
|
|
1585
|
+
};
|
|
1586
|
+
if (snapshot.draft.kind === "none") return {
|
|
1587
|
+
target,
|
|
1588
|
+
action: "remove",
|
|
1589
|
+
watermarkIds: snapshot.selectedIds
|
|
1590
|
+
};
|
|
1591
|
+
const watermark = toWatermarkInput(snapshot.draft);
|
|
1592
|
+
if (snapshot.selectedIds.length) return {
|
|
1593
|
+
target,
|
|
1594
|
+
action: "replace",
|
|
1595
|
+
watermarkIds: snapshot.selectedIds,
|
|
1596
|
+
watermark
|
|
1597
|
+
};
|
|
1598
|
+
if ("source" in watermark) throw new Error("Select the existing picture watermark in this scope, or choose a new picture.");
|
|
1599
|
+
return {
|
|
1600
|
+
target,
|
|
1601
|
+
action: "insert",
|
|
1602
|
+
watermark
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function rejectReceipt(result) {
|
|
1606
|
+
if (result.success) return false;
|
|
1607
|
+
const stale = result.failure?.code === "REVISION_MISMATCH";
|
|
1608
|
+
publish({
|
|
1609
|
+
stale,
|
|
1610
|
+
phase: "editing",
|
|
1611
|
+
canApply: false,
|
|
1612
|
+
error: stale ? STALE_MESSAGE : result.failure?.message ?? "The watermark could not be updated."
|
|
1613
|
+
});
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
async function prepare(token) {
|
|
1617
|
+
try {
|
|
1618
|
+
const error = getWatermarkDraftError(snapshot.draft);
|
|
1619
|
+
if (error) {
|
|
1620
|
+
publish({
|
|
1621
|
+
phase: "editing",
|
|
1622
|
+
error,
|
|
1623
|
+
canApply: false,
|
|
1624
|
+
preview: null
|
|
1625
|
+
});
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const input = snapshot.draft.kind === "none" && !snapshot.selectedIds.length ? null : buildInput();
|
|
1629
|
+
const preview = snapshot.draft.kind === "none" ? null : await editor.host.watermarkPreview({
|
|
1630
|
+
watermark: toWatermarkInput(snapshot.draft),
|
|
1631
|
+
sectionId: snapshot.sectionId ?? sections[0]?.address.sectionId,
|
|
1632
|
+
expectedRevision: revision
|
|
1633
|
+
});
|
|
1634
|
+
if (!current(token)) return;
|
|
1635
|
+
if (!input || !snapshot.dirty || !editable()) {
|
|
1636
|
+
publish({
|
|
1637
|
+
phase: "editing",
|
|
1638
|
+
preview,
|
|
1639
|
+
canApply: false,
|
|
1640
|
+
error: null
|
|
1641
|
+
});
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const receipt = await editor.doc.watermarks.apply(input, {
|
|
1645
|
+
expectedRevision: revision,
|
|
1646
|
+
dryRun: true
|
|
1647
|
+
});
|
|
1648
|
+
if (!current(token)) return;
|
|
1649
|
+
if (!receipt.success) {
|
|
1650
|
+
rejectReceipt(receipt);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
preparedInput = input;
|
|
1654
|
+
const affected = receipt.affectedSlots.map(location);
|
|
1655
|
+
publish({
|
|
1656
|
+
phase: "editing",
|
|
1657
|
+
preview,
|
|
1658
|
+
error: null,
|
|
1659
|
+
canApply: affected.length > 0 && !snapshot.stale && editable(),
|
|
1660
|
+
impact: affected.length ? `Changes: ${affected.length > 3 ? `${affected.length} page locations` : affected.join("; ")}. Other locations stay as they are.` : "No matching watermark will change.",
|
|
1661
|
+
impactDetails: affected.length > 3 ? affected : []
|
|
1662
|
+
});
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
if (current(token)) publish({
|
|
1665
|
+
phase: "editing",
|
|
1666
|
+
error: failureMessage(error),
|
|
1667
|
+
canApply: false,
|
|
1668
|
+
preview: null
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
function schedulePreview() {
|
|
1673
|
+
const token = invalidate();
|
|
1674
|
+
publish({
|
|
1675
|
+
phase: "previewing",
|
|
1676
|
+
canApply: false,
|
|
1677
|
+
error: null,
|
|
1678
|
+
impact: "",
|
|
1679
|
+
impactDetails: [],
|
|
1680
|
+
preview: null
|
|
1681
|
+
});
|
|
1682
|
+
timer = setTimeout(() => {
|
|
1683
|
+
prepare(token);
|
|
1684
|
+
}, 120);
|
|
1685
|
+
}
|
|
1686
|
+
const model = {
|
|
1687
|
+
getSnapshot: () => snapshot,
|
|
1688
|
+
subscribe(listener) {
|
|
1689
|
+
listeners.add(listener);
|
|
1690
|
+
listener(snapshot);
|
|
1691
|
+
return () => {
|
|
1692
|
+
listeners.delete(listener);
|
|
1693
|
+
};
|
|
1694
|
+
},
|
|
1695
|
+
async reload() {
|
|
1696
|
+
const token = invalidate();
|
|
1697
|
+
publish({
|
|
1698
|
+
phase: "loading",
|
|
1699
|
+
canApply: false,
|
|
1700
|
+
stale: false,
|
|
1701
|
+
error: null,
|
|
1702
|
+
impactDetails: [],
|
|
1703
|
+
preview: null
|
|
1704
|
+
});
|
|
1705
|
+
try {
|
|
1706
|
+
const [marks, sectionList] = await Promise.all([editor.doc.watermarks.list(), editor.doc.sections.list()]);
|
|
1707
|
+
if (!current(token)) return;
|
|
1708
|
+
if (marks.items.length !== marks.total || sectionList.items.length !== sectionList.total) throw new Error("The complete watermark scope could not be loaded.");
|
|
1709
|
+
if (sectionList.evaluatedRevision && sectionList.evaluatedRevision !== marks.evaluatedRevision) throw new Error(STALE_MESSAGE);
|
|
1710
|
+
catalog = marks.items;
|
|
1711
|
+
sections = sectionList.items;
|
|
1712
|
+
revision = marks.evaluatedRevision;
|
|
1713
|
+
const available = scopedItems();
|
|
1714
|
+
const selected = available.length === 1 ? available[0] : void 0;
|
|
1715
|
+
const draft = createWatermarkDraft(selected);
|
|
1716
|
+
originalDraft = JSON.stringify(draft);
|
|
1717
|
+
publish({
|
|
1718
|
+
draft,
|
|
1719
|
+
dirty: false,
|
|
1720
|
+
selectedIds: selected ? [selected.watermarkId] : [],
|
|
1721
|
+
sections: sections.map((item) => ({
|
|
1722
|
+
id: item.address.sectionId,
|
|
1723
|
+
label: `Section ${item.index + 1}`
|
|
1724
|
+
})),
|
|
1725
|
+
readonlyReason: editable() ? null : options.getMode() === "suggesting" ? "Watermarks cannot be changed in Suggesting mode." : "This document is read-only.",
|
|
1726
|
+
impact: available.length > 1 ? "Select the watermark copies you want to edit, or add a new watermark." : ""
|
|
1727
|
+
});
|
|
1728
|
+
publishCatalog();
|
|
1729
|
+
await prepare(token);
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
if (current(token)) publish({
|
|
1732
|
+
phase: "editing",
|
|
1733
|
+
error: failureMessage(error),
|
|
1734
|
+
canApply: false
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
patchDraft(patch) {
|
|
1739
|
+
if (!alive || snapshot.phase === "loading" || snapshot.phase === "applying" || !editable()) return;
|
|
1740
|
+
const draft = patchWatermarkDraft(snapshot.draft, patch);
|
|
1741
|
+
publish({
|
|
1742
|
+
draft,
|
|
1743
|
+
dirty: JSON.stringify(draft) !== originalDraft
|
|
1744
|
+
});
|
|
1745
|
+
schedulePreview();
|
|
1746
|
+
},
|
|
1747
|
+
setScope(sectionId, variant) {
|
|
1748
|
+
if (!alive || snapshot.phase === "loading" || snapshot.phase === "applying") return;
|
|
1749
|
+
if (sectionId != null && !sections.some((item) => item.address.sectionId === sectionId)) return;
|
|
1750
|
+
publish({
|
|
1751
|
+
sectionId,
|
|
1752
|
+
variant
|
|
1753
|
+
});
|
|
1754
|
+
publishCatalog();
|
|
1755
|
+
const available = scopedItems();
|
|
1756
|
+
const visible = new Set(available.map((item) => item.watermarkId));
|
|
1757
|
+
let selectedIds = snapshot.selectedIds.filter((id) => visible.has(id));
|
|
1758
|
+
if (snapshot.dirty && snapshot.draft.kind === "none" && !selectedIds.length && available.length === 1) selectedIds = [available[0].watermarkId];
|
|
1759
|
+
publish({ selectedIds });
|
|
1760
|
+
if (!snapshot.dirty) model.selectWatermarks(available.length === 1 ? [available[0].watermarkId] : []);
|
|
1761
|
+
else schedulePreview();
|
|
1762
|
+
},
|
|
1763
|
+
selectWatermarks(ids) {
|
|
1764
|
+
if (!alive || snapshot.phase === "loading" || snapshot.phase === "applying") return;
|
|
1765
|
+
const available = scopedItems();
|
|
1766
|
+
const selectedIds = [...new Set(ids)].filter((id) => available.some((item) => item.watermarkId === id));
|
|
1767
|
+
const draft = createWatermarkDraft(available.find((item) => item.watermarkId === selectedIds[0]));
|
|
1768
|
+
originalDraft = JSON.stringify(draft);
|
|
1769
|
+
publish({
|
|
1770
|
+
selectedIds,
|
|
1771
|
+
draft,
|
|
1772
|
+
dirty: false
|
|
1773
|
+
});
|
|
1774
|
+
schedulePreview();
|
|
1775
|
+
},
|
|
1776
|
+
async apply() {
|
|
1777
|
+
if (!alive || !snapshot.canApply || !preparedInput || !editable() || options.getEditor() !== editor) return;
|
|
1778
|
+
const input = preparedInput;
|
|
1779
|
+
const token = invalidate();
|
|
1780
|
+
publish({
|
|
1781
|
+
phase: "applying",
|
|
1782
|
+
canApply: false,
|
|
1783
|
+
error: null
|
|
1784
|
+
});
|
|
1785
|
+
options.announce?.("Applying watermark changes. Closing the dialog will not cancel them.");
|
|
1786
|
+
try {
|
|
1787
|
+
const receipt = await editor.doc.watermarks.apply(input, { expectedRevision: revision });
|
|
1788
|
+
if (!current(token)) {
|
|
1789
|
+
options.announce?.(receipt.success ? "Watermark updated." : "The watermark could not be updated.");
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (rejectReceipt(receipt)) return;
|
|
1793
|
+
options.announce?.(input.action === "remove" ? "Watermark removed." : "Watermark updated.");
|
|
1794
|
+
onSuccess();
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
if (current(token)) publish({
|
|
1797
|
+
phase: "editing",
|
|
1798
|
+
error: failureMessage(error),
|
|
1799
|
+
canApply: false
|
|
1800
|
+
});
|
|
1801
|
+
else options.announce?.(failureMessage(error));
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
const stopEvents = editor.host?.events?.subscribe((event) => {
|
|
1806
|
+
if (!alive || snapshot.phase === "applying") return;
|
|
1807
|
+
if (event.type === "collaboration:document-replaced") {
|
|
1808
|
+
invalidate();
|
|
1809
|
+
publish({
|
|
1810
|
+
phase: "editing",
|
|
1811
|
+
stale: true,
|
|
1812
|
+
canApply: false,
|
|
1813
|
+
error: STALE_MESSAGE
|
|
1814
|
+
});
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (![
|
|
1818
|
+
"document:mutated",
|
|
1819
|
+
"mutation:committed",
|
|
1820
|
+
"collaboration:remote-changed"
|
|
1821
|
+
].includes(event.type)) return;
|
|
1822
|
+
invalidate();
|
|
1823
|
+
if (snapshot.dirty) publish({
|
|
1824
|
+
phase: "editing",
|
|
1825
|
+
stale: true,
|
|
1826
|
+
canApply: false,
|
|
1827
|
+
error: STALE_MESSAGE
|
|
1828
|
+
});
|
|
1829
|
+
else timer = setTimeout(() => {
|
|
1830
|
+
model.reload();
|
|
1831
|
+
}, 120);
|
|
1832
|
+
});
|
|
1833
|
+
return {
|
|
1834
|
+
model,
|
|
1835
|
+
dispose() {
|
|
1836
|
+
alive = false;
|
|
1837
|
+
invalidate();
|
|
1838
|
+
stopEvents?.();
|
|
1839
|
+
listeners.clear();
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
//#endregion
|
|
537
1844
|
//#region src/public/ui/format-painter-helpers.js
|
|
538
1845
|
/**
|
|
539
1846
|
* Maps SDRunProps field names to InlineRunPatch field names.
|
|
@@ -11563,6 +12870,15 @@ function createSuperDocUI(options) {
|
|
|
11563
12870
|
getQuickGallery: () => stylesSub.get().quickGallery,
|
|
11564
12871
|
getActiveParagraphStyle: () => computeActiveParagraphStyle(state.selection, getStyleCatalog().cache).style
|
|
11565
12872
|
};
|
|
12873
|
+
const watermark = createWatermarkController({
|
|
12874
|
+
getEditor,
|
|
12875
|
+
getMode: readDocumentMode,
|
|
12876
|
+
getFonts: () => fonts.getFamilyOptions(),
|
|
12877
|
+
getContainer: () => superdoc.element ?? null,
|
|
12878
|
+
openSurface: typeof superdoc.openSurface === "function" ? (request) => superdoc.openSurface(request) : null
|
|
12879
|
+
});
|
|
12880
|
+
documentResetHooks.push(watermark.close);
|
|
12881
|
+
documentModeChangeHooks.push(watermark.close);
|
|
11566
12882
|
const createScope = () => {
|
|
11567
12883
|
const scopeUnsubs = [];
|
|
11568
12884
|
return {
|
|
@@ -11584,6 +12900,7 @@ function createSuperDocUI(options) {
|
|
|
11584
12900
|
};
|
|
11585
12901
|
const destroy = () => {
|
|
11586
12902
|
if (disposed) return;
|
|
12903
|
+
watermark.destroy();
|
|
11587
12904
|
clearContentControlHighlight();
|
|
11588
12905
|
disposed = true;
|
|
11589
12906
|
releaseSharedUiTrackedChangesCatalog(uiTrackedChangesCatalogHost, uiTrackedChangesCatalogState);
|
|
@@ -11666,6 +12983,7 @@ function createSuperDocUI(options) {
|
|
|
11666
12983
|
tables,
|
|
11667
12984
|
search,
|
|
11668
12985
|
contextMenu,
|
|
12986
|
+
watermark,
|
|
11669
12987
|
styles,
|
|
11670
12988
|
formatPainter: {
|
|
11671
12989
|
setPointerSelecting(flag) {
|
|
@@ -11878,4 +13196,81 @@ function commandIsActive(descriptor, selection) {
|
|
|
11878
13196
|
return descriptor.activeMark ? selection.activeMarks.includes(descriptor.activeMark) : false;
|
|
11879
13197
|
}
|
|
11880
13198
|
//#endregion
|
|
11881
|
-
|
|
13199
|
+
Object.defineProperty(exports, "BUILT_IN_COMMAND_IDS", {
|
|
13200
|
+
enumerable: true,
|
|
13201
|
+
get: function() {
|
|
13202
|
+
return BUILT_IN_COMMAND_IDS;
|
|
13203
|
+
}
|
|
13204
|
+
});
|
|
13205
|
+
Object.defineProperty(exports, "DOM_CLASS_NAMES", {
|
|
13206
|
+
enumerable: true,
|
|
13207
|
+
get: function() {
|
|
13208
|
+
return DOM_CLASS_NAMES;
|
|
13209
|
+
}
|
|
13210
|
+
});
|
|
13211
|
+
Object.defineProperty(exports, "applyV2CommentInvalidationFromMutation", {
|
|
13212
|
+
enumerable: true,
|
|
13213
|
+
get: function() {
|
|
13214
|
+
return applyV2CommentInvalidationFromMutation;
|
|
13215
|
+
}
|
|
13216
|
+
});
|
|
13217
|
+
Object.defineProperty(exports, "closeInteractionHistory", {
|
|
13218
|
+
enumerable: true,
|
|
13219
|
+
get: function() {
|
|
13220
|
+
return closeInteractionHistory;
|
|
13221
|
+
}
|
|
13222
|
+
});
|
|
13223
|
+
Object.defineProperty(exports, "createImageFilePicker", {
|
|
13224
|
+
enumerable: true,
|
|
13225
|
+
get: function() {
|
|
13226
|
+
return createImageFilePicker;
|
|
13227
|
+
}
|
|
13228
|
+
});
|
|
13229
|
+
Object.defineProperty(exports, "createInteractionHistory", {
|
|
13230
|
+
enumerable: true,
|
|
13231
|
+
get: function() {
|
|
13232
|
+
return createInteractionHistory;
|
|
13233
|
+
}
|
|
13234
|
+
});
|
|
13235
|
+
Object.defineProperty(exports, "createSuperDocUI", {
|
|
13236
|
+
enumerable: true,
|
|
13237
|
+
get: function() {
|
|
13238
|
+
return createSuperDocUI;
|
|
13239
|
+
}
|
|
13240
|
+
});
|
|
13241
|
+
Object.defineProperty(exports, "executeFirstPartyCommandAsync", {
|
|
13242
|
+
enumerable: true,
|
|
13243
|
+
get: function() {
|
|
13244
|
+
return executeFirstPartyCommandAsync;
|
|
13245
|
+
}
|
|
13246
|
+
});
|
|
13247
|
+
Object.defineProperty(exports, "getV2TrackedChangeMutationImpact", {
|
|
13248
|
+
enumerable: true,
|
|
13249
|
+
get: function() {
|
|
13250
|
+
return getV2TrackedChangeMutationImpact;
|
|
13251
|
+
}
|
|
13252
|
+
});
|
|
13253
|
+
Object.defineProperty(exports, "recordInteraction", {
|
|
13254
|
+
enumerable: true,
|
|
13255
|
+
get: function() {
|
|
13256
|
+
return recordInteraction;
|
|
13257
|
+
}
|
|
13258
|
+
});
|
|
13259
|
+
Object.defineProperty(exports, "registerFirstPartyCommandMutation", {
|
|
13260
|
+
enumerable: true,
|
|
13261
|
+
get: function() {
|
|
13262
|
+
return registerFirstPartyCommandMutation;
|
|
13263
|
+
}
|
|
13264
|
+
});
|
|
13265
|
+
Object.defineProperty(exports, "resolveImageSrc", {
|
|
13266
|
+
enumerable: true,
|
|
13267
|
+
get: function() {
|
|
13268
|
+
return resolveImageSrc;
|
|
13269
|
+
}
|
|
13270
|
+
});
|
|
13271
|
+
Object.defineProperty(exports, "shallowEqual", {
|
|
13272
|
+
enumerable: true,
|
|
13273
|
+
get: function() {
|
|
13274
|
+
return shallowEqual;
|
|
13275
|
+
}
|
|
13276
|
+
});
|