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.
- package/.github/workflows/sync-and-publish.yml +114 -66
- package/.last-synced-tag +1 -1
- package/README-TYPO3.md +1 -1
- package/README.md +8 -1
- package/backend/action-button/deferred-action.ts +2 -0
- package/backend/action-button/immediate-action.ts +2 -0
- package/backend/ajax-data-handler.ts +1 -116
- package/backend/clipboard-panel.ts +5 -5
- package/backend/code-editor/element/code-mirror-element.ts +1 -1
- package/backend/code-editor/stream-parser/typoscript.ts +1 -1
- package/backend/color-picker.ts +5 -4
- package/backend/color-scheme-switch.ts +1 -1
- package/backend/column-selector-button.ts +1 -1
- package/backend/context-menu-actions.ts +1 -0
- package/backend/context-menu.ts +3 -3
- package/backend/copy-to-clipboard.ts +1 -1
- package/backend/date-time-picker.ts +10 -16
- package/backend/drag-tooltip.ts +1 -1
- package/backend/drag-uploader.ts +34 -36
- package/backend/element/alert-element.ts +2 -2
- package/backend/element/breadcrumb.ts +306 -0
- package/backend/element/combobox-element.ts +712 -0
- package/backend/element/dispatch-modal-button.ts +1 -1
- package/backend/element/draggable-resizable-element.ts +1 -1
- package/backend/element/editable-page-title.ts +6 -5
- package/backend/element/icon-element.ts +2 -2
- package/backend/element/pagination.ts +4 -4
- package/backend/element/progress-bar-element.ts +2 -2
- package/backend/element/progress-tracker-element.ts +2 -2
- package/backend/element/pseudo-button.ts +1 -1
- package/backend/element/qrcode-element.ts +129 -0
- package/backend/element/spinner-element.ts +1 -1
- package/backend/element/thumbnail-element.ts +3 -0
- package/backend/form-engine/container/files-control-container.ts +4 -4
- package/backend/form-engine/container/flex-form-section-container.ts +0 -1
- package/backend/form-engine/container/inline-control-container.ts +4 -4
- package/backend/form-engine/container/site-language-container.ts +2 -2
- package/backend/form-engine/element/abstract-sortable-select-items.ts +2 -2
- package/backend/form-engine/element/color-element.ts +2 -1
- package/backend/form-engine/element/datetime-element.ts +2 -1
- package/backend/form-engine/element/extra/char-counter.ts +1 -1
- package/backend/form-engine/element/online-media-form-element.ts +1 -1
- package/backend/form-engine/element/select-country-element.ts +82 -0
- package/backend/form-engine/element/select-tree-toolbar.ts +20 -18
- package/backend/form-engine/element/select-tree.ts +6 -1
- package/backend/form-engine/element/suggest/result-container.ts +1 -1
- package/backend/form-engine/element/suggest/result-item.ts +1 -1
- package/backend/form-engine/element/table-wizard-element.ts +1 -1
- package/backend/form-engine/field-control/password-generator.ts +2 -1
- package/backend/form-engine/field-wizard/value-picker.ts +7 -34
- package/backend/form-engine/inline-relation/inline-response-interface.ts +0 -1
- package/backend/form-engine-link-browser-adapter.ts +1 -5
- package/backend/form-engine-review.ts +2 -2
- package/backend/form-engine-suggest.ts +1 -1
- package/backend/form-engine-validation.ts +19 -80
- package/backend/form-engine.ts +40 -42
- package/backend/grid-editor.ts +4 -4
- package/backend/hotkeys/hotkey-storage.ts +18 -3
- package/backend/hotkeys.ts +46 -27
- package/backend/image-manipulation.ts +4 -4
- package/backend/layout-module/drag-drop.ts +1 -1
- package/backend/link-browser.ts +1 -11
- package/backend/live-search/element/backend-search.ts +1 -1
- package/backend/live-search/element/hint.ts +1 -1
- package/backend/live-search/element/provider/default-result-item.ts +1 -1
- package/backend/live-search/element/provider/page-provider-result-item.ts +1 -1
- package/backend/live-search/element/result/item/action/action-container.ts +1 -1
- package/backend/live-search/element/result/item/action/action.ts +2 -2
- package/backend/live-search/element/result/item/item-container.ts +2 -2
- package/backend/live-search/element/result/item/item.ts +1 -1
- package/backend/live-search/element/result/result-container.ts +1 -6
- package/backend/live-search/element/result/result-detail-container.ts +1 -1
- package/backend/live-search/element/result/result-pagination.ts +2 -3
- package/backend/live-search/element/search-option-item.ts +1 -1
- package/backend/live-search/live-search-shortcut.ts +14 -1
- package/backend/localization/finisher/noop-finisher.ts +48 -0
- package/backend/localization/finisher/redirect-finisher.ts +55 -0
- package/backend/localization/finisher/reload-finisher.ts +45 -0
- package/backend/localization/localization-button.ts +52 -0
- package/backend/localization/localization-finisher.ts +50 -0
- package/backend/localization/localization-wizard.ts +451 -0
- package/backend/localization/steps/confirm-step.ts +61 -0
- package/backend/localization/steps/content-record-selection-step.ts +389 -0
- package/backend/localization/steps/finisher-step.ts +179 -0
- package/backend/localization/steps/handler-selection-step.ts +200 -0
- package/backend/localization/steps/mode-step.ts +199 -0
- package/backend/localization/steps/source-language-step.ts +187 -0
- package/backend/localization/steps/target-language-step.ts +195 -0
- package/backend/localization.ts +2 -377
- package/backend/login/pasted-password-checker.ts +22 -0
- package/backend/login-refresh.ts +1 -1
- package/backend/login.ts +32 -5
- package/backend/modal.ts +108 -81
- package/backend/module/iframe.ts +1 -1
- package/backend/module/router.ts +12 -18
- package/backend/module-menu.ts +7 -2
- package/backend/multi-record-selection-edit-action.ts +1 -0
- package/backend/multi-step-wizard.ts +50 -50
- package/backend/new-content-element-wizard-button.ts +1 -1
- package/backend/new-record-wizard.ts +102 -9
- package/backend/notification.ts +3 -3
- package/backend/pagetsconfig/pagetsconfig-includes.ts +1 -1
- package/backend/record-download-button.ts +2 -3
- package/backend/record-usage/record-usage-store.ts +65 -0
- package/backend/recordlist.ts +70 -0
- package/backend/resource/resource.ts +4 -0
- package/backend/security/element/csp-reports.ts +7 -85
- package/backend/security/element/sudo-mode.ts +1 -1
- package/backend/settings/editor/editable-setting.ts +2 -1
- package/backend/settings/editor.ts +18 -6
- package/backend/settings/type/base.ts +1 -1
- package/backend/settings/type/bool.ts +1 -1
- package/backend/settings/type/color.ts +1 -1
- package/backend/settings/type/int.ts +2 -2
- package/backend/settings/type/number.ts +2 -2
- package/backend/settings/type/page.ts +1 -1
- package/backend/settings/type/string.ts +2 -2
- package/backend/settings/type/stringlist.ts +1 -1
- package/backend/settings/type/url.ts +43 -0
- package/backend/storage/abstract-client-storage.ts +1 -1
- package/backend/storage/module-state-storage.ts +5 -0
- package/backend/switch-user.ts +1 -1
- package/backend/tests/form-engine-validation-test.ts +12 -12
- package/backend/tests/login/pasted-password-checker-test.ts +43 -0
- package/backend/toolbar/live-search.ts +2 -7
- package/backend/tree/file-storage-browser.ts +1 -1
- package/backend/tree/file-storage-tree-container.ts +9 -5
- package/backend/tree/file-storage-tree.ts +13 -1
- package/backend/tree/page-browser.ts +3 -3
- package/backend/tree/page-tree-element.ts +11 -8
- package/backend/tree/tree-module-state.ts +1 -1
- package/backend/tree/tree-node-toggle.ts +1 -1
- package/backend/tree/tree-toolbar.ts +4 -3
- package/backend/tree/tree.ts +5 -5
- package/backend/user-settings-manager.ts +25 -1
- package/backend/utility/dom-helper.ts +11 -1
- package/backend/utility/format-utility.ts +30 -0
- package/backend/utility.ts +0 -21
- package/backend/viewport/content-container.ts +4 -1
- package/backend/viewport/resizable-navigation.ts +3 -3
- package/backend/wizard/move-content-element.ts +2 -2
- package/core/ajax/ajax-request.ts +5 -2
- package/core/authentication/mfa-provider/totp.ts +1 -1
- package/core/directive/markdown.ts +2 -2
- package/core/event/throttle-event.ts +1 -1
- package/core/factory/url-factory.ts +73 -0
- package/core/lit-helper.ts +2 -2
- package/core/tests/factory/url-factory-test.ts +105 -0
- package/dashboard/dashboard.ts +100 -5
- package/extensionmanager/distribution-image.ts +1 -1
- package/filelist/context-menu-actions.ts +21 -16
- package/filelist/file-delete.ts +1 -1
- package/filelist/file-list-actions.ts +9 -1
- package/filelist/file-list-rename-handler.ts +7 -4
- package/filelist/file-list-replace-handler.ts +141 -0
- package/filelist/file-list.ts +1 -0
- package/filelist/{create-folder.ts → resource-creation.ts} +2 -2
- package/form/backend/form-editor/component/property-grid-editor.ts +4 -4
- package/form/backend/form-editor/core.ts +1 -2
- package/form/backend/form-manager/view-model.ts +28 -67
- package/install/installer.ts +4 -4
- package/install/module/maintenance/reference-index.ts +154 -0
- package/install/renderable/flash-message.ts +1 -1
- package/install/renderable/info-box.ts +1 -1
- package/install/renderable/language-packs.ts +3 -3
- package/install/renderable/offset-group.ts +1 -1
- package/install/renderable/wrap-group.ts +1 -1
- package/install/router.ts +2 -1
- package/package.json +2 -2
- package/rte-ckeditor/ckeditor5.ts +3 -3
- package/rte-ckeditor/plugin/typo3-link.ts +2 -33
- package/rte-ckeditor/rte-link-browser.ts +1 -0
- package/scheduler/form-engine/element/timing-options-element.ts +74 -0
- package/scheduler/new-scheduler-task-wizard-button.ts +51 -0
- package/scheduler/scheduler-add-group.ts +28 -0
- package/scheduler/scheduler.ts +3 -359
- package/scheduler/setup-check-button.ts +50 -0
- package/styleguide/element/theme-switcher-element.ts +2 -1
- package/sys-note/element/delete-button.ts +1 -1
- package/tstemplate/template-analyzer.ts +1 -1
- package/viewpage/main.ts +5 -3
- package/workspaces/backend.ts +34 -31
- package/workspaces/preview.ts +6 -8
- package/workspaces/renderable/comment-view.ts +3 -3
- package/workspaces/renderable/diff-view.ts +3 -3
- package/workspaces/renderable/history-view.ts +3 -3
- package/workspaces/renderable/record-information.ts +2 -2
- package/workspaces/renderable/record-table.ts +26 -6
- package/workspaces/renderable/send-to-stage-form.ts +1 -9
- package/workspaces/toolbar/workspaces-menu.ts +5 -3
- package/workspaces/workspaces.ts +1 -36
- package/backend/document-save-actions.ts +0 -159
- package/backend/hotkeys/events.ts +0 -47
- package/backend/hotkeys/hotkey-struct.ts +0 -58
- package/backend/hotkeys/negotiator.ts +0 -53
- package/backend/wizard.ts +0 -331
- package/filelist/file-replace.ts +0 -37
- package/lowlevel/reference-index.ts +0 -44
- package/scheduler/scheduler-editable-group-name.ts +0 -307
package/backend/context-menu.ts
CHANGED
|
@@ -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
|
|
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
|
|
22
|
-
import { unsafeHTML } from 'lit/directives/unsafe-html
|
|
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
|
|
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
|
|
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:
|
|
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 ===
|
|
161
|
-
|
|
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 ===
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
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
|
-
|
|
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,
|
|
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,
|
|
265
|
+
options.maxDate = options.parseDate(inputElement.dataset.dateMaxDate, ISO8601_LOCALTIME);
|
|
272
266
|
options.maxDate.setSeconds(59);
|
|
273
267
|
}
|
|
274
268
|
|
package/backend/drag-tooltip.ts
CHANGED
|
@@ -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
|
|
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';
|
package/backend/drag-uploader.ts
CHANGED
|
@@ -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(
|
|
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} (${
|
|
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} (${
|
|
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,
|
|
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('- ' +
|
|
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 =
|
|
864
|
+
fileSizeColumn.textContent = FormatUtility.fileSizeAsString(fileInfo.size);
|
|
869
865
|
this.row.append(fileSizeColumn);
|
|
870
866
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
876
|
+
const permissionsColumn = document.createElement('td');
|
|
877
|
+
permissionsColumn.innerHTML = permissions;
|
|
878
|
+
this.row.append(permissionsColumn);
|
|
882
879
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|