typo3-typescript-sources 14.2.0 → 14.3.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 +6 -6
- package/.last-synced-tag +1 -1
- package/README.md +1 -1
- package/backend/clear-cache.ts +7 -7
- package/backend/color-picker.ts +44 -36
- package/backend/context-menu-actions.ts +7 -7
- package/backend/date-time-picker.ts +37 -15
- package/backend/element/contextual-record-edit-trigger.ts +4 -2
- package/backend/element/editable-page-title.ts +17 -14
- package/backend/form-engine/element/datetime-element.ts +1 -1
- package/{styleguide/filter.ts → backend/layout-module/page-layout-event.ts} +8 -8
- package/backend/layout-module/page-layout.ts +102 -0
- package/backend/layout-module/toggle-hidden-element.ts +126 -0
- package/backend/security/element/csp-reports.ts +1 -6
- package/backend/settings/editor/editable-setting.ts +3 -8
- package/backend/settings/type/base.ts +1 -1
- package/{setup → backend}/setup-module.ts +2 -2
- package/backend/toolbar/clear-cache-menu.ts +11 -4
- package/backend/tree/page-position-select.ts +52 -41
- package/backend/tree/tree.ts +27 -6
- package/dashboard/dashboard.ts +0 -3
- package/form/backend/form-editor/stage-component.ts +3 -3
- package/form/backend/form-manager/view-model.ts +15 -13
- package/linkvalidator/linkvalidator.ts +13 -46
- package/package.json +2 -2
- package/backend/page-actions.ts +0 -91
|
@@ -90,6 +90,7 @@ jobs:
|
|
|
90
90
|
echo "New version found: $NEW_TAG (previous: $LAST_SYNCED)"
|
|
91
91
|
echo "has_new_version=true" >> $GITHUB_OUTPUT
|
|
92
92
|
echo "typo3_version=$NEW_TAG" >> $GITHUB_OUTPUT
|
|
93
|
+
echo "release_tag=${NEW_TAG#v}" >> $GITHUB_OUTPUT
|
|
93
94
|
fi
|
|
94
95
|
|
|
95
96
|
- name: Setup Git
|
|
@@ -271,12 +272,11 @@ jobs:
|
|
|
271
272
|
Source: https://github.com/TYPO3/typo3/tree/${TYPO3_VERSION}
|
|
272
273
|
Path: Build/Sources/TypeScript/"
|
|
273
274
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
git tag -a "${NPM_VERSION}" -m "Release ${NPM_VERSION} from TYPO3 ${TYPO3_VERSION}"
|
|
275
|
+
RELEASE_TAG="${{ steps.check.outputs.release_tag }}"
|
|
276
|
+
git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG} from TYPO3 ${TYPO3_VERSION}"
|
|
277
277
|
|
|
278
278
|
git push origin "${TARGET_BRANCH}"
|
|
279
|
-
git push origin "${
|
|
279
|
+
git push origin "${RELEASE_TAG}"
|
|
280
280
|
|
|
281
281
|
- name: Setup Node.js
|
|
282
282
|
if: steps.check.outputs.has_new_version == 'true'
|
|
@@ -306,8 +306,8 @@ jobs:
|
|
|
306
306
|
env:
|
|
307
307
|
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
|
|
308
308
|
with:
|
|
309
|
-
tag_name: ${{ steps.check.outputs.
|
|
310
|
-
release_name: TYPO3 ${{ steps.check.outputs.
|
|
309
|
+
tag_name: ${{ steps.check.outputs.release_tag }}
|
|
310
|
+
release_name: TYPO3 ${{ steps.check.outputs.release_tag }}
|
|
311
311
|
body: |
|
|
312
312
|
Synchronized with TYPO3 Core ${{ steps.check.outputs.typo3_version }}
|
|
313
313
|
|
package/.last-synced-tag
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v14.
|
|
1
|
+
v14.3.0
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install typo3-typescript-sources
|
|
|
16
16
|
|
|
17
17
|
- **Original Repository:** [TYPO3/typo3](https://github.com/TYPO3/typo3)
|
|
18
18
|
- **Source Path:** `Build/Sources/TypeScript/`
|
|
19
|
-
- **Current TYPO3 Version:** `v14.
|
|
19
|
+
- **Current TYPO3 Version:** `v14.3.0`
|
|
20
20
|
|
|
21
21
|
## 🌿 Branches
|
|
22
22
|
|
package/backend/clear-cache.ts
CHANGED
|
@@ -46,15 +46,15 @@ class ClearCache {
|
|
|
46
46
|
const request = new AjaxRequest(TYPO3.settings.ajaxUrls.clearcache_page).post({ id: pageId });
|
|
47
47
|
request.then(async (response: AjaxResponse): Promise<void> => {
|
|
48
48
|
const data = await response.resolve();
|
|
49
|
-
if (data
|
|
50
|
-
Notification.
|
|
51
|
-
data.title,
|
|
52
|
-
data.message
|
|
49
|
+
if (data?.success === false) {
|
|
50
|
+
Notification.error(
|
|
51
|
+
data.title ?? cacheLabels.get('notification.error.title'),
|
|
52
|
+
data.message ?? cacheLabels.get('notification.error.message'),
|
|
53
53
|
);
|
|
54
54
|
} else {
|
|
55
|
-
Notification.
|
|
56
|
-
data.title,
|
|
57
|
-
data.message
|
|
55
|
+
Notification.success(
|
|
56
|
+
data?.title ?? cacheLabels.get('notification.success.title'),
|
|
57
|
+
data?.message ?? cacheLabels.get('notification.success.message'),
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
}, (): void => {
|
package/backend/color-picker.ts
CHANGED
|
@@ -67,45 +67,53 @@ export class Typo3BackendColorPicker extends LitElement {
|
|
|
67
67
|
await DocumentService.ready();
|
|
68
68
|
|
|
69
69
|
const inputElement = this.getInputElement();
|
|
70
|
-
if (inputElement) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} else {
|
|
74
|
-
this.color = inputElement.value;
|
|
75
|
-
}
|
|
70
|
+
if (!inputElement) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if (!inputElement.value && this.color) {
|
|
75
|
+
inputElement.value = this.color;
|
|
76
|
+
} else {
|
|
77
|
+
this.color = inputElement.value;
|
|
78
|
+
}
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
format: 'hex',
|
|
84
|
-
opacity: this.opacity,
|
|
85
|
-
swatches: this.swatches,
|
|
86
|
-
preset: false,
|
|
87
|
-
color: this.color,
|
|
88
|
-
// Casting to `unknown` to prevent TypeScript Error
|
|
89
|
-
// > TS2589: Type instantiation is excessively deep and possibly infinite.
|
|
90
|
-
parent: (this.closest('dialog') ?? '') as unknown,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
alwan.on('color', (e): void => {
|
|
94
|
-
this.color = e.hex;
|
|
95
|
-
inputElement.value = this.color;
|
|
96
|
-
inputElement.dispatchEvent(new Event('blur'));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// input: react on user input
|
|
100
|
-
// change: react on indirect changes, e.g. a value picker
|
|
101
|
-
['input', 'change'].forEach((eventName: string): void => {
|
|
102
|
-
new RegularEvent(eventName, (e: Event): void => {
|
|
103
|
-
const input = (e.target as HTMLInputElement);
|
|
104
|
-
this.color = input.value;
|
|
105
|
-
alwan.setColor(this.color);
|
|
106
|
-
}).bindTo(inputElement);
|
|
107
|
-
});
|
|
80
|
+
if (inputElement.disabled || inputElement.readOnly) {
|
|
81
|
+
return;
|
|
108
82
|
}
|
|
83
|
+
|
|
84
|
+
const alwan = new Alwan(inputElement, {
|
|
85
|
+
position: 'bottom-start',
|
|
86
|
+
format: 'hex',
|
|
87
|
+
opacity: this.opacity,
|
|
88
|
+
swatches: this.swatches,
|
|
89
|
+
preset: false,
|
|
90
|
+
color: this.color,
|
|
91
|
+
// Casting to `unknown` to prevent TypeScript Error
|
|
92
|
+
// > TS2589: Type instantiation is excessively deep and possibly infinite.
|
|
93
|
+
parent: (this.closest('dialog') ?? '') as unknown,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// When the color picker opens, the input loses focus, making value changes difficult.
|
|
97
|
+
// Restore focus to the input after the picker is opened.
|
|
98
|
+
alwan.on('open', (): void => {
|
|
99
|
+
inputElement.focus();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
alwan.on('color', (e): void => {
|
|
103
|
+
this.color = e.hex;
|
|
104
|
+
inputElement.value = this.color;
|
|
105
|
+
inputElement.dispatchEvent(new Event('blur'));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// input: react on user input
|
|
109
|
+
// change: react on indirect changes, e.g. a value picker
|
|
110
|
+
['input', 'change'].forEach((eventName: string): void => {
|
|
111
|
+
new RegularEvent(eventName, (e: Event): void => {
|
|
112
|
+
const input = (e.target as HTMLInputElement);
|
|
113
|
+
this.color = input.value;
|
|
114
|
+
alwan.setColor(this.color);
|
|
115
|
+
}).bindTo(inputElement);
|
|
116
|
+
});
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
protected override render(): TemplateResult {
|
|
@@ -304,15 +304,15 @@ class ContextMenuActions {
|
|
|
304
304
|
(new AjaxRequest(TYPO3.settings.ajaxUrls.clearcache_page)).post({ id: uid }).then(
|
|
305
305
|
async (response: AjaxResponse): Promise<void> => {
|
|
306
306
|
const data = await response.resolve();
|
|
307
|
-
if (data
|
|
308
|
-
Notification.
|
|
309
|
-
data.title,
|
|
310
|
-
data.message
|
|
307
|
+
if (data?.success === false) {
|
|
308
|
+
Notification.error(
|
|
309
|
+
data.title ?? cacheLabels.get('notification.error.title'),
|
|
310
|
+
data.message ?? cacheLabels.get('notification.error.message'),
|
|
311
311
|
);
|
|
312
312
|
} else {
|
|
313
|
-
Notification.
|
|
314
|
-
data.title,
|
|
315
|
-
data.message
|
|
313
|
+
Notification.success(
|
|
314
|
+
data?.title ?? cacheLabels.get('notification.success.title'),
|
|
315
|
+
data?.message ?? cacheLabels.get('notification.success.message'),
|
|
316
316
|
);
|
|
317
317
|
}
|
|
318
318
|
},
|
|
@@ -15,6 +15,7 @@ import flatpickr from 'flatpickr';
|
|
|
15
15
|
import Persistent from '@typo3/backend/storage/persistent';
|
|
16
16
|
import ShortcutButtonsPlugin from 'shortcut-buttons-flatpickr';
|
|
17
17
|
import { DateTime } from 'luxon';
|
|
18
|
+
import DomHelper from '@typo3/backend/utility/dom-helper';
|
|
18
19
|
import ThrottleEvent from '@typo3/core/event/throttle-event';
|
|
19
20
|
import type { PostValidationEvent } from '@typo3/backend/form-engine-validation';
|
|
20
21
|
import type { DateConfiguration } from '@typo3/backend/type/date-configuration';
|
|
@@ -39,7 +40,7 @@ class DateTimePicker {
|
|
|
39
40
|
* note: this function can be called multiple times (e.g. after AJAX requests) because it only
|
|
40
41
|
* applies to fields which haven't been used yet.
|
|
41
42
|
*/
|
|
42
|
-
public initialize(element: HTMLInputElement): void {
|
|
43
|
+
public initialize(element: HTMLInputElement, appendTo: HTMLElement | null = null): void {
|
|
43
44
|
if (!(element instanceof HTMLInputElement) || typeof element.dataset.datepickerInitialized !== 'undefined') {
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
@@ -55,17 +56,19 @@ class DateTimePicker {
|
|
|
55
56
|
|
|
56
57
|
element.dataset.datepickerInitialized = '1';
|
|
57
58
|
import('flatpickr/dist/l10n').then((): void => {
|
|
58
|
-
this.initializeField(element, userLocale as flatpickr.Options.LocaleKey);
|
|
59
|
+
this.initializeField(element, userLocale as flatpickr.Options.LocaleKey, appendTo);
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* Initialize a single field
|
|
64
65
|
*/
|
|
65
|
-
private initializeField(inputElement: HTMLInputElement, locale: flatpickr.Options.LocaleKey): void {
|
|
66
|
-
const scrollEvent = this.getScrollEvent();
|
|
66
|
+
private initializeField(inputElement: HTMLInputElement, locale: flatpickr.Options.LocaleKey, appendTo: HTMLElement | null = null): void {
|
|
67
67
|
const options = this.getDateOptions(inputElement);
|
|
68
68
|
options.locale = locale;
|
|
69
|
+
if (appendTo) {
|
|
70
|
+
options.appendTo = appendTo;
|
|
71
|
+
}
|
|
69
72
|
|
|
70
73
|
// Custom "first day of week" user preference
|
|
71
74
|
const dow = Persistent.get('dateTimeFirstDayOfWeek');
|
|
@@ -80,14 +83,35 @@ class DateTimePicker {
|
|
|
80
83
|
flatpickr.l10ns.default.firstDayOfWeek = dowNumber;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
const usePopover = appendTo instanceof HTMLElement && appendTo.localName === 'typo3-formengine-element-datetime';
|
|
87
|
+
|
|
88
|
+
if (usePopover) {
|
|
89
|
+
// Disable flatpickr's own positioning; native popover + CSS anchor positioning handle it.
|
|
90
|
+
options.position = (): void => { /* handled by popover */ };
|
|
91
|
+
options.onOpen = [
|
|
92
|
+
(_dates: Date[], _currentDateString: string, self: flatpickr.Instance): void => {
|
|
93
|
+
if (!self.calendarContainer.hasAttribute('popover')) {
|
|
94
|
+
self.calendarContainer.setAttribute('popover', 'manual');
|
|
95
|
+
}
|
|
96
|
+
self.calendarContainer.showPopover();
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
options.onClose = (_dates: Date[], _currentDateString: string, self: flatpickr.Instance): void => {
|
|
100
|
+
if (self.calendarContainer.matches(':popover-open')) {
|
|
101
|
+
self.calendarContainer.hidePopover();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
const scrollEvent = this.getScrollEvent();
|
|
106
|
+
options.onOpen = [
|
|
107
|
+
(): void => {
|
|
108
|
+
scrollEvent.bindTo(DomHelper.scrollEventTarget(inputElement));
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
options.onClose = (): void => {
|
|
112
|
+
scrollEvent.release();
|
|
113
|
+
};
|
|
114
|
+
}
|
|
91
115
|
|
|
92
116
|
// initialize the date time picker on this element
|
|
93
117
|
const dateTimePicker = flatpickr(inputElement, options);
|
|
@@ -116,9 +140,7 @@ class DateTimePicker {
|
|
|
116
140
|
|
|
117
141
|
/**
|
|
118
142
|
* Due to some whack CSS the scrollPosition of the document stays 0 which renders a stuck date time picker.
|
|
119
|
-
* Because of this the position is recalculated on scrolling
|
|
120
|
-
*
|
|
121
|
-
* @return {ThrottleEvent}
|
|
143
|
+
* Because of this the position is recalculated on scrolling the nearest scrollable ancestor.
|
|
122
144
|
*/
|
|
123
145
|
private getScrollEvent(): ThrottleEvent {
|
|
124
146
|
return new ThrottleEvent('scroll', (): void => {
|
|
@@ -61,12 +61,14 @@ export class ContextualRecordEditTriggerElement extends PseudoButtonLitElement {
|
|
|
61
61
|
private setupMessageHandling(modal: ModalElement): void {
|
|
62
62
|
const messageTarget = top;
|
|
63
63
|
let savedRecordTitle = '';
|
|
64
|
+
let hasSaved = false;
|
|
64
65
|
let closeConfirmed = false;
|
|
65
66
|
const messageHandler = (event: MessageEvent): void => {
|
|
66
67
|
if (event.origin !== window.location.origin) {
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
if (event.data?.actionName === 'typo3:editform:saved') {
|
|
71
|
+
hasSaved = true;
|
|
70
72
|
savedRecordTitle = event.data.recordTitle ?? '';
|
|
71
73
|
}
|
|
72
74
|
if (event.data?.actionName === 'typo3:editform:closed') {
|
|
@@ -94,14 +96,14 @@ export class ContextualRecordEditTriggerElement extends PseudoButtonLitElement {
|
|
|
94
96
|
|
|
95
97
|
modal.addEventListener('typo3-modal-hidden', () => {
|
|
96
98
|
messageTarget.removeEventListener('message', messageHandler);
|
|
97
|
-
if (
|
|
99
|
+
if (hasSaved) {
|
|
98
100
|
top.document.dispatchEvent(new CustomEvent('typo3:pagetree:refresh'));
|
|
99
101
|
if (top.TYPO3?.Backend?.ContentContainer) {
|
|
100
102
|
top.TYPO3.Backend.ContentContainer.refresh();
|
|
101
103
|
}
|
|
102
104
|
Notification.success(
|
|
103
105
|
labels.get('notification.record_updated.title'),
|
|
104
|
-
labels.get('notification.record_updated.message', [savedRecordTitle]),
|
|
106
|
+
savedRecordTitle !== '' ? labels.get('notification.record_updated.message', [savedRecordTitle]) : undefined,
|
|
105
107
|
);
|
|
106
108
|
} else {
|
|
107
109
|
this.focus();
|
|
@@ -84,13 +84,19 @@ export class EditablePageTitle extends LitElement {
|
|
|
84
84
|
.wrapper {
|
|
85
85
|
position: relative;
|
|
86
86
|
margin: -1px 0;
|
|
87
|
+
padding-inline-end: 1.5em;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
.wrapper:has(> form) .page-title {
|
|
91
|
+
visibility: hidden;
|
|
92
|
+
pointer-events: none;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
.wrapper > form {
|
|
96
|
+
position: absolute;
|
|
97
|
+
top: 0;
|
|
98
|
+
left: 0;
|
|
99
|
+
right: 0;
|
|
94
100
|
padding-inline-end: 2.5em;
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -190,18 +196,15 @@ export class EditablePageTitle extends LitElement {
|
|
|
190
196
|
return html`<div class="wrapper"><h1>${this.pageTitle}</h1></div>`;
|
|
191
197
|
}
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<div class="wrapper">
|
|
199
|
+
return html`
|
|
200
|
+
<div class="wrapper">
|
|
201
|
+
<div class="page-title">
|
|
197
202
|
<h1 @dblclick="${(): void => { this.startEditing(); }}">${this.pageTitle}</h1>
|
|
198
203
|
${this.composeEditButton()}
|
|
199
|
-
</div
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return content;
|
|
204
|
+
</div>
|
|
205
|
+
${this._isEditing ? this.composeEditForm() : nothing}
|
|
206
|
+
</div>
|
|
207
|
+
`;
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
private isEditable(): boolean {
|
|
@@ -268,7 +271,7 @@ export class EditablePageTitle extends LitElement {
|
|
|
268
271
|
|
|
269
272
|
private composeEditForm(): TemplateResult {
|
|
270
273
|
return html`
|
|
271
|
-
<form
|
|
274
|
+
<form @submit="${ this.updatePageTitle }">
|
|
272
275
|
<label class="screen-reader" for="input">${this.labels.input}</label>
|
|
273
276
|
<input
|
|
274
277
|
autocomplete="off"
|
|
@@ -47,7 +47,7 @@ class DatetimeElement extends HTMLElement {
|
|
|
47
47
|
|
|
48
48
|
this.registerEventHandler();
|
|
49
49
|
import('../../date-time-picker').then(({ default: DateTimePicker }): void => {
|
|
50
|
-
DateTimePicker.initialize(this.element);
|
|
50
|
+
DateTimePicker.initialize(this.element, this);
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* The TYPO3 project - inspiring people to share!
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
export class HiddenContentCountChangedEvent extends CustomEvent<{ count: number }> {
|
|
15
|
+
static readonly eventName = 'typo3:page-layout:hidden-content-count-changed';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
17
|
+
constructor(count: number) {
|
|
18
|
+
super(HiddenContentCountChangedEvent.eventName, {
|
|
19
|
+
detail: { count },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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 AjaxRequest from '@typo3/core/ajax/ajax-request';
|
|
15
|
+
import type { AjaxResponse } from '@typo3/core/ajax/ajax-response';
|
|
16
|
+
import Notification from '@typo3/backend/notification';
|
|
17
|
+
import RegularEvent from '@typo3/core/event/regular-event';
|
|
18
|
+
import { sudoModeInterceptor } from '@typo3/backend/security/sudo-mode-interceptor';
|
|
19
|
+
import layoutLabels from '~labels/backend.layout';
|
|
20
|
+
import { HiddenContentCountChangedEvent } from './page-layout-event';
|
|
21
|
+
import '@typo3/backend/element/icon-element';
|
|
22
|
+
import type { PageLayoutToggleHidden } from './toggle-hidden-element';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Module: @typo3/backend/layout-module/page-layout
|
|
26
|
+
*
|
|
27
|
+
* Main entry point for the page layout module.
|
|
28
|
+
* Registers all web components and provides inline visibility toggling.
|
|
29
|
+
*/
|
|
30
|
+
new RegularEvent('click', async (event: Event, delegateTarget?: Element): Promise<void> => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
event.stopPropagation();
|
|
33
|
+
|
|
34
|
+
const target = delegateTarget as HTMLButtonElement;
|
|
35
|
+
if (target.disabled) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
target.disabled = true;
|
|
39
|
+
|
|
40
|
+
const contentElement = target.closest('.t3js-page-ce') as HTMLElement | null;
|
|
41
|
+
if (contentElement === null) {
|
|
42
|
+
target.disabled = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const table = contentElement.dataset.table;
|
|
47
|
+
const uid = parseInt(contentElement.dataset.uid ?? '0', 10);
|
|
48
|
+
const isHidden = contentElement.classList.contains('t3js-hidden-record');
|
|
49
|
+
const iconElement = target.querySelector('typo3-backend-icon');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const response: AjaxResponse = await new AjaxRequest(TYPO3.settings.ajaxUrls.record_toggle_visibility)
|
|
53
|
+
.addMiddleware(sudoModeInterceptor)
|
|
54
|
+
.post({
|
|
55
|
+
table: table,
|
|
56
|
+
uid: uid,
|
|
57
|
+
action: isHidden ? 'show' : 'hide',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const data = await response.resolve();
|
|
61
|
+
|
|
62
|
+
// Update hidden state classes on the content element wrapper
|
|
63
|
+
contentElement.classList.toggle('t3-page-ce-hidden', !data.isVisible);
|
|
64
|
+
contentElement.classList.toggle('t3js-hidden-record', !data.isVisible);
|
|
65
|
+
|
|
66
|
+
// If the element was just hidden and the "show hidden" toggle is inactive,
|
|
67
|
+
// hide the element from view so it disappears from the layout.
|
|
68
|
+
if (!data.isVisible) {
|
|
69
|
+
const toggleHidden = document.querySelector('typo3-backend-page-layout-toggle-hidden') as PageLayoutToggleHidden | null;
|
|
70
|
+
if (toggleHidden && !toggleHidden.active) {
|
|
71
|
+
contentElement.style.display = 'none';
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
contentElement.style.display = '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update toggle button icon and title
|
|
78
|
+
iconElement?.setAttribute('identifier', data.isVisible ? 'actions-edit-hide' : 'actions-edit-unhide');
|
|
79
|
+
target.title = data.isVisible ? layoutLabels.get('hide') : layoutLabels.get('unHide');
|
|
80
|
+
|
|
81
|
+
// Update record icon in context menu trigger (reflects hidden overlay)
|
|
82
|
+
const recordIconElement = contentElement.querySelector('.t3-page-ce-header-left [data-contextmenu-trigger] .t3js-icon');
|
|
83
|
+
if (recordIconElement && data.icon) {
|
|
84
|
+
recordIconElement.replaceWith(document.createRange().createContextualFragment(data.icon));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update hidden element counter in the toolbar toggle button
|
|
88
|
+
const count = document.querySelectorAll('.t3js-hidden-record').length;
|
|
89
|
+
document.dispatchEvent(new HiddenContentCountChangedEvent(count));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error && typeof (error as AjaxResponse).resolve === 'function') {
|
|
92
|
+
const data = await (error as AjaxResponse).resolve();
|
|
93
|
+
for (const message of data.messages ?? []) {
|
|
94
|
+
Notification.error(message.title, message.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
target.disabled = false;
|
|
99
|
+
}
|
|
100
|
+
}).delegateTo(document, 'button[data-action="content-element-visibility-toggle"]');
|
|
101
|
+
|
|
102
|
+
export { HiddenContentCountChangedEvent } from './page-layout-event';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { html, type TemplateResult } from 'lit';
|
|
15
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
16
|
+
import { PseudoButtonLitElement } from '@typo3/backend/element/pseudo-button';
|
|
17
|
+
import PersistentStorage from '@typo3/backend/storage/persistent';
|
|
18
|
+
import layoutLabels from '~labels/backend.layout';
|
|
19
|
+
import '@typo3/backend/element/icon-element';
|
|
20
|
+
import { HiddenContentCountChangedEvent } from './page-layout-event';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Module: @typo3/backend/layout-module/toggle-hidden-element
|
|
24
|
+
*
|
|
25
|
+
* Toolbar toggle button for showing/hiding hidden content elements
|
|
26
|
+
* in the page module. Updates its counter reactively when content
|
|
27
|
+
* elements are toggled.
|
|
28
|
+
*/
|
|
29
|
+
@customElement('typo3-backend-page-layout-toggle-hidden')
|
|
30
|
+
export class PageLayoutToggleHidden extends PseudoButtonLitElement {
|
|
31
|
+
@property({ type: Boolean, reflect: true }) active: boolean = false;
|
|
32
|
+
@property({ type: Number }) count: number = 0;
|
|
33
|
+
|
|
34
|
+
public override connectedCallback(): void {
|
|
35
|
+
super.connectedCallback();
|
|
36
|
+
this.syncDropdownToggleStatus();
|
|
37
|
+
document.addEventListener(HiddenContentCountChangedEvent.eventName, this.onCountChanged);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override disconnectedCallback(): void {
|
|
41
|
+
super.disconnectedCallback();
|
|
42
|
+
document.removeEventListener(HiddenContentCountChangedEvent.eventName, this.onCountChanged);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public override updated(changedProperties: Map<string, unknown>): void {
|
|
46
|
+
if (changedProperties.has('active')) {
|
|
47
|
+
this.syncDropdownToggleStatus();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected override createRenderRoot(): HTMLElement | ShadowRoot {
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected override render(): TemplateResult {
|
|
56
|
+
return html`<span class="dropdown-item-status"></span><typo3-backend-icon identifier="actions-eye" size="small"></typo3-backend-icon>${layoutLabels.get('hiddenCE')} (${this.count})`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected override buttonActivated(): void {
|
|
60
|
+
if (this.hasAttribute('disabled')) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.setAttribute('disabled', '');
|
|
64
|
+
|
|
65
|
+
const show = !this.active;
|
|
66
|
+
const hiddenElements = document.querySelectorAll('.t3js-hidden-record') as NodeListOf<HTMLElement>;
|
|
67
|
+
|
|
68
|
+
for (const hiddenElement of hiddenElements) {
|
|
69
|
+
hiddenElement.style.display = 'flow-root';
|
|
70
|
+
const scrollHeight = hiddenElement.scrollHeight;
|
|
71
|
+
|
|
72
|
+
// Always set `overflow: clip` after storing scrollHeight
|
|
73
|
+
// * For hidden state `height: 0px` is already set.
|
|
74
|
+
// * For visible state setting `overflow: clip` is fine anyway.
|
|
75
|
+
hiddenElement.style.overflow = 'clip';
|
|
76
|
+
|
|
77
|
+
if (!show) {
|
|
78
|
+
// * Invisible elements must not be accessible/focusable by keyboard.
|
|
79
|
+
// * Spacing between content elements is kept uniform by collapsed margins,
|
|
80
|
+
// hidden elements have a height of 0 and the margins of the surrounding elements
|
|
81
|
+
// cannot collapse, causing a visual gap.
|
|
82
|
+
// Therefore do not display the element at all by setting `display: none`.
|
|
83
|
+
hiddenElement.addEventListener('transitionend', (): void => {
|
|
84
|
+
hiddenElement.style.display = 'none';
|
|
85
|
+
hiddenElement.style.overflow = '';
|
|
86
|
+
}, { once: true });
|
|
87
|
+
|
|
88
|
+
// We use requestAnimationFrame() as we have to set the container's height at first before resizing to
|
|
89
|
+
// collapsed-element-height. This results in a smooth animation.
|
|
90
|
+
requestAnimationFrame((): void => {
|
|
91
|
+
hiddenElement.style.height = scrollHeight + 'px';
|
|
92
|
+
requestAnimationFrame((): void => {
|
|
93
|
+
hiddenElement.style.height = '0px';
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
hiddenElement.addEventListener('transitionend', (): void => {
|
|
98
|
+
hiddenElement.style.display = '';
|
|
99
|
+
hiddenElement.style.overflow = '';
|
|
100
|
+
hiddenElement.style.height = '';
|
|
101
|
+
}, { once: true });
|
|
102
|
+
|
|
103
|
+
hiddenElement.style.height = scrollHeight + 'px';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.active = show;
|
|
108
|
+
PersistentStorage.set('moduleData.web_layout.showHidden', show ? '1' : '0').then((): void => {
|
|
109
|
+
this.removeAttribute('disabled');
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private syncDropdownToggleStatus(): void {
|
|
114
|
+
this.dataset.dropdowntoggleStatus = this.active ? 'active' : 'inactive';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private readonly onCountChanged = (event: Event): void => {
|
|
118
|
+
this.count = (event as HiddenContentCountChangedEvent).detail.count;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
declare global {
|
|
123
|
+
interface HTMLElementTagNameMap {
|
|
124
|
+
'typo3-backend-page-layout-toggle-hidden': PageLayoutToggleHidden;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -133,16 +133,11 @@ export class CspReports extends LitElement {
|
|
|
133
133
|
|
|
134
134
|
protected override render(): TemplateResult {
|
|
135
135
|
return html`
|
|
136
|
-
<div class="infolist-container
|
|
136
|
+
<div class="infolist-container">
|
|
137
137
|
<div class="infolist">
|
|
138
138
|
<div class="infolist-header">
|
|
139
139
|
${this.renderNavigation()}
|
|
140
140
|
</div>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<div class="infolist-container">
|
|
145
|
-
<div class="infolist">
|
|
146
141
|
<div class="infolist-content">
|
|
147
142
|
<div class="table-fit mb-0">
|
|
148
143
|
<table class="table table-striped">
|
|
@@ -35,8 +35,7 @@ interface SettingDefinition {
|
|
|
35
35
|
label: string,
|
|
36
36
|
description: string|null,
|
|
37
37
|
readonly: boolean,
|
|
38
|
-
|
|
39
|
-
enum: Record<string, string>|Array<string>,
|
|
38
|
+
enum: Record<string, string>,
|
|
40
39
|
categories: string[],
|
|
41
40
|
tags: string[],
|
|
42
41
|
options: Record<string, unknown>,
|
|
@@ -119,10 +118,6 @@ export class EditableSettingElement extends LitElement {
|
|
|
119
118
|
|
|
120
119
|
protected updateFieldAttributes(element: HTMLElement): void {
|
|
121
120
|
const { definition, value } = this.setting;
|
|
122
|
-
// Force conversion to an object, as PHP json_encode encodes ['0' => 'foo'] as
|
|
123
|
-
// ['foo'] instead of {'0' => 'foo'}
|
|
124
|
-
const enumEntries = Object.entries(definition.enum || {});
|
|
125
|
-
|
|
126
121
|
const attributes = {
|
|
127
122
|
key: definition.key,
|
|
128
123
|
formid: `setting-${definition.key}`,
|
|
@@ -130,9 +125,9 @@ export class EditableSettingElement extends LitElement {
|
|
|
130
125
|
value: Array.isArray(value) ? JSON.stringify(value) : String(value),
|
|
131
126
|
debug: this.mode === SettingsMode.advanced,
|
|
132
127
|
readonly: definition.readonly,
|
|
133
|
-
enum:
|
|
128
|
+
enum: Object.keys(definition.enum).length > 0 ? JSON.stringify(definition.enum) : false,
|
|
134
129
|
default: Array.isArray(definition.default) ? JSON.stringify(definition.default) : String(definition.default),
|
|
135
|
-
options:
|
|
130
|
+
options: JSON.stringify(definition.options),
|
|
136
131
|
};
|
|
137
132
|
for (const [key, value] of Object.entries(attributes)) {
|
|
138
133
|
if (typeof value === 'boolean') {
|
|
@@ -36,7 +36,7 @@ export abstract class BaseElement<
|
|
|
36
36
|
@property({ type: String }) key: string;
|
|
37
37
|
@property({ type: String }) formid: string;
|
|
38
38
|
@property({ type: Boolean }) readonly: boolean = false;
|
|
39
|
-
@property({ type: Object }) enum: Record<string, string
|
|
39
|
+
@property({ type: Object }) enum: Record<string, string> | undefined;
|
|
40
40
|
@property({ type: Boolean }) debug: boolean = false;
|
|
41
41
|
@property({ type: Object }) options: OptionsType;
|
|
42
42
|
|
|
@@ -18,8 +18,8 @@ import RegularEvent from '@typo3/core/event/regular-event';
|
|
|
18
18
|
import Modal from '@typo3/backend/modal';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Module: @typo3/
|
|
22
|
-
* @exports @typo3/
|
|
21
|
+
* Module: @typo3/backend/setup-module
|
|
22
|
+
* @exports @typo3/backend/setup-module
|
|
23
23
|
*/
|
|
24
24
|
class SetupModule {
|
|
25
25
|
private avatarWindowRef: Window;
|
|
@@ -71,10 +71,17 @@ class ClearCacheMenu {
|
|
|
71
71
|
(new AjaxRequest(ajaxUrl)).post({}).then(
|
|
72
72
|
async (response: AjaxResponse): Promise<void> => {
|
|
73
73
|
const data = await response.resolve();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
if (data?.success === false) {
|
|
75
|
+
Notification.error(
|
|
76
|
+
data.title ?? cacheLabels.get('notification.error.title'),
|
|
77
|
+
data.message ?? cacheLabels.get('notification.error.message'),
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
Notification.success(
|
|
81
|
+
data?.title ?? cacheLabels.get('notification.success.title'),
|
|
82
|
+
data?.message ?? cacheLabels.get('notification.success.message'),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
78
85
|
},
|
|
79
86
|
(): void => {
|
|
80
87
|
Notification.error(
|
|
@@ -108,31 +108,17 @@ export class PagePositionSelect extends LitElement {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
protected renderTree(): TemplateResult {
|
|
111
|
-
const initialized = () => {
|
|
111
|
+
const initialized = async () => {
|
|
112
112
|
// set up toolbar now with updated properties
|
|
113
113
|
const toolbar = this.querySelector('typo3-backend-tree-toolbar') as TreeToolbar;
|
|
114
114
|
toolbar.tree = this.tree;
|
|
115
|
-
this.tree.ensureActiveNodeLoaded(this.activePageId)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (activeNode) {
|
|
123
|
-
activeNode.checked = true;
|
|
124
|
-
this.updateBreadcrumb(this.tree.nodes);
|
|
125
|
-
this.tree.requestUpdate();
|
|
126
|
-
this.tree.updateComplete.then(() => {
|
|
127
|
-
this.tree.scrollNodeIntoViewIfNeeded(activeNode);
|
|
128
|
-
this.tree.focusNode(activeNode);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
);
|
|
115
|
+
await this.tree.ensureActiveNodeLoaded(this.activePageId);
|
|
116
|
+
const activeNode = await this.applyActiveInsertPosition();
|
|
117
|
+
if (activeNode) {
|
|
118
|
+
await this.tree.updateComplete;
|
|
119
|
+
this.tree.scrollNodeIntoViewIfNeeded(activeNode);
|
|
120
|
+
this.tree.focusNode(activeNode);
|
|
121
|
+
}
|
|
136
122
|
};
|
|
137
123
|
|
|
138
124
|
return html`
|
|
@@ -145,38 +131,63 @@ export class PagePositionSelect extends LitElement {
|
|
|
145
131
|
.setup=${this.configuration}
|
|
146
132
|
@tree:initialized=${initialized}
|
|
147
133
|
@typo3:tree:node-selected="${this.handleNodeSelected}"
|
|
134
|
+
@typo3:tree:filter-applied=${() => this.applyActiveInsertPosition()}
|
|
135
|
+
@typo3:tree:filter-reset=${() => this.applyActiveInsertPosition()}
|
|
148
136
|
>
|
|
149
137
|
</typo3-backend-component-page-position-select-tree>
|
|
150
138
|
`;
|
|
151
139
|
}
|
|
152
140
|
|
|
153
|
-
private handleNodeSelected(event: CustomEvent): void {
|
|
141
|
+
private async handleNodeSelected(event: CustomEvent): Promise<void> {
|
|
154
142
|
const selectedNode: TreeNodeInterface = event.detail.node;
|
|
155
143
|
const [pageUid, insertPosition] = selectedNode.identifier.split('-');
|
|
156
144
|
this.insertPosition = (insertPosition ?? 'inside') as Position;
|
|
157
145
|
this.activePageId = Number(pageUid);
|
|
158
146
|
|
|
159
|
-
const
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
if (node.checked) {
|
|
163
|
-
this.tree.focusNode(node);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
147
|
+
const activeNode = /^\d+$/.test(selectedNode.identifier)
|
|
148
|
+
? await this.applyActiveInsertPosition()
|
|
149
|
+
: this.syncActiveInsertPositionState();
|
|
166
150
|
|
|
167
|
-
|
|
168
|
-
this.
|
|
169
|
-
|
|
170
|
-
this.insertPosition
|
|
171
|
-
));
|
|
172
|
-
};
|
|
151
|
+
if (activeNode) {
|
|
152
|
+
this.tree.focusNode(activeNode);
|
|
153
|
+
}
|
|
173
154
|
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
155
|
+
this.dispatchEvent(new InsertPositionChangeEvent(
|
|
156
|
+
this.activePageId,
|
|
157
|
+
this.insertPosition
|
|
158
|
+
));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async applyActiveInsertPosition(): Promise<TreeNodeInterface|null> {
|
|
162
|
+
if (this.activePageId === null || this.activePageId === undefined) {
|
|
163
|
+
return null;
|
|
179
164
|
}
|
|
165
|
+
let currentNode = this.tree.nodes.find(n => n.identifier === String(this.activePageId));
|
|
166
|
+
if (!currentNode) {
|
|
167
|
+
// Load its rootline and expand the path
|
|
168
|
+
await this.tree.ensureActiveNodeLoaded(this.activePageId);
|
|
169
|
+
currentNode = this.tree.nodes.find(n => n.identifier === String(this.activePageId));
|
|
170
|
+
if (!currentNode) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await this.tree.expandNodeParents(currentNode);
|
|
176
|
+
await this.toggleDynamicInsertNodes(currentNode);
|
|
177
|
+
return this.syncActiveInsertPositionState();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private syncActiveInsertPositionState(): TreeNodeInterface|null {
|
|
181
|
+
let activeNode: TreeNodeInterface|null = null;
|
|
182
|
+
this.tree.nodes.forEach((node: TreeNodeInterface) => {
|
|
183
|
+
node.checked = this.isActivePagePositionNode(node);
|
|
184
|
+
if (node.checked) {
|
|
185
|
+
activeNode = node;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
this.updateBreadcrumb(this.tree.nodes);
|
|
189
|
+
this.tree.requestUpdate();
|
|
190
|
+
return activeNode;
|
|
180
191
|
}
|
|
181
192
|
|
|
182
193
|
private async toggleDynamicInsertNodes(currentNode: TreeNodeInterface): Promise<void> {
|
package/backend/tree/tree.ts
CHANGED
|
@@ -87,6 +87,24 @@ export class TreeNodeMap<T extends TreeNodeInterface = TreeNodeInterface> {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export class TreeFilterAppliedEvent extends CustomEvent<{searchTerm: string, resultCount: number}> {
|
|
91
|
+
static readonly eventName = 'typo3:tree:filter-applied';
|
|
92
|
+
constructor(searchTerm: string, resultCount: number) {
|
|
93
|
+
super(TreeFilterAppliedEvent.eventName, {
|
|
94
|
+
detail: { searchTerm, resultCount },
|
|
95
|
+
bubbles: true,
|
|
96
|
+
composed: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class TreeFilterResetEvent extends Event {
|
|
102
|
+
static readonly eventName = 'typo3:tree:filter-reset';
|
|
103
|
+
constructor() {
|
|
104
|
+
super(TreeFilterResetEvent.eventName, { bubbles: true, composed: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
export class Tree extends LitElement {
|
|
91
109
|
@property({ type: Object }) setup?: {[keys: string]: any} = null;
|
|
92
110
|
@state() settings: TreeSettings = {
|
|
@@ -474,22 +492,25 @@ export class Tree extends LitElement {
|
|
|
474
492
|
}).then(() => {
|
|
475
493
|
this.loading = false;
|
|
476
494
|
this.currentFilterRequest = null;
|
|
495
|
+
this.dispatchEvent(new TreeFilterAppliedEvent(this.searchTerm, this.searchResults));
|
|
477
496
|
});
|
|
478
497
|
} else {
|
|
479
498
|
// restore original state without filters
|
|
480
|
-
this.resetFilter()
|
|
481
|
-
|
|
499
|
+
this.resetFilter().then(() => {
|
|
500
|
+
this.loading = false;
|
|
501
|
+
this.dispatchEvent(new TreeFilterResetEvent());
|
|
502
|
+
});
|
|
482
503
|
}
|
|
483
504
|
}
|
|
484
505
|
|
|
485
|
-
public resetFilter(): void
|
|
506
|
+
public async resetFilter(): Promise<void>
|
|
486
507
|
{
|
|
487
508
|
this.searchTerm = '';
|
|
488
509
|
this.searchResults = 0;
|
|
489
510
|
if (this.unfilteredNodes.length > 0) {
|
|
490
511
|
const currentlySelected = this.getSelectedNodes()[0];
|
|
491
512
|
if (typeof currentlySelected === 'undefined') {
|
|
492
|
-
this.loadData();
|
|
513
|
+
await this.loadData();
|
|
493
514
|
return;
|
|
494
515
|
}
|
|
495
516
|
this.nodeMap = new TreeNodeMap(this.enhanceNodes(JSON.parse(this.unfilteredNodes)));
|
|
@@ -499,10 +520,10 @@ export class Tree extends LitElement {
|
|
|
499
520
|
if (currentlySelectedNode) {
|
|
500
521
|
this.selectNode(currentlySelectedNode, false);
|
|
501
522
|
} else {
|
|
502
|
-
this.loadData();
|
|
523
|
+
await this.loadData();
|
|
503
524
|
}
|
|
504
525
|
} else {
|
|
505
|
-
this.loadData();
|
|
526
|
+
await this.loadData();
|
|
506
527
|
}
|
|
507
528
|
}
|
|
508
529
|
|
package/dashboard/dashboard.ts
CHANGED
|
@@ -583,7 +583,7 @@ function wireAbstractViewFormElementToolbarEventListeners(toolbar: HTMLElement,
|
|
|
583
583
|
}
|
|
584
584
|
|
|
585
585
|
qs('abstractViewToolbarRemoveElement')?.addEventListener('click', function() {
|
|
586
|
-
getViewModel().showRemoveFormElementModal();
|
|
586
|
+
getViewModel().showRemoveFormElementModal(formElement);
|
|
587
587
|
});
|
|
588
588
|
}
|
|
589
589
|
|
|
@@ -631,7 +631,7 @@ export function createAndAddAbstractViewFormElementToolbar(
|
|
|
631
631
|
});
|
|
632
632
|
|
|
633
633
|
toolbar.addEventListener('toolbar-remove-element', () => {
|
|
634
|
-
getViewModel().showRemoveFormElementModal();
|
|
634
|
+
getViewModel().showRemoveFormElementModal(formElement);
|
|
635
635
|
});
|
|
636
636
|
}
|
|
637
637
|
|
|
@@ -908,7 +908,7 @@ export function renderFormElementStageItem(formElement: FormElement, template: H
|
|
|
908
908
|
});
|
|
909
909
|
|
|
910
910
|
stageItem.addEventListener('toolbar-remove-element', () => {
|
|
911
|
-
getViewModel().showRemoveFormElementModal();
|
|
911
|
+
getViewModel().showRemoveFormElementModal(formElement);
|
|
912
912
|
});
|
|
913
913
|
|
|
914
914
|
template.replaceChildren(stageItem);
|
|
@@ -39,19 +39,21 @@ enum Identifiers {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function newFormSetup(formManagerApp: FormManager): void {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
document.querySelectorAll<HTMLElement>(Identifiers.newFormModalTrigger).forEach((element: HTMLElement): void => {
|
|
43
|
+
element.addEventListener('click', async (e: Event): Promise<void> => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
await topLevelModuleImport('@typo3/form/backend/form-wizard/form-wizard.js');
|
|
46
|
+
const content = html`<typo3-backend-form-wizard .formManager="${formManagerApp}"
|
|
47
|
+
></typo3-backend-form-wizard>`;
|
|
48
|
+
|
|
49
|
+
Modal.advanced({
|
|
50
|
+
title: formManagerLabels.get('formManager.newFormWizard.step1.title'),
|
|
51
|
+
content: content,
|
|
52
|
+
severity: SeverityEnum.notice,
|
|
53
|
+
size: Modal.sizes.medium,
|
|
54
|
+
staticBackdrop: true,
|
|
55
|
+
buttons: []
|
|
56
|
+
});
|
|
55
57
|
});
|
|
56
58
|
});
|
|
57
59
|
}
|
|
@@ -13,22 +13,17 @@
|
|
|
13
13
|
|
|
14
14
|
import DocumentService from '@typo3/core/document-service';
|
|
15
15
|
import Notification from '@typo3/backend/notification';
|
|
16
|
+
import { ProgressBarElement } from '@typo3/backend/element/progress-bar-element';
|
|
16
17
|
import RegularEvent from '@typo3/core/event/regular-event';
|
|
17
18
|
import SortableTable from '@typo3/backend/sortable-table';
|
|
18
19
|
|
|
19
20
|
enum Selectors {
|
|
20
|
-
// Check
|
|
21
|
-
linktypesSelectorCheck = '.t3js-linkvalidator-settings input[type="checkbox"].options-by-type-check',
|
|
22
21
|
actionButtonSelectorCheck = '.t3js-linkvalidator-action-button-check',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
toggleAllLinktypesSelectorReport = '.t3js-linkvalidator-settings input[type="checkbox"].options-by-type-toggle-all-report',
|
|
26
|
-
linktypesSelectorReport = '.t3js-linkvalidator-settings input[type="checkbox"].options-by-type-report',
|
|
27
|
-
actionButtonSelectorReport = '.t3js-linkvalidator-action-button-report'
|
|
22
|
+
actionButtonSelectorReport = '.t3js-linkvalidator-action-button-report',
|
|
23
|
+
reportTable = '.t3js-linkvalidator-report-table',
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
enum Identifier {
|
|
31
|
-
toggleAllLinktypesIdReport = 'options-by-type-toggle-all-report',
|
|
32
27
|
brokenLinksTableIdReport = 'typo3-broken-links-table'
|
|
33
28
|
}
|
|
34
29
|
|
|
@@ -36,6 +31,8 @@ enum Identifier {
|
|
|
36
31
|
* Module: @typo3/linkvalidator/linkvalidator
|
|
37
32
|
*/
|
|
38
33
|
class Linkvalidator {
|
|
34
|
+
private progressBar: ProgressBarElement | null = null;
|
|
35
|
+
|
|
39
36
|
constructor() {
|
|
40
37
|
DocumentService.ready().then((): void => {
|
|
41
38
|
const linkList = document.getElementById(Identifier.brokenLinksTableIdReport);
|
|
@@ -49,51 +46,21 @@ class Linkvalidator {
|
|
|
49
46
|
this.initializeEvents();
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
document.querySelector(Selectors.actionButtonSelectorReport)?.toggleAttribute(
|
|
59
|
-
'disabled',
|
|
60
|
-
!document.querySelectorAll('input[type="checkbox"]:checked').length
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Enables the "Toggle all" checkbox on document load if all child checkboxes are checked
|
|
66
|
-
*/
|
|
67
|
-
private toggleTriggerCheckBoxReport(): void {
|
|
68
|
-
const checkBoxes: NodeListOf<HTMLInputElement> = document.querySelectorAll(Selectors.linktypesSelectorReport);
|
|
69
|
-
(document.getElementById(Identifier.toggleAllLinktypesIdReport) as HTMLInputElement).checked = Linkvalidator.allCheckBoxesAreChecked(checkBoxes);
|
|
49
|
+
private getProgress(): ProgressBarElement {
|
|
50
|
+
if (!this.progressBar || !this.progressBar.isConnected) {
|
|
51
|
+
this.progressBar = document.createElement('typo3-backend-progress-bar');
|
|
52
|
+
document.querySelector(Selectors.reportTable).prepend(this.progressBar);
|
|
53
|
+
}
|
|
54
|
+
return this.progressBar;
|
|
70
55
|
}
|
|
71
56
|
|
|
72
57
|
private initializeEvents(): void {
|
|
73
|
-
// toggleAll (checkboxes): on change
|
|
74
|
-
new RegularEvent('change', (e: Event, currentTarget: HTMLInputElement): void => {
|
|
75
|
-
const checkBoxes: NodeListOf<HTMLInputElement> = document.querySelectorAll(Selectors.linktypesSelectorReport);
|
|
76
|
-
const checkIt = !Linkvalidator.allCheckBoxesAreChecked(checkBoxes);
|
|
77
|
-
|
|
78
|
-
checkBoxes.forEach((checkBox: HTMLInputElement): void => {
|
|
79
|
-
checkBox.checked = checkIt;
|
|
80
|
-
});
|
|
81
|
-
currentTarget.checked = checkIt;
|
|
82
|
-
this.toggleActionButtonReport();
|
|
83
|
-
}).delegateTo(document, Selectors.toggleAllLinktypesSelectorReport);
|
|
84
|
-
|
|
85
|
-
// toggle (checkbox): on change
|
|
86
|
-
new RegularEvent('change', (): void => {
|
|
87
|
-
this.toggleTriggerCheckBoxReport();
|
|
88
|
-
this.toggleActionButtonReport();
|
|
89
|
-
}).delegateTo(document, Selectors.linktypesSelectorReport);
|
|
90
|
-
|
|
91
58
|
new RegularEvent('click', (e: PointerEvent, actionButton: HTMLInputElement): void => {
|
|
92
59
|
Notification.success(actionButton.dataset.notificationMessage || 'Event triggered', '', 2);
|
|
93
60
|
}).delegateTo(document, Selectors.actionButtonSelectorCheck);
|
|
94
61
|
|
|
95
|
-
new RegularEvent('click', (
|
|
96
|
-
|
|
62
|
+
new RegularEvent('click', (): void => {
|
|
63
|
+
this.getProgress().start();
|
|
97
64
|
}).delegateTo(document, Selectors.actionButtonSelectorReport);
|
|
98
65
|
}
|
|
99
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typo3-typescript-sources",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.3.0",
|
|
4
4
|
"description": "TYPO3 Core TypeScript Sources - Extracted from TYPO3/typo3 repository",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"typo3": {
|
|
30
30
|
"sourceRepository": "https://github.com/TYPO3/typo3",
|
|
31
|
-
"sourceTag": "v14.
|
|
31
|
+
"sourceTag": "v14.3.0",
|
|
32
32
|
"sourcePath": "Build/Sources/TypeScript"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/backend/page-actions.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of the TYPO3 CMS project.
|
|
3
|
-
*
|
|
4
|
-
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
-
* the terms of the GNU General Public License, either version 2
|
|
6
|
-
* of the License, or any later version.
|
|
7
|
-
*
|
|
8
|
-
* For the full copyright and license information, please read the
|
|
9
|
-
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
-
*
|
|
11
|
-
* The TYPO3 project - inspiring people to share!
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import DocumentService from '@typo3/core/document-service';
|
|
15
|
-
import RegularEvent from '@typo3/core/event/regular-event';
|
|
16
|
-
import PersistentStorage from './storage/persistent';
|
|
17
|
-
|
|
18
|
-
enum IdentifierEnum {
|
|
19
|
-
hiddenElements = '.t3js-hidden-record',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Module: @typo3/backend/page-actions
|
|
24
|
-
* JavaScript implementations for page actions
|
|
25
|
-
*/
|
|
26
|
-
class PageActions {
|
|
27
|
-
constructor() {
|
|
28
|
-
DocumentService.ready().then((): void => {
|
|
29
|
-
const pageLayoutToggleShowHidden = document.getElementById('pageLayoutToggleShowHidden') as HTMLButtonElement|null;
|
|
30
|
-
if (pageLayoutToggleShowHidden !== null) {
|
|
31
|
-
new RegularEvent('click', this.toggleContentElementVisibility).bindTo(pageLayoutToggleShowHidden);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Toggles the "Show hidden content elements"
|
|
38
|
-
*/
|
|
39
|
-
private toggleContentElementVisibility(e: Event): void {
|
|
40
|
-
const me = e.target as HTMLButtonElement;
|
|
41
|
-
const hiddenElements = document.querySelectorAll(IdentifierEnum.hiddenElements) as NodeListOf<HTMLElement>;
|
|
42
|
-
const show = me.dataset.dropdowntoggleStatus !== 'active';
|
|
43
|
-
me.disabled = true;
|
|
44
|
-
|
|
45
|
-
for (const hiddenElement of hiddenElements) {
|
|
46
|
-
hiddenElement.style.display = 'flow-root';
|
|
47
|
-
const scrollHeight = hiddenElement.scrollHeight;
|
|
48
|
-
|
|
49
|
-
// Always set `overflow: clip` after storing scrollHeight
|
|
50
|
-
// * For hidden state `height: 0px` is already set.
|
|
51
|
-
// * For visible state setting `overflow: clip` is fine anyway.
|
|
52
|
-
hiddenElement.style.overflow = 'clip';
|
|
53
|
-
|
|
54
|
-
if (!show) {
|
|
55
|
-
// * Invisible elements must not be accessible/focusable by keyboard.
|
|
56
|
-
// * Spacing between content elements is kept uniform by collapsed margins,
|
|
57
|
-
// hidden elements have a height of 0 and the margins of the surrounding elements
|
|
58
|
-
// cannot collapse, causing a visual gap.
|
|
59
|
-
// Therefore do not display the element at all by setting `display: none`.
|
|
60
|
-
hiddenElement.addEventListener('transitionend', (): void => {
|
|
61
|
-
hiddenElement.style.display = 'none';
|
|
62
|
-
hiddenElement.style.overflow = '';
|
|
63
|
-
}, { once: true });
|
|
64
|
-
|
|
65
|
-
// We use requestAnimationFrame() as we have to set the container's height at first before resizing to
|
|
66
|
-
// collapsed-element-height. This results in a smooth animation.
|
|
67
|
-
requestAnimationFrame(function() {
|
|
68
|
-
hiddenElement.style.height = scrollHeight + 'px';
|
|
69
|
-
requestAnimationFrame(function() {
|
|
70
|
-
hiddenElement.style.height = 0 + 'px';
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
} else {
|
|
74
|
-
hiddenElement.addEventListener('transitionend', (): void => {
|
|
75
|
-
hiddenElement.style.display = '';
|
|
76
|
-
hiddenElement.style.overflow = '';
|
|
77
|
-
hiddenElement.style.height = '';
|
|
78
|
-
}, { once: true });
|
|
79
|
-
|
|
80
|
-
hiddenElement.style.height = scrollHeight + 'px';
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
me.dataset.dropdowntoggleStatus = show ? 'active' : 'inactive';
|
|
85
|
-
PersistentStorage.set('moduleData.web_layout.showHidden', show ? '1' : '0').then((): void => {
|
|
86
|
-
me.disabled = false;
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default new PageActions();
|