typo3-typescript-sources 13.4.20

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 (415) hide show
  1. package/.github/workflows/sync-and-publish.yml +265 -0
  2. package/.last-synced-tag +1 -0
  3. package/LICENSE.txt +339 -0
  4. package/README-TYPO3.md +166 -0
  5. package/README.md +28 -0
  6. package/adminpanel/admin-panel.ts +375 -0
  7. package/adminpanel/modules/cache.ts +27 -0
  8. package/adminpanel/modules/preview.ts +75 -0
  9. package/backend/action-button/abstract-action.ts +22 -0
  10. package/backend/action-button/deferred-action.ts +41 -0
  11. package/backend/action-button/immediate-action.ts +31 -0
  12. package/backend/action-dispatcher.ts +104 -0
  13. package/backend/ajax-data-handler/message-interface.ts +18 -0
  14. package/backend/ajax-data-handler/response-interface.ts +20 -0
  15. package/backend/ajax-data-handler.ts +214 -0
  16. package/backend/backend-exception.ts +22 -0
  17. package/backend/broadcast-message.ts +50 -0
  18. package/backend/broadcast-service.ts +52 -0
  19. package/backend/browse-database.ts +38 -0
  20. package/backend/clear-cache.ts +88 -0
  21. package/backend/clipboard-panel.ts +240 -0
  22. package/backend/close-current-window.ts +2 -0
  23. package/backend/code-editor/autocomplete/completion-result.ts +105 -0
  24. package/backend/code-editor/autocomplete/ts-code-completion.ts +142 -0
  25. package/backend/code-editor/autocomplete/ts-parser.ts +485 -0
  26. package/backend/code-editor/autocomplete/ts-ref.ts +164 -0
  27. package/backend/code-editor/element/code-mirror-element.ts +282 -0
  28. package/backend/code-editor/language/typoscript.ts +181 -0
  29. package/backend/code-editor/stream-parser/typoscript.ts +1600 -0
  30. package/backend/color-picker.ts +158 -0
  31. package/backend/color-scheme-switch.ts +179 -0
  32. package/backend/column-selector-button.ts +302 -0
  33. package/backend/context-help.ts +68 -0
  34. package/backend/context-menu-actions.ts +322 -0
  35. package/backend/context-menu.ts +751 -0
  36. package/backend/copy-to-clipboard.ts +79 -0
  37. package/backend/date-time-picker.ts +270 -0
  38. package/backend/document-save-actions.ts +159 -0
  39. package/backend/drag-tooltip.ts +285 -0
  40. package/backend/drag-uploader.ts +914 -0
  41. package/backend/element/alert-element.ts +116 -0
  42. package/backend/element/dispatch-modal-button.ts +50 -0
  43. package/backend/element/draggable-resizable-element.ts +249 -0
  44. package/backend/element/editable-page-title.ts +306 -0
  45. package/backend/element/icon-element.ts +84 -0
  46. package/backend/element/immediate-action-element.ts +89 -0
  47. package/backend/element/pagination.ts +59 -0
  48. package/backend/element/progress-bar-element.ts +172 -0
  49. package/backend/element/progress-tracker-element.ts +119 -0
  50. package/backend/element/pseudo-button.ts +58 -0
  51. package/backend/element/spinner-element.ts +51 -0
  52. package/backend/element/thumbnail-element.ts +80 -0
  53. package/backend/element-browser.ts +182 -0
  54. package/backend/enum/data-transfer-types.ts +21 -0
  55. package/backend/enum/icon-types.ts +31 -0
  56. package/backend/enum/key-types.ts +27 -0
  57. package/backend/enum/severity.ts +20 -0
  58. package/backend/enum/viewport/scaffold-identifier.ts +24 -0
  59. package/backend/event/client-request.ts +25 -0
  60. package/backend/event/consumable.ts +18 -0
  61. package/backend/event/consumer-scope.ts +54 -0
  62. package/backend/event/event-dispatcher.ts +26 -0
  63. package/backend/event/interaction-request-assignment.ts +24 -0
  64. package/backend/event/interaction-request-map.ts +75 -0
  65. package/backend/event/interaction-request.ts +48 -0
  66. package/backend/event/trigger-request.ts +56 -0
  67. package/backend/form/submit-interceptor.ts +66 -0
  68. package/backend/form-engine/container/files-control-container.ts +716 -0
  69. package/backend/form-engine/container/flex-form-container-container.ts +166 -0
  70. package/backend/form-engine/container/flex-form-section-container.ts +210 -0
  71. package/backend/form-engine/container/inline-control-container.ts +1230 -0
  72. package/backend/form-engine/container/site-language-container.ts +616 -0
  73. package/backend/form-engine/element/abstract-sortable-select-items.ts +186 -0
  74. package/backend/form-engine/element/category-element.ts +160 -0
  75. package/backend/form-engine/element/color-element.ts +81 -0
  76. package/backend/form-engine/element/datetime-element.ts +66 -0
  77. package/backend/form-engine/element/extra/char-counter.ts +112 -0
  78. package/backend/form-engine/element/extra/select-box-filter.ts +85 -0
  79. package/backend/form-engine/element/folder-element.ts +66 -0
  80. package/backend/form-engine/element/group-element.ts +47 -0
  81. package/backend/form-engine/element/json-element.ts +52 -0
  82. package/backend/form-engine/element/link-element.ts +121 -0
  83. package/backend/form-engine/element/mfa-info-element.ts +169 -0
  84. package/backend/form-engine/element/modifier/resizable.ts +27 -0
  85. package/backend/form-engine/element/modifier/tabbable.ts +29 -0
  86. package/backend/form-engine/element/online-media-form-element.ts +65 -0
  87. package/backend/form-engine/element/password-element.ts +71 -0
  88. package/backend/form-engine/element/select-multiple-side-by-side-element.ts +83 -0
  89. package/backend/form-engine/element/select-single-element.ts +82 -0
  90. package/backend/form-engine/element/select-tree-element.ts +148 -0
  91. package/backend/form-engine/element/select-tree-toolbar.ts +118 -0
  92. package/backend/form-engine/element/select-tree.ts +210 -0
  93. package/backend/form-engine/element/slug-element.ts +243 -0
  94. package/backend/form-engine/element/suggest/result-container.ts +118 -0
  95. package/backend/form-engine/element/suggest/result-item.ts +90 -0
  96. package/backend/form-engine/element/table-permission-element.ts +101 -0
  97. package/backend/form-engine/element/table-wizard-element.ts +427 -0
  98. package/backend/form-engine/element/text-element.ts +52 -0
  99. package/backend/form-engine/element/text-table-element.ts +31 -0
  100. package/backend/form-engine/field-control/add-record.ts +40 -0
  101. package/backend/form-engine/field-control/edit-popup.ts +64 -0
  102. package/backend/form-engine/field-control/insert-clipboard.ts +50 -0
  103. package/backend/form-engine/field-control/link-popup.ts +53 -0
  104. package/backend/form-engine/field-control/list-module.ts +40 -0
  105. package/backend/form-engine/field-control/password-generator.ts +102 -0
  106. package/backend/form-engine/field-control/reset-selection.ts +48 -0
  107. package/backend/form-engine/field-wizard/localization-state-selector.ts +67 -0
  108. package/backend/form-engine/field-wizard/value-picker.ts +123 -0
  109. package/backend/form-engine/field-wizard/value-slider.ts +86 -0
  110. package/backend/form-engine/inline-relation/ajax-dispatcher.ts +124 -0
  111. package/backend/form-engine/inline-relation/inline-response-interface.ts +41 -0
  112. package/backend/form-engine-link-browser-adapter.ts +129 -0
  113. package/backend/form-engine-review.ts +155 -0
  114. package/backend/form-engine-suggest.ts +133 -0
  115. package/backend/form-engine-validation.ts +686 -0
  116. package/backend/form-engine.ts +1391 -0
  117. package/backend/global-event-handler.ts +252 -0
  118. package/backend/grid-editor.ts +1077 -0
  119. package/backend/hashing/md5.ts +252 -0
  120. package/backend/helper.ts +24 -0
  121. package/backend/hotkeys/hotkey-storage.ts +63 -0
  122. package/backend/hotkeys.ts +216 -0
  123. package/backend/icons.ts +236 -0
  124. package/backend/image-manipulation.ts +885 -0
  125. package/backend/info-window.ts +46 -0
  126. package/backend/input/clearable.ts +128 -0
  127. package/backend/input/index.d.ts +8 -0
  128. package/backend/java-script-module-import-event-handler.ts +19 -0
  129. package/backend/key-bindings.ts +42 -0
  130. package/backend/layout-module/drag-drop.ts +359 -0
  131. package/backend/layout-module/paste.ts +165 -0
  132. package/backend/link-browser.ts +89 -0
  133. package/backend/live-search/element/backend-search.ts +34 -0
  134. package/backend/live-search/element/hint.ts +33 -0
  135. package/backend/live-search/element/provider/default-result-item.ts +52 -0
  136. package/backend/live-search/element/provider/page-provider-result-item.ts +52 -0
  137. package/backend/live-search/element/result/item/action/action-container.ts +135 -0
  138. package/backend/live-search/element/result/item/action/action.ts +56 -0
  139. package/backend/live-search/element/result/item/item-container.ts +224 -0
  140. package/backend/live-search/element/result/item/item.ts +74 -0
  141. package/backend/live-search/element/result/result-container.ts +110 -0
  142. package/backend/live-search/element/result/result-detail-container.ts +50 -0
  143. package/backend/live-search/element/result/result-pagination.ts +122 -0
  144. package/backend/live-search/element/search-option-item.ts +70 -0
  145. package/backend/live-search/live-search-configurator.ts +54 -0
  146. package/backend/live-search/live-search-shortcut.ts +29 -0
  147. package/backend/live-search/result-types/backend-module-result-type.ts +8 -0
  148. package/backend/live-search/result-types/default-result-type.ts +21 -0
  149. package/backend/live-search/result-types/page-result-type.ts +23 -0
  150. package/backend/localization.ts +390 -0
  151. package/backend/login-refresh.ts +525 -0
  152. package/backend/login.ts +179 -0
  153. package/backend/mail-link-handler.ts +45 -0
  154. package/backend/modal-interface.ts +19 -0
  155. package/backend/modal.ts +685 -0
  156. package/backend/module/iframe.ts +125 -0
  157. package/backend/module/router.ts +275 -0
  158. package/backend/module-menu.ts +550 -0
  159. package/backend/module.ts +133 -0
  160. package/backend/multi-record-selection-action.ts +44 -0
  161. package/backend/multi-record-selection-delete-action.ts +108 -0
  162. package/backend/multi-record-selection-edit-action.ts +56 -0
  163. package/backend/multi-record-selection.ts +468 -0
  164. package/backend/multi-step-wizard.ts +568 -0
  165. package/backend/new-content-element-wizard-button.ts +51 -0
  166. package/backend/new-multiple-pages.ts +108 -0
  167. package/backend/new-record-wizard.ts +680 -0
  168. package/backend/notification.ts +366 -0
  169. package/backend/offset.ts +40 -0
  170. package/backend/online-media.ts +110 -0
  171. package/backend/page-actions.ts +91 -0
  172. package/backend/page-link-handler.ts +51 -0
  173. package/backend/pagetsconfig/pagetsconfig-includes.ts +77 -0
  174. package/backend/popover.ts +165 -0
  175. package/backend/record-download-button.ts +104 -0
  176. package/backend/record-link-handler.ts +31 -0
  177. package/backend/record-search.ts +44 -0
  178. package/backend/recordlist.ts +230 -0
  179. package/backend/resource/resource.ts +35 -0
  180. package/backend/security/element/csp-reports.ts +406 -0
  181. package/backend/security/element/sudo-mode.ts +249 -0
  182. package/backend/security/sudo-mode-interceptor.ts +19 -0
  183. package/backend/settings/editor/editable-setting.ts +231 -0
  184. package/backend/settings/editor.ts +451 -0
  185. package/backend/settings/enum/settings-mode.enum.ts +24 -0
  186. package/backend/settings/type/base.ts +200 -0
  187. package/backend/settings/type/bool.ts +56 -0
  188. package/backend/settings/type/color.ts +64 -0
  189. package/backend/settings/type/int.ts +85 -0
  190. package/backend/settings/type/number.ts +61 -0
  191. package/backend/settings/type/page.ts +100 -0
  192. package/backend/settings/type/string.ts +77 -0
  193. package/backend/settings/type/stringlist.ts +87 -0
  194. package/backend/severity.ts +88 -0
  195. package/backend/site-inline-actions.ts +32 -0
  196. package/backend/sortable-table.ts +208 -0
  197. package/backend/storage/abstract-client-storage.ts +76 -0
  198. package/backend/storage/browser-session.ts +28 -0
  199. package/backend/storage/client.ts +28 -0
  200. package/backend/storage/module-state-storage.ts +118 -0
  201. package/backend/storage/persistent.ts +204 -0
  202. package/backend/switch-user.ts +82 -0
  203. package/backend/tabs.ts +72 -0
  204. package/backend/telephone-link-handler.ts +40 -0
  205. package/backend/tests/backend/storage/browser-storage-test.ts +87 -0
  206. package/backend/tests/backend/storage/client-test.ts +87 -0
  207. package/backend/tests/backend/utility/dom-helper-test.ts +51 -0
  208. package/backend/tests/backend-exception-test.ts +28 -0
  209. package/backend/tests/element/immediate-action-element-test.ts +81 -0
  210. package/backend/tests/form-engine-validation-test.ts +304 -0
  211. package/backend/tests/grid-editor-test.ts +29 -0
  212. package/backend/tests/hashing/md5Test.ts +28 -0
  213. package/backend/tests/icons-test.ts +23 -0
  214. package/backend/tests/notification-test.ts +161 -0
  215. package/backend/tests/popover-test.ts +83 -0
  216. package/backend/toolbar/clear-cache-menu.ts +88 -0
  217. package/backend/toolbar/live-search.ts +284 -0
  218. package/backend/toolbar/shortcut-menu.ts +254 -0
  219. package/backend/toolbar/system-information-menu.ts +161 -0
  220. package/backend/toolbar.ts +41 -0
  221. package/backend/tree/file-storage-browser.ts +194 -0
  222. package/backend/tree/file-storage-tree-container.ts +465 -0
  223. package/backend/tree/file-storage-tree.ts +42 -0
  224. package/backend/tree/page-browser.ts +272 -0
  225. package/backend/tree/page-tree-element.ts +602 -0
  226. package/backend/tree/page-tree.ts +152 -0
  227. package/backend/tree/tree-module-state.ts +159 -0
  228. package/backend/tree/tree-node-toggle.ts +31 -0
  229. package/backend/tree/tree-node.ts +97 -0
  230. package/backend/tree/tree-toolbar.ts +117 -0
  231. package/backend/tree/tree.ts +1595 -0
  232. package/backend/url-link-handler.ts +36 -0
  233. package/backend/user-pass-login.ts +255 -0
  234. package/backend/user-settings-manager.ts +189 -0
  235. package/backend/utility/collapse-state-persister.ts +205 -0
  236. package/backend/utility/collapse-state-search.ts +183 -0
  237. package/backend/utility/dom-helper.ts +91 -0
  238. package/backend/utility/drag-drop-utility.ts +47 -0
  239. package/backend/utility/message-utility.ts +45 -0
  240. package/backend/utility/top-level-module-import.ts +32 -0
  241. package/backend/utility.ts +194 -0
  242. package/backend/viewport/abstract-container.ts +22 -0
  243. package/backend/viewport/content-container.ts +98 -0
  244. package/backend/viewport/loader.ts +28 -0
  245. package/backend/viewport/navigation-container.ts +119 -0
  246. package/backend/viewport/resizable-navigation.ts +176 -0
  247. package/backend/viewport/toolbar.ts +27 -0
  248. package/backend/viewport/topbar.ts +39 -0
  249. package/backend/viewport.ts +46 -0
  250. package/backend/window-manager.ts +84 -0
  251. package/backend/wizard/move-content-element.ts +107 -0
  252. package/backend/wizard/move-page.ts +74 -0
  253. package/backend/wizard.ts +331 -0
  254. package/belog/backend-log.ts +88 -0
  255. package/beuser/backend-user-listing.ts +40 -0
  256. package/beuser/permissions.ts +291 -0
  257. package/core/ajax/ajax-request-types.ts +15 -0
  258. package/core/ajax/ajax-request.ts +202 -0
  259. package/core/ajax/ajax-response.ts +57 -0
  260. package/core/ajax/input-transformer.ts +92 -0
  261. package/core/ajax/simple-response-interface.ts +5 -0
  262. package/core/authentication/mfa-provider/totp.ts +54 -0
  263. package/core/cache/dedupe-async-task.ts +95 -0
  264. package/core/directive/markdown.ts +82 -0
  265. package/core/directive/nl2br.ts +16 -0
  266. package/core/document-service.ts +35 -0
  267. package/core/event/debounce-event.ts +40 -0
  268. package/core/event/event-interface.ts +24 -0
  269. package/core/event/regular-event.ts +58 -0
  270. package/core/event/request-animation-frame-event.ts +42 -0
  271. package/core/event/throttle-event.ts +54 -0
  272. package/core/java-script-item-handler.ts +29 -0
  273. package/core/java-script-item-processor.ts +171 -0
  274. package/core/lit-helper.ts +126 -0
  275. package/core/literals.ts +17 -0
  276. package/core/referrer-refresh.ts +2 -0
  277. package/core/security-utility.ts +89 -0
  278. package/core/tests/ajax/ajax-request-test.ts +290 -0
  279. package/core/tests/ajax/input-transformer-test.ts +64 -0
  280. package/core/tests/cache/dedupe-async-task-test.ts +152 -0
  281. package/core/tests/event/throttle-event-test.ts +47 -0
  282. package/core/tests/literals-test.ts +24 -0
  283. package/core/tests/security-utility-test.ts +50 -0
  284. package/core/utility/types.ts +9 -0
  285. package/dashboard/chart-initializer.ts +136 -0
  286. package/dashboard/dashboard.ts +1487 -0
  287. package/extensionmanager/distribution-image.ts +55 -0
  288. package/extensionmanager/main.ts +291 -0
  289. package/extensionmanager/repository.ts +195 -0
  290. package/extensionmanager/update.ts +118 -0
  291. package/extensionmanager/upload-form.ts +42 -0
  292. package/filelist/browse-files.ts +148 -0
  293. package/filelist/browse-folders.ts +94 -0
  294. package/filelist/context-menu-actions.ts +335 -0
  295. package/filelist/create-folder.ts +69 -0
  296. package/filelist/file-delete.ts +69 -0
  297. package/filelist/file-list-actions.ts +132 -0
  298. package/filelist/file-list-dragdrop.ts +139 -0
  299. package/filelist/file-list-rename-handler.ts +128 -0
  300. package/filelist/file-list-transfer-handler.ts +138 -0
  301. package/filelist/file-list.ts +396 -0
  302. package/filelist/file-replace.ts +37 -0
  303. package/filelist/linkbrowser-file-handler.ts +94 -0
  304. package/filelist/linkbrowser-folder-handler.ts +74 -0
  305. package/filelist/rename-file.ts +109 -0
  306. package/form/backend/form-editor/component/property-grid-editor.ts +340 -0
  307. package/form/backend/form-editor/core.ts +2104 -0
  308. package/form/backend/form-editor/helper.ts +226 -0
  309. package/form/backend/form-editor/inspector-component.ts +2323 -0
  310. package/form/backend/form-editor/mediator.ts +941 -0
  311. package/form/backend/form-editor/modals-component.ts +433 -0
  312. package/form/backend/form-editor/stage-component.ts +908 -0
  313. package/form/backend/form-editor/tree-component.ts +548 -0
  314. package/form/backend/form-editor/view-model.ts +1343 -0
  315. package/form/backend/form-editor.ts +846 -0
  316. package/form/backend/form-manager/main.ts +39 -0
  317. package/form/backend/form-manager/view-model.ts +789 -0
  318. package/form/backend/form-manager.ts +180 -0
  319. package/form/backend/helper.ts +60 -0
  320. package/form/frontend/date-picker.ts +38 -0
  321. package/frontend/default_frontend.ts +82 -0
  322. package/impexp/context-menu-actions.ts +50 -0
  323. package/impexp/import-export.ts +62 -0
  324. package/indexed-search/pagination.ts +26 -0
  325. package/indexed-search/statistics.ts +50 -0
  326. package/info/translation-status.ts +48 -0
  327. package/install/ajax/ajax-queue.ts +83 -0
  328. package/install/init-install.ts +2 -0
  329. package/install/init-installer.ts +2 -0
  330. package/install/install.ts +25 -0
  331. package/install/installer.ts +458 -0
  332. package/install/message-interface.ts +18 -0
  333. package/install/module/abstract-inline-module.ts +27 -0
  334. package/install/module/abstract-interactable-module.ts +107 -0
  335. package/install/module/environment/environment-check.ts +92 -0
  336. package/install/module/environment/folder-structure.ts +145 -0
  337. package/install/module/environment/image-processing.ts +141 -0
  338. package/install/module/environment/mail-test.ts +127 -0
  339. package/install/module/environment/php-info.ts +50 -0
  340. package/install/module/environment/system-information.ts +50 -0
  341. package/install/module/maintenance/cache.ts +59 -0
  342. package/install/module/maintenance/clear-tables.ts +134 -0
  343. package/install/module/maintenance/clear-typo3temp-files.ts +137 -0
  344. package/install/module/maintenance/create-admin.ts +126 -0
  345. package/install/module/maintenance/database-analyzer.ts +266 -0
  346. package/install/module/maintenance/dump-autoload.ts +58 -0
  347. package/install/module/maintenance/language-packs.ts +338 -0
  348. package/install/module/maintenance/reset-backend-user-uc.ts +58 -0
  349. package/install/module/password-strength.ts +56 -0
  350. package/install/module/settings/change-install-tool-password.ts +101 -0
  351. package/install/module/settings/extension-configuration.ts +224 -0
  352. package/install/module/settings/features.ts +102 -0
  353. package/install/module/settings/local-configuration.ts +316 -0
  354. package/install/module/settings/presets.ts +147 -0
  355. package/install/module/settings/system-maintainer.ts +111 -0
  356. package/install/module/upgrade/core-update.ts +255 -0
  357. package/install/module/upgrade/extension-compat-tester.ts +195 -0
  358. package/install/module/upgrade/extension-scanner.ts +381 -0
  359. package/install/module/upgrade/tca-ext-tables-check.ts +93 -0
  360. package/install/module/upgrade/tca-migrations-check.ts +101 -0
  361. package/install/module/upgrade/upgrade-docs.ts +310 -0
  362. package/install/module/upgrade/upgrade-wizards.ts +612 -0
  363. package/install/renderable/clearable.ts +113 -0
  364. package/install/renderable/flash-message.ts +64 -0
  365. package/install/renderable/info-box.ts +66 -0
  366. package/install/renderable/language-packs.ts +304 -0
  367. package/install/renderable/offset-group.ts +38 -0
  368. package/install/renderable/severity.ts +49 -0
  369. package/install/renderable/wrap-group.ts +36 -0
  370. package/install/router.ts +468 -0
  371. package/linkvalidator/linkvalidator-modal.ts +45 -0
  372. package/linkvalidator/linkvalidator.ts +101 -0
  373. package/lowlevel/query-generator.ts +132 -0
  374. package/lowlevel/reference-index.ts +44 -0
  375. package/opendocs/toolbar/opendocs-menu.ts +117 -0
  376. package/package.json +34 -0
  377. package/recycler/recycler.ts +539 -0
  378. package/redirects/event-handler.ts +108 -0
  379. package/redirects/form-engine-evaluation.ts +34 -0
  380. package/redirects/redirects-module.ts +58 -0
  381. package/rte-ckeditor/ckeditor5.ts +402 -0
  382. package/rte-ckeditor/css-prefixer.ts +94 -0
  383. package/rte-ckeditor/legacy/ckeditor5-bundle.ts +17 -0
  384. package/rte-ckeditor/legacy/ckeditor5-inspector.ts +2 -0
  385. package/rte-ckeditor/plugin/typo3-link.ts +810 -0
  386. package/rte-ckeditor/plugin/whitespace.ts +114 -0
  387. package/rte-ckeditor/rte-link-browser.ts +112 -0
  388. package/rte-ckeditor/tests/css-prefixer-test.ts +314 -0
  389. package/scheduler/scheduler-add-group.ts +119 -0
  390. package/scheduler/scheduler-editable-group-name.ts +307 -0
  391. package/scheduler/scheduler-sortable-groups.ts +59 -0
  392. package/scheduler/scheduler.ts +466 -0
  393. package/setup/setup-module.ts +122 -0
  394. package/styleguide/checkbox-demo.ts +5 -0
  395. package/styleguide/element/theme-switcher-element.ts +101 -0
  396. package/styleguide/filter.ts +22 -0
  397. package/styleguide/find-icons.ts +78 -0
  398. package/styleguide/processing-indicator.ts +78 -0
  399. package/styleguide/render-notifications.ts +64 -0
  400. package/sys-note/element/delete-button.ts +78 -0
  401. package/tstemplate/constant-editor.ts +64 -0
  402. package/tstemplate/information-module.ts +31 -0
  403. package/tstemplate/template-analyzer.ts +77 -0
  404. package/viewpage/main.ts +253 -0
  405. package/workspaces/backend.ts +979 -0
  406. package/workspaces/preview.ts +232 -0
  407. package/workspaces/renderable/comment-view.ts +79 -0
  408. package/workspaces/renderable/diff-view.ts +58 -0
  409. package/workspaces/renderable/history-view.ts +97 -0
  410. package/workspaces/renderable/record-information.ts +118 -0
  411. package/workspaces/renderable/record-table.ts +355 -0
  412. package/workspaces/renderable/send-to-stage-form.ts +108 -0
  413. package/workspaces/toolbar/workspaces-menu.ts +190 -0
  414. package/workspaces/utility/icon-helper.ts +57 -0
  415. package/workspaces/workspaces.ts +136 -0
@@ -0,0 +1,885 @@
1
+ /*
2
+ * This file is part of the TYPO3 CMS project.
3
+ *
4
+ * It is free software; you can redistribute it and/or modify it under
5
+ * the terms of the GNU General Public License, either version 2
6
+ * of the License, or any later version.
7
+ *
8
+ * For the full copyright and license information, please read the
9
+ * LICENSE.txt file that was distributed with this source code.
10
+ *
11
+ * The TYPO3 project - inspiring people to share!
12
+ */
13
+
14
+ import { html, type TemplateResult } from 'lit';
15
+ import { unsafeHTML } from 'lit/directives/unsafe-html';
16
+ import { styleMap } from 'lit/directives/style-map';
17
+ import type { AjaxResponse } from '@typo3/core/ajax/ajax-response';
18
+ import AjaxRequest from '@typo3/core/ajax/ajax-request';
19
+ import RegularEvent from '@typo3/core/event/regular-event';
20
+ import FormEngineValidation from '@typo3/backend/form-engine-validation';
21
+ import Cropper from 'cropperjs';
22
+ import { default as Modal, type ModalElement } from './modal';
23
+ import '@typo3/backend/element/spinner-element';
24
+ import { renderNodes } from '@typo3/core/lit-helper';
25
+ import { topLevelModuleImport } from '@typo3/backend/utility/top-level-module-import';
26
+ import { Offset } from '@typo3/backend/offset';
27
+ import type { DraggableResizableEvent, PointerEventNames, DraggableResizableElement } from '@typo3/backend/element/draggable-resizable-element';
28
+ import type { EventInterface } from '@typo3/core/event/event-interface';
29
+
30
+ export interface Area {
31
+ x: number;
32
+ y: number;
33
+ height: number;
34
+ width: number;
35
+ }
36
+
37
+ interface Ratio {
38
+ id: string;
39
+ title: string;
40
+ value: number;
41
+ }
42
+
43
+ interface CropVariant {
44
+ title: string;
45
+ id: string;
46
+ selectedRatio: string;
47
+ cropArea?: Area;
48
+ focusArea?: Area;
49
+ coverAreas?: Area[];
50
+ allowedAspectRatios: {[key: string]: Ratio};
51
+ }
52
+
53
+ interface CropperEvent extends CustomEvent {
54
+ detail: Cropper.Data;
55
+ }
56
+
57
+ /**
58
+ * Module: @typo3/backend/image-manipulation
59
+ * Contains all logic for the image crop GUI including setting focusAreas
60
+ * @exports @typo3/backend/image-manipulation
61
+ */
62
+ class ImageManipulation {
63
+ private initialized: boolean = false;
64
+ private triggerListener: EventInterface = null;
65
+ private trigger: HTMLElement;
66
+ private currentModal: ModalElement;
67
+ private cropVariantTriggers: NodeListOf<HTMLElement>;
68
+ private activeCropVariantTrigger: HTMLElement;
69
+ private cropInfo: HTMLElement;
70
+ private imageOriginalSizeFactor: number;
71
+ private readonly cropImageSelector: string = '#t3js-crop-image';
72
+ private readonly coverAreaSelector: string = '.t3js-cropper-cover-area';
73
+ private readonly cropInfoSelector: string = '.t3js-cropper-info-crop';
74
+ private readonly focusAreaSelector: string = 'typo3-backend-draggable-resizable';
75
+ private readonly focusAreaVisualElementSelector: string = 'typo3-backend-draggable-resizable .cropper-focus-area';
76
+ private focusAreaEl: DraggableResizableElement;
77
+ // Initialize an empty object to prevent undefined cropBox error on modal load.
78
+ private cropBox: HTMLElement;
79
+ private cropper: Cropper;
80
+ private currentCropVariant: CropVariant;
81
+ private data: any;
82
+ private readonly defaultFocusArea: Area = {
83
+ height: 1 / 3,
84
+ width: 1 / 3,
85
+ x: 0,
86
+ y: 0,
87
+ };
88
+ private defaultOpts: Cropper.Options = {
89
+ autoCrop: true,
90
+ autoCropArea: 0.7,
91
+ dragMode: 'crop',
92
+ guides: true,
93
+ responsive: true,
94
+ viewMode: 1,
95
+ zoomable: false,
96
+ checkCrossOrigin: false,
97
+ };
98
+
99
+ /**
100
+ * @desc window.setTimeout shim
101
+ * @param {Function} fn - The function to execute
102
+ * @param {number} ms - The time in [ms] to wait until execution
103
+ */
104
+ public static wait(fn: () => void, ms: number): void {
105
+ window.setTimeout(fn, ms);
106
+ }
107
+
108
+ /**
109
+ * @desc Takes a number, and converts it to CSS percentage length
110
+ * @param {number} num - The number to convert
111
+ */
112
+ public static toCssPercent(num: number): string {
113
+ return `${num * 100}%`;
114
+ }
115
+
116
+ /**
117
+ * @desc Serializes crop variants for persistence or preview
118
+ * @param {Object} cropVariants
119
+ */
120
+ private static serializeCropVariants(cropVariants: object): string {
121
+ const omitUnused: any = (key: any, value: any) => {
122
+ return (
123
+ key === 'id'
124
+ || key === 'title'
125
+ || key === 'allowedAspectRatios'
126
+ || key === 'coverAreas'
127
+ ) ? undefined : value;
128
+ };
129
+
130
+ return JSON.stringify(cropVariants, omitUnused);
131
+ }
132
+
133
+ private static isEmptyObject(subject: Record<any, any>): boolean {
134
+ return !subject
135
+ || typeof subject !== 'object'
136
+ || Object.keys(subject).length === 0
137
+ || JSON.stringify(subject) === '{}';
138
+ }
139
+
140
+ private static resolvePointerEventNames(): PointerEventNames {
141
+ /**
142
+ * To make the focusarea draggable, cropper must be disabled by register the same events as cropper does.
143
+ * Copied from https://github.com/fengyuanchen/cropperjs/blob/main/src/js/constants.js
144
+ */
145
+ const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
146
+ const IS_TOUCH_DEVICE = IS_BROWSER && window.document.documentElement ? 'ontouchstart' in window.document.documentElement : false;
147
+ const HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in window : false;
148
+ const EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? ['touchmove'] : ['mousemove'];
149
+ const EVENT_TOUCH_START = IS_TOUCH_DEVICE ? ['touchstart'] : ['mousedown'];
150
+ const EVENT_TOUCH_END = IS_TOUCH_DEVICE ? ['touchend', 'touchcancel'] : ['mouseup'];
151
+ const EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? ['pointerdown'] : EVENT_TOUCH_START;
152
+ const EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? ['pointermove'] : EVENT_TOUCH_MOVE;
153
+ const EVENT_POINTER_UP = HAS_POINTER_EVENT ? ['pointerup', 'pointercancel'] : EVENT_TOUCH_END;
154
+
155
+ return {
156
+ touchStart: EVENT_TOUCH_START,
157
+ touchMove: EVENT_TOUCH_MOVE,
158
+ touchEnd: EVENT_TOUCH_END,
159
+ pointerDown: EVENT_POINTER_DOWN,
160
+ pointerMove: EVENT_POINTER_MOVE,
161
+ pointerUp: EVENT_POINTER_UP,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * @desc Assign a handler to .t3js-image-manipulation-trigger.
167
+ * Show the modal and kick-off image manipulation
168
+ */
169
+ public initializeTrigger(): void {
170
+ if (this.triggerListener) {
171
+ return;
172
+ }
173
+ this.triggerListener = new RegularEvent(
174
+ 'click',
175
+ (evt: Event, target: HTMLElement): void => {
176
+ evt.preventDefault();
177
+ this.trigger = target;
178
+ this.show();
179
+ }
180
+ );
181
+ this.triggerListener.delegateTo(document, '.t3js-image-manipulation-trigger');
182
+ }
183
+
184
+ /**
185
+ * @desc Initialize the cropper modal and dispatch the cropper init
186
+ */
187
+ private async initializeCropperModal(): Promise<void> {
188
+ const image: HTMLImageElement = this.currentModal.querySelector(this.cropImageSelector);
189
+
190
+ await new Promise<void>((resolve) => {
191
+ if (image.complete) {
192
+ resolve();
193
+ } else {
194
+ image.addEventListener('load', (): void => resolve());
195
+ }
196
+ });
197
+ this.init();
198
+ }
199
+
200
+ /**
201
+ * @desc Load the image and setup the modal UI
202
+ */
203
+ private show(): void {
204
+ const triggerData = this.trigger.dataset;
205
+ const modalTitle: string = triggerData.modalTitle;
206
+ const buttonPreviewText: string = triggerData.buttonPreviewText;
207
+ const buttonDismissText: string = triggerData.buttonDismissText;
208
+ const buttonSaveText: string = triggerData.buttonSaveText;
209
+ const imageUri: string = triggerData.url;
210
+ const payload: Record<string, string> = JSON.parse(triggerData.payload);
211
+
212
+ /**
213
+ * Open modal with image to crop
214
+ */
215
+ this.currentModal = Modal.advanced({
216
+ additionalCssClasses: ['modal-image-manipulation', 'cropper'],
217
+ buttons: [
218
+ {
219
+ btnClass: 'btn-default float-start',
220
+ name: 'preview',
221
+ icon: 'actions-view',
222
+ text: buttonPreviewText,
223
+ },
224
+ {
225
+ btnClass: 'btn-default',
226
+ name: 'dismiss',
227
+ icon: 'actions-close',
228
+ text: buttonDismissText,
229
+ },
230
+ {
231
+ btnClass: 'btn-primary',
232
+ name: 'save',
233
+ icon: 'actions-document-save',
234
+ text: buttonSaveText,
235
+ },
236
+ ],
237
+ content: html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`,
238
+ size: Modal.sizes.full,
239
+ style: Modal.styles.dark,
240
+ title: modalTitle,
241
+ staticBackdrop: true
242
+ });
243
+
244
+ this.currentModal.addEventListener('typo3-modal-shown', (): void => {
245
+ new AjaxRequest(imageUri).post(payload).then(async (response: AjaxResponse): Promise<void> => {
246
+ const htmlResponse = await response.resolve();
247
+ this.currentModal.templateResultContent = html`${unsafeHTML(htmlResponse)}`;
248
+ this.currentModal.updateComplete.then(() => this.initializeCropperModal());
249
+ });
250
+ });
251
+
252
+ this.currentModal.addEventListener('typo3-modal-hide', (): void => {
253
+ this.destroy();
254
+ });
255
+ }
256
+
257
+ /**
258
+ * @desc Initializes the cropper UI and sets up all the event bindings for the UI
259
+ */
260
+ private init(): void {
261
+ const image: HTMLImageElement = this.currentModal.querySelector(this.cropImageSelector);
262
+ const data: string = this.trigger.dataset.cropVariants;
263
+
264
+ if (!data) {
265
+ throw new TypeError('ImageManipulation: No cropVariants data found for image');
266
+ }
267
+
268
+ // if we have data already set we assume an internal reinit eg. after resizing
269
+ this.data = ImageManipulation.isEmptyObject(this.data) ? JSON.parse(data) : this.data;
270
+
271
+ this.cropVariantTriggers = this.currentModal.querySelectorAll('.t3js-crop-variant-trigger');
272
+ this.activeCropVariantTrigger = this.currentModal.querySelector('.t3js-crop-variant-trigger.is-active');
273
+ this.cropInfo = this.currentModal.querySelector(this.cropInfoSelector);
274
+ this.currentCropVariant = this.data[this.activeCropVariantTrigger.dataset.cropVariantId];
275
+
276
+ /**
277
+ * Assign EventListener to cropVariantTriggers
278
+ */
279
+ this.cropVariantTriggers.forEach((el: HTMLElement) =>
280
+ el.addEventListener('click', (e: Event): void => {
281
+ /**
282
+ * Is the current cropVariantTrigger is active, bail out.
283
+ * Bootstrap doesn't provide this functionality when collapsing the Collapse panels
284
+ */
285
+ if ((e.currentTarget as HTMLElement).classList.contains('is-active')) {
286
+ e.stopPropagation();
287
+ e.preventDefault();
288
+ return;
289
+ }
290
+
291
+ this.activeCropVariantTrigger.classList.remove('is-active');
292
+ (e.currentTarget as HTMLElement).classList.add('is-active');
293
+ this.activeCropVariantTrigger = e.currentTarget as HTMLElement;
294
+ const cropVariant: CropVariant = this.data[this.activeCropVariantTrigger.dataset.cropVariantId];
295
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
296
+ cropVariant.cropArea = this.convertRelativeToAbsoluteCropArea(cropVariant.cropArea, imageData);
297
+ this.currentCropVariant = Object.assign({}, cropVariant);
298
+ this.update(cropVariant);
299
+ })
300
+ );
301
+
302
+ /**
303
+ * Assign EventListener to aspectRatioTrigger
304
+ */
305
+ new RegularEvent(
306
+ 'click',
307
+ (evt: Event, target: HTMLElement): void => {
308
+ const ratioId: string = target.dataset.bsOption;
309
+ this.handleAspectRatioChange(ratioId);
310
+ }
311
+ ).delegateTo(this.currentModal, 'label[data-method=setAspectRatio]');
312
+
313
+ new RegularEvent('keydown', (evt: KeyboardEvent, target: HTMLElement): void => {
314
+ if (!['Enter', 'Space'].includes(evt.code)) {
315
+ return;
316
+ }
317
+ evt.preventDefault();
318
+ evt.stopImmediatePropagation();
319
+
320
+ const setAspectRatioLabel = target.closest('label[data-method="setAspectRatio"]') as HTMLElement;
321
+ const ratioId: string = setAspectRatioLabel.dataset.bsOption;
322
+
323
+ setAspectRatioLabel.querySelector('input').checked = true;
324
+ this.handleAspectRatioChange(ratioId);
325
+ }).delegateTo(this.currentModal, 'label[data-method="setAspectRatio"] input[type="radio"]');
326
+
327
+ /**
328
+ * Assign EventListener to saveButton
329
+ */
330
+ new RegularEvent('click', (): void => this.save(this.data))
331
+ .delegateTo(this.currentModal, 'button[name=save]');
332
+
333
+ /**
334
+ * Assign EventListener to previewButton if preview url exists
335
+ */
336
+ if (this.trigger.dataset.previewUrl) {
337
+ new RegularEvent('click', (): void => this.openPreview(this.data))
338
+ .delegateTo(this.currentModal, 'button[name=preview]');
339
+ } else {
340
+ this.currentModal.querySelectorAll('button[name=preview]')
341
+ .forEach((previewButton: HTMLElement) => previewButton.style.display = 'none');
342
+ }
343
+
344
+ /**
345
+ * Assign EventListener to dismissButton
346
+ */
347
+ new RegularEvent('click', (): void => this.currentModal.hideModal())
348
+ .delegateTo(this.currentModal, 'button[name=dismiss]');
349
+
350
+ /**
351
+ * Assign EventListener to resetButton
352
+ */
353
+ new RegularEvent(
354
+ 'click',
355
+ (evt: Event, target: HTMLElement): void => {
356
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
357
+ const resetCropVariantString: string = target.dataset.cropVariant;
358
+ evt.preventDefault();
359
+ evt.stopPropagation();
360
+ if (!resetCropVariantString) {
361
+ throw new TypeError('TYPO3 Cropper: No cropVariant data attribute found on reset element.');
362
+ }
363
+ const resetCropVariant: CropVariant = JSON.parse(resetCropVariantString);
364
+ const absoluteCropArea: Area = this.convertRelativeToAbsoluteCropArea(resetCropVariant.cropArea, imageData);
365
+ this.currentCropVariant = Object.assign({}, resetCropVariant, { cropArea: absoluteCropArea });
366
+ this.update(this.currentCropVariant);
367
+ }
368
+ ).delegateTo(this.currentModal, 'button[name=reset]');
369
+
370
+ // if we start without an cropArea, maximize the cropper
371
+ if (ImageManipulation.isEmptyObject(this.currentCropVariant.cropArea)) {
372
+ this.defaultOpts = Object.assign({ autoCropArea: 1 }, this.defaultOpts);
373
+ }
374
+
375
+ /**
376
+ * Initialise the cropper
377
+ */
378
+ this.cropper = new Cropper(image, Object.assign({}, this.defaultOpts, {
379
+ ready: () => {
380
+ this.cropBuiltHandler();
381
+ // Initialize current crop variant
382
+ this.update(this.currentCropVariant);
383
+ },
384
+ crop: this.cropMoveHandler.bind(this),
385
+ data: this.currentCropVariant.cropArea,
386
+ }));
387
+ }
388
+
389
+ /**
390
+ * @desc Internal cropper handler. Called when the cropper has been instantiated
391
+ */
392
+ private readonly cropBuiltHandler = (): void => {
393
+ this.initialized = true;
394
+
395
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
396
+ const image: HTMLImageElement = this.currentModal.querySelector(this.cropImageSelector);
397
+
398
+ // Make the image in the backdrop visible again.
399
+ // TODO: Check why this doesn't happen automatically.
400
+ this.currentModal.querySelector('.cropper-canvas img')?.classList.remove('cropper-hide');
401
+
402
+ this.imageOriginalSizeFactor = parseInt(image.dataset.originalWidth, 10) / imageData.naturalWidth;
403
+
404
+ // iterate over the crop variants and set up their respective preview
405
+ this.cropVariantTriggers.forEach((elem: HTMLElement): void => {
406
+ const cropVariantId: string = elem.dataset.cropVariantId;
407
+ const cropArea: Area = this.convertRelativeToAbsoluteCropArea(
408
+ this.data[cropVariantId].cropArea,
409
+ imageData,
410
+ );
411
+ const variant: CropVariant = Object.assign({}, this.data[cropVariantId], { cropArea });
412
+ this.updatePreviewThumbnail(variant, elem);
413
+ this.currentModal.querySelector(`[data-crop-variant-container="${variant.id}"]`)?.querySelector(`[data-bs-option="${variant.selectedRatio}"]`)?.classList.add('active');
414
+ });
415
+
416
+ this.currentCropVariant.cropArea = this.convertRelativeToAbsoluteCropArea(
417
+ this.currentCropVariant.cropArea,
418
+ imageData,
419
+ );
420
+ // can't use .t3js-* as selector because it is an extraneous selector
421
+ this.cropBox = this.currentModal.querySelector('.cropper-crop-box');
422
+
423
+ if (this.currentCropVariant.selectedRatio) {
424
+ // set data explicitly or setAspectRatio up-scales the crop
425
+ this.currentModal.querySelector(`[data-bs-option='${this.currentCropVariant.selectedRatio}']`)
426
+ ?.classList.add('active');
427
+ }
428
+ };
429
+
430
+ /**
431
+ * @desc Internal cropper handler. Called when the cropping area is moving
432
+ */
433
+ private readonly cropMoveHandler = (e: CropperEvent): void => {
434
+ if (!this.initialized) {
435
+ return;
436
+ }
437
+
438
+ const minCroppedWidth = 15;
439
+ const minCroppedHeight = 15;
440
+ let width = Math.floor(e.detail.width);
441
+ let height = Math.floor(e.detail.height);
442
+
443
+ if (width < minCroppedWidth || height < minCroppedHeight) {
444
+ width = Math.max(minCroppedHeight, height);
445
+ height = Math.max(minCroppedWidth, width);
446
+
447
+ this.cropper.setData({
448
+ width: width,
449
+ height: height,
450
+ });
451
+ }
452
+
453
+ this.currentCropVariant.cropArea = Object.assign({}, this.currentCropVariant.cropArea, {
454
+ width: Math.floor(width),
455
+ height: Math.floor(height),
456
+ x: Math.floor(e.detail.x),
457
+ y: Math.floor(e.detail.y),
458
+ });
459
+
460
+ if (this.focusAreaEl && this.currentCropVariant?.focusArea) {
461
+ this.focusAreaEl.offset = this.convertAreaToOffset(this.currentCropVariant.focusArea, this.cropBox);
462
+ }
463
+
464
+ this.updatePreviewThumbnail(this.currentCropVariant, this.activeCropVariantTrigger);
465
+ this.updateCropVariantData(this.currentCropVariant);
466
+ const naturalWidth: number = Math.round(this.currentCropVariant.cropArea.width * this.imageOriginalSizeFactor);
467
+ const naturalHeight: number = Math.round(this.currentCropVariant.cropArea.height * this.imageOriginalSizeFactor);
468
+ this.cropInfo.innerText = `${naturalWidth}×${naturalHeight} px`;
469
+ };
470
+
471
+ private handleAspectRatioChange(ratioId: string): void {
472
+ const temp: CropVariant = Object.assign({}, this.currentCropVariant);
473
+ const ratio: Ratio = temp.allowedAspectRatios[ratioId];
474
+ this.setAspectRatio(ratio);
475
+ // set data explicitly or setAspectRatio upscales the crop
476
+ this.setCropArea(temp.cropArea);
477
+ this.currentCropVariant = Object.assign({}, temp, { selectedRatio: ratioId });
478
+
479
+ this.update(this.currentCropVariant);
480
+ }
481
+
482
+ /**
483
+ * @desc Update current cropArea position and size when changing cropVariants
484
+ * @param {CropVariant} cropVariant - The new cropVariant to update the UI with
485
+ */
486
+ private async update(cropVariant: CropVariant): Promise<void> {
487
+ const temp: CropVariant = Object.assign({}, cropVariant);
488
+ const selectedRatio: Ratio = cropVariant.allowedAspectRatios[cropVariant.selectedRatio];
489
+
490
+ // Set cropInfo to current container context
491
+ this.cropInfo = this.currentModal.querySelector(`[data-crop-variant-container="${cropVariant.id}"]`)?.querySelector(this.cropInfoSelector);
492
+ // highlight the currently selected ratio of the active cropping variant
493
+ this.currentModal.querySelector(`[data-crop-variant-container="${cropVariant.id}"]`)?.querySelector('[data-bs-option].active')?.classList.remove('active');
494
+ this.currentModal.querySelector(`[data-crop-variant-container="${cropVariant.id}"]`)?.querySelector(`[data-bs-option="${cropVariant.selectedRatio}"]`)?.classList.add('active');
495
+
496
+ /**
497
+ * Setting the aspect ratio cause a redraw of the crop area so we need to manually reset it to last data
498
+ */
499
+ this.setAspectRatio(selectedRatio);
500
+ this.setCropArea(temp.cropArea);
501
+ this.currentCropVariant = Object.assign({}, temp, cropVariant);
502
+ this.cropBox?.querySelectorAll(this.coverAreaSelector)?.forEach((el: HTMLElement) => el.remove());
503
+ this.cropBox?.querySelectorAll(this.focusAreaSelector)?.forEach((el: HTMLElement) => el.remove());
504
+
505
+ // check if new cropVariant has focusArea
506
+ if (cropVariant.focusArea) {
507
+ // init or reinit focusArea
508
+ if (ImageManipulation.isEmptyObject(cropVariant.focusArea)) {
509
+ this.currentCropVariant.focusArea = Object.assign({}, this.defaultFocusArea);
510
+ }
511
+ this.focusAreaEl = this.initFocusArea(this.cropBox);
512
+ } else {
513
+ this.focusAreaEl = null;
514
+ }
515
+
516
+ // check if new cropVariant has coverAreas
517
+ if (cropVariant.coverAreas) {
518
+ // init or reinit focusArea
519
+ this.initCoverAreas(this.cropBox, this.currentCropVariant.coverAreas);
520
+ }
521
+ this.updatePreviewThumbnail(this.currentCropVariant, this.activeCropVariantTrigger);
522
+ }
523
+
524
+ /**
525
+ * @desc Initializes the focus area inside a container and registers the resizable and draggable interfaces to it
526
+ * @param {HTMLElement} container
527
+ */
528
+ private initFocusArea(container: HTMLElement): DraggableResizableElement {
529
+ topLevelModuleImport('@typo3/backend/element/draggable-resizable-element.js');
530
+
531
+ const focusAreaEl = top.document.createElement('typo3-backend-draggable-resizable');
532
+ // assign area declaration (as persisted in the database)
533
+ focusAreaEl.setAttribute('offset', JSON.stringify(this.convertAreaToOffset(this.currentCropVariant.focusArea, container)));
534
+ // use the same events as cropper.js does
535
+ focusAreaEl.setAttribute('pointereventnames', JSON.stringify(ImageManipulation.resolvePointerEventNames()));
536
+
537
+ focusAreaEl.addEventListener('draggable-resizable-started', () => {
538
+ // disable outer cropper, when interacting with inner draggable-resizable-element
539
+ this.cropper.disable();
540
+ });
541
+ focusAreaEl.addEventListener('draggable-resizable-updated', () => {
542
+ const coverAreas = this.currentCropVariant.coverAreas;
543
+ const focusArea = this.convertOffsetToArea(focusAreaEl.offset, container);
544
+ // retrieve the inner visual element of the lit-element
545
+ const visualElement = focusAreaEl.querySelector(this.focusAreaVisualElementSelector);
546
+ if (this.checkFocusAndCoverAreasCollision(focusArea, coverAreas)) {
547
+ visualElement.classList.add('has-nodrop');
548
+ } else {
549
+ visualElement.classList.remove('has-nodrop');
550
+ }
551
+ });
552
+ focusAreaEl.addEventListener('draggable-resizable-finished', (evt: DraggableResizableEvent) => {
553
+ const coverAreas = this.currentCropVariant.coverAreas;
554
+ const focusArea = this.convertOffsetToArea(focusAreaEl.offset, container);
555
+ if (this.checkFocusAndCoverAreasCollision(focusArea, coverAreas)) {
556
+ focusAreaEl.revert(evt.detail.originOffset);
557
+ } else {
558
+ this.scaleAndMoveFocusArea(focusArea);
559
+ }
560
+ const visualElement = focusAreaEl.querySelector(this.focusAreaVisualElementSelector);
561
+ visualElement.classList.remove('has-nodrop');
562
+ // re-enable outer cropper again
563
+ this.cropper.enable();
564
+ });
565
+
566
+ container.appendChild(focusAreaEl);
567
+ this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea);
568
+
569
+ return focusAreaEl;
570
+ }
571
+
572
+ /**
573
+ * @desc Initialise cover areas inside the cropper container
574
+ * @param {HTMLElement} container - The container element to append the cover areas
575
+ * @param {Array<Area>} coverAreas - An array of areas to construct the cover area elements from
576
+ */
577
+ private initCoverAreas(container: HTMLElement, coverAreas: Area[]): void {
578
+ coverAreas.forEach((coverArea: Area): void => {
579
+ const styles = {
580
+ height: ImageManipulation.toCssPercent(coverArea.height),
581
+ left: ImageManipulation.toCssPercent(coverArea.x),
582
+ top: ImageManipulation.toCssPercent(coverArea.y),
583
+ width: ImageManipulation.toCssPercent(coverArea.width),
584
+ };
585
+ const coverAreaCanvas = html`
586
+ <div class="cropper-cover-area t3js-cropper-cover-area" style=${styleMap(styles)}></div>
587
+ `;
588
+ this.renderElements(coverAreaCanvas, container);
589
+ });
590
+ }
591
+
592
+ /**
593
+ * @desc Sync the cropping (and focus area) to the preview thumbnail
594
+ * @param {CropVariant} cropVariant - The crop variant to preview in the thumbnail
595
+ * @param {HTMLElement} cropVariantTrigger - The crop variant element containing the thumbnail
596
+ */
597
+ private updatePreviewThumbnail(cropVariant: CropVariant, cropVariantTrigger: HTMLElement): void {
598
+ const cropperPreviewThumbnailCrop: HTMLElement =
599
+ cropVariantTrigger.querySelector('.t3js-cropper-preview-thumbnail-crop-area');
600
+ const cropperPreviewThumbnailImage: HTMLElement =
601
+ cropVariantTrigger.querySelector('.t3js-cropper-preview-thumbnail-crop-image');
602
+ const cropperPreviewThumbnailFocus: HTMLElement =
603
+ cropVariantTrigger.querySelector('.t3js-cropper-preview-thumbnail-focus-area');
604
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
605
+
606
+ // update the position/dimension of the crop area in the preview
607
+ Object.assign(cropperPreviewThumbnailCrop.style, {
608
+ height: ImageManipulation.toCssPercent(cropVariant.cropArea.height / imageData.naturalHeight),
609
+ left: ImageManipulation.toCssPercent(cropVariant.cropArea.x / imageData.naturalWidth),
610
+ top: ImageManipulation.toCssPercent(cropVariant.cropArea.y / imageData.naturalHeight),
611
+ width: ImageManipulation.toCssPercent(cropVariant.cropArea.width / imageData.naturalWidth),
612
+ });
613
+
614
+ // show and update focusArea in the preview only if we really have one configured
615
+ if (cropVariant.focusArea) {
616
+ Object.assign(cropperPreviewThumbnailFocus.style, {
617
+ height: ImageManipulation.toCssPercent(cropVariant.focusArea.height),
618
+ left: ImageManipulation.toCssPercent(cropVariant.focusArea.x),
619
+ top: ImageManipulation.toCssPercent(cropVariant.focusArea.y),
620
+ width: ImageManipulation.toCssPercent(cropVariant.focusArea.width),
621
+ });
622
+ }
623
+
624
+ // destruct the preview container's CSS properties
625
+ const cropperPreviewThumbnailCropStyles = getComputedStyle(cropperPreviewThumbnailCrop);
626
+ const styles = {
627
+ width: cropperPreviewThumbnailCropStyles.getPropertyValue('width'),
628
+ height: cropperPreviewThumbnailCropStyles.getPropertyValue('height'),
629
+ left: cropperPreviewThumbnailCropStyles.getPropertyValue('left'),
630
+ top: cropperPreviewThumbnailCropStyles.getPropertyValue('top'),
631
+ };
632
+ /**
633
+ * Apply negative margins on the previewThumbnailImage to make the illusion of an offset
634
+ */
635
+ Object.assign(cropperPreviewThumbnailImage.style, {
636
+ height: `${parseFloat(styles.height) * (1 / (cropVariant.cropArea.height / imageData.naturalHeight))}px`,
637
+ margin: `${-1 * parseFloat(styles.left)}px`,
638
+ marginTop: `${-1 * parseFloat(styles.top)}px`,
639
+ width: `${parseFloat(styles.width) * (1 / (cropVariant.cropArea.width / imageData.naturalWidth))}px`,
640
+ });
641
+ }
642
+
643
+ /**
644
+ * @desc Calculation logic for moving the focus area given the
645
+ * specified constrains of a crop and an optional cover area
646
+ * @param {Area} focusArea - The translation data
647
+ */
648
+ private scaleAndMoveFocusArea(focusArea: Area): void {
649
+ this.currentCropVariant.focusArea = focusArea;
650
+ this.updatePreviewThumbnail(this.currentCropVariant, this.activeCropVariantTrigger);
651
+ this.updateCropVariantData(this.currentCropVariant);
652
+ }
653
+
654
+ /**
655
+ * @desc Immutably updates the currently selected cropVariant data
656
+ * @param {CropVariant} currentCropVariant - The cropVariant to immutably save
657
+ */
658
+ private updateCropVariantData(currentCropVariant: CropVariant): void {
659
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
660
+ const absoluteCropArea: Area = this.convertAbsoluteToRelativeCropArea(currentCropVariant.cropArea, imageData);
661
+ this.data[currentCropVariant.id] = Object.assign({}, currentCropVariant, { cropArea: absoluteCropArea });
662
+ }
663
+
664
+ /**
665
+ * @desc Sets the cropper to a specific ratio
666
+ * @param {ratio} ratio - The ratio value to apply
667
+ */
668
+ private setAspectRatio(ratio: Ratio): void {
669
+ this.cropper.setAspectRatio(ratio.value);
670
+ }
671
+
672
+ /**
673
+ * @desc Sets the cropper to a specific crop area
674
+ * @param {cropArea} cropArea - The crop area to apply
675
+ */
676
+ private setCropArea(cropArea: Area): void {
677
+ const currentRatio: Ratio = this.currentCropVariant.allowedAspectRatios[this.currentCropVariant.selectedRatio];
678
+ if (currentRatio.value === 0) {
679
+ this.cropper.setData({
680
+ height: cropArea.height,
681
+ width: cropArea.width,
682
+ x: cropArea.x,
683
+ y: cropArea.y,
684
+ });
685
+ } else {
686
+ this.cropper.setData({
687
+ height: cropArea.height,
688
+ width: cropArea.height * currentRatio.value,
689
+ x: cropArea.x,
690
+ y: cropArea.y,
691
+ });
692
+ }
693
+ }
694
+
695
+ /**
696
+ * @desc Checks is one focus area and one or more cover areas overlap
697
+ * @param focusArea
698
+ * @param coverAreas
699
+ */
700
+ private checkFocusAndCoverAreasCollision(focusArea: Area, coverAreas: Area[]): boolean {
701
+ if (!coverAreas) {
702
+ return false;
703
+ }
704
+ return coverAreas
705
+ .some((coverArea: Area): boolean => {
706
+ return focusArea.x < coverArea.x + coverArea.width
707
+ && coverArea.x < focusArea.x + focusArea.width
708
+ && focusArea.y < coverArea.y + coverArea.height
709
+ && coverArea.y < focusArea.height + focusArea.y;
710
+ });
711
+ }
712
+
713
+ /**
714
+ * @desc Converts a crop area from absolute pixel-based into relative length values
715
+ * @param {Area} cropArea - The crop area to convert from
716
+ * @param {Cropper.ImageData} imageData - The image data
717
+ */
718
+ private convertAbsoluteToRelativeCropArea(cropArea: Area, imageData: Cropper.ImageData): Area {
719
+ const { height, width, x, y }: Area = cropArea;
720
+ return {
721
+ height: height / imageData.naturalHeight,
722
+ width: width / imageData.naturalWidth,
723
+ x: x / imageData.naturalWidth,
724
+ y: y / imageData.naturalHeight,
725
+ };
726
+ }
727
+
728
+ /**
729
+ * @desc Converts a crop area from relative into absolute pixel-based length values
730
+ * @param {Area} cropArea - The crop area to convert from
731
+ * @param {Cropper.ImageData} imageData - The image data
732
+ */
733
+ private convertRelativeToAbsoluteCropArea(cropArea: Area, imageData: Cropper.ImageData): Area {
734
+ const { height, width, x, y }: Area = cropArea;
735
+ return {
736
+ height: height * imageData.naturalHeight,
737
+ width: width * imageData.naturalWidth,
738
+ x: x * imageData.naturalWidth,
739
+ y: y * imageData.naturalHeight,
740
+ };
741
+ }
742
+
743
+ /**
744
+ * @desc Updates the preview images in the editing section with the respective crop variants
745
+ * @param {Object} data - The internal crop variants state
746
+ */
747
+ private setPreviewImages(data: {[key: string]: CropVariant}): void {
748
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
749
+ // @ts-ignore
750
+ const image: HTMLImageElement = this.cropper.image;
751
+ const imageData: Cropper.ImageData = this.cropper.getImageData();
752
+
753
+ // iterate over the crop variants and set up their respective preview
754
+ Object.keys(data).forEach((cropVariantId: string) => {
755
+ const cropVariant: CropVariant = data[cropVariantId];
756
+ const cropData: Area = this.convertRelativeToAbsoluteCropArea(cropVariant.cropArea, imageData);
757
+
758
+ const preview = this.trigger
759
+ .closest('.form-group')
760
+ .querySelector(`.t3js-image-manipulation-preview[data-crop-variant-id="${cropVariantId}"]`);
761
+ const previewSelectedRatio = this.trigger
762
+ .closest('.form-group')
763
+ .querySelector(`.t3js-image-manipulation-selected-ratio[data-crop-variant-id="${cropVariantId}"]`); // tslint:disable-line:max-line-length
764
+
765
+ if (!(preview instanceof HTMLElement)) {
766
+ return;
767
+ }
768
+
769
+ let previewWidth: number = preview.getBoundingClientRect().width;
770
+ let previewHeight: number = parseInt(preview.dataset.previewHeight, 10);
771
+
772
+ // adjust aspect ratio of preview width/height
773
+ const aspectRatio: number = cropData.width / cropData.height;
774
+ const tmpHeight: number = previewWidth / aspectRatio;
775
+ if (tmpHeight > previewHeight) {
776
+ previewWidth = previewHeight * aspectRatio;
777
+ } else {
778
+ previewHeight = tmpHeight;
779
+ }
780
+ // preview should never be up-scaled
781
+ if (previewWidth > cropData.width) {
782
+ previewWidth = cropData.width;
783
+ previewHeight = cropData.height;
784
+ }
785
+
786
+ const ratio: number = previewWidth / cropData.width;
787
+ const imageStyles = {
788
+ height: `${imageData.naturalHeight * ratio}px`,
789
+ left: `${-cropData.x * ratio}px`,
790
+ top: `${-cropData.y * ratio}px`,
791
+ width: `${imageData.naturalWidth * ratio}px`,
792
+ };
793
+ const containerStyles = {
794
+ width: `${previewWidth}px`,
795
+ height: `${previewHeight}px`,
796
+ };
797
+ const viewBox = html`
798
+ <span class="thumbnail thumbnail-status">
799
+ <div class="cropper-preview-container" style="${styleMap(containerStyles)}">
800
+ <img src="${image.src}" style="${styleMap(imageStyles)}">
801
+ </div>
802
+ </span>
803
+ `;
804
+ while (preview.firstChild) {
805
+ preview.removeChild(preview.firstChild);
806
+ }
807
+ this.renderElements(viewBox, preview);
808
+
809
+ const ratioTitleWindow = this.currentModal.ownerDocument.defaultView;
810
+ const ratioTitleText = this.currentModal.querySelector(`.t3-js-ratio-title[data-ratio-id="${cropVariant.id}${cropVariant.selectedRatio}"]`); // tslint:disable-line:max-line-length
811
+ if (previewSelectedRatio instanceof HTMLElement && ratioTitleText instanceof ratioTitleWindow.HTMLElement) {
812
+ previewSelectedRatio.innerText = ratioTitleText.innerText;
813
+ }
814
+ });
815
+ }
816
+
817
+ /**
818
+ * @desc Opens a preview view with the crop variants
819
+ * @param {object} data - The whole data object containing all the cropVariants
820
+ */
821
+ private openPreview(data: object): void {
822
+ const cropVariants: string = ImageManipulation.serializeCropVariants(data);
823
+ let previewUrl: string = this.trigger.dataset.previewUrl;
824
+ previewUrl = previewUrl + (previewUrl.includes('?') ? '&' : '?') + 'cropVariants=' + encodeURIComponent(cropVariants);
825
+ window.open(previewUrl, 'TYPO3ImageManipulationPreview');
826
+ }
827
+
828
+ /**
829
+ * @desc Saves the edited cropVariants to a hidden field
830
+ * @param {Record<string, CropVariant>} data - The whole data object containing all the cropVariants
831
+ */
832
+ private save(data: Record<string, CropVariant>): void {
833
+ const cropVariants: string = ImageManipulation.serializeCropVariants(data);
834
+ const hiddenField: HTMLInputElement = document.querySelector(`#${this.trigger.dataset.field}`);
835
+ this.trigger.dataset.cropVariants = JSON.stringify(data);
836
+ this.setPreviewImages(data);
837
+ hiddenField.value = cropVariants;
838
+ FormEngineValidation.markFieldAsChanged(hiddenField);
839
+ this.currentModal.hideModal();
840
+ }
841
+
842
+ /**
843
+ * @desc Destroy the ImageManipulation including cropper and alike
844
+ */
845
+ private destroy(): void {
846
+ if (this.currentModal) {
847
+ if (this.cropper instanceof Cropper) {
848
+ this.cropper.destroy();
849
+ }
850
+ this.initialized = false;
851
+ this.cropper = null;
852
+ this.currentModal = null;
853
+ this.data = null;
854
+ }
855
+ }
856
+
857
+ private convertAreaToOffset(area: Area, container: HTMLElement): Offset {
858
+ const containerBounds = container.getBoundingClientRect();
859
+ return new Offset(
860
+ area.x * containerBounds.width,
861
+ area.y * containerBounds.height,
862
+ area.width * containerBounds.width,
863
+ area.height * containerBounds.height,
864
+ );
865
+ }
866
+
867
+ private convertOffsetToArea(offset: Offset, container: HTMLElement): Area {
868
+ const containerBounds = container.getBoundingClientRect();
869
+ return {
870
+ x: offset.left / containerBounds.width,
871
+ y: offset.top / containerBounds.height,
872
+ width: offset.width / containerBounds.width,
873
+ height: offset.height / containerBounds.height,
874
+ };
875
+ }
876
+
877
+ private renderElements(template: TemplateResult, target: HTMLElement, selector?: string): HTMLElement|null {
878
+ const nodes = renderNodes(template);
879
+ const elements = Array.from(nodes).filter((node: Node): boolean => node instanceof HTMLElement);
880
+ elements.forEach((element: HTMLElement) => target.appendChild(element));
881
+ return selector ? target.querySelector(selector) : null;
882
+ }
883
+ }
884
+
885
+ export default new ImageManipulation();