@embedpdf/viewer-chrome 3.0.0-next.13 → 3.0.0-next.15

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.
Files changed (157) hide show
  1. package/README.md +175 -132
  2. package/dist/annotation-fonts-BqoLZ4Mp.js +80 -0
  3. package/dist/annotation-fonts-BqoLZ4Mp.js.map +1 -0
  4. package/dist/annotation-fonts-Dt0IQXhz.cjs +91 -0
  5. package/dist/annotation-fonts-Dt0IQXhz.cjs.map +1 -0
  6. package/dist/annotation-style--rPIZsuP.js +879 -0
  7. package/dist/annotation-style--rPIZsuP.js.map +1 -0
  8. package/dist/annotation-style-Bzvvp8-N.cjs +879 -0
  9. package/dist/annotation-style-Bzvvp8-N.cjs.map +1 -0
  10. package/dist/ar-D9XKAmDx.js +567 -0
  11. package/dist/ar-D9XKAmDx.js.map +1 -0
  12. package/dist/ar-DnEj5vbP.cjs +567 -0
  13. package/dist/ar-DnEj5vbP.cjs.map +1 -0
  14. package/dist/chrome.cjs +378 -221
  15. package/dist/chrome.cjs.map +1 -1
  16. package/dist/chrome.d.cts +5 -34
  17. package/dist/chrome.d.ts +5 -34
  18. package/dist/chrome.js +377 -218
  19. package/dist/chrome.js.map +1 -1
  20. package/dist/commands-BmgxhJ8B.js +953 -0
  21. package/dist/commands-BmgxhJ8B.js.map +1 -0
  22. package/dist/commands-CYt4SFUX.cjs +1060 -0
  23. package/dist/commands-CYt4SFUX.cjs.map +1 -0
  24. package/dist/commands.cjs +12 -595
  25. package/dist/commands.d.cts +29 -4
  26. package/dist/commands.d.ts +29 -4
  27. package/dist/commands.js +2 -593
  28. package/dist/comments-panel-MUVgLCBI.js +514 -0
  29. package/dist/comments-panel-MUVgLCBI.js.map +1 -0
  30. package/dist/comments-panel-pEcQg4Vs.cjs +514 -0
  31. package/dist/comments-panel-pEcQg4Vs.cjs.map +1 -0
  32. package/dist/de-Bdr4aWzm.js +566 -0
  33. package/dist/de-Bdr4aWzm.js.map +1 -0
  34. package/dist/de-Dv6zvbnd.cjs +566 -0
  35. package/dist/de-Dv6zvbnd.cjs.map +1 -0
  36. package/dist/dialog-B5GBisKv.js +55 -0
  37. package/dist/dialog-B5GBisKv.js.map +1 -0
  38. package/dist/dialog-t8F13BL_.cjs +60 -0
  39. package/dist/dialog-t8F13BL_.cjs.map +1 -0
  40. package/dist/es-BP09DR9w.cjs +566 -0
  41. package/dist/es-BP09DR9w.cjs.map +1 -0
  42. package/dist/es-BSXz5tBv.js +566 -0
  43. package/dist/es-BSXz5tBv.js.map +1 -0
  44. package/dist/form-design-panel-B5MXTZ96.cjs +418 -0
  45. package/dist/form-design-panel-B5MXTZ96.cjs.map +1 -0
  46. package/dist/form-design-panel-vWWU9uB-.js +418 -0
  47. package/dist/form-design-panel-vWWU9uB-.js.map +1 -0
  48. package/dist/fr-BBQQn7tJ.cjs +566 -0
  49. package/dist/fr-BBQQn7tJ.cjs.map +1 -0
  50. package/dist/fr-Ckn3K9v1.js +566 -0
  51. package/dist/fr-Ckn3K9v1.js.map +1 -0
  52. package/dist/he-DRsG7bH7.cjs +567 -0
  53. package/dist/he-DRsG7bH7.cjs.map +1 -0
  54. package/dist/he-wdGU5PkU.js +567 -0
  55. package/dist/he-wdGU5PkU.js.map +1 -0
  56. package/dist/icons-BrK5iVL8.js +856 -0
  57. package/dist/icons-BrK5iVL8.js.map +1 -0
  58. package/dist/icons-x0Tc05bs.cjs +1010 -0
  59. package/dist/icons-x0Tc05bs.cjs.map +1 -0
  60. package/dist/index.cjs +3162 -5106
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +636 -143
  63. package/dist/index.d.ts +636 -143
  64. package/dist/index.js +3083 -4975
  65. package/dist/index.js.map +1 -1
  66. package/dist/it-BuP4o0xQ.cjs +566 -0
  67. package/dist/it-BuP4o0xQ.cjs.map +1 -0
  68. package/dist/it-Us-VmvIu.js +566 -0
  69. package/dist/it-Us-VmvIu.js.map +1 -0
  70. package/dist/ja-Cizj0-5O.js +566 -0
  71. package/dist/ja-Cizj0-5O.js.map +1 -0
  72. package/dist/ja-DovEd6ts.cjs +566 -0
  73. package/dist/ja-DovEd6ts.cjs.map +1 -0
  74. package/dist/ko-B-qJEol2.cjs +566 -0
  75. package/dist/ko-B-qJEol2.cjs.map +1 -0
  76. package/dist/ko-DlrXliE1.js +566 -0
  77. package/dist/ko-DlrXliE1.js.map +1 -0
  78. package/dist/labels-BiM9wp9K.cjs +21 -0
  79. package/dist/labels-BiM9wp9K.cjs.map +1 -0
  80. package/dist/labels-C6_i87Oj.js +16 -0
  81. package/dist/labels-C6_i87Oj.js.map +1 -0
  82. package/dist/link-editor-CSjqPnj1.cjs +125 -0
  83. package/dist/link-editor-CSjqPnj1.cjs.map +1 -0
  84. package/dist/link-editor-SxM1nnJU.js +125 -0
  85. package/dist/link-editor-SxM1nnJU.js.map +1 -0
  86. package/dist/measurement-Bs61XWbJ.js +325 -0
  87. package/dist/measurement-Bs61XWbJ.js.map +1 -0
  88. package/dist/measurement-CcCsHPOX.cjs +326 -0
  89. package/dist/measurement-CcCsHPOX.cjs.map +1 -0
  90. package/dist/nl-0aF7M_0h.js +566 -0
  91. package/dist/nl-0aF7M_0h.js.map +1 -0
  92. package/dist/nl-D3KZDDiJ.cjs +566 -0
  93. package/dist/nl-D3KZDDiJ.cjs.map +1 -0
  94. package/dist/pl-BW--AaCI.js +566 -0
  95. package/dist/pl-BW--AaCI.js.map +1 -0
  96. package/dist/pl-DSFhAUqx.cjs +566 -0
  97. package/dist/pl-DSFhAUqx.cjs.map +1 -0
  98. package/dist/pt-C5S-8MC5.cjs +566 -0
  99. package/dist/pt-C5S-8MC5.cjs.map +1 -0
  100. package/dist/pt-DIhpU5pT.js +566 -0
  101. package/dist/pt-DIhpU5pT.js.map +1 -0
  102. package/dist/redact-confirm-B6j36TPw.js +83 -0
  103. package/dist/redact-confirm-B6j36TPw.js.map +1 -0
  104. package/dist/redact-confirm-CX_SY2ZN.cjs +83 -0
  105. package/dist/redact-confirm-CX_SY2ZN.cjs.map +1 -0
  106. package/dist/redaction-panel-BmoIl-Ll.cjs +96 -0
  107. package/dist/redaction-panel-BmoIl-Ll.cjs.map +1 -0
  108. package/dist/redaction-panel-IJDshSJI.js +96 -0
  109. package/dist/redaction-panel-IJDshSJI.js.map +1 -0
  110. package/dist/search-panel-HfIhJm1M.js +249 -0
  111. package/dist/search-panel-HfIhJm1M.js.map +1 -0
  112. package/dist/search-panel-s9IoiOtv.cjs +249 -0
  113. package/dist/search-panel-s9IoiOtv.cjs.map +1 -0
  114. package/dist/sheet-BDsa_Sup.cjs +442 -0
  115. package/dist/sheet-BDsa_Sup.cjs.map +1 -0
  116. package/dist/sheet-R6XDgyEf.js +365 -0
  117. package/dist/sheet-R6XDgyEf.js.map +1 -0
  118. package/dist/sign-dialog-CgnR7akj.cjs +155 -0
  119. package/dist/sign-dialog-CgnR7akj.cjs.map +1 -0
  120. package/dist/sign-dialog-GDqCoQhA.js +155 -0
  121. package/dist/sign-dialog-GDqCoQhA.js.map +1 -0
  122. package/dist/signature-inspector-DyO313Qf.cjs +166 -0
  123. package/dist/signature-inspector-DyO313Qf.cjs.map +1 -0
  124. package/dist/signature-inspector-okFz8do6.js +166 -0
  125. package/dist/signature-inspector-okFz8do6.js.map +1 -0
  126. package/dist/signature-maker-BhCmYyHD.js +393 -0
  127. package/dist/signature-maker-BhCmYyHD.js.map +1 -0
  128. package/dist/signature-maker-BoYa8vgA.cjs +393 -0
  129. package/dist/signature-maker-BoYa8vgA.cjs.map +1 -0
  130. package/dist/signatures-panel-B56PC6d9.cjs +300 -0
  131. package/dist/signatures-panel-B56PC6d9.cjs.map +1 -0
  132. package/dist/signatures-panel-CgnFfhFm.js +300 -0
  133. package/dist/signatures-panel-CgnFfhFm.js.map +1 -0
  134. package/dist/stamp-store-BHvRs-Do.cjs +147 -0
  135. package/dist/stamp-store-BHvRs-Do.cjs.map +1 -0
  136. package/dist/stamp-store-WuF-L3Jj.js +112 -0
  137. package/dist/stamp-store-WuF-L3Jj.js.map +1 -0
  138. package/dist/stamps-panel-B9NYGayn.cjs +231 -0
  139. package/dist/stamps-panel-B9NYGayn.cjs.map +1 -0
  140. package/dist/stamps-panel-CDO0FPyK.js +231 -0
  141. package/dist/stamps-panel-CDO0FPyK.js.map +1 -0
  142. package/dist/toolbar-CJu2wKTU.cjs +1659 -0
  143. package/dist/toolbar-CJu2wKTU.cjs.map +1 -0
  144. package/dist/toolbar-z9od3tKl.js +1564 -0
  145. package/dist/toolbar-z9od3tKl.js.map +1 -0
  146. package/dist/zh-cn-3tmMeHqv.cjs +566 -0
  147. package/dist/zh-cn-3tmMeHqv.cjs.map +1 -0
  148. package/dist/zh-cn-CxkUUBct.js +566 -0
  149. package/dist/zh-cn-CxkUUBct.js.map +1 -0
  150. package/package.json +6 -4
  151. package/src/styles.css +153 -89
  152. package/dist/commands.cjs.map +0 -1
  153. package/dist/commands.js.map +0 -1
  154. package/dist/es-DYwzfxyt.cjs +0 -293
  155. package/dist/es-DYwzfxyt.cjs.map +0 -1
  156. package/dist/es-sN6uqBgD.js +0 -293
  157. package/dist/es-sN6uqBgD.js.map +0 -1
@@ -0,0 +1,953 @@
1
+ import { DocumentsToken } from "@embedpdf/react/runtime";
2
+ import { StageToken, ZoomMode } from "@embedpdf/react/stage";
3
+ import { InteractionToken } from "@embedpdf/react/interaction";
4
+ import { SelectionToken, copySelection } from "@embedpdf/react/selection";
5
+ import { AnnotationToken } from "@embedpdf/react/annotation";
6
+ import { StampToken } from "@embedpdf/react/stamp";
7
+ import { MeasurementToken } from "@embedpdf/react/measurement";
8
+ import { RedactionToken } from "@embedpdf/react/redaction";
9
+ import { ActionsToken } from "@embedpdf/react/actions";
10
+ import { FormToken } from "@embedpdf/react/form";
11
+ import { SignatureToken } from "@embedpdf/react/signature";
12
+ import { LinkToken } from "@embedpdf/react/link";
13
+ import { I18nToken } from "@embedpdf/react/i18n";
14
+ import { CommandsToken } from "@embedpdf/react/commands";
15
+ import { HistoryToken } from "@embedpdf/react/history";
16
+ import { ShellToken } from "@embedpdf/react/shell";
17
+ import { barsInMode, modeCommandOf, modesOf, panelCommandOf } from "@embedpdf/core-ui";
18
+ import { pickFile } from "@embedpdf/web";
19
+ //#region src/config/modes.ts
20
+ /** The exclusivity tag every mode surface opens with. */
21
+ const MODE_TAG = "mode";
22
+ /** The open mode's surface (`mode:<mode>`), or `null` while viewing. */
23
+ function openModeSurfaceOf(shell) {
24
+ return shell.listOpenSurfaces().find((surface) => surface.exclusive === "mode")?.id ?? null;
25
+ }
26
+ /** The layout's modes, in bar order, each titled by its first bar with a title. */
27
+ function modeSpecsOf(data) {
28
+ return modesOf(data).map((mode) => {
29
+ const titled = barsInMode(data, mode).map((barId) => data.bars[barId]).find((bar) => bar.title !== void 0 || bar.titleKey !== void 0);
30
+ return {
31
+ mode,
32
+ title: titled?.title,
33
+ titleKey: titled?.titleKey
34
+ };
35
+ });
36
+ }
37
+ /** Whether two lists of modes are the same, title for title. */
38
+ const sameModeSpecs = (left, right) => left.length === right.length && left.every((spec, i) => spec.mode === right[i].mode && spec.title === right[i].title && spec.titleKey === right[i].titleKey);
39
+ /**
40
+ * A command for each mode that has none yet (`isTaken` says which ids do): it opens the mode,
41
+ * or closes it when it is open, and shows the default pointer. Without a title, its tab reads
42
+ * the mode's own name.
43
+ */
44
+ function modeCommandsFor(specs, isTaken) {
45
+ return specs.filter((spec) => !isTaken(modeCommandOf(spec.mode))).map((spec) => {
46
+ const id = modeCommandOf(spec.mode);
47
+ return {
48
+ id,
49
+ label: spec.title ?? (spec.titleKey === void 0 ? spec.mode : void 0),
50
+ labelKey: spec.titleKey,
51
+ run: (commandContext) => {
52
+ commandContext.tryGet(ShellToken)?.toggle(id, { exclusive: MODE_TAG });
53
+ commandContext.tryGet(InteractionToken)?.activateTool("pointer");
54
+ },
55
+ active: (commandContext) => commandContext.tryGet(ShellToken)?.isOpen(id) ?? false
56
+ };
57
+ });
58
+ }
59
+ //#endregion
60
+ //#region src/config/open-files.ts
61
+ const OPEN_COMMAND_ID = "document:open";
62
+ const PDF_TYPE = "application/pdf";
63
+ /** Whether a file is a PDF, by its type or, where the system gives it none, its name. */
64
+ const isPdfFile = (file) => file.type === PDF_TYPE || /\.pdf$/i.test(file.name);
65
+ /**
66
+ * Whether a drag may bring a PDF: it carries files, and one of them is a PDF or of a type the
67
+ * browser doesn't know. Text, links and anything dragged inside the page carry no files; where
68
+ * the browser doesn't list the files while dragging, they may be PDFs.
69
+ */
70
+ function mayCarryPdf(transfer) {
71
+ if (!transfer || !Array.from(transfer.types).includes("Files")) return false;
72
+ const files = Array.from(transfer.items ?? []).filter((item) => item.kind === "file");
73
+ return files.length === 0 || files.some((item) => item.type === PDF_TYPE || item.type === "");
74
+ }
75
+ const givenFilesOf = (args) => {
76
+ const files = args?.files;
77
+ return Array.isArray(files) ? files : null;
78
+ };
79
+ /** The file the user picks in the file dialog, as a list: empty when they close it. */
80
+ const pickedFiles = () => pickFile({ accept: `.pdf,${PDF_TYPE}` }).then((file) => file ? [file] : []);
81
+ /**
82
+ * Open each file as a new tab, the first one shown. Its tab is there at once, named like a `src`
83
+ * file's (its name without `.pdf`), while the file is read; one that can't be opened says why
84
+ * in its tab.
85
+ */
86
+ function openFiles(documents, files) {
87
+ documents.openAll(files.map((file) => ({
88
+ name: file.name.replace(/\.pdf$/i, "") || file.name,
89
+ source: async () => ({
90
+ kind: "bytes",
91
+ bytes: new Uint8Array(await file.arrayBuffer())
92
+ })
93
+ })));
94
+ }
95
+ const openFileCommand = {
96
+ id: OPEN_COMMAND_ID,
97
+ labelKey: "commands.open",
98
+ icon: "fileImport",
99
+ shortcut: "Mod+O",
100
+ categories: ["documents.open"],
101
+ visible: (commandContext) => commandContext.tryGet(DocumentsToken)?.canOpenFiles() ?? false,
102
+ run: async (commandContext) => {
103
+ const documents = commandContext.tryGet(DocumentsToken);
104
+ if (!documents) return;
105
+ openFiles(documents, givenFilesOf(commandContext.args) ?? await pickedFiles());
106
+ }
107
+ };
108
+ //#endregion
109
+ //#region src/config/commands.ts
110
+ /**
111
+ * The command vocabulary — the semantic layer. Every command is a pure value:
112
+ * label key, icon, shortcut, derivations, and a `run` (or a declarative
113
+ * surface target). Commands reach plugins through a typed
114
+ * `commandContext.tryGet(Token)`, never by string id and cast.
115
+ *
116
+ * Each command names its most specific feature in `categories` (the tree in
117
+ * `../features.ts`), so `disable` and `readOnly` turn it off, and whether the
118
+ * session may run it in `allowed`, from the owning plugin's `can*` check
119
+ * (docs/conventions/permissions.md), so a user who may not doesn't see it.
120
+ * `enabled` is only for what can't run right now.
121
+ *
122
+ * Wiring:
123
+ * zoom/pan/pointer/spread/scroll/rotate → real Stage / Interaction / PageEdit
124
+ * modes → shell exclusive surfaces ('mode')
125
+ * panels → the layout's `panel:<id>` (panels.ts)
126
+ * menus / modals → declarative shell targets
127
+ * annotate + shape tools → real interaction tools
128
+ * form tools → real form plugin palette (draw-to-place)
129
+ * insert tools → real stamp and signature panels +
130
+ * image/attachment click-then-pick
131
+ * history undo/redo → real History
132
+ */
133
+ /** The dialog the Go to Page command opens without a page to go to (ui/go-to-page.tsx). */
134
+ const GO_TO_PAGE_DIALOG = "go-to-page";
135
+ /** The keyboard shortcuts sheet, which `?` opens (ui/shortcuts-sheet.tsx). */
136
+ const SHORTCUTS_DIALOG = "shortcuts";
137
+ /** The confirmation before marked areas are removed for good (ui/redact-confirm.tsx). */
138
+ const REDACT_CONFIRM_DIALOG = "redact-confirm";
139
+ /** The form designer's sidebar (ui/form-design-panel.tsx), which `form:design` opens. */
140
+ const FORM_DESIGN_PANEL = "form-design";
141
+ /** Form design's resting tool, the form plugin's: widgets select, move and resize there. */
142
+ const FORM_DESIGN_TOOL = "form-edit";
143
+ /** The form plugin's field tools, each placing a field (the Form bar's `tool:*` buttons). */
144
+ const FORM_FIELD_TOOLS = [
145
+ "form-text",
146
+ "form-checkbox",
147
+ "form-radio",
148
+ "form-combobox",
149
+ "form-listbox",
150
+ "form-button",
151
+ "form-signature"
152
+ ];
153
+ /**
154
+ * Whether a tool designs a form: design's resting tool, or a field tool. Neither fills, so the
155
+ * form lends its widgets to be selected, moved and resized.
156
+ */
157
+ const isFormDesignTool = (tool) => tool === "form-edit" || tool != null && FORM_FIELD_TOOLS.includes(tool);
158
+ /** Whether a form is being designed: the active tool designs it. */
159
+ const isDesigningForm = (commandContext) => isFormDesignTool(commandContext.tryGet(InteractionToken)?.getActiveToolId());
160
+ /** Where selection-made stamps go: the user's own library, persisted. */
161
+ const CUSTOM_LIBRARY_ID = "embedpdf-custom";
162
+ const stage = (commandContext) => commandContext.tryGet(StageToken);
163
+ const interaction = (commandContext) => commandContext.tryGet(InteractionToken);
164
+ const anno = (commandContext) => commandContext.tryGet(AnnotationToken);
165
+ const textSelection = (commandContext) => commandContext.tryGet(SelectionToken);
166
+ /** Drawing an annotation of any kind: `annotation.canCreate()`. */
167
+ const CREATES_ANNOTATIONS = {
168
+ allowed: (commandContext) => anno(commandContext)?.canCreate() ?? true,
169
+ notAllowedKey: "commands.notAllowed.annotations-create"
170
+ };
171
+ /** Placing and changing form fields: `form.canDesign()`. */
172
+ const DESIGNS_FORMS = {
173
+ allowed: (commandContext) => commandContext.tryGet(FormToken)?.canDesign() ?? true,
174
+ notAllowedKey: "commands.notAllowed.doc-forms-modify"
175
+ };
176
+ /** Placing a stamp or a signature mark: `stamp.canPlace()`. */
177
+ const PLACES_STAMPS = {
178
+ allowed: (commandContext) => commandContext.tryGet(StampToken)?.canPlace(commandContext.documentId ?? void 0) ?? true,
179
+ notAllowedKey: "commands.notAllowed.annotations-create"
180
+ };
181
+ const sameTextRange = (left, right) => left === right || !!left && !!right && left.start.page.objectNumber === right.start.page.objectNumber && left.start.index === right.start.index && left.end.page.objectNumber === right.end.page.objectNumber && left.end.index === right.end.index;
182
+ /**
183
+ * What a command needs to open and close the layout's panel `id`: it runs the panel's own
184
+ * `panel:<id>` command (config/panels.ts), so it opens only what that one may, shows only while
185
+ * the layout has the panel and `visible` says so, and is active while the panel is open.
186
+ */
187
+ const togglesPanel = (id, visible) => ({
188
+ run: (commandContext) => commandContext.tryGet(CommandsToken)?.execute(panelCommandOf(id), { documentId: commandContext.documentId ?? void 0 }),
189
+ active: (commandContext) => commandContext.tryGet(ShellToken)?.isOpen(id) ?? false,
190
+ visible: (commandContext) => (commandContext.tryGet(CommandsToken)?.hasCommand(panelCommandOf(id)) ?? false) && (visible?.(commandContext) ?? true)
191
+ });
192
+ /** A measuring tool can run on the current page: it has a scale (`measurement.canMeasure`). */
193
+ const canMeasureNow = (commandContext) => {
194
+ const page = stage(commandContext)?.getCurrentPage();
195
+ return page != null && (commandContext.tryGet(MeasurementToken)?.canMeasure(page) ?? false);
196
+ };
197
+ const hasAnnotationSelection = (commandContext) => (anno(commandContext)?.selection.list().length ?? 0) > 0;
198
+ /** Whether people may change every selected annotation by hand (no turned-off feature froze it). */
199
+ const selectionEditable = (commandContext) => {
200
+ const annotation = anno(commandContext);
201
+ return (annotation?.selection.list() ?? []).every(({ ref }) => annotation.isEditable(ref));
202
+ };
203
+ /** Strip items gate per subtype (comment hidden on links/widgets) through
204
+ * one derivation over the selected DTOs, not per-command lookups. */
205
+ const selectionSubtypes = (commandContext) => new Set((anno(commandContext)?.selection.list() ?? []).map((annotation) => annotation.subtype));
206
+ /**
207
+ * The selection's `link` value: a target, `null` (linkable but none set), or
208
+ * `undefined` when the selection cannot carry a link at all (widgets, mixed
209
+ * link states) — the schema decides, never a subtype blocklist.
210
+ */
211
+ /** The selection's link target, as the annotation holds it (destinations in the file's coordinates). */
212
+ const selectionLink = (commandContext) => {
213
+ const props = anno(commandContext)?.selection.getProperties();
214
+ if (!props || !props.properties.some((property) => property.key === "link") || props.mixed.includes("link")) return void 0;
215
+ return props.values.link ?? null;
216
+ };
217
+ /**
218
+ * toolId → the same icon + accent definition its toolbar button uses, recorded
219
+ * as a side effect of the `tool()` command definitions below — one source of
220
+ * truth, so the tool cursor (ui/tool-cursor.tsx) and the button can never
221
+ * drift apart.
222
+ */
223
+ const TOOL_ICONS = {};
224
+ TOOL_ICONS["stamp"] = { icon: "rubberStamp" };
225
+ const toolAccent = (commandContext, toolId, accent) => {
226
+ if (!accent) return null;
227
+ const anno = commandContext.tryGet(AnnotationToken);
228
+ if (!anno) return null;
229
+ const defaults = anno.tools.getDefaults(toolId);
230
+ const colorOf = (key) => defaults[key] ?? void 0;
231
+ return {
232
+ primary: colorOf(accent.primary),
233
+ secondary: accent.secondary ? colorOf(accent.secondary) : void 0
234
+ };
235
+ };
236
+ /** toolId → the feature its command belongs to, for the tools of yours that extend it. */
237
+ const TOOL_FEATURES = {};
238
+ /** A tool command without its label: activates a real interaction tool; active = it's the tool.
239
+ * The icon previews the tool's current defaults — keyed by the same toolId
240
+ * as run/active, so the accent can't drift to another tool's colors. */
241
+ const toolCommand = (id, toolId, icon, { feature, check, accent }) => {
242
+ if (icon) TOOL_ICONS[toolId] = {
243
+ icon,
244
+ ...accent ? { accent } : {}
245
+ };
246
+ TOOL_FEATURES[toolId] = feature;
247
+ return {
248
+ id,
249
+ ...icon ? { icon } : {},
250
+ categories: [feature],
251
+ ...check,
252
+ run: (commandContext) => interaction(commandContext)?.activateTool(toolId),
253
+ active: (commandContext) => interaction(commandContext)?.getActiveToolId() === toolId,
254
+ enabled: (commandContext) => interaction(commandContext) != null,
255
+ iconAccent: (commandContext) => toolAccent(commandContext, toolId, accent)
256
+ };
257
+ };
258
+ const tool = (id, toolId, labelKey, icon, options) => ({
259
+ ...toolCommand(id, toolId, icon, options),
260
+ labelKey
261
+ });
262
+ /**
263
+ * Selected text made into a highlight, an underline or a strikeout, with the colors of the
264
+ * tool that makes the same: shown only while there's a selection.
265
+ */
266
+ const markupSelection = (id, toolId, labelKey, icon, feature) => ({
267
+ id,
268
+ labelKey,
269
+ icon,
270
+ categories: [feature],
271
+ ...CREATES_ANNOTATIONS,
272
+ visible: (commandContext) => textSelection(commandContext)?.hasSelection() ?? false,
273
+ run: (commandContext) => anno(commandContext)?.createFromSelection(toolId),
274
+ iconAccent: (commandContext) => toolAccent(commandContext, toolId, { primary: "color" })
275
+ });
276
+ /** The page number `page:go-to` was given (`args: { page }`, counted from 1), or `null`. */
277
+ const pageArgOf = (args) => {
278
+ const page = args?.page;
279
+ return typeof page === "number" && Number.isFinite(page) ? Math.round(page) : null;
280
+ };
281
+ /** A fixed zoom level (fraction), e.g. 1 = 100%. */
282
+ const zoomLevel = (id, level, label) => ({
283
+ id,
284
+ labelKey: label,
285
+ run: (commandContext) => stage(commandContext)?.zoomTo({ level }),
286
+ enabled: (commandContext) => stage(commandContext) != null
287
+ });
288
+ const spread = (id, mode, labelKey, icon) => ({
289
+ id,
290
+ labelKey,
291
+ icon,
292
+ run: (commandContext) => stage(commandContext)?.updateSettings({ spread: mode }),
293
+ active: (commandContext) => stage(commandContext)?.getSettings().spread === mode,
294
+ enabled: (commandContext) => stage(commandContext) != null
295
+ });
296
+ const defaultCommands = [
297
+ {
298
+ id: "zoom:in",
299
+ labelKey: "commands.zoom.in",
300
+ icon: "zoomIn",
301
+ shortcut: ["Mod+=", "Mod+NumpadAdd"],
302
+ run: (commandContext) => stage(commandContext)?.zoomIn(),
303
+ enabled: (commandContext) => stage(commandContext) != null
304
+ },
305
+ {
306
+ id: "zoom:out",
307
+ labelKey: "commands.zoom.out",
308
+ icon: "zoomOut",
309
+ shortcut: ["Mod+-", "Mod+NumpadSubtract"],
310
+ run: (commandContext) => stage(commandContext)?.zoomOut(),
311
+ enabled: (commandContext) => stage(commandContext) != null
312
+ },
313
+ {
314
+ id: "zoom:fit-page",
315
+ labelKey: "commands.zoom.fitPage",
316
+ icon: "fitToPage",
317
+ shortcut: "Mod+0",
318
+ run: (commandContext) => stage(commandContext)?.fitPage(),
319
+ active: (commandContext) => stage(commandContext)?.getZoomMode() === ZoomMode.FitPage,
320
+ enabled: (commandContext) => stage(commandContext) != null
321
+ },
322
+ {
323
+ id: "zoom:fit-width",
324
+ labelKey: "commands.zoom.fitWidth",
325
+ icon: "fitToWidth",
326
+ shortcut: "Mod+1",
327
+ run: (commandContext) => stage(commandContext)?.fitWidth(),
328
+ active: (commandContext) => stage(commandContext)?.getZoomMode() === ZoomMode.FitWidth,
329
+ enabled: (commandContext) => stage(commandContext) != null
330
+ },
331
+ {
332
+ id: "zoom:automatic",
333
+ labelKey: "commands.zoom.automatic",
334
+ run: (commandContext) => stage(commandContext)?.fitAutomatic(),
335
+ active: (commandContext) => stage(commandContext)?.getZoomMode() === ZoomMode.Automatic,
336
+ enabled: (commandContext) => stage(commandContext) != null
337
+ },
338
+ zoomLevel("zoom:50", .5, "commands.zoom.p50"),
339
+ zoomLevel("zoom:100", 1, "commands.zoom.p100"),
340
+ zoomLevel("zoom:150", 1.5, "commands.zoom.p150"),
341
+ zoomLevel("zoom:200", 2, "commands.zoom.p200"),
342
+ zoomLevel("zoom:400", 4, "commands.zoom.p400"),
343
+ {
344
+ id: "menu:zoom",
345
+ labelKey: "commands.zoom.menu",
346
+ icon: "zoomIn",
347
+ menu: "zoom"
348
+ },
349
+ {
350
+ id: "page:next",
351
+ labelKey: "commands.page.next",
352
+ icon: "chevronRight",
353
+ shortcut: "ArrowRight",
354
+ run: (commandContext) => stage(commandContext)?.nextPage(),
355
+ enabled: (commandContext) => stage(commandContext)?.canGoNext() ?? false
356
+ },
357
+ {
358
+ id: "page:previous",
359
+ labelKey: "commands.page.previous",
360
+ icon: "chevronLeft",
361
+ shortcut: "ArrowLeft",
362
+ run: (commandContext) => stage(commandContext)?.previousPage(),
363
+ enabled: (commandContext) => stage(commandContext)?.canGoPrevious() ?? false
364
+ },
365
+ {
366
+ id: "page:first",
367
+ labelKey: "commands.page.first",
368
+ shortcut: "Home",
369
+ run: (commandContext) => stage(commandContext)?.goToFirstPage(),
370
+ enabled: (commandContext) => stage(commandContext)?.canGoPrevious() ?? false
371
+ },
372
+ {
373
+ id: "page:last",
374
+ labelKey: "commands.page.last",
375
+ shortcut: "End",
376
+ run: (commandContext) => stage(commandContext)?.goToLastPage(),
377
+ enabled: (commandContext) => stage(commandContext)?.canGoNext() ?? false
378
+ },
379
+ {
380
+ id: "page:go-to",
381
+ labelKey: "commands.page.goTo",
382
+ run: (commandContext) => {
383
+ const view = stage(commandContext);
384
+ if (!view) return;
385
+ const page = pageArgOf(commandContext.args);
386
+ if (page === null) {
387
+ commandContext.tryGet(ShellToken)?.open(GO_TO_PAGE_DIALOG, { exclusive: "modal" });
388
+ return;
389
+ }
390
+ view.goToPage(Math.min(Math.max(page, 1), view.getPageCount()) - 1);
391
+ },
392
+ enabled: (commandContext) => (stage(commandContext)?.getPageCount() ?? 0) > 0
393
+ },
394
+ {
395
+ id: "tool:pan",
396
+ labelKey: "commands.pan",
397
+ icon: "hand",
398
+ run: (commandContext) => interaction(commandContext)?.activateTool("pan"),
399
+ active: (commandContext) => interaction(commandContext)?.getActiveToolId() === "pan",
400
+ enabled: (commandContext) => interaction(commandContext) != null
401
+ },
402
+ {
403
+ id: "tool:pointer",
404
+ labelKey: "commands.pointer",
405
+ icon: "pointer",
406
+ run: (commandContext) => interaction(commandContext)?.activateTool("pointer"),
407
+ active: (commandContext) => interaction(commandContext)?.getActiveToolId() === "pointer",
408
+ enabled: (commandContext) => interaction(commandContext) != null
409
+ },
410
+ {
411
+ id: "menu:document",
412
+ labelKey: "commands.menu",
413
+ icon: "menu",
414
+ menu: "document"
415
+ },
416
+ {
417
+ id: "menu:view",
418
+ labelKey: "commands.viewControls",
419
+ icon: "viewSettings",
420
+ menu: "view"
421
+ },
422
+ openFileCommand,
423
+ {
424
+ id: "document:download",
425
+ labelKey: "commands.download",
426
+ icon: "download",
427
+ shortcut: "Mod+S",
428
+ categories: ["documents.download"],
429
+ allowed: (commandContext) => commandContext.tryGet(DocumentsToken)?.canDownload() ?? true,
430
+ notAllowedKey: "commands.notAllowed.doc-download",
431
+ run: (commandContext) => {
432
+ const id = commandContext.documentId ?? void 0;
433
+ const documents = commandContext.tryGet(DocumentsToken);
434
+ if (!documents) return;
435
+ documents.download(id).then((bytes) => {
436
+ const blob = new Blob([bytes], { type: "application/pdf" });
437
+ const url = URL.createObjectURL(blob);
438
+ const anchor = document.createElement("a");
439
+ anchor.href = url;
440
+ anchor.download = "document.pdf";
441
+ anchor.click();
442
+ URL.revokeObjectURL(url);
443
+ }).catch((error) => console.warn("[snippet-react] download failed", error));
444
+ },
445
+ enabled: (commandContext) => commandContext.documentId != null
446
+ },
447
+ {
448
+ id: "document:print",
449
+ labelKey: "commands.print",
450
+ icon: "print",
451
+ shortcut: "Mod+P",
452
+ categories: ["documents.print"],
453
+ allowed: (commandContext) => commandContext.tryGet(DocumentsToken)?.canPrint() ?? true,
454
+ notAllowedKey: "commands.notAllowed.doc-print",
455
+ run: (commandContext) => {
456
+ const actions = commandContext.documentId != null ? commandContext.tryGet(ActionsToken) : null;
457
+ if (actions) actions.runDocumentVerb("print", () => window.print());
458
+ else window.print();
459
+ }
460
+ },
461
+ {
462
+ id: "view:fullscreen",
463
+ labelKey: "commands.fullscreen",
464
+ icon: "externalLink",
465
+ run: () => {
466
+ if (document.fullscreenElement) document.exitFullscreen();
467
+ else document.documentElement.requestFullscreen().catch(() => {});
468
+ },
469
+ active: () => Boolean(document.fullscreenElement)
470
+ },
471
+ spread("view:single-page", "none", "commands.spread.none", "singlePage"),
472
+ spread("view:two-pages", "odd", "commands.spread.odd", "doublePage"),
473
+ spread("view:cover-page", "even", "commands.spread.even", "book2"),
474
+ {
475
+ id: "view:scroll-vertical",
476
+ labelKey: "commands.scroll.vertical",
477
+ icon: "vertical",
478
+ run: (commandContext) => stage(commandContext)?.updateSettings({ layout: "vertical" }),
479
+ active: (commandContext) => stage(commandContext)?.getSettings().layout === "vertical",
480
+ enabled: (commandContext) => stage(commandContext) != null
481
+ },
482
+ {
483
+ id: "view:scroll-horizontal",
484
+ labelKey: "commands.scroll.horizontal",
485
+ icon: "horizontal",
486
+ run: (commandContext) => stage(commandContext)?.updateSettings({ layout: "horizontal" }),
487
+ active: (commandContext) => stage(commandContext)?.getSettings().layout === "horizontal",
488
+ enabled: (commandContext) => stage(commandContext) != null
489
+ },
490
+ {
491
+ id: "view:rotate-clockwise",
492
+ labelKey: "commands.rotate.clockwise",
493
+ icon: "rotateClockwise",
494
+ run: (commandContext) => stage(commandContext)?.rotateViewBy(90),
495
+ enabled: (commandContext) => stage(commandContext) != null
496
+ },
497
+ {
498
+ id: "view:rotate-counterclockwise",
499
+ labelKey: "commands.rotate.counterclockwise",
500
+ icon: "rotateCounterClockwise",
501
+ run: (commandContext) => stage(commandContext)?.rotateViewBy(-90),
502
+ enabled: (commandContext) => stage(commandContext) != null
503
+ },
504
+ {
505
+ id: "mode:view",
506
+ labelKey: "commands.mode.view",
507
+ run: (commandContext) => {
508
+ const shell = commandContext.tryGet(ShellToken);
509
+ const open = shell ? openModeSurfaceOf(shell) : null;
510
+ if (open) shell?.close(open);
511
+ interaction(commandContext)?.activateTool("pointer");
512
+ },
513
+ active: (commandContext) => {
514
+ const shell = commandContext.tryGet(ShellToken);
515
+ return shell ? openModeSurfaceOf(shell) === null : true;
516
+ }
517
+ },
518
+ modeCommand("mode:annotate", "commands.mode.annotate", { feature: "annotations" }),
519
+ modeCommand("mode:shapes", "commands.mode.shapes", { feature: "shapes" }),
520
+ modeCommand("mode:insert", "commands.mode.insert"),
521
+ modeCommand("mode:form", "commands.mode.form", { feature: "forms.building" }),
522
+ modeCommand("mode:redact", "commands.mode.redact", { feature: "redaction" }),
523
+ modeCommand("mode:measure", "commands.mode.measure", { feature: "measurement" }),
524
+ tool("tool:highlight", "highlight", "commands.annotate.highlight", "highlight", {
525
+ feature: "annotations.highlight",
526
+ check: CREATES_ANNOTATIONS,
527
+ accent: { primary: "color" }
528
+ }),
529
+ tool("tool:strikeout", "strikeout", "commands.annotate.strikeout", "strikethrough", {
530
+ feature: "annotations.markup",
531
+ check: CREATES_ANNOTATIONS,
532
+ accent: { primary: "color" }
533
+ }),
534
+ tool("tool:underline", "underline", "commands.annotate.underline", "underline", {
535
+ feature: "annotations.markup",
536
+ check: CREATES_ANNOTATIONS,
537
+ accent: { primary: "color" }
538
+ }),
539
+ tool("tool:squiggly", "squiggly", "commands.annotate.squiggly", "squiggly", {
540
+ feature: "annotations.markup",
541
+ check: CREATES_ANNOTATIONS,
542
+ accent: { primary: "color" }
543
+ }),
544
+ tool("tool:ink", "ink", "commands.annotate.ink", "pencilMarker", {
545
+ feature: "annotations.drawing",
546
+ check: CREATES_ANNOTATIONS,
547
+ accent: { primary: "color" }
548
+ }),
549
+ tool("tool:ink-highlight", "ink-highlight", "commands.annotate.inkHighlight", "inkHighlighter", {
550
+ feature: "annotations.drawing",
551
+ check: CREATES_ANNOTATIONS,
552
+ accent: { primary: "color" }
553
+ }),
554
+ tool("tool:free-text", "free-text", "commands.annotate.text", "freeText", {
555
+ feature: "annotations.text",
556
+ check: CREATES_ANNOTATIONS,
557
+ accent: { primary: "fontColor" }
558
+ }),
559
+ tool("tool:insert-text", "insert-text", "commands.annotate.insertText", "insertText", {
560
+ feature: "annotations.text",
561
+ check: CREATES_ANNOTATIONS,
562
+ accent: { primary: "color" }
563
+ }),
564
+ tool("tool:replace-text", "replace-text", "commands.annotate.replaceText", "replaceText", {
565
+ feature: "annotations.text",
566
+ check: CREATES_ANNOTATIONS,
567
+ accent: { primary: "color" }
568
+ }),
569
+ tool("tool:callout", "free-text-callout", "commands.annotate.callout", "callout", {
570
+ feature: "annotations.text",
571
+ check: CREATES_ANNOTATIONS,
572
+ accent: {
573
+ primary: "color",
574
+ secondary: "interiorColor"
575
+ }
576
+ }),
577
+ tool("tool:note", "note", "commands.annotate.note", "message", {
578
+ feature: "annotations.notes",
579
+ check: CREATES_ANNOTATIONS
580
+ }),
581
+ tool("tool:link", "link", "commands.annotate.link", "link", {
582
+ feature: "annotations.links",
583
+ check: CREATES_ANNOTATIONS
584
+ }),
585
+ tool("tool:square", "square", "commands.shapes.rectangle", "square", {
586
+ feature: "shapes",
587
+ check: CREATES_ANNOTATIONS,
588
+ accent: {
589
+ primary: "color",
590
+ secondary: "interiorColor"
591
+ }
592
+ }),
593
+ tool("tool:circle", "circle", "commands.shapes.circle", "circle", {
594
+ feature: "shapes",
595
+ check: CREATES_ANNOTATIONS,
596
+ accent: {
597
+ primary: "color",
598
+ secondary: "interiorColor"
599
+ }
600
+ }),
601
+ tool("tool:line", "line", "commands.shapes.line", "line", {
602
+ feature: "shapes",
603
+ check: CREATES_ANNOTATIONS,
604
+ accent: { primary: "color" }
605
+ }),
606
+ tool("tool:arrow", "arrow", "commands.shapes.arrow", "lineArrow", {
607
+ feature: "shapes",
608
+ check: CREATES_ANNOTATIONS,
609
+ accent: { primary: "color" }
610
+ }),
611
+ tool("tool:polygon", "polygon", "commands.shapes.polygon", "polygon", {
612
+ feature: "shapes",
613
+ check: CREATES_ANNOTATIONS,
614
+ accent: {
615
+ primary: "color",
616
+ secondary: "interiorColor"
617
+ }
618
+ }),
619
+ tool("tool:polyline", "polyline", "commands.shapes.polyline", "zigzag", {
620
+ feature: "shapes",
621
+ check: CREATES_ANNOTATIONS,
622
+ accent: { primary: "color" }
623
+ }),
624
+ {
625
+ id: "tool:stamp",
626
+ labelKey: "commands.insert.stamp",
627
+ icon: "rubberStamp",
628
+ categories: ["stamps"],
629
+ ...PLACES_STAMPS,
630
+ ...togglesPanel("stamps", (commandContext) => commandContext.tryGet(StampToken) != null)
631
+ },
632
+ tool("tool:attachment", "attachment", "commands.insert.attachment", "paperclip", {
633
+ feature: "attachments.adding",
634
+ check: CREATES_ANNOTATIONS
635
+ }),
636
+ {
637
+ id: "tool:signature",
638
+ labelKey: "commands.insert.signature",
639
+ icon: "signature",
640
+ categories: ["signatures.signing"],
641
+ ...PLACES_STAMPS,
642
+ ...togglesPanel("signatures", (commandContext) => commandContext.tryGet(StampToken) != null && commandContext.tryGet(SignatureToken) != null)
643
+ },
644
+ tool("tool:image", "image", "commands.insert.image", "photo", {
645
+ feature: "stamps",
646
+ check: CREATES_ANNOTATIONS
647
+ }),
648
+ {
649
+ id: "form:design",
650
+ labelKey: "commands.form.design",
651
+ icon: "formDesign",
652
+ categories: ["forms.building"],
653
+ ...DESIGNS_FORMS,
654
+ run: (commandContext) => {
655
+ const tools = interaction(commandContext);
656
+ const shell = commandContext.tryGet(ShellToken);
657
+ if (!tools) return;
658
+ if (isDesigningForm(commandContext)) {
659
+ tools.activateTool("pointer");
660
+ shell?.close(FORM_DESIGN_PANEL);
661
+ return;
662
+ }
663
+ if (shell && !shell.isOpen("mode:form")) shell.open("mode:form", { exclusive: "mode" });
664
+ tools.activateTool(FORM_DESIGN_TOOL);
665
+ shell?.open(FORM_DESIGN_PANEL);
666
+ },
667
+ active: isDesigningForm,
668
+ enabled: (commandContext) => interaction(commandContext) != null
669
+ },
670
+ tool("tool:text-field", "form-text", "commands.form.textfield", "formTextfield", {
671
+ feature: "forms.building",
672
+ check: DESIGNS_FORMS
673
+ }),
674
+ tool("tool:checkbox", "form-checkbox", "commands.form.checkbox", "formCheckbox", {
675
+ feature: "forms.building",
676
+ check: DESIGNS_FORMS
677
+ }),
678
+ tool("tool:radio", "form-radio", "commands.form.radio", "formRadio", {
679
+ feature: "forms.building",
680
+ check: DESIGNS_FORMS
681
+ }),
682
+ tool("tool:combo-box", "form-combobox", "commands.form.select", "formSelect", {
683
+ feature: "forms.building",
684
+ check: DESIGNS_FORMS
685
+ }),
686
+ tool("tool:list-box", "form-listbox", "commands.form.listbox", "formListbox", {
687
+ feature: "forms.building",
688
+ check: DESIGNS_FORMS
689
+ }),
690
+ tool("tool:button", "form-button", "commands.form.button", "formButton", {
691
+ feature: "forms.building",
692
+ check: DESIGNS_FORMS
693
+ }),
694
+ tool("tool:signature-field", "form-signature", "commands.form.signature", "signature", {
695
+ feature: "forms.building",
696
+ check: DESIGNS_FORMS
697
+ }),
698
+ tool("tool:redact", "redact", "commands.redact.mark", "redactArea", {
699
+ feature: "redaction",
700
+ check: {
701
+ allowed: (commandContext) => commandContext.tryGet(RedactionToken)?.canMark() ?? true,
702
+ notAllowedKey: "commands.notAllowed.annotations-create"
703
+ }
704
+ }),
705
+ {
706
+ id: "redaction:apply",
707
+ labelKey: "commands.redact.applyAll",
708
+ icon: "redact",
709
+ categories: ["redaction"],
710
+ allowed: (commandContext) => commandContext.tryGet(RedactionToken)?.canApply() ?? true,
711
+ modal: REDACT_CONFIRM_DIALOG,
712
+ enabled: (commandContext) => {
713
+ const redaction = commandContext.tryGet(RedactionToken);
714
+ return !!redaction && redaction.getPendingCount() > 0 && !redaction.isApplying();
715
+ }
716
+ },
717
+ {
718
+ ...tool("tool:distance", "distance", "measurement.distance", "distance", {
719
+ feature: "measurement",
720
+ check: CREATES_ANNOTATIONS,
721
+ accent: { primary: "color" }
722
+ }),
723
+ enabled: canMeasureNow
724
+ },
725
+ {
726
+ ...tool("tool:perimeter", "perimeter", "measurement.perimeter", "perimeter", {
727
+ feature: "measurement",
728
+ check: CREATES_ANNOTATIONS,
729
+ accent: { primary: "color" }
730
+ }),
731
+ enabled: canMeasureNow
732
+ },
733
+ {
734
+ ...tool("tool:area", "area", "measurement.area", "area", {
735
+ feature: "measurement",
736
+ check: CREATES_ANNOTATIONS,
737
+ accent: { primary: "color" }
738
+ }),
739
+ enabled: canMeasureNow
740
+ },
741
+ {
742
+ id: "measurement:calibrate",
743
+ labelKey: "measurement.calibrate",
744
+ icon: "calibrate",
745
+ categories: ["measurement"],
746
+ allowed: (commandContext) => commandContext.tryGet(MeasurementToken)?.canCalibrate() ?? true,
747
+ notAllowedKey: "commands.notAllowed.doc-annotate-modify",
748
+ enabled: (commandContext) => commandContext.tryGet(MeasurementToken) != null,
749
+ active: (commandContext) => interaction(commandContext)?.getActiveToolId() === "calibrate",
750
+ run: (commandContext) => commandContext.tryGet(MeasurementToken)?.startCalibration()
751
+ },
752
+ {
753
+ id: "annotation:cancel-creation",
754
+ labelKey: "commands.annotate.cancelCreation",
755
+ shortcut: "Escape",
756
+ enabled: (commandContext) => anno(commandContext)?.draft.get() != null,
757
+ run: (commandContext) => anno(commandContext)?.draft.cancel()
758
+ },
759
+ {
760
+ id: "annotation:delete",
761
+ labelKey: "commands.annotate.delete",
762
+ icon: "trash",
763
+ shortcut: ["Delete", "Backspace"],
764
+ categories: ["annotations"],
765
+ run: (commandContext) => void anno(commandContext)?.selection.delete(),
766
+ visible: hasAnnotationSelection,
767
+ enabled: (commandContext) => {
768
+ const annotation = anno(commandContext);
769
+ const selected = annotation?.selection.list() ?? [];
770
+ return selected.length > 0 && selected.every(({ ref }) => annotation.canDelete(ref) && annotation.isEditable(ref));
771
+ }
772
+ },
773
+ {
774
+ id: "annotation:comment",
775
+ labelKey: "commands.comment",
776
+ icon: "comment",
777
+ categories: ["comments"],
778
+ ...togglesPanel("comments", (commandContext) => hasAnnotationSelection(commandContext) && !selectionSubtypes(commandContext).has("widget"))
779
+ },
780
+ {
781
+ id: "annotation:style",
782
+ labelKey: "commands.style",
783
+ icon: "palette",
784
+ categories: ["annotations"],
785
+ ...togglesPanel("style", (commandContext) => (anno(commandContext)?.selection.getProperties().properties.length ?? 0) > 0)
786
+ },
787
+ {
788
+ id: "annotation:stamp-from-selection",
789
+ labelKey: "commands.annotate.stampFromSelection",
790
+ icon: "rubberStampPlus",
791
+ categories: ["stamps"],
792
+ allowed: (commandContext) => commandContext.tryGet(StampToken)?.canCreateFromAnnotations(commandContext.documentId ?? void 0) ?? true,
793
+ notAllowedKey: "commands.notAllowed.doc-download",
794
+ run: (commandContext) => {
795
+ const annotation = anno(commandContext);
796
+ const stamp = commandContext.tryGet(StampToken);
797
+ const documentId = commandContext.documentId;
798
+ if (!annotation || !stamp || documentId == null) return;
799
+ const dtos = annotation.selection.list();
800
+ const page = dtos[0]?.ref.page;
801
+ if (page === void 0) return;
802
+ const i18n = commandContext.tryGet(I18nToken);
803
+ const label = i18n?.t("demo.stampsCustomLabel") ?? "Custom stamp";
804
+ const libraryName = i18n?.t("demo.stampsCustomLibrary") ?? "My stamps";
805
+ const libraryId = CUSTOM_LIBRARY_ID;
806
+ (stamp.getLibrary(libraryId) ? Promise.resolve(libraryId) : stamp.createLibrary(libraryName, {
807
+ id: libraryId,
808
+ categories: ["custom"]
809
+ }).then(({ library }) => library.id)).then((id) => stamp.createAssetFromAnnotations(page, dtos.map((annotation) => annotation.ref), {
810
+ libraryId: id,
811
+ label: `${label} ${stamp.listAssets({ libraryId: id }).length + 1}`
812
+ }, { documentId })).then(async () => {
813
+ const shell = commandContext.tryGet(ShellToken);
814
+ if (!shell) return;
815
+ if (!shell.isOpen("stamps")) await commandContext.tryGet(CommandsToken)?.execute(panelCommandOf("stamps"), { documentId });
816
+ shell.updateSurfaceProps("stamps", { libraryId });
817
+ }).catch((error) => console.warn("[embedpdf] stamp from selection failed", error));
818
+ },
819
+ visible: (commandContext) => commandContext.tryGet(StampToken) != null && hasAnnotationSelection(commandContext) && !selectionSubtypes(commandContext).has("widget") && !selectionSubtypes(commandContext).has("redact") && new Set((anno(commandContext)?.selection.list() ?? []).map((annotation) => annotation.ref.page.objectNumber)).size === 1
820
+ },
821
+ {
822
+ id: "annotation:group",
823
+ labelKey: "commands.annotate.group",
824
+ icon: "group",
825
+ categories: ["annotations"],
826
+ run: (commandContext) => void anno(commandContext)?.selection.group(),
827
+ visible: (commandContext) => (anno(commandContext)?.selection.canGroup() ?? false) && selectionEditable(commandContext)
828
+ },
829
+ {
830
+ id: "annotation:ungroup",
831
+ labelKey: "commands.annotate.ungroup",
832
+ icon: "ungroup",
833
+ categories: ["annotations"],
834
+ run: (commandContext) => void anno(commandContext)?.selection.ungroup(),
835
+ visible: (commandContext) => (anno(commandContext)?.selection.canUngroup() ?? false) && selectionEditable(commandContext)
836
+ },
837
+ {
838
+ id: "selection:copy",
839
+ labelKey: "commands.selection.copy",
840
+ icon: "copy",
841
+ categories: ["text.copy"],
842
+ allowed: (commandContext) => textSelection(commandContext)?.canCopy() ?? true,
843
+ notAllowedKey: "commands.notAllowed.doc-text-copy",
844
+ visible: (commandContext) => textSelection(commandContext)?.hasSelection() ?? false,
845
+ run: (commandContext) => {
846
+ const selection = textSelection(commandContext);
847
+ if (!selection) return;
848
+ const copiedRange = selection.getRange();
849
+ copySelection(selection).then((text) => {
850
+ if (text !== "" && sameTextRange(selection.getRange(), copiedRange)) selection.clear();
851
+ }, () => {});
852
+ }
853
+ },
854
+ markupSelection("selection:highlight", "highlight", "commands.annotate.highlight", "highlight", "annotations.highlight"),
855
+ markupSelection("selection:underline", "underline", "commands.annotate.underline", "underline", "annotations.markup"),
856
+ markupSelection("selection:strikeout", "strikeout", "commands.annotate.strikeout", "strikethrough", "annotations.markup"),
857
+ {
858
+ id: "annotation:link",
859
+ labelKey: "commands.annotate.link",
860
+ icon: "link",
861
+ categories: ["annotations.links"],
862
+ run: (commandContext) => commandContext.tryGet(ShellToken)?.toggle("link-editor"),
863
+ active: (commandContext) => commandContext.tryGet(ShellToken)?.isOpen("link-editor") ?? false,
864
+ visible: (commandContext) => selectionLink(commandContext) === null
865
+ },
866
+ {
867
+ id: "annotation:goto-link",
868
+ labelKey: "commands.annotate.gotoLink",
869
+ icon: "externalLink",
870
+ run: (commandContext) => {
871
+ const target = selectionLink(commandContext);
872
+ if (target) commandContext.tryGet(LinkToken)?.activate(target);
873
+ },
874
+ visible: (commandContext) => selectionLink(commandContext) != null
875
+ },
876
+ {
877
+ id: "annotation:remove-link",
878
+ labelKey: "commands.annotate.removeLink",
879
+ icon: "linkOff",
880
+ categories: ["annotations.links"],
881
+ run: (commandContext) => anno(commandContext)?.selection.updateLink(null),
882
+ visible: (commandContext) => selectionLink(commandContext) != null
883
+ },
884
+ {
885
+ id: "help:shortcuts",
886
+ labelKey: "commands.shortcuts",
887
+ shortcut: "?",
888
+ modal: SHORTCUTS_DIALOG,
889
+ enabled: (commandContext) => commandContext.tryGet(ShellToken) != null
890
+ },
891
+ {
892
+ id: "history:undo",
893
+ labelKey: "commands.undo",
894
+ icon: "arrowBackUp",
895
+ shortcut: "Mod+z",
896
+ run: (commandContext) => commandContext.tryGet(HistoryToken)?.undo(),
897
+ enabled: (commandContext) => commandContext.tryGet(HistoryToken)?.canUndo() ?? false
898
+ },
899
+ {
900
+ id: "history:redo",
901
+ labelKey: "commands.redo",
902
+ icon: "arrowForwardUp",
903
+ shortcut: ["Shift+Mod+z", "Mod+y"],
904
+ run: (commandContext) => commandContext.tryGet(HistoryToken)?.redo(),
905
+ enabled: (commandContext) => commandContext.tryGet(HistoryToken)?.canRedo() ?? false
906
+ }
907
+ ];
908
+ const MODE_SURFACES = [
909
+ "mode:annotate",
910
+ "mode:shapes",
911
+ "mode:insert",
912
+ "mode:form",
913
+ "mode:redact",
914
+ "mode:measure"
915
+ ];
916
+ function modeCommand(id, labelKey, { feature } = {}) {
917
+ return {
918
+ id,
919
+ labelKey,
920
+ ...feature ? { categories: [feature] } : {},
921
+ run: (commandContext) => {
922
+ commandContext.tryGet(ShellToken)?.toggle(id, { exclusive: "mode" });
923
+ interaction(commandContext)?.activateTool("pointer");
924
+ },
925
+ active: (commandContext) => commandContext.tryGet(ShellToken)?.isOpen(id) ?? false
926
+ };
927
+ }
928
+ /**
929
+ * A `tool:<id>` command for each tool of yours (`plugins.annotation.tools`) that no command has
930
+ * (`isTaken` says which ids do): labelled by its `meta.label`, else its id; drawn with its
931
+ * `meta.icon`, else the icon of the tool it extends; in that tool's feature, else `annotations`.
932
+ */
933
+ function toolCommandsFor(tools, isTaken) {
934
+ return tools.flatMap((input) => {
935
+ const id = `tool:${input.id}`;
936
+ if (isTaken(id)) return [];
937
+ const base = input.extends;
938
+ const meta = input.meta ?? {};
939
+ const icon = typeof meta.icon === "string" ? meta.icon : base && TOOL_ICONS[base]?.icon;
940
+ return [{
941
+ ...toolCommand(id, input.id, icon || void 0, {
942
+ feature: base && TOOL_FEATURES[base] || "annotations",
943
+ check: CREATES_ANNOTATIONS,
944
+ ...base && TOOL_ICONS[base]?.accent ? { accent: TOOL_ICONS[base].accent } : {}
945
+ }),
946
+ label: typeof meta.label === "string" ? meta.label : input.id
947
+ }];
948
+ });
949
+ }
950
+ //#endregion
951
+ export { openModeSurfaceOf as _, REDACT_CONFIRM_DIALOG as a, defaultCommands as c, toolCommandsFor as d, OPEN_COMMAND_ID as f, modeSpecsOf as g, modeCommandsFor as h, MODE_SURFACES as i, isDesigningForm as l, mayCarryPdf as m, FORM_DESIGN_TOOL as n, SHORTCUTS_DIALOG as o, isPdfFile as p, GO_TO_PAGE_DIALOG as r, TOOL_ICONS as s, FORM_DESIGN_PANEL as t, isFormDesignTool as u, sameModeSpecs as v };
952
+
953
+ //# sourceMappingURL=commands-BmgxhJ8B.js.map