typo3-typescript-sources 13.4.26 → 14.0.0

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 (199) hide show
  1. package/.github/workflows/sync-and-publish.yml +114 -66
  2. package/.last-synced-tag +1 -1
  3. package/README-TYPO3.md +1 -1
  4. package/README.md +8 -1
  5. package/backend/action-button/deferred-action.ts +2 -0
  6. package/backend/action-button/immediate-action.ts +2 -0
  7. package/backend/ajax-data-handler.ts +1 -116
  8. package/backend/clipboard-panel.ts +5 -5
  9. package/backend/code-editor/element/code-mirror-element.ts +1 -1
  10. package/backend/code-editor/stream-parser/typoscript.ts +1 -1
  11. package/backend/color-picker.ts +5 -4
  12. package/backend/color-scheme-switch.ts +1 -1
  13. package/backend/column-selector-button.ts +1 -1
  14. package/backend/context-menu-actions.ts +1 -0
  15. package/backend/context-menu.ts +3 -3
  16. package/backend/copy-to-clipboard.ts +1 -1
  17. package/backend/date-time-picker.ts +10 -16
  18. package/backend/drag-tooltip.ts +1 -1
  19. package/backend/drag-uploader.ts +34 -36
  20. package/backend/element/alert-element.ts +2 -2
  21. package/backend/element/breadcrumb.ts +306 -0
  22. package/backend/element/combobox-element.ts +712 -0
  23. package/backend/element/dispatch-modal-button.ts +1 -1
  24. package/backend/element/draggable-resizable-element.ts +1 -1
  25. package/backend/element/editable-page-title.ts +6 -5
  26. package/backend/element/icon-element.ts +2 -2
  27. package/backend/element/pagination.ts +4 -4
  28. package/backend/element/progress-bar-element.ts +2 -2
  29. package/backend/element/progress-tracker-element.ts +2 -2
  30. package/backend/element/pseudo-button.ts +1 -1
  31. package/backend/element/qrcode-element.ts +129 -0
  32. package/backend/element/spinner-element.ts +1 -1
  33. package/backend/element/thumbnail-element.ts +3 -0
  34. package/backend/form-engine/container/files-control-container.ts +4 -4
  35. package/backend/form-engine/container/flex-form-section-container.ts +0 -1
  36. package/backend/form-engine/container/inline-control-container.ts +4 -4
  37. package/backend/form-engine/container/site-language-container.ts +2 -2
  38. package/backend/form-engine/element/abstract-sortable-select-items.ts +2 -2
  39. package/backend/form-engine/element/color-element.ts +2 -1
  40. package/backend/form-engine/element/datetime-element.ts +2 -1
  41. package/backend/form-engine/element/extra/char-counter.ts +1 -1
  42. package/backend/form-engine/element/online-media-form-element.ts +1 -1
  43. package/backend/form-engine/element/select-country-element.ts +82 -0
  44. package/backend/form-engine/element/select-tree-toolbar.ts +20 -18
  45. package/backend/form-engine/element/select-tree.ts +6 -1
  46. package/backend/form-engine/element/suggest/result-container.ts +1 -1
  47. package/backend/form-engine/element/suggest/result-item.ts +1 -1
  48. package/backend/form-engine/element/table-wizard-element.ts +1 -1
  49. package/backend/form-engine/field-control/password-generator.ts +2 -1
  50. package/backend/form-engine/field-wizard/value-picker.ts +7 -34
  51. package/backend/form-engine/inline-relation/inline-response-interface.ts +0 -1
  52. package/backend/form-engine-link-browser-adapter.ts +1 -5
  53. package/backend/form-engine-review.ts +2 -2
  54. package/backend/form-engine-suggest.ts +1 -1
  55. package/backend/form-engine-validation.ts +19 -80
  56. package/backend/form-engine.ts +40 -42
  57. package/backend/grid-editor.ts +4 -4
  58. package/backend/hotkeys/hotkey-storage.ts +18 -3
  59. package/backend/hotkeys.ts +46 -27
  60. package/backend/image-manipulation.ts +4 -4
  61. package/backend/layout-module/drag-drop.ts +1 -1
  62. package/backend/link-browser.ts +1 -11
  63. package/backend/live-search/element/backend-search.ts +1 -1
  64. package/backend/live-search/element/hint.ts +1 -1
  65. package/backend/live-search/element/provider/default-result-item.ts +1 -1
  66. package/backend/live-search/element/provider/page-provider-result-item.ts +1 -1
  67. package/backend/live-search/element/result/item/action/action-container.ts +1 -1
  68. package/backend/live-search/element/result/item/action/action.ts +2 -2
  69. package/backend/live-search/element/result/item/item-container.ts +2 -2
  70. package/backend/live-search/element/result/item/item.ts +1 -1
  71. package/backend/live-search/element/result/result-container.ts +1 -6
  72. package/backend/live-search/element/result/result-detail-container.ts +1 -1
  73. package/backend/live-search/element/result/result-pagination.ts +2 -3
  74. package/backend/live-search/element/search-option-item.ts +1 -1
  75. package/backend/live-search/live-search-shortcut.ts +14 -1
  76. package/backend/localization/finisher/noop-finisher.ts +48 -0
  77. package/backend/localization/finisher/redirect-finisher.ts +55 -0
  78. package/backend/localization/finisher/reload-finisher.ts +45 -0
  79. package/backend/localization/localization-button.ts +52 -0
  80. package/backend/localization/localization-finisher.ts +50 -0
  81. package/backend/localization/localization-wizard.ts +451 -0
  82. package/backend/localization/steps/confirm-step.ts +61 -0
  83. package/backend/localization/steps/content-record-selection-step.ts +389 -0
  84. package/backend/localization/steps/finisher-step.ts +179 -0
  85. package/backend/localization/steps/handler-selection-step.ts +200 -0
  86. package/backend/localization/steps/mode-step.ts +199 -0
  87. package/backend/localization/steps/source-language-step.ts +187 -0
  88. package/backend/localization/steps/target-language-step.ts +195 -0
  89. package/backend/localization.ts +2 -377
  90. package/backend/login/pasted-password-checker.ts +22 -0
  91. package/backend/login-refresh.ts +1 -1
  92. package/backend/login.ts +32 -5
  93. package/backend/modal.ts +108 -81
  94. package/backend/module/iframe.ts +1 -1
  95. package/backend/module/router.ts +12 -18
  96. package/backend/module-menu.ts +7 -2
  97. package/backend/multi-record-selection-edit-action.ts +1 -0
  98. package/backend/multi-step-wizard.ts +50 -50
  99. package/backend/new-content-element-wizard-button.ts +1 -1
  100. package/backend/new-record-wizard.ts +102 -9
  101. package/backend/notification.ts +3 -3
  102. package/backend/pagetsconfig/pagetsconfig-includes.ts +1 -1
  103. package/backend/record-download-button.ts +2 -3
  104. package/backend/record-usage/record-usage-store.ts +65 -0
  105. package/backend/recordlist.ts +70 -0
  106. package/backend/resource/resource.ts +4 -0
  107. package/backend/security/element/csp-reports.ts +7 -85
  108. package/backend/security/element/sudo-mode.ts +1 -1
  109. package/backend/settings/editor/editable-setting.ts +2 -1
  110. package/backend/settings/editor.ts +18 -6
  111. package/backend/settings/type/base.ts +1 -1
  112. package/backend/settings/type/bool.ts +1 -1
  113. package/backend/settings/type/color.ts +1 -1
  114. package/backend/settings/type/int.ts +2 -2
  115. package/backend/settings/type/number.ts +2 -2
  116. package/backend/settings/type/page.ts +1 -1
  117. package/backend/settings/type/string.ts +2 -2
  118. package/backend/settings/type/stringlist.ts +1 -1
  119. package/backend/settings/type/url.ts +43 -0
  120. package/backend/storage/abstract-client-storage.ts +1 -1
  121. package/backend/storage/module-state-storage.ts +5 -0
  122. package/backend/switch-user.ts +1 -1
  123. package/backend/tests/form-engine-validation-test.ts +12 -12
  124. package/backend/tests/login/pasted-password-checker-test.ts +43 -0
  125. package/backend/toolbar/live-search.ts +2 -7
  126. package/backend/tree/file-storage-browser.ts +1 -1
  127. package/backend/tree/file-storage-tree-container.ts +9 -5
  128. package/backend/tree/file-storage-tree.ts +13 -1
  129. package/backend/tree/page-browser.ts +3 -3
  130. package/backend/tree/page-tree-element.ts +11 -8
  131. package/backend/tree/tree-module-state.ts +1 -1
  132. package/backend/tree/tree-node-toggle.ts +1 -1
  133. package/backend/tree/tree-toolbar.ts +4 -3
  134. package/backend/tree/tree.ts +5 -5
  135. package/backend/user-settings-manager.ts +25 -1
  136. package/backend/utility/dom-helper.ts +11 -1
  137. package/backend/utility/format-utility.ts +30 -0
  138. package/backend/utility.ts +0 -21
  139. package/backend/viewport/content-container.ts +4 -1
  140. package/backend/viewport/resizable-navigation.ts +3 -3
  141. package/backend/wizard/move-content-element.ts +2 -2
  142. package/core/ajax/ajax-request.ts +5 -2
  143. package/core/authentication/mfa-provider/totp.ts +1 -1
  144. package/core/directive/markdown.ts +2 -2
  145. package/core/event/throttle-event.ts +1 -1
  146. package/core/factory/url-factory.ts +73 -0
  147. package/core/lit-helper.ts +2 -2
  148. package/core/tests/factory/url-factory-test.ts +105 -0
  149. package/dashboard/dashboard.ts +100 -5
  150. package/extensionmanager/distribution-image.ts +1 -1
  151. package/filelist/context-menu-actions.ts +21 -16
  152. package/filelist/file-delete.ts +1 -1
  153. package/filelist/file-list-actions.ts +9 -1
  154. package/filelist/file-list-rename-handler.ts +7 -4
  155. package/filelist/file-list-replace-handler.ts +141 -0
  156. package/filelist/file-list.ts +1 -0
  157. package/filelist/{create-folder.ts → resource-creation.ts} +2 -2
  158. package/form/backend/form-editor/component/property-grid-editor.ts +4 -4
  159. package/form/backend/form-editor/core.ts +1 -2
  160. package/form/backend/form-manager/view-model.ts +28 -67
  161. package/install/installer.ts +4 -4
  162. package/install/module/maintenance/reference-index.ts +154 -0
  163. package/install/renderable/flash-message.ts +1 -1
  164. package/install/renderable/info-box.ts +1 -1
  165. package/install/renderable/language-packs.ts +3 -3
  166. package/install/renderable/offset-group.ts +1 -1
  167. package/install/renderable/wrap-group.ts +1 -1
  168. package/install/router.ts +2 -1
  169. package/package.json +2 -2
  170. package/rte-ckeditor/ckeditor5.ts +3 -3
  171. package/rte-ckeditor/plugin/typo3-link.ts +2 -33
  172. package/rte-ckeditor/rte-link-browser.ts +1 -0
  173. package/scheduler/form-engine/element/timing-options-element.ts +74 -0
  174. package/scheduler/new-scheduler-task-wizard-button.ts +51 -0
  175. package/scheduler/scheduler-add-group.ts +28 -0
  176. package/scheduler/scheduler.ts +3 -359
  177. package/scheduler/setup-check-button.ts +50 -0
  178. package/styleguide/element/theme-switcher-element.ts +2 -1
  179. package/sys-note/element/delete-button.ts +1 -1
  180. package/tstemplate/template-analyzer.ts +1 -1
  181. package/viewpage/main.ts +5 -3
  182. package/workspaces/backend.ts +34 -31
  183. package/workspaces/preview.ts +6 -8
  184. package/workspaces/renderable/comment-view.ts +3 -3
  185. package/workspaces/renderable/diff-view.ts +3 -3
  186. package/workspaces/renderable/history-view.ts +3 -3
  187. package/workspaces/renderable/record-information.ts +2 -2
  188. package/workspaces/renderable/record-table.ts +26 -6
  189. package/workspaces/renderable/send-to-stage-form.ts +1 -9
  190. package/workspaces/toolbar/workspaces-menu.ts +5 -3
  191. package/workspaces/workspaces.ts +1 -36
  192. package/backend/document-save-actions.ts +0 -159
  193. package/backend/hotkeys/events.ts +0 -47
  194. package/backend/hotkeys/hotkey-struct.ts +0 -58
  195. package/backend/hotkeys/negotiator.ts +0 -53
  196. package/backend/wizard.ts +0 -331
  197. package/filelist/file-replace.ts +0 -37
  198. package/lowlevel/reference-index.ts +0 -44
  199. package/scheduler/scheduler-editable-group-name.ts +0 -307
@@ -15,11 +15,11 @@ import { AjaxResponse } from '@typo3/core/ajax/ajax-response';
15
15
  import AjaxRequest from '@typo3/core/ajax/ajax-request';
16
16
  import ContextMenuActions from './context-menu-actions';
17
17
  import '@typo3/backend/element/spinner-element';
18
- import { customElement, queryAll, state } from 'lit/decorators.js';
18
+ import { customElement, queryAll, state } from 'lit/decorators';
19
19
  import { html, LitElement, nothing, type TemplateResult } from 'lit';
20
20
  import { delay } from '@typo3/core/lit-helper';
21
- import { styleMap } from 'lit/directives/style-map.js';
22
- import { unsafeHTML } from 'lit/directives/unsafe-html.js';
21
+ import { styleMap } from 'lit/directives/style-map';
22
+ import { unsafeHTML } from 'lit/directives/unsafe-html';
23
23
  import { Task, initialState } from '@lit/task';
24
24
  import Notification from '@typo3/backend/notification';
25
25
 
@@ -11,7 +11,7 @@
11
11
  * The TYPO3 project - inspiring people to share!
12
12
  */
13
13
 
14
- import { customElement, property } from 'lit/decorators.js';
14
+ import { customElement, property } from 'lit/decorators';
15
15
  import { PseudoButtonLitElement } from '@typo3/backend/element/pseudo-button';
16
16
  import Notification from '@typo3/backend/notification';
17
17
  import { lll } from '@typo3/core/lit-helper';
@@ -18,7 +18,7 @@ import ThrottleEvent from '@typo3/core/event/throttle-event';
18
18
  import type { PostValidationEvent } from '@typo3/backend/form-engine-validation';
19
19
  import '@typo3/backend/input/clearable';
20
20
 
21
- const ISO8601_UTC = 'ISO8601_UTC';
21
+ const ISO8601_LOCALTIME = 'ISO8601_LOCALTIME';
22
22
 
23
23
  interface FlatpickrInputElement extends HTMLInputElement {
24
24
  _flatpickr: flatpickr.Instance;
@@ -150,31 +150,25 @@ class DateTimePicker {
150
150
  // We configure a dummy dateFormat token and use luxon parsing/formatting
151
151
  // internally, as flatpickr cannot be configured to ignore timezones
152
152
  // (in order to behave similar to <input type=datetime-local>)
153
- dateFormat: ISO8601_UTC,
153
+ dateFormat: ISO8601_LOCALTIME,
154
154
  defaultHour: now.getHours(),
155
155
  defaultMinute: now.getMinutes(),
156
156
  enableSeconds: false,
157
157
  enableTime: false,
158
158
  formatDate: (date: Date, format: string) => {
159
159
  const dt = DateTime.fromJSDate(date);
160
- if (format === ISO8601_UTC) {
161
- // JS dates are always encoded in localtime (flatpickr can only operate in localtime) and are now "intepreted" as UTC:
162
- // We discard the intermediate local-time offset; that means the hours and minutes from localtime are rendered as-is
163
- return dt.toUTC().plus(dt.offset * 60 * 1000).toISO({ suppressMilliseconds: true });
160
+ if (format === ISO8601_LOCALTIME) {
161
+ return dt.toISO({ suppressMilliseconds: true, includeOffset: false });
164
162
  }
165
163
  return dt.toFormat(format);
166
164
  },
167
165
  parseDate: (currentDateString: string, format: string): Date => {
168
- if (format === ISO8601_UTC) {
169
- const isoDt = DateTime.fromISO(currentDateString, { zone: 'utc' });
170
- if (!isoDt.isValid) {
166
+ if (format === ISO8601_LOCALTIME) {
167
+ const localDate = DateTime.fromISO(currentDateString);
168
+ if (!localDate.isValid) {
171
169
  throw new Error('Invalid ISO8601 date: ' + currentDateString);
172
170
  }
173
- const localDate = isoDt.toLocal();
174
- // This is the reverse operation of the formatting in `formatDate`:
175
- // We substract the localtime offset to generate a local-time JS Date instance,
176
- // that will contain the same local-time hour/time values as the UTC input.
177
- return localDate.minus(localDate.offset * 60 * 1000).toJSDate();
171
+ return localDate.toJSDate();
178
172
  }
179
173
  return DateTime.fromFormat(currentDateString, format).toJSDate();
180
174
  },
@@ -264,11 +258,11 @@ class DateTimePicker {
264
258
  }
265
259
 
266
260
  if (inputElement.dataset.dateMinDate !== undefined) {
267
- options.minDate = options.parseDate(inputElement.dataset.dateMinDate, ISO8601_UTC);
261
+ options.minDate = options.parseDate(inputElement.dataset.dateMinDate, ISO8601_LOCALTIME);
268
262
  options.minDate.setSeconds(0);
269
263
  }
270
264
  if (inputElement.dataset.dateMaxDate !== undefined) {
271
- options.maxDate = options.parseDate(inputElement.dataset.dateMaxDate, ISO8601_UTC);
265
+ options.maxDate = options.parseDate(inputElement.dataset.dateMaxDate, ISO8601_LOCALTIME);
272
266
  options.maxDate.setSeconds(59);
273
267
  }
274
268
 
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { html, LitElement, nothing, type TemplateResult } from 'lit';
15
- import { customElement, property, state } from 'lit/decorators.js';
15
+ import { customElement, property, state } from 'lit/decorators';
16
16
  import { BroadcastMessage } from '@typo3/backend/broadcast-message';
17
17
  import BroadcastService from '@typo3/backend/broadcast-service';
18
18
  import { DataTransferTypes } from '@typo3/backend/enum/data-transfer-types';
@@ -28,6 +28,7 @@ import DomHelper from '@typo3/backend/utility/dom-helper';
28
28
  import { KeyTypesEnum } from '@typo3/backend/enum/key-types';
29
29
  import '@typo3/backend/element/progress-bar-element';
30
30
  import type { ProgressBarElement } from '@typo3/backend/element/progress-bar-element';
31
+ import { FormatUtility } from '@typo3/backend/utility/format-utility';
31
32
 
32
33
  /**
33
34
  * Possible actions for conflicts w/ existing files
@@ -39,6 +40,11 @@ enum Action {
39
40
  USE_EXISTING = 'useExisting',
40
41
  }
41
42
 
43
+ enum Mode {
44
+ MANAGE = 'manage',
45
+ BROWSE = 'browse',
46
+ }
47
+
42
48
  /**
43
49
  * Properties of a file as returned from the AJAX action; essential, this is a serialized instance of
44
50
  * \TYPO3\CMS\Core\Resource\File plus some extra properties (see FileController::flattenResultDataValue())
@@ -80,7 +86,7 @@ export default class DragUploader {
80
86
  public filesExtensionsDisallowed: string;
81
87
  public fileDenyPattern: RegExp | null;
82
88
  public maxFileSize: number;
83
- public trigger: HTMLElement;
89
+ public trigger: HTMLElement | null;
84
90
  public target: string;
85
91
  public reloadUrl: string;
86
92
  public manualTable: boolean;
@@ -111,13 +117,13 @@ export default class DragUploader {
111
117
  constructor(element: HTMLElement) {
112
118
  this.body = document.querySelector('body');
113
119
  this.element = element;
114
- const hasTrigger = this.element.dataset.dropzoneTrigger !== undefined;
115
120
  this.trigger = document.querySelector(this.element.dataset.dropzoneTrigger);
116
121
  this.defaultAction = this.element.dataset.defaultAction as Action || Action.SKIP;
117
122
  this.dropzone = document.createElement('div');
118
123
  this.dropzone.classList.add('dropzone');
119
124
  this.dropzone.setAttribute('hidden', 'hidden');
120
125
  this.irreObjectUid = this.element.dataset.fileIrreObject;
126
+ this.manualTable = this.element.hasAttribute('data-manual-table');
121
127
 
122
128
  const dropZoneEscapedTarget = document.querySelector(this.element.dataset.dropzoneTarget);
123
129
  if (this.irreObjectUid && DomHelper.nextAll(dropZoneEscapedTarget).length !== 0) {
@@ -229,7 +235,7 @@ export default class DragUploader {
229
235
  }
230
236
  });
231
237
 
232
- this.bindUploadButton(hasTrigger === true ? this.trigger : this.element);
238
+ this.bindUploadButton(this.trigger !== null ? this.trigger : this.element);
233
239
  }
234
240
 
235
241
  public static init(): void {
@@ -260,18 +266,6 @@ export default class DragUploader {
260
266
  });
261
267
  }
262
268
 
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
269
  /**
276
270
  * @param {string} irreObject
277
271
  * @param {UploadedFile} file
@@ -390,7 +384,7 @@ export default class DragUploader {
390
384
  });
391
385
  NProgress.inc(this.percentagePerFile);
392
386
  } else {
393
- new FileQueueItem(this, file, Action.SKIP);
387
+ new FileQueueItem(this, file, Action.SKIP, this.fileList.dataset.mode === Mode.BROWSE ? Mode.BROWSE : Mode.MANAGE);
394
388
  }
395
389
  });
396
390
  ajaxCalls.push(request);
@@ -500,11 +494,11 @@ export default class DragUploader {
500
494
  : this.askForOverride[i].original.icon}
501
495
  </td>
502
496
  <td>
503
- ${this.askForOverride[i].original.name} (${DragUploader.fileSizeAsString(this.askForOverride[i].original.size)})<br />
497
+ ${this.askForOverride[i].original.name} (${FormatUtility.fileSizeAsString(this.askForOverride[i].original.size)})<br />
504
498
  ${DateTime.fromSeconds(this.askForOverride[i].original.mtime).toLocaleString(DateTime.DATETIME_MED)}
505
499
  </td>
506
500
  <td>
507
- ${this.askForOverride[i].uploaded.name} (${DragUploader.fileSizeAsString(this.askForOverride[i].uploaded.size)})<br />
501
+ ${this.askForOverride[i].uploaded.name} (${FormatUtility.fileSizeAsString(this.askForOverride[i].uploaded.size)})<br />
508
502
  ${DateTime.fromMillis(this.askForOverride[i].uploaded.lastModified).toLocaleString(DateTime.DATETIME_MED)}
509
503
  </td>
510
504
  <td>
@@ -596,7 +590,7 @@ export default class DragUploader {
596
590
  fileInfo.original,
597
591
  );
598
592
  } else if (fileInfo.action !== Action.SKIP) {
599
- new FileQueueItem(this, fileInfo.uploaded, fileInfo.action);
593
+ new FileQueueItem(this, fileInfo.uploaded, fileInfo.action, this.fileList.dataset.mode === Mode.BROWSE ? Mode.BROWSE : Mode.MANAGE);
600
594
  }
601
595
  }
602
596
  this.askForOverride = [];
@@ -612,6 +606,7 @@ export default class DragUploader {
612
606
 
613
607
  class FileQueueItem {
614
608
  private readonly row: HTMLElement;
609
+ private readonly mode: Mode;
615
610
  private readonly progress: HTMLElement;
616
611
  private readonly file: File;
617
612
  private readonly override: Action;
@@ -621,10 +616,11 @@ class FileQueueItem {
621
616
  private readonly progressBar: ProgressBarElement;
622
617
  private readonly dragUploader: DragUploader;
623
618
 
624
- constructor(dragUploader: DragUploader, file: File, override: Action) {
619
+ constructor(dragUploader: DragUploader, file: File, override: Action, mode: Mode) {
625
620
  this.dragUploader = dragUploader;
626
621
  this.file = file;
627
622
  this.override = override;
623
+ this.mode = mode;
628
624
 
629
625
  this.row = document.createElement('tr');
630
626
  this.row.classList.add('upload-queue-item');
@@ -676,7 +672,7 @@ class FileQueueItem {
676
672
  if (this.dragUploader.maxFileSize > 0 && this.file.size > this.dragUploader.maxFileSize) {
677
673
  this.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded']
678
674
  .replace(/\{0\}/g, this.file.name)
679
- .replace(/\{1\}/g, DragUploader.fileSizeAsString(this.dragUploader.maxFileSize)));
675
+ .replace(/\{1\}/g, FormatUtility.fileSizeAsString(this.dragUploader.maxFileSize)));
680
676
  this.progressBar.value = 100;
681
677
  this.progressBar.severity = SeverityEnum.error;
682
678
 
@@ -695,7 +691,7 @@ class FileQueueItem {
695
691
  this.progressBar.value = 100;
696
692
  this.progressBar.severity = SeverityEnum.error;
697
693
  } else {
698
- this.updateMessage('- ' + DragUploader.fileSizeAsString(this.file.size));
694
+ this.updateMessage('- ' + FormatUtility.fileSizeAsString(this.file.size));
699
695
 
700
696
  const formData = new FormData();
701
697
  formData.append('data[upload][1][target]', this.dragUploader.target);
@@ -865,24 +861,26 @@ class FileQueueItem {
865
861
  this.row.append(fileExtColumn);
866
862
 
867
863
  const fileSizeColumn = document.createElement('td');
868
- fileSizeColumn.textContent = DragUploader.fileSizeAsString(fileInfo.size);
864
+ fileSizeColumn.textContent = FormatUtility.fileSizeAsString(fileInfo.size);
869
865
  this.row.append(fileSizeColumn);
870
866
 
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
- }
867
+ if (this.mode === Mode.MANAGE) {
868
+ let permissions = '';
869
+ if (fileInfo.permissions.read) {
870
+ permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>';
871
+ }
872
+ if (fileInfo.permissions.write) {
873
+ permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>';
874
+ }
878
875
 
879
- const permissionsColumn = document.createElement('td');
880
- permissionsColumn.innerHTML = permissions;
881
- this.row.append(permissionsColumn);
876
+ const permissionsColumn = document.createElement('td');
877
+ permissionsColumn.innerHTML = permissions;
878
+ this.row.append(permissionsColumn);
882
879
 
883
- const emptyColumn = document.createElement('td');
884
- emptyColumn.textContent = '-';
885
- this.row.append(emptyColumn);
880
+ const emptyColumn = document.createElement('td');
881
+ emptyColumn.textContent = '-';
882
+ this.row.append(emptyColumn);
883
+ }
886
884
 
887
885
  // add spacing cells when more columns are displayed (column selector)
888
886
  for (let i = this.row.querySelectorAll('td').length; i < this.dragUploader.fileListColumnCount; i++) {
@@ -11,9 +11,9 @@
11
11
  * The TYPO3 project - inspiring people to share!
12
12
  */
13
13
 
14
- import { customElement, property } from 'lit/decorators.js';
14
+ import { customElement, property } from 'lit/decorators';
15
15
  import { html, LitElement, nothing, type TemplateResult } from 'lit';
16
- import { classMap, type ClassInfo } from 'lit/directives/class-map.js';
16
+ import { classMap, type ClassInfo } from 'lit/directives/class-map';
17
17
  import { SeverityEnum } from '@typo3/backend/enum/severity';
18
18
  import Severity from '../severity';
19
19
  import '@typo3/backend/element/icon-element';
@@ -0,0 +1,306 @@
1
+ /*
2
+ * This file is part of the TYPO3 CMS project.
3
+ *
4
+ * It is free software; you can redistribute it and/or modify it under
5
+ * the terms of the GNU General Public License, either version 2
6
+ * of the License, or any later version.
7
+ *
8
+ * For the full copyright and license information, please read the
9
+ * LICENSE.txt file that was distributed with this source code.
10
+ *
11
+ * The TYPO3 project - inspiring people to share!
12
+ */
13
+
14
+ import { html, LitElement, type TemplateResult, render, type PropertyValues, nothing } from 'lit';
15
+ import { customElement, property } from 'lit/decorators';
16
+ import { classMap } from 'lit/directives/class-map';
17
+ import '@typo3/backend/element/icon-element';
18
+ import 'bootstrap'; // for data-bs-toggle="dropdown"
19
+
20
+ interface BreadcrumbNodeInterface {
21
+ identifier: string;
22
+ label: string;
23
+ icon: string|null;
24
+ iconOverlay: string|null;
25
+ url: string|null;
26
+ forceShowIcon: boolean;
27
+ // calculated properties
28
+ __width?: number;
29
+ }
30
+
31
+ /**
32
+ * Module: @typo3/backend/element/breadcrumb
33
+ *
34
+ * @example
35
+ * <typo3-breadcrumb
36
+ * nodes='[
37
+ * {
38
+ * "identifier": "12",
39
+ * "type": "page",
40
+ * "label": "Title",
41
+ * "icon": "icon-identifier",
42
+ * "iconOverlay": "icon-overlay-identifier",
43
+ * }
44
+ * ]'
45
+ * ></typo3-breadcrumb>
46
+ *
47
+ * @internal this is subject to change
48
+ */
49
+ @customElement('typo3-breadcrumb')
50
+ export class Breadcrumb extends LitElement
51
+ {
52
+ @property({ type: Array }) nodes: BreadcrumbNodeInterface[] = [];
53
+ @property({ type: Boolean }) alignRight: boolean = false;
54
+ @property({ type: String }) label: string = 'Breadcrumb';
55
+ private identifier: string;
56
+ private collapsedNodes: BreadcrumbNodeInterface[] = [];
57
+ private resizeObserver!: ResizeObserver;
58
+ private dropdownButtonWidth: number | null = null;
59
+ private measurementContainer: HTMLElement | null = null;
60
+
61
+ public override connectedCallback()
62
+ {
63
+ super.connectedCallback();
64
+ this.identifier = Date.now().toString(36) + Math.random().toString(36).slice(2);
65
+ this.resizeObserver = new ResizeObserver(() => this.adjustBreadcrumb());
66
+ this.resizeObserver.observe(this);
67
+ }
68
+
69
+ public override disconnectedCallback()
70
+ {
71
+ this.resizeObserver.disconnect();
72
+ super.disconnectedCallback();
73
+ }
74
+
75
+ protected override createRenderRoot(): HTMLElement | ShadowRoot {
76
+ return this;
77
+ }
78
+
79
+ protected override async willUpdate(changedProperties: PropertyValues) {
80
+ if (changedProperties.has('nodes')) {
81
+ await this.createMeasurementContainer();
82
+ await this.measureDropdownToggle();
83
+ await this.measureNodeWidths();
84
+ }
85
+ }
86
+
87
+ protected override render(): TemplateResult
88
+ {
89
+ const visibleNodes = this.nodes.filter(
90
+ (node) => !this.collapsedNodes.includes(node)
91
+ );
92
+
93
+ return html`
94
+ <nav class="breadcrumb breadcrumb-collapsible${this.alignRight ? ' breadcrumb-right' : ''}" aria-label="${this.label}">
95
+ ${this.collapsedNodes.length > 0 ? html`
96
+ <div class="breadcrumb-item dropdown" role="presentation">
97
+ ${this.renderDropdownToggle()}
98
+ <div class="dropdown-menu">
99
+ ${this.collapsedNodes.map((node) => this.renderDropdownItem(node))}
100
+ </div>
101
+ </div>
102
+ ` : null}
103
+ ${visibleNodes.map((node) => { /* eslint-disable @stylistic/indent */
104
+ const classes = classMap({ 'breadcrumb-item': true, 'breadcrumb-item-first': this.isFirstNode(node), 'breadcrumb-item-last': this.isLastNode(node) });
105
+ return html`<div class=${classes} role="presentation">${this.renderBreadcrumbElement(node)}</div>`;
106
+ })}
107
+ </nav>
108
+ `;
109
+ }
110
+
111
+ private async createMeasurementContainer(): Promise<void>
112
+ {
113
+ if (this.measurementContainer === null) {
114
+ this.measurementContainer = document.createElement('div');
115
+ this.measurementContainer.classList.add('breadcrumb-measurement');
116
+ this.measurementContainer.ariaHidden = 'true';
117
+ this.measurementContainer.style.position = 'absolute';
118
+ this.measurementContainer.style.visibility = 'hidden';
119
+ this.measurementContainer.style.pointerEvents = 'none';
120
+ this.measurementContainer.style.width = 'auto';
121
+ this.measurementContainer.style.whiteSpace = 'nowrap';
122
+ this.renderRoot.appendChild(this.measurementContainer);
123
+ }
124
+ }
125
+
126
+ private async measureDropdownToggle(): Promise<void>
127
+ {
128
+ if (this.dropdownButtonWidth === null) {
129
+ const toggleTemplate = html`<div class="breadcrumb-item dropdown">${this.renderDropdownToggle()}</div>`;
130
+ render(toggleTemplate, this.measurementContainer!);
131
+ const toggleElement = this.measurementContainer!.querySelector('div');
132
+ this.dropdownButtonWidth = toggleElement?.offsetWidth ?? 0;
133
+ render(nothing, this.measurementContainer);
134
+ }
135
+ }
136
+
137
+ private async measureNodeWidths(): Promise<void>
138
+ {
139
+ const originalNodes = this.nodes;
140
+ const updatedNodes = this.nodes.map(node => {
141
+ // Skip nodes that already have a width
142
+ if (node.__width) {
143
+ return node;
144
+ }
145
+
146
+ const template = html`
147
+ <div
148
+ class=${classMap({ 'breadcrumb-item': true, 'breadcrumb-item-first': this.isFirstNode(node), 'breadcrumb-item-last': this.isLastNode(node) })}
149
+ role="presentation"
150
+ >
151
+ ${this.renderBreadcrumbElement(node)}
152
+ </div>
153
+ `;
154
+ render(template, this.measurementContainer!);
155
+ const element = this.measurementContainer!.querySelector('div');
156
+ const width = element?.offsetWidth ?? 0;
157
+ render(nothing, this.measurementContainer);
158
+
159
+ return { ...node, __width: width };
160
+ });
161
+
162
+ const hasChanged = !this.areNodeCollectionsEqual(originalNodes, updatedNodes);
163
+ if (hasChanged) {
164
+ this.nodes = updatedNodes;
165
+ }
166
+ }
167
+
168
+ private async adjustBreadcrumb(): Promise<void>
169
+ {
170
+ const breadcrumbContainer = this.renderRoot?.querySelector('.breadcrumb');
171
+ if (!breadcrumbContainer) {
172
+ return;
173
+ }
174
+
175
+ this.collapsedNodes = [];
176
+ const lastNode = this.nodes[this.nodes.length - 1];
177
+ let totalWidth = lastNode?.__width || 0;
178
+
179
+ for (const node of this.nodes.slice(0, -1).reverse()) {
180
+ if (breadcrumbContainer.clientWidth > (totalWidth + this.dropdownButtonWidth + node.__width)) {
181
+ totalWidth += node.__width;
182
+ } else {
183
+ this.collapsedNodes.push(node);
184
+ }
185
+ }
186
+
187
+ this.collapsedNodes.reverse();
188
+ this.requestUpdate();
189
+ }
190
+
191
+ private renderDropdownToggle(): TemplateResult
192
+ {
193
+ return html`
194
+ <button
195
+ type="button"
196
+ id="breadcrumb-toggle-${this.identifier}"
197
+ class="breadcrumb-element"
198
+ aria-haspopup="true"
199
+ aria-expanded="false"
200
+ data-bs-toggle="dropdown"
201
+ >
202
+ ...
203
+ </button>
204
+ `;
205
+ }
206
+
207
+ private renderDropdownItem(node: BreadcrumbNodeInterface): TemplateResult|null
208
+ {
209
+ return node.url !== null ? html`
210
+ <button
211
+ type="button"
212
+ class="dropdown-item"
213
+ title="${node.label}"
214
+ @click=${(e: Event) => this.triggerAction(e, node)}
215
+ >
216
+ ${this.renderIcon(node)}
217
+ <span class="dropdown-item-label">${node.label}</span>
218
+ </button>
219
+ ` : html`
220
+ <div
221
+ class="dropdown-item"
222
+ title="${node.label}"
223
+ >
224
+ ${this.renderIcon(node)}
225
+ <span class="dropdown-item-label">${node.label}</span>
226
+ </div>
227
+ `;
228
+ }
229
+
230
+ private renderBreadcrumbElement(node: BreadcrumbNodeInterface): TemplateResult|null
231
+ {
232
+ return node.url !== null ? html`
233
+ <button
234
+ type="button"
235
+ class="breadcrumb-element"
236
+ title="${node.label}"
237
+ aria-current=${this.isLastNode(node) ? 'page' : 'false'}
238
+ @click=${(e: Event) => this.triggerAction(e, node)}
239
+ >
240
+ ${node.forceShowIcon || this.isLastNode(node) ? this.renderIcon(node) : nothing}
241
+ <span class="breadcrumb-element-label">${node.label}</span>
242
+ </button>
243
+ ` : html `
244
+ <div
245
+ class="breadcrumb-element"
246
+ title="${node.label}"
247
+ aria-current=${this.isLastNode(node) ? 'page' : 'false'}
248
+ >
249
+ ${this.isLastNode(node) ? this.renderIcon(node) : nothing}
250
+ <span class="breadcrumb-element-label">${node.label}</span>
251
+ </div>
252
+ `;
253
+ }
254
+
255
+ private renderIcon(node: BreadcrumbNodeInterface): TemplateResult|null
256
+ {
257
+ return node.icon
258
+ ? html`
259
+ <typo3-backend-icon
260
+ identifier="${node.icon}"
261
+ overlay="${node.iconOverlay}"
262
+ size="small"
263
+ ></typo3-backend-icon>
264
+ `
265
+ : null;
266
+ }
267
+
268
+ private triggerAction(e: Event, node: BreadcrumbNodeInterface): void
269
+ {
270
+ import('@typo3/backend/viewport').then(({ default: Viewport }): void => {
271
+ Viewport.ContentContainer.setUrl(node.url);
272
+ });
273
+ }
274
+
275
+ private isFirstNode(node: BreadcrumbNodeInterface) {
276
+ return this.nodes[0] === node;
277
+ }
278
+
279
+ private isLastNode(node: BreadcrumbNodeInterface) {
280
+ return this.nodes[this.nodes.length - 1] === node;
281
+ }
282
+
283
+ private areNodeCollectionsEqual(nodes: BreadcrumbNodeInterface[], newNodes: BreadcrumbNodeInterface[]): boolean
284
+ {
285
+ if (newNodes.length !== nodes.length){
286
+ return false;
287
+ }
288
+
289
+ for (let i = 0; i < newNodes.length; i++) {
290
+ if (!this.areNodesEqual(nodes[i], newNodes[i])) {
291
+ return false;
292
+ }
293
+ }
294
+
295
+ return true;
296
+ }
297
+
298
+ private areNodesEqual(nodeA: BreadcrumbNodeInterface, nodeB: BreadcrumbNodeInterface): boolean
299
+ {
300
+ return nodeA.identifier === nodeB.identifier &&
301
+ nodeA.label === nodeB.label &&
302
+ nodeA.icon === nodeB.icon &&
303
+ nodeA.iconOverlay === nodeB.iconOverlay &&
304
+ nodeA.__width === nodeB.__width;
305
+ }
306
+ }