typo3-typescript-sources 14.2.0 → 14.3.1
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/clipboard-panel.ts +20 -17
- package/backend/color-picker.ts +44 -36
- package/backend/context-menu-actions.ts +7 -7
- package/backend/date-time-picker.ts +38 -16
- package/backend/element/contextual-record-edit-trigger.ts +4 -2
- package/backend/element/editable-page-title.ts +17 -14
- package/backend/form-engine/container/inline-control-container.ts +2 -1
- 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/layout-module/velocity-scroll.ts +123 -0
- package/backend/live-search/live-search.ts +2 -2
- package/backend/modal.ts +79 -4
- package/backend/page-wizard/helper/wizard-helper.ts +5 -2
- 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 +3 -3
- 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/inspector-component.ts +1 -1
- 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/workspaces/renderable/record-table.ts +1 -1
- package/backend/page-actions.ts +0 -91
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Module: @typo3/backend/layout-module/velocity-scroll
|
|
16
|
+
*
|
|
17
|
+
* Enables auto-scrolling while dragging near the viewport edge.
|
|
18
|
+
*
|
|
19
|
+
* Self-binds a delegated `dragstart` listener for all `[draggable="true"]`
|
|
20
|
+
* elements on the page. May also be invoked directly:
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* // inside a dragstart event handler:
|
|
24
|
+
* initVelocityScroll(event);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import DocumentService from '@typo3/core/document-service';
|
|
28
|
+
import RegularEvent from '@typo3/core/event/regular-event';
|
|
29
|
+
|
|
30
|
+
const EDGE_RATIO = 0.2;
|
|
31
|
+
const MAX_STRENGTH_RATIO = 2.5;
|
|
32
|
+
|
|
33
|
+
DocumentService.ready().then((): void => {
|
|
34
|
+
new RegularEvent('dragstart', (event: Event): void => {
|
|
35
|
+
initVelocityScroll(event as DragEvent);
|
|
36
|
+
}).delegateTo(document, '[draggable="true"]');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function initVelocityScroll(event: DragEvent): void {
|
|
40
|
+
const scrollingElement = document.scrollingElement;
|
|
41
|
+
if (!(scrollingElement instanceof HTMLElement)) {
|
|
42
|
+
console.warn('Scrolling element is not an HTMLElement. Velocity scroll will not work.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Horizontal scrolling may live on an inner container (e.g. the page module's
|
|
47
|
+
// .t3-grid-container with overflow-x: auto) rather than the document, so target
|
|
48
|
+
// the nearest horizontally scrollable ancestor of the drag source if any exists.
|
|
49
|
+
const horizontalElement = findHorizontalScrollAncestor(event.target as Element | null) ?? scrollingElement;
|
|
50
|
+
|
|
51
|
+
let scrollSpeedVertical = 0;
|
|
52
|
+
let scrollSpeedHorizontal = 0;
|
|
53
|
+
let running = true;
|
|
54
|
+
let lastFrameTime = performance.now();
|
|
55
|
+
|
|
56
|
+
// Disable smooth scrolling for the duration of the drag and restore on dragend
|
|
57
|
+
const originalVerticalScrollBehavior = scrollingElement.style.scrollBehavior;
|
|
58
|
+
const originalHorizontalScrollBehavior = horizontalElement.style.scrollBehavior;
|
|
59
|
+
scrollingElement.style.scrollBehavior = 'auto';
|
|
60
|
+
horizontalElement.style.scrollBehavior = 'auto';
|
|
61
|
+
|
|
62
|
+
const tick = (now: number): void => {
|
|
63
|
+
const dt = (now - lastFrameTime) / 1000;
|
|
64
|
+
lastFrameTime = now;
|
|
65
|
+
|
|
66
|
+
const maxVertical = scrollingElement.scrollHeight - scrollingElement.clientHeight;
|
|
67
|
+
const maxHorizontal = horizontalElement.scrollWidth - horizontalElement.clientWidth;
|
|
68
|
+
scrollingElement.scrollTop = Math.max(0, Math.min(scrollingElement.scrollTop + scrollSpeedVertical * dt, maxVertical));
|
|
69
|
+
horizontalElement.scrollLeft = Math.max(0, Math.min(horizontalElement.scrollLeft + scrollSpeedHorizontal * dt, maxHorizontal));
|
|
70
|
+
|
|
71
|
+
if (running) {
|
|
72
|
+
requestAnimationFrame(tick);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
requestAnimationFrame(tick);
|
|
76
|
+
|
|
77
|
+
// Progress within the edge zone goes from 0 (just entered) to 1 (at the edge).
|
|
78
|
+
// Squaring it makes scrolling accelerate noticeably near the edge.
|
|
79
|
+
const axisStrength = (distance: number, edge: number, max: number): number =>
|
|
80
|
+
distance < edge ? ((1 - distance / edge) ** 2) * max : 0;
|
|
81
|
+
|
|
82
|
+
const autoScroll = (event: DragEvent): void => {
|
|
83
|
+
const verticalEdge = window.innerHeight * EDGE_RATIO;
|
|
84
|
+
const horizontalEdge = window.innerWidth * EDGE_RATIO;
|
|
85
|
+
const maxVerticalStrength = window.innerHeight * MAX_STRENGTH_RATIO;
|
|
86
|
+
const maxHorizontalStrength = window.innerWidth * MAX_STRENGTH_RATIO;
|
|
87
|
+
|
|
88
|
+
scrollSpeedVertical =
|
|
89
|
+
axisStrength(window.innerHeight - event.clientY, verticalEdge, maxVerticalStrength) -
|
|
90
|
+
axisStrength(event.clientY, verticalEdge, maxVerticalStrength);
|
|
91
|
+
scrollSpeedHorizontal =
|
|
92
|
+
axisStrength(window.innerWidth - event.clientX, horizontalEdge, maxHorizontalStrength) -
|
|
93
|
+
axisStrength(event.clientX, horizontalEdge, maxHorizontalStrength);
|
|
94
|
+
};
|
|
95
|
+
autoScroll(event);
|
|
96
|
+
window.addEventListener('dragover', autoScroll);
|
|
97
|
+
|
|
98
|
+
// Bind teardown on window: the source element may be detached or replaced
|
|
99
|
+
// during the drop (drag-drop.ts re-renders content via AJAX), in which case
|
|
100
|
+
// a `dragend` listener on the source element would never fire.
|
|
101
|
+
window.addEventListener('dragend', (): void => {
|
|
102
|
+
window.removeEventListener('dragover', autoScroll);
|
|
103
|
+
scrollSpeedVertical = 0;
|
|
104
|
+
scrollSpeedHorizontal = 0;
|
|
105
|
+
running = false;
|
|
106
|
+
scrollingElement.style.scrollBehavior = originalVerticalScrollBehavior;
|
|
107
|
+
horizontalElement.style.scrollBehavior = originalHorizontalScrollBehavior;
|
|
108
|
+
}, { once: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function findHorizontalScrollAncestor(element: Element | null): HTMLElement | null {
|
|
112
|
+
let current = element;
|
|
113
|
+
while (current instanceof HTMLElement) {
|
|
114
|
+
if (current.scrollWidth > current.clientWidth) {
|
|
115
|
+
const overflowX = window.getComputedStyle(current).overflowX;
|
|
116
|
+
if (overflowX === 'auto' || overflowX === 'scroll') {
|
|
117
|
+
return current;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
current = current.parentElement;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* The TYPO3 project - inspiring people to share!
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import Modal from '@typo3/backend/modal';
|
|
14
|
+
import Modal, { Sizes } from '@typo3/backend/modal';
|
|
15
15
|
import '@typo3/backend/element/icon-element';
|
|
16
16
|
import '@typo3/backend/live-search/element/hint';
|
|
17
17
|
import '@typo3/backend/live-search/element/result/result-pagination';
|
|
@@ -105,7 +105,7 @@ class LiveSearch {
|
|
|
105
105
|
content: url.toString(),
|
|
106
106
|
title: coreLabels.get('labels.search'),
|
|
107
107
|
severity: SeverityEnum.notice,
|
|
108
|
-
size:
|
|
108
|
+
size: Sizes.large,
|
|
109
109
|
ajaxCallback: (): void => {
|
|
110
110
|
const liveSearchContainer = modal.querySelector('typo3-backend-live-search');
|
|
111
111
|
const searchForm = liveSearchContainer.querySelector('form');
|
package/backend/modal.ts
CHANGED
|
@@ -46,6 +46,30 @@ export enum Sizes {
|
|
|
46
46
|
expand = 'expand',
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export enum Size {
|
|
50
|
+
small = 'small',
|
|
51
|
+
default = 'default',
|
|
52
|
+
medium = 'medium',
|
|
53
|
+
large = 'large',
|
|
54
|
+
full = 'full',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type SizeConfig = { width?: Size; height?: Size };
|
|
58
|
+
|
|
59
|
+
const isValidSize = (value: unknown): value is Sizes | SizeConfig => {
|
|
60
|
+
if (typeof value === 'string') {
|
|
61
|
+
return value in Sizes;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value !== 'object' || value === null) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const config = value as SizeConfig;
|
|
67
|
+
return Object.keys(value).length > 0
|
|
68
|
+
&& Object.keys(value).every(key => key === 'width' || key === 'height')
|
|
69
|
+
&& (config.width === undefined || config.width in Size)
|
|
70
|
+
&& (config.height === undefined || config.height in Size);
|
|
71
|
+
};
|
|
72
|
+
|
|
49
73
|
export enum Positions {
|
|
50
74
|
center = 'center',
|
|
51
75
|
top = 'top',
|
|
@@ -89,7 +113,7 @@ export interface Configuration {
|
|
|
89
113
|
severity: SeverityEnum;
|
|
90
114
|
buttons: Array<Button>;
|
|
91
115
|
style: Styles;
|
|
92
|
-
size: Sizes;
|
|
116
|
+
size: Sizes | SizeConfig;
|
|
93
117
|
position: Positions;
|
|
94
118
|
additionalCssClasses: Array<string>;
|
|
95
119
|
callback: ModalCallbackFunction | null;
|
|
@@ -110,7 +134,6 @@ export class ModalElement extends LitElement {
|
|
|
110
134
|
@property({ type: String, reflect: true }) type: Types = Types.default;
|
|
111
135
|
@property({ type: String, reflect: true }) severity: SeverityEnum = SeverityEnum.notice;
|
|
112
136
|
@property({ type: String, reflect: true }) variant: Styles = Styles.default;
|
|
113
|
-
@property({ type: String, reflect: true }) size: Sizes = Sizes.default;
|
|
114
137
|
@property({ type: String, reflect: true }) position: Positions = Positions.center;
|
|
115
138
|
@property({ type: Boolean }) staticBackdrop: boolean = false;
|
|
116
139
|
@property({ type: Boolean }) hideCloseButton: boolean = false;
|
|
@@ -128,6 +151,7 @@ export class ModalElement extends LitElement {
|
|
|
128
151
|
public userData: { [key: string]: any } = {};
|
|
129
152
|
|
|
130
153
|
private readonly uniqueId: number;
|
|
154
|
+
#size: Sizes | SizeConfig = Sizes.default;
|
|
131
155
|
|
|
132
156
|
constructor() {
|
|
133
157
|
super();
|
|
@@ -135,6 +159,43 @@ export class ModalElement extends LitElement {
|
|
|
135
159
|
this.uniqueId = ++uniqueIdCounter;
|
|
136
160
|
}
|
|
137
161
|
|
|
162
|
+
@property({
|
|
163
|
+
reflect: true,
|
|
164
|
+
converter: {
|
|
165
|
+
toAttribute: (value: Sizes | SizeConfig): string =>
|
|
166
|
+
typeof value === 'string' ? value : JSON.stringify(value),
|
|
167
|
+
fromAttribute: (value: string | null): unknown => {
|
|
168
|
+
if (value === null) {
|
|
169
|
+
return Sizes.default;
|
|
170
|
+
}
|
|
171
|
+
if (isValidSize(value)) {
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(value);
|
|
176
|
+
} catch {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
get size(): Sizes | SizeConfig {
|
|
183
|
+
return this.#size;
|
|
184
|
+
}
|
|
185
|
+
set size(value: unknown) {
|
|
186
|
+
const valid = isValidSize(value);
|
|
187
|
+
this.#size = valid ? value : Sizes.default;
|
|
188
|
+
if (!valid && this.isConnected) {
|
|
189
|
+
// Input was invalid and got sanitized: force the attribute to match,
|
|
190
|
+
// because Lit suppresses attribute reflection on the attribute → property
|
|
191
|
+
// path, which would otherwise leave the invalid string in the DOM.
|
|
192
|
+
const expected = typeof this.#size === 'string' ? this.#size : JSON.stringify(this.#size);
|
|
193
|
+
if (this.getAttribute('size') !== expected) {
|
|
194
|
+
this.setAttribute('size', expected);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
138
199
|
public setContent(content: TemplateResult | JQuery | Element | DocumentFragment): void {
|
|
139
200
|
this.templateResultContent = content;
|
|
140
201
|
}
|
|
@@ -201,7 +262,7 @@ export class ModalElement extends LitElement {
|
|
|
201
262
|
`modal-type-${this.type}`,
|
|
202
263
|
`modal-style-${this.variant}`,
|
|
203
264
|
`modal-severity-${Severity.getCssClass(this.severity)}`,
|
|
204
|
-
|
|
265
|
+
...this.getSizeClasses(),
|
|
205
266
|
`modal-position-${this.position}`,
|
|
206
267
|
...this.additionalCssClasses,
|
|
207
268
|
]);
|
|
@@ -236,6 +297,20 @@ export class ModalElement extends LitElement {
|
|
|
236
297
|
`;
|
|
237
298
|
}
|
|
238
299
|
|
|
300
|
+
private getSizeClasses(): string[] {
|
|
301
|
+
if (typeof this.size === 'string') {
|
|
302
|
+
return [`modal-size-${this.size}`];
|
|
303
|
+
}
|
|
304
|
+
const classes: string[] = [];
|
|
305
|
+
if (this.size.width !== undefined) {
|
|
306
|
+
classes.push(`modal-width-${this.size.width}`);
|
|
307
|
+
}
|
|
308
|
+
if (this.size.height !== undefined) {
|
|
309
|
+
classes.push(`modal-height-${this.size.height}`);
|
|
310
|
+
}
|
|
311
|
+
return classes;
|
|
312
|
+
}
|
|
313
|
+
|
|
239
314
|
private handleDialogClose(): void {
|
|
240
315
|
this.trigger('typo3-modal-hidden');
|
|
241
316
|
}
|
|
@@ -599,7 +674,7 @@ class Modal {
|
|
|
599
674
|
? configuration.severity
|
|
600
675
|
: this.defaultConfiguration.severity;
|
|
601
676
|
configuration.buttons = <Array<Button>>configuration.buttons || this.defaultConfiguration.buttons;
|
|
602
|
-
configuration.size =
|
|
677
|
+
configuration.size = isValidSize(configuration.size)
|
|
603
678
|
? configuration.size
|
|
604
679
|
: this.defaultConfiguration.size;
|
|
605
680
|
configuration.style = typeof configuration.style === 'string' && configuration.style in Styles
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { topLevelModuleImport } from '@typo3/backend/utility/top-level-module-import';
|
|
15
15
|
import { html } from 'lit';
|
|
16
|
-
import Modal from '@typo3/backend/modal';
|
|
16
|
+
import Modal, { Size } from '@typo3/backend/modal';
|
|
17
17
|
import { SeverityEnum } from '@typo3/backend/enum/severity';
|
|
18
18
|
import backendLayoutLabels from '~labels/backend.layout';
|
|
19
19
|
import type { PageWizardConfiguration } from '@typo3/backend/page-wizard/page-wizard-configuration';
|
|
@@ -25,7 +25,10 @@ export const openPageWizardModal = async(configuration?: PageWizardConfiguration
|
|
|
25
25
|
title: backendLayoutLabels.get('newPage'),
|
|
26
26
|
content: html`<typo3-backend-page-wizard .configuration="${configuration}"></typo3-backend-page-wizard>`,
|
|
27
27
|
severity: SeverityEnum.notice,
|
|
28
|
-
size:
|
|
28
|
+
size: {
|
|
29
|
+
width: Size.medium,
|
|
30
|
+
height: Size.large
|
|
31
|
+
},
|
|
29
32
|
staticBackdrop: true,
|
|
30
33
|
buttons: []
|
|
31
34
|
});
|
|
@@ -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;
|
|
@@ -72,7 +72,7 @@ class SetupModule {
|
|
|
72
72
|
if (typeof evt.data.objectGroup === 'undefined') {
|
|
73
73
|
throw new Error('No object group defined for message');
|
|
74
74
|
}
|
|
75
|
-
const avatarMatches = evt.data.objectGroup.match(/avatar-(.+)$/);
|
|
75
|
+
const avatarMatches = evt.data.objectGroup.match(/(?:^|-)avatar-(.+)$/);
|
|
76
76
|
if (avatarMatches === null) {
|
|
77
77
|
// Received message isn't provisioned for current InlineControlContainer instance
|
|
78
78
|
return;
|
|
@@ -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> {
|