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,1391 @@
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
+ /**
15
+ * contains all JS functions related to TYPO3 TCEforms/FormEngine
16
+ *
17
+ * there are separate issues in this main object
18
+ * - functions, related to Element Browser ("Popup Window") and select fields
19
+ * - filling select fields (by wizard etc) from outside, formerly known via "setFormValueFromBrowseWin"
20
+ * - select fields: move selected items up and down via buttons, remove items etc
21
+ */
22
+
23
+ import DocumentService from '@typo3/core/document-service';
24
+ import $ from 'jquery';
25
+ import FormEngineValidation from '@typo3/backend/form-engine-validation';
26
+ import { default as Modal, type ModalElement } from '@typo3/backend/modal';
27
+ import * as MessageUtility from '@typo3/backend/utility/message-utility';
28
+ import Severity from '@typo3/backend/severity';
29
+ import * as BackendExceptionModule from '@typo3/backend/backend-exception';
30
+ import InteractionRequestMap from '@typo3/backend/event/interaction-request-map';
31
+ import type InteractionRequest from '@typo3/backend/event/interaction-request';
32
+ import type TriggerRequest from '@typo3/backend/event/trigger-request';
33
+ import Utility from '@typo3/backend/utility';
34
+ import { selector } from '@typo3/core/literals';
35
+ import '@typo3/backend/form-engine/element/extra/char-counter';
36
+ import type { PromiseControls } from '@typo3/backend/event/interaction-request-assignment';
37
+ import Hotkeys, { ModifierKeys } from '@typo3/backend/hotkeys';
38
+ import RegularEvent from '@typo3/core/event/regular-event';
39
+
40
+ export interface OnFieldChangeItem {
41
+ name: string;
42
+ data: {[key: string]: string|number|boolean|null}
43
+ }
44
+
45
+ type FormEngineType = {
46
+ [functionName: string]: any,
47
+ consumeTypes: string[],
48
+ Validation: typeof FormEngineValidation,
49
+ interactionRequestMap: typeof InteractionRequestMap,
50
+ formName: string,
51
+ formElement: HTMLFormElement,
52
+ openedPopupWindow: Window | null,
53
+ browserUrl: string,
54
+ doSaveFieldName: string,
55
+ };
56
+
57
+ type OnChangeFieldHandlerCallback = (data: object, e: Event) => void;
58
+ type PreviewActionCallback = (targetName: string, previewUrl: string, $actionElement: JQuery, modal: ModalElement) => void;
59
+ type NewActionCallback = (targetName: string, $actionElement: JQuery) => void;
60
+ type DuplicateActionCallback = (targetName: string, $actionElement: JQuery) => void;
61
+ type DeleteActionCallback = (targetName: string, $actionElement: JQuery) => void;
62
+
63
+ /**
64
+ * Module: @typo3/backend/form-engine
65
+ */
66
+ export default (function() {
67
+
68
+ function handleConsumeResponse(interactionRequest: InteractionRequest, response: boolean): void {
69
+ if (response) {
70
+ FormEngine.interactionRequestMap.resolveFor(interactionRequest);
71
+ } else {
72
+ FormEngine.interactionRequestMap.rejectFor(interactionRequest);
73
+ }
74
+ }
75
+
76
+ const onFieldChangeHandlers: Map<string, OnChangeFieldHandlerCallback> = new Map();
77
+
78
+ // @see \TYPO3\CMS\Backend\Form\Behavior\UpdateValueOnFieldChange
79
+ onFieldChangeHandlers.set('typo3-backend-form-update-value', (data: {elementName: string}) => {
80
+ const valueField = document.querySelector(selector`[name="${data.elementName}"]`);
81
+ const humanReadableField = document.querySelector(selector`[data-formengine-input-name="${data.elementName}"]`);
82
+ FormEngine.Validation.updateInputField(data.elementName);
83
+ if (valueField !== null) {
84
+ FormEngine.Validation.markFieldAsChanged(valueField as HTMLInputElement);
85
+ FormEngine.Validation.validateField(valueField as HTMLInputElement);
86
+ }
87
+ if (humanReadableField !== null && humanReadableField !== valueField) {
88
+ FormEngine.Validation.validateField(humanReadableField as HTMLTextAreaElement);
89
+ }
90
+ });
91
+ // @see \TYPO3\CMS\Backend\Form\Behavior\ReloadOnFieldChange
92
+ onFieldChangeHandlers.set('typo3-backend-form-reload', (data: {confirmation: boolean}) => {
93
+ const saveDocumentWithoutValidation = (): void => {
94
+ // Shortcut method to suspend FormEngine validation on purpose as user attempts to switch to another document type
95
+ // and fields may become irrelevant after switching the type (e.g. the "URL" field when switching a page's doktype from "External URL" to "Standard").
96
+ // This is a workaround! FormEngine must be able to determine on a field basis whether the field is still relevant or not.
97
+ FormEngine.Validation.suspend();
98
+ FormEngine.saveDocument();
99
+ FormEngine.Validation.resume();
100
+ };
101
+
102
+ if (!data.confirmation) {
103
+ saveDocumentWithoutValidation();
104
+ return;
105
+ }
106
+ const modal = Modal.advanced({
107
+ title: TYPO3.lang['FormEngine.refreshRequiredTitle'],
108
+ content: TYPO3.lang['FormEngine.refreshRequiredContent'],
109
+ severity: Severity.warning,
110
+ staticBackdrop: true,
111
+ buttons: [
112
+ {
113
+ text: TYPO3.lang['FormEngine.refreshRequiredCancel'] || TYPO3.lang['button.cancel'] || 'Cancel',
114
+ active: true,
115
+ btnClass: 'btn-default',
116
+ name: 'cancel',
117
+ trigger: () => {
118
+ modal.hideModal();
119
+ }
120
+ },
121
+ {
122
+ text: TYPO3.lang['FormEngine.refreshRequiredConfirm'] || TYPO3.lang['button.ok'] || 'OK',
123
+ btnClass: 'btn-' + Severity.getCssClass(Severity.warning),
124
+ name: 'ok',
125
+ trigger: () => {
126
+ FormEngine.disableDocHeaderButtons();
127
+ FormEngine.closeModalsRecursive();
128
+ saveDocumentWithoutValidation();
129
+ }
130
+ }
131
+ ]
132
+ });
133
+ });
134
+ // @see \TYPO3\CMS\Backend\Form\Behavior\UpdateBitmaskOnFieldChange
135
+ onFieldChangeHandlers.set('typo3-backend-form-update-bitmask', (data: {position: number, total: number, invert: boolean, elementName: string }, evt: Event) => {
136
+ const targetRef = evt.target; // clicked element
137
+ const elementRef = FormEngine.formElement[data.elementName]; // (hidden) element holding value
138
+ const active = (targetRef as HTMLInputElement).checked !== data.invert; // `xor` either checked or inverted
139
+ const mask = Math.pow(2, data.position);
140
+ const unmask = Math.pow(2, data.total) - mask - 1;
141
+ elementRef.value = active ? (elementRef.value | mask) : (elementRef.value & unmask);
142
+ elementRef.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
143
+ });
144
+
145
+ /**
146
+ * @exports @typo3/backend/form-engine
147
+ */
148
+ const FormEngine: FormEngineType = {
149
+ consumeTypes: ['typo3.setUrl', 'typo3.beforeSetUrl', 'typo3.refresh'],
150
+ Validation: FormEngineValidation,
151
+ interactionRequestMap: InteractionRequestMap,
152
+ formName: TYPO3.settings.FormEngine.formName,
153
+ formElement: undefined,
154
+ openedPopupWindow: null,
155
+ browserUrl: '',
156
+ doSaveFieldName: ''
157
+ };
158
+
159
+ Object.defineProperty(
160
+ FormEngine,
161
+ 'formElement',
162
+ {
163
+ get: () => document.forms.namedItem(FormEngine.formName),
164
+ enumerable: true,
165
+ configurable: false,
166
+ }
167
+ );
168
+
169
+ /**
170
+ * Opens a popup window with the element browser (browser.php)
171
+ *
172
+ * @param {string} mode can be "db" or "file"
173
+ * @param {string} params additional params for the browser window
174
+ * @param {string} entryPoint the entry point, which should be expanded by default
175
+ */
176
+ FormEngine.openPopupWindow = function(mode: string, params: string, entryPoint: string): ModalElement {
177
+ const queryParams: {mode: string, bparams: string, expandPage?: string, expandFolder?: string} = {
178
+ mode: mode,
179
+ bparams: params
180
+ };
181
+ if (entryPoint) {
182
+ if (mode === 'db') {
183
+ queryParams.expandPage = entryPoint;
184
+ } else {
185
+ queryParams.expandFolder = entryPoint;
186
+ }
187
+ }
188
+ return Modal.advanced({
189
+ type: Modal.types.iframe,
190
+ content: FormEngine.browserUrl + '&' + (new URLSearchParams(queryParams)).toString(),
191
+ size: Modal.sizes.large
192
+ });
193
+ };
194
+
195
+ /**
196
+ * properly fills the select field from the popup window (element browser, link browser)
197
+ * or from a multi-select (two selects side-by-side)
198
+ * previously known as "setFormValueFromBrowseWin"
199
+ *
200
+ * @param {string} fieldName Formerly known as "fsetFormValueFromBrowseWinName" name of the field, like [tt_content][2387][header]
201
+ * @param {string|number} value The value to fill in (could be an integer)
202
+ * @param {string} label The visible name in the selector
203
+ * @param {string} title The title when hovering over it
204
+ * @param {string} exclusiveValues If the select field has exclusive options that are not combine-able
205
+ * @param {HTMLOptionElement} optionEl The HTMLOptionElement object of the selected <option> tag
206
+ */
207
+ FormEngine.setSelectOptionFromExternalSource = function(
208
+ fieldName: string,
209
+ value: string,
210
+ label: string,
211
+ title: string,
212
+ exclusiveValues: string[] = [],
213
+ optionEl: HTMLOptionElement = undefined,
214
+ ): void {
215
+ let
216
+ fieldEl,
217
+ $fieldEl,
218
+ isMultiple = false,
219
+ isList = false;
220
+
221
+ $fieldEl = FormEngine.getFieldElement(fieldName);
222
+ fieldEl = $fieldEl.get(0);
223
+ const originalFieldEl = $fieldEl.get(0);
224
+
225
+ if (originalFieldEl === null || value === '--div--' || originalFieldEl instanceof HTMLOptGroupElement) {
226
+ return;
227
+ }
228
+
229
+ // Check if the form object has a "_list" element
230
+ // The "_list" element exists for multiple selection select types
231
+ const $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
232
+ if ($listFieldEl.length > 0) {
233
+ $fieldEl = $listFieldEl;
234
+ fieldEl = $fieldEl.get(0);
235
+
236
+ isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
237
+ isList = true;
238
+ }
239
+
240
+ if (isMultiple || isList) {
241
+ const $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
242
+ const availableFieldEl = $availableFieldEl.get(0);
243
+
244
+ // If multiple values are not allowed, clear anything that is in the control already
245
+ if (!isMultiple) {
246
+ for (const el of fieldEl.querySelectorAll('option') as NodeListOf<HTMLOptionElement>) {
247
+ const $option = $availableFieldEl.find(selector`option[value="${$(el).attr('value')}"]`);
248
+ if ($option.length) {
249
+ $option.removeClass('hidden').prop('disabled', false);
250
+ FormEngine.enableOptGroup($option.get(0));
251
+ }
252
+ }
253
+ $fieldEl.empty();
254
+ }
255
+
256
+ // Clear elements if exclusive values are found
257
+ if (exclusiveValues.length > 0) {
258
+ let reenableOptions = false;
259
+
260
+ // the new value is exclusive => remove all existing values
261
+ if (exclusiveValues.includes(value)) {
262
+ $fieldEl.empty();
263
+ reenableOptions = true;
264
+ } else if ($fieldEl.find('option').length == 1) {
265
+ // there is an old value, and it was exclusive => it has to be removed
266
+ if (exclusiveValues.includes($fieldEl.find('option').prop('value'))) {
267
+ $fieldEl.empty();
268
+ reenableOptions = true;
269
+ }
270
+ }
271
+
272
+ if (reenableOptions && typeof optionEl !== 'undefined') {
273
+ optionEl.closest('select').querySelectorAll('[disabled]').forEach(function (disabledOption: HTMLOptionElement) {
274
+ disabledOption.classList.remove('hidden');
275
+ disabledOption.disabled = false;
276
+ FormEngine.enableOptGroup(disabledOption);
277
+ });
278
+ }
279
+ }
280
+
281
+ // Inserting the new element
282
+ let addNewValue = true;
283
+
284
+ // check if there is a "_mul" field (a field on the right) and if the field was already added
285
+ const $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
286
+ if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
287
+ for (const optionEl of fieldEl.querySelectorAll('option') as NodeListOf<HTMLOptionElement>) {
288
+ if (optionEl.value == value) {
289
+ addNewValue = false;
290
+ break;
291
+ }
292
+ }
293
+
294
+ if (addNewValue && typeof optionEl !== 'undefined') {
295
+ optionEl.classList.add('hidden');
296
+ optionEl.disabled = true;
297
+ // In case the disabled option was the last active option and is in an optGroup, also disable the optGroup
298
+ const optGroup = <HTMLOptGroupElement>optionEl.parentElement;
299
+ if (optGroup instanceof HTMLOptGroupElement
300
+ && optGroup.querySelectorAll('option:not([disabled]):not([hidden]):not(.hidden)').length === 0
301
+ ) {
302
+ optGroup.disabled = true;
303
+ optGroup.classList.add('hidden');
304
+ }
305
+ }
306
+ }
307
+
308
+ // element can be added
309
+ if (addNewValue) {
310
+ // finally add the option
311
+ const $option = $('<option></option>');
312
+ $option.attr({ value: value, title: title }).text(label);
313
+ $option.appendTo($fieldEl);
314
+
315
+ // set the hidden field
316
+ FormEngine.updateHiddenFieldValueFromSelect(fieldEl, originalFieldEl);
317
+
318
+ FormEngine.Validation.markFieldAsChanged(originalFieldEl);
319
+ FormEngine.Validation.validateField(fieldEl);
320
+ FormEngine.Validation.validateField(availableFieldEl);
321
+ }
322
+
323
+ } else {
324
+
325
+ // The incoming value consists of the table name, an underscore and the uid
326
+ // or just the uid
327
+ // For a single selection field we need only the uid, so we extract it
328
+ const pattern = /_(\d+)$/
329
+ , result = value.toString().match(pattern);
330
+
331
+ if (result != null) {
332
+ value = result[1];
333
+ }
334
+
335
+ // Change the selected value
336
+ $fieldEl.val(value);
337
+ FormEngine.Validation.validateField(fieldEl);
338
+ }
339
+ };
340
+
341
+ /**
342
+ * sets the value of the hidden field, from the select list, always executed after the select field was updated
343
+ * previously known as global function setHiddenFromList()
344
+ *
345
+ * @param {HTMLElement} selectFieldEl the select field
346
+ * @param {HTMLElement} originalFieldEl the hidden form field
347
+ */
348
+ FormEngine.updateHiddenFieldValueFromSelect = function(selectFieldEl: HTMLSelectElement, originalFieldEl: HTMLSelectElement): void {
349
+ const selectedValues: string[] = Array.from(selectFieldEl.options).map((el: HTMLOptionElement) => el.value);
350
+
351
+ // make a comma separated list, if it is a multi-select
352
+ // set the values to the final hidden field
353
+ originalFieldEl.value = selectedValues.join(',');
354
+ originalFieldEl.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
355
+ };
356
+
357
+ /**
358
+ * returns a jQuery object for the given form name of the current form,
359
+ * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
360
+ * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
361
+ *
362
+ * @param {String} fieldName the field name to check for, optional
363
+ * @returns {*|HTMLElement}
364
+ */
365
+ FormEngine.getFormElement = function(fieldName: string): JQuery|HTMLElement|void {
366
+ const $formEl = $(selector`form[name="${FormEngine.formName}"]:first`);
367
+ if (fieldName) {
368
+ const $fieldEl = FormEngine.getFieldElement(fieldName)
369
+ , $listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
370
+
371
+ // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
372
+ if ($fieldEl.length > 0 &&
373
+ (
374
+ ($fieldEl.prop('type') === 'select-one') ||
375
+ ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
376
+ )
377
+ ) {
378
+ return $formEl;
379
+ } else {
380
+ console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
381
+ alert('Form field is invalid');
382
+ }
383
+ } else {
384
+ return $formEl;
385
+ }
386
+ };
387
+
388
+
389
+ /**
390
+ * Returns a jQuery object of the field DOM element of the current form, can also be used to
391
+ * request an alternative field like "_list", "_avail" or "_mul"
392
+ *
393
+ * @param {String} fieldName the name of the field (<input name="fieldName">)
394
+ * @param {String} appendix optional
395
+ * @param {Boolean} noFallback if set, then the appendix value is returned no matter if it exists or not
396
+ */
397
+ FormEngine.getFieldElement = function(fieldName: string, appendix: string, noFallback: boolean): JQuery {
398
+ // if an appendix is set, return the field with the appendix (like _mul or _list)
399
+ if (appendix) {
400
+ let $fieldEl;
401
+ switch (appendix) {
402
+ case '_list':
403
+ $fieldEl = $(selector`:input[data-formengine-input-name="${fieldName}"]:not([type=hidden])`, FormEngine.formElement);
404
+ break;
405
+ case '_avail':
406
+ $fieldEl = $(selector`:input[data-relatedfieldname="${fieldName}"]`, FormEngine.formElement);
407
+ break;
408
+ case '_mul':
409
+ $fieldEl = $(selector`:input[type=hidden][data-formengine-input-name="${fieldName}"]`, FormEngine.formElement);
410
+ break;
411
+ default:
412
+ $fieldEl = null;
413
+ break;
414
+ }
415
+ if (($fieldEl && $fieldEl.length > 0) || noFallback === true) {
416
+ return $fieldEl;
417
+ }
418
+ }
419
+
420
+ return $(FormEngine.formElement.elements.namedItem(fieldName));
421
+ };
422
+
423
+ /**
424
+ * Initialize events for all form engine relevant tasks.
425
+ * This function only needs to be called once on page load,
426
+ * as it using deferrer methods only
427
+ */
428
+ FormEngine.initializeEvents = function() {
429
+ if (top.TYPO3 && typeof top.TYPO3.Backend !== 'undefined') {
430
+ top.TYPO3.Backend.consumerScope.attach(FormEngine);
431
+ window.addEventListener('pagehide', () => top.TYPO3.Backend.consumerScope.detach(FormEngine), { once: true });
432
+ }
433
+
434
+ new RegularEvent('click', (e: Event): void => {
435
+ e.preventDefault();
436
+ FormEngine.preventExitIfNotSaved(
437
+ FormEngine.preventExitIfNotSavedCallback
438
+ );
439
+ }).delegateTo(document, '.t3js-editform-close');
440
+
441
+ new RegularEvent('click', (e: Event): void => {
442
+ e.preventDefault();
443
+ FormEngine.previewAction(e, FormEngine.previewActionCallback);
444
+ }).delegateTo(document, '.t3js-editform-view');
445
+
446
+ new RegularEvent('click', (e: Event): void => {
447
+ e.preventDefault();
448
+ FormEngine.newAction(e, FormEngine.newActionCallback);
449
+ }).delegateTo(document, '.t3js-editform-new');
450
+
451
+ new RegularEvent('click', (e: Event): void => {
452
+ e.preventDefault();
453
+ FormEngine.duplicateAction(e, FormEngine.duplicateActionCallback);
454
+ }).delegateTo(document, '.t3js-editform-duplicate');
455
+
456
+ new RegularEvent('click', (e: Event): void => {
457
+ e.preventDefault();
458
+ FormEngine.deleteAction(e, FormEngine.deleteActionCallback);
459
+ }).delegateTo(document, '.t3js-editform-delete-record');
460
+
461
+ new RegularEvent('change', (event: Event, target: HTMLInputElement): void => {
462
+ target.closest('.t3js-formengine-field-item').classList.toggle('disabled');
463
+ }).delegateTo(document, '.t3-form-field-eval-null-checkbox input[type="checkbox"]');
464
+
465
+ new RegularEvent('change', (event: Event, target: HTMLInputElement): void => {
466
+ FormEngine.toggleCheckboxField(target);
467
+ FormEngine.Validation.markFieldAsChanged(target);
468
+ }).delegateTo(document, '.t3js-form-field-eval-null-placeholder-checkbox input[type="checkbox"]');
469
+
470
+ new RegularEvent('click', (e: Event, target: HTMLElement): void => {
471
+ e.preventDefault();
472
+ e.stopPropagation();
473
+
474
+ const mode = target.dataset.mode;
475
+ const params = target.dataset.params;
476
+ const entryPoint = target.dataset.entryPoint;
477
+
478
+ FormEngine.openPopupWindow(mode, params, entryPoint);
479
+ }).delegateTo(document, '.t3js-element-browser');
480
+
481
+ new RegularEvent('click', (evt: Event, target: HTMLElement): void => {
482
+ const items = JSON.parse(target.dataset.formengineFieldChangeItems);
483
+ FormEngine.processOnFieldChange(items, evt);
484
+ }).delegateTo(document, '[data-formengine-field-change-event="click"]');
485
+
486
+ new RegularEvent('change', (evt: Event, target: HTMLElement): void => {
487
+ const items = JSON.parse(target.dataset.formengineFieldChangeItems);
488
+ FormEngine.processOnFieldChange(items, evt);
489
+ }).delegateTo(document, '[data-formengine-field-change-event="change"]');
490
+
491
+ FormEngine.formElement.addEventListener('submit', function (e: SubmitEvent) {
492
+ const form = e.target as HTMLFormElement;
493
+ if (form.closeDoc?.value !== '0') {
494
+ return;
495
+ }
496
+
497
+ if (e.submitter !== null && (e.submitter.tagName === 'A' || e.submitter.hasAttribute('form')) && !e.defaultPrevented) {
498
+ const saveField = form.querySelector(selector`input[name="${FormEngine.doSaveFieldName}"]`) as HTMLInputElement|null;
499
+ if (saveField !== null) {
500
+ saveField.value = '1';
501
+ }
502
+ }
503
+ });
504
+
505
+ window.addEventListener('message', FormEngine.handlePostMessage);
506
+ };
507
+
508
+ FormEngine.consume = function(interactionRequest: TriggerRequest): Promise<void> {
509
+ if (!interactionRequest) {
510
+ // @todo better return a rejected promise
511
+ throw new BackendExceptionModule.BackendException('No interaction request given', 1496589980);
512
+ }
513
+
514
+ let promiseControls: PromiseControls<void>;
515
+ const promise = new Promise<void>((resolve, reject) => {
516
+ promiseControls = { resolve, reject };
517
+ });
518
+
519
+ if (interactionRequest.concernsTypes(FormEngine.consumeTypes)) {
520
+ const outerMostRequest = interactionRequest.outerMostRequest;
521
+
522
+ FormEngine.interactionRequestMap.attachFor(
523
+ outerMostRequest,
524
+ promiseControls
525
+ );
526
+ // resolve or reject with previous user choice
527
+ if (outerMostRequest.isProcessed()) {
528
+ handleConsumeResponse(
529
+ outerMostRequest,
530
+ outerMostRequest.getProcessedData().response
531
+ );
532
+ // show confirmation dialog
533
+ } else if (FormEngine.hasChange() || FormEngine.isNew()) {
534
+ FormEngine.preventExitIfNotSaved(function(response: boolean) {
535
+ outerMostRequest.setProcessedData(
536
+ { response: response }
537
+ );
538
+ handleConsumeResponse(outerMostRequest, response);
539
+ });
540
+ // resolve directly
541
+ } else {
542
+ FormEngine.interactionRequestMap.resolveFor(outerMostRequest);
543
+ }
544
+ }
545
+
546
+ return promise;
547
+ };
548
+
549
+ FormEngine.handlePostMessage = function (e: MessageEvent) {
550
+ if (!MessageUtility.MessageUtility.verifyOrigin(e.origin)) {
551
+ throw 'Denied message sent by ' + e.origin;
552
+ }
553
+
554
+ if (e.data.actionName === 'typo3:elementBrowser:elementAdded') {
555
+ if (typeof e.data.fieldName === 'undefined') {
556
+ throw 'fieldName not defined in message';
557
+ }
558
+
559
+ if (typeof e.data.value === 'undefined') {
560
+ throw 'value not defined in message';
561
+ }
562
+
563
+ const label = e.data.label || e.data.value;
564
+ const title = e.data.title || label;
565
+ const exclusiveValues = Utility.trimExplode(',', e.data?.exclusiveValues ?? '');
566
+
567
+ FormEngine.setSelectOptionFromExternalSource(e.data.fieldName, e.data.value, label, title, exclusiveValues);
568
+ }
569
+ };
570
+
571
+ /**
572
+ * Initializes the remaining character views based on the fields' maxlength attribute
573
+ */
574
+ FormEngine.initializeRemainingCharacterViews = function() {
575
+ // all fields with a "maxlength" attribute
576
+ const elementsWithMaxLengths: NodeListOf<HTMLInputElement|HTMLTextAreaElement> = document.querySelectorAll('[maxlength]:not([data-input-type="datetimepicker"]):not(.t3js-color-picker)');
577
+ elementsWithMaxLengths.forEach((element): void => {
578
+ const fieldItem = element.closest('.t3js-formengine-field-item');
579
+ if (fieldItem !== null && fieldItem.querySelector('typo3-backend-formengine-char-counter') === null) {
580
+ const charCounterElement = document.createElement('typo3-backend-formengine-char-counter');
581
+ charCounterElement.setAttribute('target', `[data-formengine-input-name="${selector`${element.dataset.formengineInputName}`}"]`);
582
+ fieldItem.append(charCounterElement);
583
+ }
584
+ });
585
+ };
586
+
587
+ /**
588
+ * Initializes the left character count needed to reach the minimum value based on the field's minlength attribute
589
+ */
590
+ FormEngine.initializeMinimumCharactersLeftViews = function () {
591
+ const addOrUpdateCounter = (minCharacterCountLeft: string, event: Event) => {
592
+ const parent = (event.currentTarget as HTMLInputElement).closest('.t3js-formengine-field-item');
593
+ const counter = parent.querySelector('.t3js-charcounter-min');
594
+ const labelValue = TYPO3.lang['FormEngine.minCharactersLeft'].replace('{0}', minCharacterCountLeft);
595
+ if (counter) {
596
+ counter.querySelector('span').innerHTML = labelValue;
597
+ } else {
598
+ const counter = document.createElement('div');
599
+ counter.classList.add('t3js-charcounter-min');
600
+ const label = document.createElement('span');
601
+ label.classList.add('badge', 'badge-danger');
602
+ label.innerHTML = labelValue;
603
+ counter.append(label);
604
+ let wrapper = parent.querySelector('.t3js-charcounter-wrapper');
605
+ if (!wrapper) {
606
+ wrapper = document.createElement('div');
607
+ wrapper.classList.add('t3js-charcounter-wrapper');
608
+ parent.append(wrapper);
609
+ }
610
+ wrapper.prepend(counter);
611
+ }
612
+ };
613
+ const removeCounter = (event: Event) => {
614
+ const parent = (event.currentTarget as HTMLInputElement).closest('.t3js-formengine-field-item');
615
+ const counter = parent.querySelector('.t3js-charcounter-min');
616
+ if (counter) {
617
+ counter.remove();
618
+ }
619
+ };
620
+
621
+ const minlengthElements = document.querySelectorAll('[minlength]:not([data-input-type="datetimepicker"]):not(.t3js-charcounter-min-initialized)');
622
+ minlengthElements.forEach((field: HTMLInputElement|HTMLTextAreaElement) => {
623
+ field.addEventListener('focus', (event) => {
624
+ const minCharacterCountLeft = FormEngine.getMinCharacterLeftCount(field);
625
+ if (minCharacterCountLeft > 0) {
626
+ addOrUpdateCounter(minCharacterCountLeft, event);
627
+ }
628
+ });
629
+
630
+ field.addEventListener('blur', removeCounter);
631
+
632
+ field.addEventListener('keyup', (event) => {
633
+ const minCharacterCountLeft = FormEngine.getMinCharacterLeftCount(field);
634
+ if (minCharacterCountLeft > 0) {
635
+ addOrUpdateCounter(minCharacterCountLeft, event);
636
+ } else {
637
+ removeCounter(event);
638
+ }
639
+ });
640
+ });
641
+ };
642
+
643
+ /**
644
+ * Get the properties required for proper rendering of the character counter
645
+ *
646
+ * @param {HTMLElement} field
647
+ * @returns number
648
+ */
649
+ FormEngine.getMinCharacterLeftCount = function (field: HTMLInputElement|HTMLTextAreaElement) {
650
+ const text = field.value;
651
+ const minlength = field.minLength;
652
+ const currentFieldLength = text.length;
653
+
654
+ // minLength doesn't care about empty fields.
655
+ if (currentFieldLength === 0) {
656
+ return 0;
657
+ }
658
+
659
+ const numberOfLineBreaks = (text.match(/\n/g) || []).length; // count line breaks
660
+ const minimumCharactersLeft = minlength - currentFieldLength - numberOfLineBreaks;
661
+
662
+ return minimumCharactersLeft;
663
+ };
664
+
665
+ /**
666
+ * Initialize input / text field "null" checkbox CSS overlay if no placeholder is set.
667
+ */
668
+ FormEngine.initializeNullNoPlaceholderCheckboxes = function(): void {
669
+ document.querySelectorAll('.t3-form-field-eval-null-checkbox').forEach((el: HTMLElement) => {
670
+ // Add disabled class to "t3js-formengine-field-item" if the null checkbox is NOT set,
671
+ // This activates a CSS overlay "disabling" the input field and everything around.
672
+ const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement;
673
+ const fieldItem = el.closest('.t3js-formengine-field-item');
674
+ if (!checkbox.checked) {
675
+ fieldItem.classList.add('disabled');
676
+ }
677
+ });
678
+ };
679
+
680
+ /**
681
+ * Initialize input / text field "null" checkbox placeholder / real field if placeholder is set.
682
+ */
683
+ FormEngine.initializeNullWithPlaceholderCheckboxes = function(): void {
684
+ document.querySelectorAll('.t3js-form-field-eval-null-placeholder-checkbox').forEach((el: HTMLElement) => {
685
+ FormEngine.toggleCheckboxField(el.querySelector('input[type="checkbox"]'), false);
686
+ });
687
+ };
688
+
689
+ /**
690
+ * Set initial state of both div's (one containing actual field, other containing placeholder field)
691
+ * depending on whether checkbox is checked or not
692
+ */
693
+ FormEngine.toggleCheckboxField = function(checkbox: HTMLInputElement, triggerFocusWhenChecked: boolean = true): void {
694
+ const item = checkbox.closest('.t3js-formengine-field-item');
695
+ const placeholder = item.querySelector('.t3js-formengine-placeholder-placeholder') as HTMLElement;
696
+ const formFieldWrapper = item.querySelector('.t3js-formengine-placeholder-formfield') as HTMLElement;
697
+
698
+ if (checkbox.checked) {
699
+ placeholder.hidden = true;
700
+ formFieldWrapper.hidden = false;
701
+ if (triggerFocusWhenChecked) {
702
+ (formFieldWrapper.querySelector('input,select,textarea') as HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement)?.focus();
703
+ }
704
+ } else {
705
+ placeholder.hidden = false;
706
+ formFieldWrapper.hidden = true;
707
+ }
708
+ };
709
+
710
+ /**
711
+ * This is the main function that is called on page load, but also after elements are asynchronously
712
+ * called e.g. after inline elements are loaded, or a new flexform section is added.
713
+ * Use this function in your extension like this "TYPO3.FormEngine.initialize()"
714
+ * if you add new fields dynamically.
715
+ */
716
+ FormEngine.reinitialize = function(): void {
717
+ // Apply "close" button to all input / datetime fields
718
+ const clearables = document.querySelectorAll('.t3js-clearable') as NodeListOf<HTMLInputElement>;
719
+ if (clearables.length > 0) {
720
+ import('@typo3/backend/input/clearable').then(function() {
721
+ clearables.forEach(clearableField => clearableField.clearable());
722
+ });
723
+ }
724
+
725
+ FormEngine.initializeNullNoPlaceholderCheckboxes();
726
+ FormEngine.initializeNullWithPlaceholderCheckboxes();
727
+ FormEngine.initializeLocalizationStateSelector();
728
+ FormEngine.initializeMinimumCharactersLeftViews();
729
+ FormEngine.initializeRemainingCharacterViews();
730
+ };
731
+
732
+ /**
733
+ * Disable the input field on load if localization state selector is set to "parent" or "source"
734
+ */
735
+ FormEngine.initializeLocalizationStateSelector = function(): void {
736
+ document.querySelectorAll('.t3js-l10n-state-container').forEach((el: HTMLElement) => {
737
+ const input = el.closest('.t3js-formengine-field-item')?.querySelector('[data-formengine-input-name]') as HTMLInputElement|null;
738
+ if (input === undefined || input === null) {
739
+ return;
740
+ }
741
+ const currentState = (el.querySelector('input[type="radio"]:checked') as HTMLInputElement)?.value;
742
+ if (currentState === undefined) {
743
+ console.warn('The localization state of the field ' + input.dataset.formengineInputName + ' cannot be determined. This smells like a DataHandler bug.');
744
+ }
745
+ if (currentState === 'parent' || currentState === 'source') {
746
+ input.disabled = true;
747
+ }
748
+ });
749
+ };
750
+
751
+ /**
752
+ * @return {boolean}
753
+ */
754
+ FormEngine.hasChange = function(): boolean {
755
+
756
+ const formElementChanges = $(selector`form[name="${FormEngine.formName}"] .has-change`).length > 0,
757
+ inlineRecordChanges = $('[name^="data["].has-change').length > 0;
758
+ return formElementChanges || inlineRecordChanges;
759
+ };
760
+
761
+ /**
762
+ * @return {boolean}
763
+ */
764
+ FormEngine.isNew = function(): boolean {
765
+ return (document.querySelector('form[name="' + FormEngine.formName + '"] .typo3-TCEforms.is-new') !== null);
766
+ };
767
+
768
+ /**
769
+ * @param {boolean} response
770
+ */
771
+ FormEngine.preventExitIfNotSavedCallback = (): void => {
772
+ FormEngine.closeDocument();
773
+ };
774
+
775
+ /**
776
+ * Show modal to confirm following a clicked link to confirm leaving the document without saving
777
+ *
778
+ * @param {String} href
779
+ * @returns {Boolean}
780
+ */
781
+ FormEngine.preventFollowLinkIfNotSaved = function(href: string): boolean {
782
+ FormEngine.preventExitIfNotSaved(
783
+ function () {
784
+ window.location.href = href;
785
+ }
786
+ );
787
+ return false;
788
+ };
789
+
790
+ /**
791
+ * Show modal to confirm closing the document without saving.
792
+ *
793
+ * @param {Function} callback
794
+ */
795
+ FormEngine.preventExitIfNotSaved = function(callback: (response: boolean) => void): void {
796
+ callback = callback || FormEngine.preventExitIfNotSavedCallback;
797
+
798
+ if (FormEngine.hasChange() || FormEngine.isNew()) {
799
+ const title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to close without saving?';
800
+ const content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You currently have unsaved changes. Are you sure you want to discard these changes?';
801
+ const buttons: Array<{text: string, btnClass: string, name: string, active?: boolean}> = [
802
+ {
803
+ text: TYPO3.lang['buttons.confirm.close_without_save.no'] || 'No, I will continue editing',
804
+ btnClass: 'btn-default',
805
+ name: 'no'
806
+ },
807
+ {
808
+ text: TYPO3.lang['buttons.confirm.close_without_save.yes'] || 'Yes, discard my changes',
809
+ btnClass: 'btn-default',
810
+ name: 'yes'
811
+ }
812
+ ];
813
+ if ($('.has-error').length === 0) {
814
+ buttons.push({
815
+ text: TYPO3.lang['buttons.confirm.save_and_close'] || 'Save and close',
816
+ btnClass: 'btn-primary',
817
+ name: 'save',
818
+ active: true
819
+ });
820
+ }
821
+
822
+ const modal = Modal.confirm(title, content, Severity.warning, buttons);
823
+ modal.addEventListener('button.clicked', function(e: Event) {
824
+ if ((e.target as HTMLButtonElement).name === 'no') {
825
+ modal.hideModal();
826
+ } else if ((e.target as HTMLButtonElement).name === 'yes') {
827
+ modal.hideModal();
828
+ callback.call(null, true);
829
+ } else if ((e.target as HTMLButtonElement).name === 'save') {
830
+ modal.hideModal();
831
+ FormEngine.saveAndCloseDocument();
832
+ }
833
+ });
834
+ } else {
835
+ callback.call(null, true);
836
+ }
837
+ };
838
+
839
+ /**
840
+ * Show modal to confirm closing the document without saving
841
+ */
842
+ FormEngine.preventSaveIfHasErrors = function(): boolean {
843
+ if ($('.has-error').length > 0) {
844
+ const title = TYPO3.lang['label.alert.save_with_error.title'] || 'You have errors in your form!';
845
+ const content = TYPO3.lang['label.alert.save_with_error.content'] || 'Please check the form, there is at least one error in your form.';
846
+ const modal = Modal.confirm(title, content, Severity.error, [
847
+ {
848
+ text: TYPO3.lang['buttons.alert.save_with_error.ok'] || 'OK',
849
+ btnClass: 'btn-danger',
850
+ name: 'ok'
851
+ }
852
+ ]);
853
+ modal.addEventListener('button.clicked', function(e: Event) {
854
+ if ((e.target as HTMLButtonElement).name === 'ok') {
855
+ modal.hideModal();
856
+ }
857
+ });
858
+ return false;
859
+ }
860
+ return true;
861
+ };
862
+
863
+ /**
864
+ * @param {OnFieldChangeItem[]} items
865
+ * @param {Event|null|undefined} evt
866
+ */
867
+ FormEngine.processOnFieldChange = function(items: Array<OnFieldChangeItem>, evt?: Event|null) {
868
+ items.forEach((item) => {
869
+ const handler = onFieldChangeHandlers.get(item.name);
870
+ if (handler instanceof Function) {
871
+ handler.call(null, item.data || null, evt);
872
+ }
873
+ });
874
+ };
875
+
876
+ FormEngine.registerOnFieldChangeHandler = function(name: string, handler: OnChangeFieldHandlerCallback): void {
877
+ if (onFieldChangeHandlers.has(name)) {
878
+ console.warn('Handler for onFieldChange name `' + name + '` has been overridden.');
879
+ }
880
+ onFieldChangeHandlers.set(name, handler);
881
+ };
882
+
883
+ FormEngine.closeModalsRecursive = function() {
884
+ if (typeof Modal.currentModal !== 'undefined' && Modal.currentModal !== null) {
885
+ Modal.currentModal.addEventListener('typo3-modal-hidden', function () {
886
+ FormEngine.closeModalsRecursive();
887
+ });
888
+ Modal.currentModal.hideModal();
889
+ }
890
+ };
891
+
892
+ /**
893
+ * Disables all doc header buttons to prevent unintended operations.
894
+ * Useful when showing modals that require page reload (e.g., refreshRequiredConfirm)
895
+ * to prevent operations (e.g. save) on slow connections before the page is actually reloaded.
896
+ */
897
+ FormEngine.disableDocHeaderButtons = function(): void {
898
+ const docHeaderBar = document.querySelector('.t3js-module-docheader-bar-buttons');
899
+ if (!docHeaderBar) {
900
+ return;
901
+ }
902
+
903
+ // Disable all buttons and links in the doc header
904
+ docHeaderBar.querySelectorAll('button, a, input[type="submit"]').forEach((element: HTMLElement): void => {
905
+ if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
906
+ element.disabled = true;
907
+ } else if (element instanceof HTMLAnchorElement) {
908
+ // For anchor elements - use TYPO3 standard approach
909
+ element.classList.add('disabled');
910
+ element.setAttribute('aria-disabled', 'true');
911
+ }
912
+ });
913
+ };
914
+
915
+ /**
916
+ * Enables all doc header buttons.
917
+ * Called after FormEngine initialization is complete to restore button interactivity.
918
+ */
919
+ FormEngine.enableDocHeaderButtons = function(): void {
920
+ const docHeaderBar = document.querySelector('.t3js-module-docheader-bar-buttons');
921
+ if (!docHeaderBar) {
922
+ return;
923
+ }
924
+
925
+ // Enable all buttons and links in the doc header
926
+ docHeaderBar.querySelectorAll('button, a, input[type="submit"]').forEach((element: HTMLElement): void => {
927
+ if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
928
+ element.disabled = false;
929
+ } else if (element instanceof HTMLAnchorElement) {
930
+ // For anchor elements - remove TYPO3 disabled state
931
+ element.classList.remove('disabled');
932
+ element.removeAttribute('aria-disabled');
933
+ }
934
+ });
935
+ };
936
+
937
+ /**
938
+ * Preview action
939
+ *
940
+ * When there are changes:
941
+ * Will take action based on local storage preset
942
+ * If preset is not available, a modal will open
943
+ *
944
+ * @param {Event} event
945
+ * @param {Function} callback
946
+ */
947
+ FormEngine.previewAction = function(event: Event, callback: PreviewActionCallback): void {
948
+ callback = callback || FormEngine.previewActionCallback;
949
+
950
+ const previewUrl = (event.target as HTMLAnchorElement).href;
951
+ const isNew = ('isNew' in (event.target as HTMLAnchorElement).dataset);
952
+ const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedokview').attr('value', '1');
953
+ if (FormEngine.hasChange() || FormEngine.isNew()) {
954
+ FormEngine.showPreviewModal(previewUrl, isNew, $actionElement, callback);
955
+ } else {
956
+ $(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
957
+ window.open('', 'newTYPO3frontendWindow');
958
+ FormEngine.formElement.submit();
959
+ }
960
+ };
961
+
962
+ /**
963
+ * The callback for the preview action
964
+ *
965
+ * @param {string} modalButtonName
966
+ * @param {string} previewUrl
967
+ * @param {ModalElement} actionElement
968
+ */
969
+ FormEngine.previewActionCallback = function(modalButtonName: string, previewUrl: string, actionElement: ModalElement): void {
970
+ Modal.dismiss();
971
+ switch(modalButtonName) {
972
+ case 'discard':
973
+ const previewWin = window.open(previewUrl, 'newTYPO3frontendWindow');
974
+ previewWin.focus();
975
+
976
+ if (Utility.urlsPointToSameServerSideResource(previewWin.location.href, previewUrl)) {
977
+ previewWin.location.reload();
978
+ }
979
+ break;
980
+ case 'save':
981
+ $(selector`form[name="${FormEngine.formName}"]`).append($(actionElement));
982
+ window.open('', 'newTYPO3frontendWindow');
983
+ FormEngine.saveDocument();
984
+ break;
985
+ default:
986
+ break;
987
+ }
988
+ };
989
+
990
+ /**
991
+ * Show the preview modal
992
+ *
993
+ * @param {string} previewUrl
994
+ * @param {bool} isNew
995
+ * @param {element} $actionElement
996
+ * @param {Function} callback
997
+ */
998
+ FormEngine.showPreviewModal = function(previewUrl: string, isNew: boolean, $actionElement: JQuery, callback: PreviewActionCallback): void {
999
+ const title = TYPO3.lang['label.confirm.view_record_changed.title'] || 'Do you want to save before viewing?';
1000
+ const modalCancelButtonConfiguration = {
1001
+ text: TYPO3.lang['buttons.confirm.view_record_changed.cancel'] || 'Cancel',
1002
+ btnClass: 'btn-default',
1003
+ name: 'cancel'
1004
+ };
1005
+ const modaldismissViewButtonConfiguration = {
1006
+ text: TYPO3.lang['buttons.confirm.view_record_changed.no-save'] || 'View without changes',
1007
+ btnClass: 'btn-default',
1008
+ name: 'discard'
1009
+ };
1010
+ const modalsaveViewButtonConfiguration = {
1011
+ text: TYPO3.lang['buttons.confirm.view_record_changed.save'] || 'Save changes and view',
1012
+ btnClass: 'btn-primary',
1013
+ name: 'save',
1014
+ active: true
1015
+ };
1016
+ let modalButtons = [];
1017
+ let contentStrings = [];
1018
+ if (isNew) {
1019
+ if (!FormEngine.Validation.isValid()) {
1020
+ FormEngine.Validation.showErrorModal();
1021
+ return;
1022
+ }
1023
+ modalButtons = [
1024
+ modalCancelButtonConfiguration,
1025
+ modalsaveViewButtonConfiguration
1026
+ ];
1027
+ contentStrings = [
1028
+ TYPO3.lang['label.confirm.view_record_changed.content.is-new-page']
1029
+ || 'You need to save your changes before viewing the page. Do you want to save and view them now?'
1030
+ ];
1031
+ } else {
1032
+ modalButtons = [
1033
+ modalCancelButtonConfiguration,
1034
+ modaldismissViewButtonConfiguration,
1035
+ ];
1036
+ contentStrings = [
1037
+ TYPO3.lang['label.confirm.view_record_changed.content']
1038
+ || 'You currently have unsaved changes. You can either discard these changes or save and view them.'
1039
+ ];
1040
+ if (FormEngine.Validation.isValid()) {
1041
+ modalButtons.push(modalsaveViewButtonConfiguration);
1042
+ } else {
1043
+ contentStrings.push(
1044
+ TYPO3.lang['label.confirm.view_record_changed.invalid_form']
1045
+ || 'The form appears to be invalid, therefore "Save changes and view" is not available.'
1046
+ );
1047
+ }
1048
+ }
1049
+ const contentElement = document.createElement('p');
1050
+ contentStrings.forEach((item: string, i: number): void => {
1051
+ contentElement.append(item);
1052
+ if (i !== contentStrings.length - 1) {
1053
+ contentElement.append(document.createElement('br'));
1054
+ }
1055
+ });
1056
+ const modal = Modal.confirm(title, contentElement, Severity.info, modalButtons);
1057
+ modal.addEventListener('button.clicked', function (event: Event) {
1058
+ callback((event.target as HTMLButtonElement).name, previewUrl, $actionElement, modal);
1059
+ });
1060
+ };
1061
+
1062
+ /**
1063
+ * New action
1064
+ *
1065
+ * When there are changes:
1066
+ * Will take action based on local storage preset
1067
+ * If preset is not available, a modal will open
1068
+ *
1069
+ * @param {Event} event
1070
+ * @param {Function} callback
1071
+ */
1072
+ FormEngine.newAction = function(event: Event, callback: NewActionCallback): void {
1073
+ callback = callback || FormEngine.newActionCallback;
1074
+
1075
+ const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedoknew').attr('value', '1');
1076
+ const isNew = ('isNew' in (event.target as HTMLElement).dataset);
1077
+ if (FormEngine.hasChange() || FormEngine.isNew()) {
1078
+ FormEngine.showNewModal(isNew, $actionElement, callback);
1079
+ } else {
1080
+ $(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
1081
+ FormEngine.formElement.submit();
1082
+ }
1083
+ };
1084
+
1085
+ /**
1086
+ * The callback for the preview action
1087
+ *
1088
+ * @param {string} modalButtonName
1089
+ * @param {element} $actionElement
1090
+ */
1091
+ FormEngine.newActionCallback = function(modalButtonName: string, $actionElement: JQuery): void {
1092
+ const $form = $(selector`form[name="${FormEngine.formName}"]`);
1093
+ Modal.dismiss();
1094
+ switch(modalButtonName) {
1095
+ case 'no':
1096
+ $form.append($actionElement);
1097
+ FormEngine.formElement.submit();
1098
+ break;
1099
+ case 'yes':
1100
+ $form.append($actionElement);
1101
+ FormEngine.saveDocument();
1102
+ break;
1103
+ default:
1104
+ break;
1105
+ }
1106
+ };
1107
+
1108
+ /**
1109
+ * Show the new modal
1110
+ *
1111
+ * @param {bool} isNew
1112
+ * @param {element} $actionElement
1113
+ * @param {Function} callback
1114
+ */
1115
+ FormEngine.showNewModal = function(isNew: boolean, $actionElement: JQuery, callback: NewActionCallback): void {
1116
+ const title = TYPO3.lang['label.confirm.new_record_changed.title'] || 'Do you want to save before adding?';
1117
+ const content = (
1118
+ TYPO3.lang['label.confirm.new_record_changed.content']
1119
+ || 'You need to save your changes before creating a new record. Do you want to save and create now?'
1120
+ );
1121
+ let modalButtons = [];
1122
+ const modalCancelButtonConfiguration = {
1123
+ text: TYPO3.lang['buttons.confirm.new_record_changed.cancel'] || 'Cancel',
1124
+ btnClass: 'btn-default',
1125
+ name: 'cancel'
1126
+ };
1127
+ const modalNoButtonConfiguration = {
1128
+ text: TYPO3.lang['buttons.confirm.new_record_changed.no'] || 'No, just add',
1129
+ btnClass: 'btn-default',
1130
+ name: 'no'
1131
+ };
1132
+ const modalYesButtonConfiguration = {
1133
+ text: TYPO3.lang['buttons.confirm.new_record_changed.yes'] || 'Yes, save and create now',
1134
+ btnClass: 'btn-primary',
1135
+ name: 'yes',
1136
+ active: true
1137
+ };
1138
+ if (isNew) {
1139
+ modalButtons = [
1140
+ modalCancelButtonConfiguration,
1141
+ modalYesButtonConfiguration
1142
+ ];
1143
+ } else {
1144
+ modalButtons = [
1145
+ modalCancelButtonConfiguration,
1146
+ modalNoButtonConfiguration,
1147
+ modalYesButtonConfiguration
1148
+ ];
1149
+ }
1150
+ const modal = Modal.confirm(title, content, Severity.info, modalButtons);
1151
+ modal.addEventListener('button.clicked', function (event: Event) {
1152
+ callback((event.target as HTMLButtonElement).name, $actionElement);
1153
+ });
1154
+ };
1155
+
1156
+ /**
1157
+ * Duplicate action
1158
+ *
1159
+ * When there are changes:
1160
+ * Will take action based on local storage preset
1161
+ * If preset is not available, a modal will open
1162
+ */
1163
+ FormEngine.duplicateAction = function(event: Event, callback: DuplicateActionCallback): void {
1164
+ callback = callback || FormEngine.duplicateActionCallback;
1165
+
1166
+ const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_duplicatedoc').attr('value', '1');
1167
+ const isNew = ('isNew' in (event.target as HTMLElement).dataset);
1168
+ if (FormEngine.hasChange() || FormEngine.isNew()) {
1169
+ FormEngine.showDuplicateModal(isNew, $actionElement, callback);
1170
+ } else {
1171
+ $(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
1172
+ FormEngine.formElement.submit();
1173
+ }
1174
+ };
1175
+
1176
+ /**
1177
+ * The callback for the duplicate action
1178
+ *
1179
+ * @param {string} modalButtonName
1180
+ * @param {element} $actionElement
1181
+ */
1182
+ FormEngine.duplicateActionCallback = function(modalButtonName: string, $actionElement: JQuery): void {
1183
+ const $form = $(selector`form[name="${FormEngine.formName}"]`);
1184
+ Modal.dismiss();
1185
+ switch(modalButtonName) {
1186
+ case 'no':
1187
+ $form.append($actionElement);
1188
+ FormEngine.formElement.submit();
1189
+ break;
1190
+ case 'yes':
1191
+ $form.append($actionElement);
1192
+ FormEngine.saveDocument();
1193
+ break;
1194
+ default:
1195
+ break;
1196
+ }
1197
+ };
1198
+
1199
+ FormEngine.showDuplicateModal = function(isNew: boolean, $actionElement: JQuery, callback: DuplicateActionCallback): void {
1200
+ const title = TYPO3.lang['label.confirm.duplicate_record_changed.title'] || 'Do you want to save before duplicating this record?';
1201
+ const content = (
1202
+ TYPO3.lang['label.confirm.duplicate_record_changed.content']
1203
+ || 'You currently have unsaved changes. Do you want to save your changes before duplicating this record?'
1204
+ );
1205
+ let modalButtons = [];
1206
+ const modalCancelButtonConfiguration = {
1207
+ text: TYPO3.lang['buttons.confirm.duplicate_record_changed.cancel'] || 'Cancel',
1208
+ btnClass: 'btn-default',
1209
+ name: 'cancel'
1210
+ };
1211
+ const modalDismissDuplicateButtonConfiguration = {
1212
+ text: TYPO3.lang['buttons.confirm.duplicate_record_changed.no'] || 'No, just duplicate the original',
1213
+ btnClass: 'btn-default',
1214
+ name: 'no'
1215
+ };
1216
+ const modalSaveDuplicateButtonConfiguration = {
1217
+ text: TYPO3.lang['buttons.confirm.duplicate_record_changed.yes'] || 'Yes, save and duplicate this record',
1218
+ btnClass: 'btn-primary',
1219
+ name: 'yes',
1220
+ active: true
1221
+ };
1222
+ if (isNew) {
1223
+ modalButtons = [
1224
+ modalCancelButtonConfiguration,
1225
+ modalSaveDuplicateButtonConfiguration
1226
+ ];
1227
+ } else {
1228
+ modalButtons = [
1229
+ modalCancelButtonConfiguration,
1230
+ modalDismissDuplicateButtonConfiguration,
1231
+ modalSaveDuplicateButtonConfiguration
1232
+ ];
1233
+ }
1234
+ const modal = Modal.confirm(title, content, Severity.info, modalButtons);
1235
+ modal.addEventListener('button.clicked', function (event: Event) {
1236
+ callback((event.target as HTMLButtonElement).name, $actionElement);
1237
+ });
1238
+ };
1239
+
1240
+ /**
1241
+ * Delete action
1242
+ *
1243
+ * When there are changes:
1244
+ * Will take action based on local storage preset
1245
+ * If preset is not available, a modal will open
1246
+ */
1247
+ FormEngine.deleteAction = function(event: Event, callback: DeleteActionCallback): void {
1248
+ callback = callback || FormEngine.deleteActionCallback;
1249
+
1250
+ const $anchorElement = $(event.target);
1251
+
1252
+ FormEngine.showDeleteModal($anchorElement, callback);
1253
+ };
1254
+
1255
+ /**
1256
+ * The callback for the delete action
1257
+ *
1258
+ * @param {string} modalButtonName
1259
+ * @param {element} $anchorElement
1260
+ */
1261
+ FormEngine.deleteActionCallback = function(modalButtonName: string, $anchorElement: JQuery): void {
1262
+ Modal.dismiss();
1263
+ if (modalButtonName === 'yes') {
1264
+ FormEngine.invokeRecordDeletion($anchorElement);
1265
+ }
1266
+ };
1267
+
1268
+ /**
1269
+ * Show the delete modal
1270
+ *
1271
+ * @param {element} $anchorElement
1272
+ * @param {Function} callback
1273
+ */
1274
+ FormEngine.showDeleteModal = function($anchorElement: JQuery, callback: DeleteActionCallback): void {
1275
+ const title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
1276
+ let content = (TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete the record \'%s\'?').replace('%s', $anchorElement.data('record-info'));
1277
+
1278
+ if ($anchorElement.data('reference-count-message')) {
1279
+ content += '\n' + $anchorElement.data('reference-count-message');
1280
+ }
1281
+
1282
+ if ($anchorElement.data('translation-count-message')) {
1283
+ content += '\n' + $anchorElement.data('translation-count-message');
1284
+ }
1285
+
1286
+ const modal = Modal.confirm(title, content, Severity.warning, [
1287
+ {
1288
+ text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
1289
+ btnClass: 'btn-default',
1290
+ name: 'no'
1291
+ },
1292
+ {
1293
+ text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
1294
+ btnClass: 'btn-warning',
1295
+ name: 'yes',
1296
+ active: true
1297
+ }
1298
+ ]);
1299
+ modal.addEventListener('button.clicked', function (event: Event) {
1300
+ callback((event.target as HTMLButtonElement).name, $anchorElement);
1301
+ });
1302
+ };
1303
+
1304
+ /**
1305
+ * In case the given option is a child of a disabled optGroup, enable the optGroup
1306
+ */
1307
+ FormEngine.enableOptGroup = function (option: HTMLOptionElement): void {
1308
+ const optGroup = <HTMLOptGroupElement>option.parentElement;
1309
+ if (optGroup instanceof HTMLOptGroupElement && optGroup.querySelectorAll('option:not([hidden]):not([disabled]):not(.hidden)').length) {
1310
+ optGroup.hidden = false;
1311
+ optGroup.disabled = false;
1312
+ optGroup.classList.remove('hidden');
1313
+ }
1314
+ };
1315
+
1316
+ /**
1317
+ * Close current open document
1318
+ */
1319
+ FormEngine.closeDocument = function(): void {
1320
+ FormEngine.formElement.closeDoc.value = 1;
1321
+ FormEngine.formElement.submit();
1322
+ };
1323
+
1324
+ FormEngine.saveDocument = function(): void {
1325
+ const currentlyFocussed = document.activeElement;
1326
+ if (currentlyFocussed instanceof HTMLInputElement || currentlyFocussed instanceof HTMLSelectElement || currentlyFocussed instanceof HTMLTextAreaElement) {
1327
+ // Blur currently focussed :input element to trigger FormEngine's internal data normalization
1328
+ currentlyFocussed.blur();
1329
+ }
1330
+
1331
+ const saveField = FormEngine.formElement.querySelector(selector`input[name="${FormEngine.doSaveFieldName}"]`) as HTMLInputElement|null;
1332
+ if (saveField !== null) {
1333
+ saveField.value = '1';
1334
+ }
1335
+ FormEngine.formElement.requestSubmit();
1336
+ };
1337
+
1338
+ FormEngine.saveAndCloseDocument = function(): void {
1339
+ const saveAndCloseInput = document.createElement('input');
1340
+ saveAndCloseInput.type = 'hidden';
1341
+ saveAndCloseInput.name = '_saveandclosedok';
1342
+ saveAndCloseInput.value = '1';
1343
+ document.querySelector(selector`form[name="${FormEngine.formName}"]`).append(saveAndCloseInput);
1344
+
1345
+ FormEngine.saveDocument();
1346
+ };
1347
+
1348
+ /**
1349
+ * Main init function called from outside
1350
+ *
1351
+ * Sets some options and registers the DOMready handler to initialize further things
1352
+ *
1353
+ * @param {String} browserUrl
1354
+ * @param {String} doSaveFieldName
1355
+ */
1356
+ FormEngine.initialize = function(browserUrl: string, doSaveFieldName: string): void {
1357
+ FormEngine.browserUrl = browserUrl;
1358
+ // Add doSaveFieldName - fall back to to `doSave` for b/w compatibility
1359
+ FormEngine.doSaveFieldName = doSaveFieldName || 'doSave';
1360
+
1361
+ DocumentService.ready().then((): void => {
1362
+ FormEngine.initializeEvents();
1363
+ FormEngine.Validation.initialize(this);
1364
+ FormEngine.reinitialize();
1365
+ $('#t3js-ui-block').remove();
1366
+ FormEngine.enableDocHeaderButtons();
1367
+
1368
+ Hotkeys.setScope('backend/form-engine');
1369
+ Hotkeys.register([Hotkeys.normalizedCtrlModifierKey, 's'], (e: KeyboardEvent): void => {
1370
+ e.preventDefault();
1371
+
1372
+ FormEngine.saveDocument();
1373
+ }, { scope: 'backend/form-engine', allowOnEditables: true, bindElement: FormEngine.formElement._savedok });
1374
+ Hotkeys.register([Hotkeys.normalizedCtrlModifierKey, ModifierKeys.SHIFT, 's'], (e: KeyboardEvent): void => {
1375
+ e.preventDefault();
1376
+
1377
+ FormEngine.saveAndCloseDocument();
1378
+ }, { scope: 'backend/form-engine', allowOnEditables: true });
1379
+ });
1380
+ };
1381
+
1382
+ FormEngine.invokeRecordDeletion = function ($anchorElement: JQuery) {
1383
+ window.location.href = $anchorElement.attr('href');
1384
+ };
1385
+
1386
+ // make the form engine object publicly visible for other objects in the TYPO3 namespace
1387
+ TYPO3.FormEngine = FormEngine;
1388
+
1389
+ // return the object in the global space
1390
+ return FormEngine;
1391
+ })();