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,914 @@
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 DocumentService from '@typo3/core/document-service';
15
+ import { DateTime } from 'luxon';
16
+ import type { AjaxResponse } from '@typo3/core/ajax/ajax-response';
17
+ import { SeverityEnum } from './enum/severity';
18
+ import { MessageUtility } from './utility/message-utility';
19
+ import NProgress from 'nprogress';
20
+ import AjaxRequest from '@typo3/core/ajax/ajax-request';
21
+ import { default as Modal, type ModalElement, Sizes as ModalSizes } from './modal';
22
+ import Notification from './notification';
23
+ import ImmediateAction from '@typo3/backend/action-button/immediate-action';
24
+ import Md5 from '@typo3/backend/hashing/md5';
25
+ import '@typo3/backend/element/icon-element';
26
+ import RegularEvent from '@typo3/core/event/regular-event';
27
+ import DomHelper from '@typo3/backend/utility/dom-helper';
28
+ import { KeyTypesEnum } from '@typo3/backend/enum/key-types';
29
+ import '@typo3/backend/element/progress-bar-element';
30
+ import type { ProgressBarElement } from '@typo3/backend/element/progress-bar-element';
31
+
32
+ /**
33
+ * Possible actions for conflicts w/ existing files
34
+ */
35
+ enum Action {
36
+ OVERRIDE = 'replace',
37
+ RENAME = 'rename',
38
+ SKIP = 'cancel',
39
+ USE_EXISTING = 'useExisting',
40
+ }
41
+
42
+ /**
43
+ * Properties of a file as returned from the AJAX action; essential, this is a serialized instance of
44
+ * \TYPO3\CMS\Core\Resource\File plus some extra properties (see FileController::flattenResultDataValue())
45
+ */
46
+ interface UploadedFile {
47
+ name: string;
48
+ id: number;
49
+ uid: number;
50
+ icon: string;
51
+ extension: string;
52
+ permissions: { read: boolean; write: boolean };
53
+ size: number;
54
+ // formatted as ddmmyy
55
+ date: string;
56
+
57
+ mtime: number;
58
+ thumbUrl: string;
59
+ type: string;
60
+ path: string;
61
+ }
62
+
63
+ interface FileConflict {
64
+ original: UploadedFile;
65
+ uploaded: File;
66
+ action: Action;
67
+ }
68
+
69
+ interface FlashMessage {
70
+ title: string,
71
+ message: string,
72
+ severity: number
73
+ }
74
+
75
+ export default class DragUploader {
76
+ public irreObjectUid: string;
77
+ public fileList: HTMLElement;
78
+ public fileListColumnCount: number;
79
+ public filesExtensionsAllowed: string;
80
+ public filesExtensionsDisallowed: string;
81
+ public fileDenyPattern: RegExp | null;
82
+ public maxFileSize: number;
83
+ public trigger: HTMLElement;
84
+ public target: string;
85
+ public reloadUrl: string;
86
+ public manualTable: boolean;
87
+
88
+ /**
89
+ * Array of files which are asked for being overridden
90
+ */
91
+ private askForOverride: Array<FileConflict> = [];
92
+
93
+ private percentagePerFile: number = 1;
94
+
95
+ private readonly body: HTMLElement;
96
+ private readonly element: HTMLElement;
97
+ private readonly dropzone: HTMLElement;
98
+ private readonly dropzoneMask: HTMLElement;
99
+ private readonly fileInput: HTMLInputElement;
100
+ private readonly browserCapabilities: { fileReader: boolean; DnD: boolean; Progress: boolean };
101
+ private readonly dropZoneInsertBefore: boolean;
102
+ private queueLength: number;
103
+ private readonly defaultAction: Action;
104
+ private manuallyTriggered: boolean;
105
+
106
+ /**
107
+ * This property controls whether a drag was started within the document. If true, dropzones become unavailable.
108
+ */
109
+ private dragStartedInDocument: boolean = false;
110
+
111
+ constructor(element: HTMLElement) {
112
+ this.body = document.querySelector('body');
113
+ this.element = element;
114
+ const hasTrigger = this.element.dataset.dropzoneTrigger !== undefined;
115
+ this.trigger = document.querySelector(this.element.dataset.dropzoneTrigger);
116
+ this.defaultAction = this.element.dataset.defaultAction as Action || Action.SKIP;
117
+ this.dropzone = document.createElement('div');
118
+ this.dropzone.classList.add('dropzone');
119
+ this.dropzone.setAttribute('hidden', 'hidden');
120
+ this.irreObjectUid = this.element.dataset.fileIrreObject;
121
+
122
+ const dropZoneEscapedTarget = document.querySelector(this.element.dataset.dropzoneTarget);
123
+ if (this.irreObjectUid && DomHelper.nextAll(dropZoneEscapedTarget).length !== 0) {
124
+ this.dropZoneInsertBefore = true;
125
+ dropZoneEscapedTarget.before(this.dropzone);
126
+ } else {
127
+ this.dropZoneInsertBefore = false;
128
+ dropZoneEscapedTarget.after(this.dropzone);
129
+ }
130
+ this.fileInput = <HTMLInputElement>document.createElement('input');
131
+ this.fileInput.setAttribute('type', 'file');
132
+ this.fileInput.setAttribute('multiple', 'multiple');
133
+ this.fileInput.setAttribute('name', 'files[]');
134
+ this.fileInput.classList.add('upload-file-picker');
135
+ this.body.append(this.fileInput);
136
+
137
+ this.fileList = document.querySelector(this.element.dataset.progressContainer);
138
+ this.fileListColumnCount = this.fileList?.querySelectorAll('thead tr:first-child th').length + 1;
139
+ this.filesExtensionsAllowed = this.element.dataset.fileAllowed;
140
+ this.filesExtensionsDisallowed = this.element.dataset.fileDisallowed;
141
+ this.fileDenyPattern = this.element.dataset.fileDenyPattern ? new RegExp(this.element.dataset.fileDenyPattern, 'i') : null;
142
+ this.maxFileSize = parseInt(this.element.dataset.maxFileSize, 10);
143
+ this.target = this.element.dataset.targetFolder;
144
+ this.reloadUrl = this.element.dataset.reloadUrl;
145
+
146
+ this.browserCapabilities = {
147
+ fileReader: typeof FileReader !== 'undefined',
148
+ DnD: 'draggable' in document.createElement('span'),
149
+ Progress: 'upload' in new XMLHttpRequest,
150
+ };
151
+
152
+
153
+ if (!this.browserCapabilities.DnD) {
154
+ console.warn('Browser has no Drag and drop capabilities; cannot initialize DragUploader');
155
+ return;
156
+ }
157
+
158
+ this.body.addEventListener('dragstart', (): void => {
159
+ this.dragStartedInDocument = true;
160
+ });
161
+ this.body.addEventListener('dragover', this.dragFileIntoDocument);
162
+ this.body.addEventListener('dragend', this.dragAborted);
163
+ this.body.addEventListener('drop', this.ignoreDrop);
164
+
165
+ this.dropzone.innerHTML = ('<button type="button" class="dropzone-hint" aria-labelledby="dropzone-title">' +
166
+ '<div class="dropzone-hint-media">' +
167
+ '<div class="dropzone-hint-icon"></div>' +
168
+ '</div>' +
169
+ '<div class="dropzone-hint-body">' +
170
+ '<h3 id="dropzone-title" class="dropzone-hint-title">' +
171
+ TYPO3.lang['file_upload.dropzonehint.title'] +
172
+ '</h3>' +
173
+ '<p class="dropzone-hint-message">' +
174
+ TYPO3.lang['file_upload.dropzonehint.message'] +
175
+ '</p>' +
176
+ '</div>' +
177
+ '</div>'
178
+ );
179
+
180
+ this.dropzoneMask = document.createElement('div');
181
+ this.dropzoneMask.classList.add('dropzone-mask');
182
+ this.dropzone.append(this.dropzoneMask);
183
+
184
+ this.dropzone.addEventListener('dragenter', this.fileInDropzone);
185
+ this.dropzoneMask.addEventListener('dragenter', this.fileInDropzone);
186
+ this.dropzoneMask.addEventListener('dragleave', this.fileOutOfDropzone);
187
+ this.dropzoneMask.addEventListener('drop', (ev: Event) => this.handleDrop(<DragEvent>ev));
188
+
189
+ this.dropzone.addEventListener('click', () => {
190
+ this.fileInput.click();
191
+ });
192
+
193
+ const dropzoneCloseButton = document.createElement('button');
194
+ dropzoneCloseButton.classList.add('dropzone-close');
195
+ dropzoneCloseButton.type = 'button';
196
+ dropzoneCloseButton.setAttribute('aria-label', TYPO3.lang['file_upload.dropzone.close']);
197
+ dropzoneCloseButton.addEventListener('click', this.hideDropzone);
198
+ this.dropzone.append(dropzoneCloseButton);
199
+
200
+ // no filelist then create own progress table
201
+ if (this.fileList === null) {
202
+ this.fileList = document.createElement('table');
203
+ this.fileList.setAttribute('id', 'typo3-filelist');
204
+ this.fileList.classList.add('table', 'table-striped', 'table-hover', 'upload-queue');
205
+ this.fileList.innerHTML = '<tbody></tbody>';
206
+ const $tableContainer = document.createElement('div');
207
+ $tableContainer.classList.add('table-fit');
208
+ $tableContainer.setAttribute('hidden', 'hidden');
209
+ $tableContainer.append(this.fileList);
210
+
211
+ if (this.dropZoneInsertBefore) {
212
+ this.dropzone.after($tableContainer);
213
+ } else {
214
+ this.dropzone.before($tableContainer);
215
+ }
216
+ this.fileListColumnCount = 8;
217
+ this.manualTable = true;
218
+ }
219
+
220
+ this.fileInput.addEventListener('change', (event: Event) => {
221
+ this.hideDropzone(event);
222
+ this.processFiles(this.fileInput.files);
223
+ });
224
+
225
+ // Allow the user to hide the dropzone with the "Escape" key
226
+ document.addEventListener('keydown', (event: KeyboardEvent): void => {
227
+ if (event.key === KeyTypesEnum.ENTER && !this.dropzone.hasAttribute('hidden')) {
228
+ this.hideDropzone(event);
229
+ }
230
+ });
231
+
232
+ this.bindUploadButton(hasTrigger === true ? this.trigger : this.element);
233
+ }
234
+
235
+ public static init(): void {
236
+ const observer = new MutationObserver((mutations: MutationRecord[]): void => {
237
+ for (const mutation of mutations) {
238
+ if (mutation.type === 'childList') {
239
+ for (const node of [...mutation.addedNodes.values()]) {
240
+ if (!(node instanceof HTMLElement)) {
241
+ continue;
242
+ }
243
+ if (node.matches('.t3js-drag-uploader')) {
244
+ new DragUploader(node);
245
+ } else {
246
+ node.querySelectorAll('.t3js-drag-uploader').forEach((element: HTMLElement): void => {
247
+ new DragUploader(element);
248
+ });
249
+ }
250
+ }
251
+ }
252
+ }
253
+ });
254
+ observer.observe(document, { childList: true, subtree: true });
255
+
256
+ DocumentService.ready().then((): void => {
257
+ document.querySelectorAll('.t3js-drag-uploader').forEach((element: HTMLElement): void => {
258
+ new DragUploader(element);
259
+ });
260
+ });
261
+ }
262
+
263
+ public static fileSizeAsString(size: number): string {
264
+ const sizeKB: number = size / 1024;
265
+ let str = '';
266
+
267
+ if (sizeKB > 1024) {
268
+ str = (sizeKB / 1024).toFixed(1) + ' MB';
269
+ } else {
270
+ str = sizeKB.toFixed(1) + ' KB';
271
+ }
272
+ return str;
273
+ }
274
+
275
+ /**
276
+ * @param {string} irreObject
277
+ * @param {UploadedFile} file
278
+ */
279
+ public static addFileToIrre(irreObject: string, file: UploadedFile): void {
280
+ const message = {
281
+ actionName: 'typo3:foreignRelation:insert',
282
+ objectGroup: irreObject,
283
+ table: 'sys_file',
284
+ uid: file.uid,
285
+ };
286
+ MessageUtility.send(message);
287
+ }
288
+
289
+ public showDropzone(): void {
290
+ this.dropzone.removeAttribute('hidden');
291
+ }
292
+
293
+ /**
294
+ *
295
+ * @param {Event} event
296
+ */
297
+ public hideDropzone = (event: Event): void => {
298
+ event.stopPropagation();
299
+ event.preventDefault();
300
+ this.dropzone.setAttribute('hidden', 'hidden');
301
+ this.dropzone.classList.remove('drop-status-ok');
302
+ // User manually hides the dropzone, so we can reset the flag
303
+ this.manuallyTriggered = false;
304
+ };
305
+
306
+ /**
307
+ * @param {Event} event
308
+ * @returns {boolean}
309
+ */
310
+ public dragFileIntoDocument = (event: DragEvent): boolean => {
311
+ if (this.dragStartedInDocument) {
312
+ return false;
313
+ }
314
+
315
+ if (!event.dataTransfer.types.includes('Files')) {
316
+ // Not a file upload (item drag)
317
+ return false;
318
+ }
319
+
320
+ event.stopPropagation();
321
+ event.preventDefault();
322
+ (event.currentTarget as HTMLElement).classList.add('drop-in-progress');
323
+ // Only show dropzone in case $element is currently visible. This prevents
324
+ // use cases, such as opening the dropzone in a non visible tab in FormEngine.
325
+ if (this.element.offsetParent) {
326
+ this.showDropzone();
327
+ }
328
+ return false;
329
+ };
330
+
331
+ /**
332
+ *
333
+ * @param {Event} event
334
+ * @returns {Boolean}
335
+ */
336
+ public dragAborted = (event: Event): boolean => {
337
+ event.stopPropagation();
338
+ event.preventDefault();
339
+ (event.currentTarget as HTMLElement).classList.remove('drop-in-progress');
340
+ this.dragStartedInDocument = false;
341
+ return false;
342
+ };
343
+
344
+ public ignoreDrop = (event: Event): boolean => {
345
+ // stops the browser from redirecting.
346
+ event.stopPropagation();
347
+ event.preventDefault();
348
+ this.dragAborted(event);
349
+ return false;
350
+ };
351
+
352
+ public handleDrop = (event: DragEvent): void => {
353
+ this.ignoreDrop(event);
354
+ this.hideDropzone(event);
355
+ this.processFiles(event.dataTransfer.files);
356
+ };
357
+
358
+ /**
359
+ * @param {FileList} files
360
+ */
361
+ public processFiles(files: FileList): void {
362
+ this.queueLength = files.length;
363
+
364
+ if (this.fileList.parentElement.hasAttribute('hidden')) {
365
+ // Show the filelist (table)
366
+ this.fileList.parentElement.removeAttribute('hidden');
367
+ // Remove hidden state from table container (also makes column selection etc. visible)
368
+ this.fileList.closest('.t3-filelist-container')?.classList.remove('hidden');
369
+ // Hide the information container
370
+ this.fileList.closest('.filelist-main')?.querySelector('.t3-filelist-info-container')?.setAttribute('hidden', 'hidden');
371
+ }
372
+
373
+ NProgress.start();
374
+ this.percentagePerFile = 1 / files.length;
375
+
376
+ // Check for each file if is already exist before adding it to the queue
377
+ const ajaxCalls: Promise<void>[] = [];
378
+ Array.from(files).forEach((file: File) => {
379
+ const request = new AjaxRequest(TYPO3.settings.ajaxUrls.file_exists).withQueryArguments({
380
+ fileName: file.name,
381
+ fileTarget: this.target,
382
+ }).get({ cache: 'no-cache' }).then(async (response: AjaxResponse): Promise<void> => {
383
+ const data = await response.resolve();
384
+ const fileExists = typeof data.uid !== 'undefined';
385
+ if (fileExists) {
386
+ this.askForOverride.push({
387
+ original: data,
388
+ uploaded: file,
389
+ action: this.irreObjectUid ? Action.USE_EXISTING : this.defaultAction,
390
+ });
391
+ NProgress.inc(this.percentagePerFile);
392
+ } else {
393
+ new FileQueueItem(this, file, Action.SKIP);
394
+ }
395
+ });
396
+ ajaxCalls.push(request);
397
+ });
398
+
399
+ Promise.all(ajaxCalls).then((): void => {
400
+ this.drawOverrideModal();
401
+ NProgress.done();
402
+ });
403
+
404
+ this.fileInput.value = '';
405
+ }
406
+
407
+ public fileInDropzone = (): void => {
408
+ this.dropzone.classList.add('drop-status-ok');
409
+ };
410
+
411
+ public fileOutOfDropzone = (): void => {
412
+ this.dropzone.classList.remove('drop-status-ok');
413
+ // In case dropzone was not manually triggered and this is no manual (fake) table, hide it when leaving
414
+ if (!this.manuallyTriggered) {
415
+ this.dropzone.setAttribute('hidden', 'hidden');
416
+ }
417
+ };
418
+
419
+ /**
420
+ * Bind file picker to default upload button
421
+ *
422
+ * @param {Object} button
423
+ */
424
+ public bindUploadButton(button: HTMLElement): void {
425
+ button.addEventListener('click', (event: Event) => {
426
+ event.preventDefault();
427
+ this.fileInput.click();
428
+ // In case user manually triggers the dropzone, we add a flag
429
+ this.manuallyTriggered = true;
430
+ });
431
+ }
432
+
433
+ /**
434
+ * Decrements the queue and renders a flash message if queue is empty
435
+ */
436
+ public decrementQueueLength(messages?: FlashMessage[]): void {
437
+ if (this.queueLength > 0) {
438
+ this.queueLength--;
439
+ if (this.queueLength === 0) {
440
+ const timeout: number = messages && messages.length ? 5000 : 0;
441
+ if (timeout) {
442
+ for (const flashMessage of messages) {
443
+ Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity);
444
+ }
445
+ }
446
+
447
+ if (this.reloadUrl) {
448
+ // After 5 seconds (when flash messages have disappeared), provide the user the option to reload the module
449
+ setTimeout(() => {
450
+ Notification.info(
451
+ TYPO3.lang['file_upload.reload.filelist'],
452
+ TYPO3.lang['file_upload.reload.filelist.message'],
453
+ 10,
454
+ [
455
+ {
456
+ label: TYPO3.lang['file_upload.reload.filelist.actions.dismiss'],
457
+ },
458
+ {
459
+ label: TYPO3.lang['file_upload.reload.filelist.actions.reload'],
460
+ action: new ImmediateAction((): void => {
461
+ top.list_frame.document.location.href = this.reloadUrl;
462
+ })
463
+ }
464
+ ]
465
+ );
466
+ }, timeout);
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Renders the modal for existing files
474
+ */
475
+ public drawOverrideModal(): void {
476
+ const amountOfItems = Object.keys(this.askForOverride).length;
477
+ if (amountOfItems === 0) {
478
+ return;
479
+ }
480
+ const $modalContent = document.createElement('div');
481
+ let htmlContent = `
482
+ <p>${TYPO3.lang['file_upload.existingfiles.description']}</p>
483
+ <table class="table">
484
+ <thead>
485
+ <tr>
486
+ <th></th>
487
+ <th>${TYPO3.lang['file_upload.header.originalFile']}</th>
488
+ <th>${TYPO3.lang['file_upload.header.uploadedFile']}</th>
489
+ <th>${TYPO3.lang['file_upload.header.action']}</th>
490
+ </tr>
491
+ </thead>
492
+ <tbody>
493
+ `;
494
+ for (let i = 0; i < amountOfItems; ++i) {
495
+ const $record = `
496
+ <tr>
497
+ <td>
498
+ ${this.askForOverride[i].original.thumbUrl !== ''
499
+ ? `<img src="${this.askForOverride[i].original.thumbUrl}" height="40" />`
500
+ : this.askForOverride[i].original.icon}
501
+ </td>
502
+ <td>
503
+ ${this.askForOverride[i].original.name} (${DragUploader.fileSizeAsString(this.askForOverride[i].original.size)})<br />
504
+ ${DateTime.fromSeconds(this.askForOverride[i].original.mtime).toLocaleString(DateTime.DATETIME_MED)}
505
+ </td>
506
+ <td>
507
+ ${this.askForOverride[i].uploaded.name} (${DragUploader.fileSizeAsString(this.askForOverride[i].uploaded.size)})<br />
508
+ ${DateTime.fromMillis(this.askForOverride[i].uploaded.lastModified).toLocaleString(DateTime.DATETIME_MED)}
509
+ </td>
510
+ <td>
511
+ <select class="form-select t3js-actions" data-override="${i}">
512
+ ${this.irreObjectUid ? `<option value="${Action.USE_EXISTING}">${TYPO3.lang['file_upload.actions.use_existing']}</option>` : ''}
513
+ <option value="${Action.SKIP}" ${this.defaultAction === Action.SKIP ? 'selected' : ''}>${TYPO3.lang['file_upload.actions.skip']}</option>
514
+ <option value="${Action.RENAME}" ${this.defaultAction === Action.RENAME ? 'selected' : ''}>${TYPO3.lang['file_upload.actions.rename']}</option>
515
+ <option value="${Action.OVERRIDE}" ${this.defaultAction === Action.OVERRIDE ? 'selected' : ''}>${TYPO3.lang['file_upload.actions.override']}</option>
516
+ </select>
517
+ </td>
518
+ </tr>
519
+ `;
520
+ htmlContent += $record;
521
+ }
522
+
523
+ htmlContent += '</tbody></table>';
524
+ $modalContent.innerHTML = htmlContent;
525
+
526
+ const modal = Modal.advanced({
527
+ title: TYPO3.lang['file_upload.existingfiles.title'],
528
+ content: $modalContent,
529
+ severity: SeverityEnum.warning,
530
+ buttons: [
531
+ {
532
+ text: TYPO3.lang['file_upload.button.cancel'] || 'Cancel',
533
+ active: true,
534
+ btnClass: 'btn-default',
535
+ name: 'cancel',
536
+ },
537
+ {
538
+ text: TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions',
539
+ btnClass: 'btn-warning',
540
+ name: 'continue',
541
+ },
542
+ ],
543
+ additionalCssClasses: ['modal-inner-scroll'],
544
+ size: ModalSizes.large,
545
+ callback: (modal: ModalElement): void => {
546
+ const modalFooter = modal.querySelector('.modal-footer');
547
+
548
+ const allActionLabel = document.createElement('label');
549
+ allActionLabel.textContent = TYPO3.lang['file_upload.actions.all.label'];
550
+
551
+ const allActionSelect = document.createElement('span');
552
+ allActionSelect.innerHTML = `
553
+ <select class="form-select t3js-actions-all">
554
+ <option value="" selected>${TYPO3.lang['file_upload.actions.all.empty']}</option>
555
+ ${this.irreObjectUid ? `<option value="${Action.USE_EXISTING}">${TYPO3.lang['file_upload.actions.all.use_existing']}</option>` : ''}
556
+ <option value="${Action.SKIP}">${TYPO3.lang['file_upload.actions.all.skip']}</option>
557
+ <option value="${Action.RENAME}">${TYPO3.lang['file_upload.actions.all.rename']}</option>
558
+ <option value="${Action.OVERRIDE}">${TYPO3.lang['file_upload.actions.all.override']}</option>
559
+ </select>
560
+ `;
561
+
562
+ modalFooter.prepend(allActionLabel, allActionSelect);
563
+ }
564
+ });
565
+
566
+ new RegularEvent('change', (event: Event, target: HTMLSelectElement) => {
567
+ if (target.value !== '') {
568
+ // mass action was selected, apply action to every file
569
+ for (const select of modal.querySelectorAll('.t3js-actions') as NodeListOf<HTMLSelectElement>) {
570
+ const index = parseInt(select.dataset.override, 10);
571
+ select.value = target.value;
572
+ select.disabled = true;
573
+ this.askForOverride[index].action = <Action>select.value;
574
+ }
575
+ } else {
576
+ modal.querySelectorAll('.t3js-actions').forEach((select: HTMLSelectElement) => select.disabled = false);
577
+ }
578
+ }).delegateTo(modal, '.t3js-actions-all');
579
+
580
+ new RegularEvent('change', (event: Event) => {
581
+ const actionSelect = event.target as HTMLSelectElement,
582
+ index = parseInt(actionSelect.dataset.override, 10);
583
+ this.askForOverride[index].action = <Action>actionSelect.value;
584
+ }).delegateTo(modal, '.t3js-actions');
585
+
586
+ modal.addEventListener('button.clicked', (e: Event): void => {
587
+ const button = e.target as HTMLButtonElement;
588
+ if (button.name === 'cancel') {
589
+ this.askForOverride = [];
590
+ Modal.dismiss();
591
+ } else if (button.name === 'continue') {
592
+ for (const fileInfo of this.askForOverride) {
593
+ if (fileInfo.action === Action.USE_EXISTING) {
594
+ DragUploader.addFileToIrre(
595
+ this.irreObjectUid,
596
+ fileInfo.original,
597
+ );
598
+ } else if (fileInfo.action !== Action.SKIP) {
599
+ new FileQueueItem(this, fileInfo.uploaded, fileInfo.action);
600
+ }
601
+ }
602
+ this.askForOverride = [];
603
+ modal.hideModal();
604
+ }
605
+ });
606
+
607
+ modal.addEventListener('typo3-modal-hidden', () => {
608
+ this.askForOverride = [];
609
+ });
610
+ }
611
+ }
612
+
613
+ class FileQueueItem {
614
+ private readonly row: HTMLElement;
615
+ private readonly progress: HTMLElement;
616
+ private readonly file: File;
617
+ private readonly override: Action;
618
+ private readonly selector: HTMLElement;
619
+ private readonly iconCol: HTMLElement;
620
+ private readonly fileName: HTMLElement;
621
+ private readonly progressBar: ProgressBarElement;
622
+ private readonly dragUploader: DragUploader;
623
+
624
+ constructor(dragUploader: DragUploader, file: File, override: Action) {
625
+ this.dragUploader = dragUploader;
626
+ this.file = file;
627
+ this.override = override;
628
+
629
+ this.row = document.createElement('tr');
630
+ this.row.classList.add('upload-queue-item');
631
+
632
+ if (!this.dragUploader.manualTable) {
633
+ // Add selector cell, if this is a real table (e.g. not in FormEngine)
634
+ this.selector = document.createElement('td');
635
+ this.selector.classList.add('col-checkbox');
636
+ this.row.append(this.selector);
637
+ }
638
+ this.iconCol = document.createElement('td');
639
+ this.iconCol.classList.add('col-icon');
640
+ this.row.append(this.iconCol);
641
+
642
+ this.fileName = document.createElement('td');
643
+ this.fileName.classList.add('col-title', 'col-responsive');
644
+ this.fileName.textContent = file.name;
645
+ this.row.append(this.fileName);
646
+
647
+ this.progress = document.createElement('td');
648
+ this.progress.classList.add('col-progress');
649
+ this.progress.setAttribute('colspan', String(this.dragUploader.fileListColumnCount - this.row.querySelectorAll('td').length));
650
+ this.row.append(this.progress);
651
+
652
+ this.progressBar = document.createElement('typo3-backend-progress-bar');
653
+ this.progress.append(this.progressBar);
654
+
655
+ // position queue item in filelist
656
+ if (this.dragUploader.fileList.querySelectorAll('tbody tr.upload-queue-item').length === 0) {
657
+ this.dragUploader.fileList.querySelector('tbody').prepend(this.row);
658
+ this.row.classList.add('last');
659
+ } else {
660
+ this.dragUploader.fileList.querySelector('tbody tr.upload-queue-item:first-child').before(this.row);
661
+ }
662
+
663
+ // Set a disabled checkbox to the selector column, if available
664
+ if (this.selector) {
665
+ this.selector.innerHTML = (
666
+ '<span class="form-check form-check-type-toggle">' +
667
+ '<input type="checkbox" class="form-check-input t3js-multi-record-selection-check" disabled/>' +
668
+ '</span>'
669
+ );
670
+ }
671
+
672
+ // set dummy file icon
673
+ this.iconCol.innerHTML = '<typo3-backend-icon identifier="mimetypes-other-other" />';
674
+
675
+ // check file size
676
+ if (this.dragUploader.maxFileSize > 0 && this.file.size > this.dragUploader.maxFileSize) {
677
+ this.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded']
678
+ .replace(/\{0\}/g, this.file.name)
679
+ .replace(/\{1\}/g, DragUploader.fileSizeAsString(this.dragUploader.maxFileSize)));
680
+ this.progressBar.value = 100;
681
+ this.progressBar.severity = SeverityEnum.error;
682
+
683
+ // check filename/extension against deny pattern
684
+ } else if (this.dragUploader.fileDenyPattern && this.file.name.match(this.dragUploader.fileDenyPattern)) {
685
+ this.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, this.file.name));
686
+ this.progressBar.value = 100;
687
+ this.progressBar.severity = SeverityEnum.error;
688
+
689
+ } else if (this.isProhibitedByAllowedExtensionsList()) {
690
+ this.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected'].replace(/\{0\}/g, this.dragUploader.filesExtensionsAllowed));
691
+ this.progressBar.value = 100;
692
+ this.progressBar.severity = SeverityEnum.error;
693
+ } else if (this.isProhibitedByDisallowedExtensionsList()) {
694
+ this.updateMessage(TYPO3.lang['file_upload.fileExtensionDisallowed'].replace(/\{0\}/g, this.dragUploader.filesExtensionsDisallowed));
695
+ this.progressBar.value = 100;
696
+ this.progressBar.severity = SeverityEnum.error;
697
+ } else {
698
+ this.updateMessage('- ' + DragUploader.fileSizeAsString(this.file.size));
699
+
700
+ const formData = new FormData();
701
+ formData.append('data[upload][1][target]', this.dragUploader.target);
702
+ formData.append('data[upload][1][data]', '1');
703
+ formData.append('overwriteExistingFiles', this.override);
704
+ formData.append('redirect', '');
705
+ formData.append('upload_1', this.file);
706
+
707
+ // We use XMLHttpRequest as we need the `progress` event which isn't supported by fetch()
708
+ const xhr = new XMLHttpRequest();
709
+ xhr.onreadystatechange = (): void => {
710
+ if (xhr.readyState === XMLHttpRequest.DONE) {
711
+ if (xhr.status === 200) {
712
+ try {
713
+ const response = JSON.parse(xhr.responseText);
714
+ if (!response.hasErrors) {
715
+ this.uploadSuccess(response);
716
+ } else {
717
+ this.uploadError(xhr);
718
+ }
719
+ }
720
+ catch {
721
+ // In case JSON can not be parsed, the upload failed due to server errors,
722
+ // e.g. "POST Content-Length exceeds limit". Just handle as upload error.
723
+ this.uploadError(xhr);
724
+ }
725
+ } else {
726
+ this.uploadError(xhr);
727
+ }
728
+ }
729
+ };
730
+ xhr.upload.addEventListener('progress', (e: ProgressEvent) => this.updateProgress(e));
731
+ xhr.open('POST', TYPO3.settings.ajaxUrls.file_process);
732
+ xhr.send(formData);
733
+ }
734
+ }
735
+
736
+ /**
737
+ * @param {string} message
738
+ */
739
+ public updateMessage(message: string): void {
740
+ this.progressBar.label = message;
741
+ }
742
+
743
+ /**
744
+ * Remove the progress bar
745
+ */
746
+ public removeProgress(): void {
747
+ if (this.progress) {
748
+ this.progress.remove();
749
+ }
750
+ }
751
+
752
+ /**
753
+ * @param {XMLHttpRequest} response
754
+ */
755
+ public uploadError(response: XMLHttpRequest): void {
756
+ const errorText = TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, this.file.name);
757
+ this.updateMessage(errorText);
758
+ try {
759
+ const jsonResponse = JSON.parse(response.responseText) as any;
760
+ const messages = jsonResponse.messages as FlashMessage[];
761
+ if (messages && messages.length) {
762
+ for (const flashMessage of messages) {
763
+ Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity, 10);
764
+ }
765
+ }
766
+ } catch {
767
+ // do nothing in case JSON could not be parsed
768
+ }
769
+
770
+ this.progressBar.severity = SeverityEnum.error;
771
+ this.dragUploader.decrementQueueLength();
772
+ this.dragUploader.trigger?.dispatchEvent(new CustomEvent('uploadError', { detail: [this, response] }));
773
+ }
774
+
775
+ /**
776
+ * @param {ProgressEvent} event
777
+ */
778
+ public updateProgress(event: ProgressEvent): void {
779
+ const percentage = Math.round((event.loaded / event.total) * 100);
780
+ this.progressBar.value = percentage;
781
+ this.progressBar.label = `${TYPO3.lang['file_upload.upload-in-progress']} ${percentage}%`;
782
+ this.dragUploader.trigger?.dispatchEvent(new CustomEvent('updateProgress', { detail: [this, percentage, event] }));
783
+ }
784
+
785
+ /**
786
+ * @param {{upload?: UploadedFile[]}} data
787
+ */
788
+ public uploadSuccess(data: { upload?: UploadedFile[], messages?: FlashMessage[] }): void {
789
+ if (data.upload) {
790
+ this.dragUploader.decrementQueueLength(data.messages);
791
+ this.row.setAttribute('data-type', 'file');
792
+ this.row.setAttribute('data-file-uid', String(data.upload[0].uid));
793
+ this.fileName.textContent = data.upload[0].name;
794
+ this.progressBar.value = 100;
795
+ this.progressBar.label = TYPO3.lang['file_upload.uploadSucceeded'];
796
+ this.progressBar.severity = SeverityEnum.ok;
797
+
798
+ const combinedIdentifier: string = String(data.upload[0].id);
799
+
800
+ // Enable checkbox, if available
801
+ if (this.selector) {
802
+ const checkbox: HTMLInputElement = <HTMLInputElement>this.selector.querySelector('input');
803
+ if (checkbox) {
804
+ checkbox.removeAttribute('disabled');
805
+ checkbox.setAttribute('name', 'CBC[_FILE|' + Md5.hash(combinedIdentifier) + ']');
806
+ checkbox.setAttribute('value', combinedIdentifier);
807
+ }
808
+ }
809
+
810
+ // replace file icon
811
+ if (data.upload[0].icon) {
812
+ this.iconCol
813
+ .innerHTML = (
814
+ '<button type="button" class="btn btn-link" data-contextmenu-trigger="click" data-contextmenu-uid="'
815
+ + combinedIdentifier + '" data-contextmenu-table="sys_file" aria-label="'
816
+ + (TYPO3.lang['labels.contextMenu.open'] || 'Open context menu') + '">'
817
+ + data.upload[0].icon + '</span></button>'
818
+ );
819
+ }
820
+
821
+ if (this.dragUploader.irreObjectUid) {
822
+ DragUploader.addFileToIrre(
823
+ this.dragUploader.irreObjectUid,
824
+ data.upload[0],
825
+ );
826
+ setTimeout(
827
+ () => {
828
+ this.row.remove();
829
+ if (this.dragUploader.fileList.querySelectorAll('tr').length === 0) {
830
+ this.dragUploader.fileList.setAttribute('hidden', 'hidden');
831
+ this.dragUploader.fileList.closest('.t3-filelist-container')?.classList.add('hidden');
832
+ this.dragUploader.trigger?.dispatchEvent(new CustomEvent('uploadSuccess', { detail: [this, data] }));
833
+ }
834
+ },
835
+ 3000);
836
+ } else {
837
+ setTimeout(
838
+ () => {
839
+ this.showFileInfo(data.upload[0]);
840
+ this.dragUploader.trigger?.dispatchEvent(new CustomEvent('uploadSuccess', { detail: [this, data] }));
841
+ },
842
+ 3000);
843
+ }
844
+ }
845
+ }
846
+
847
+ /**
848
+ * @param {UploadedFile} fileInfo
849
+ */
850
+ public showFileInfo(fileInfo: UploadedFile): void {
851
+ this.removeProgress();
852
+ if ((document.querySelector('#filelist-searchterm') as HTMLInputElement)?.value) {
853
+ // When search is active, the PATH column is always present so we add it
854
+ const pathColumn = document.createElement('td');
855
+ pathColumn.textContent = fileInfo.path;
856
+ this.row.append(pathColumn);
857
+ }
858
+ // Controls column is deliberately empty
859
+ const controlsColumn = document.createElement('td');
860
+ controlsColumn.classList.add('col-control');
861
+ this.row.append(controlsColumn);
862
+
863
+ const fileExtColumn = document.createElement('td');
864
+ fileExtColumn.textContent = TYPO3.lang['type.file'] + ' (' + fileInfo.extension.toUpperCase() + ')';
865
+ this.row.append(fileExtColumn);
866
+
867
+ const fileSizeColumn = document.createElement('td');
868
+ fileSizeColumn.textContent = DragUploader.fileSizeAsString(fileInfo.size);
869
+ this.row.append(fileSizeColumn);
870
+
871
+ let permissions = '';
872
+ if (fileInfo.permissions.read) {
873
+ permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>';
874
+ }
875
+ if (fileInfo.permissions.write) {
876
+ permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>';
877
+ }
878
+
879
+ const permissionsColumn = document.createElement('td');
880
+ permissionsColumn.innerHTML = permissions;
881
+ this.row.append(permissionsColumn);
882
+
883
+ const emptyColumn = document.createElement('td');
884
+ emptyColumn.textContent = '-';
885
+ this.row.append(emptyColumn);
886
+
887
+ // add spacing cells when more columns are displayed (column selector)
888
+ for (let i = this.row.querySelectorAll('td').length; i < this.dragUploader.fileListColumnCount; i++) {
889
+ this.row.append(document.createElement('td'));
890
+ }
891
+ }
892
+
893
+ public isProhibitedByAllowedExtensionsList(): boolean {
894
+ if (!this.dragUploader.filesExtensionsAllowed) {
895
+ return false;
896
+ }
897
+ const extension = this.file.name.split('.').pop();
898
+ const allowed = this.dragUploader.filesExtensionsAllowed.split(',');
899
+
900
+ return !allowed.includes(extension.toLowerCase());
901
+ }
902
+
903
+ public isProhibitedByDisallowedExtensionsList(): boolean {
904
+ if (!this.dragUploader.filesExtensionsDisallowed) {
905
+ return false;
906
+ }
907
+ const extension = this.file.name.split('.').pop();
908
+ const disallowed = this.dragUploader.filesExtensionsDisallowed.split(',');
909
+
910
+ return disallowed.includes(extension.toLowerCase());
911
+ }
912
+ }
913
+
914
+ DragUploader.init();