typo3-typescript-sources 14.3.4 → 14.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.last-synced-tag +1 -1
  2. package/README.md +1 -1
  3. package/backend/drag-uploader.ts +21 -4
  4. package/backend/element/editable-page-title.ts +3 -1
  5. package/backend/element/status-indicator-element.ts +93 -0
  6. package/backend/form-engine/container/inline-control-container.ts +7 -0
  7. package/backend/form-engine/element/slug-element.ts +13 -5
  8. package/backend/form-engine/field-wizard/shortcut-validation.ts +61 -0
  9. package/backend/form-engine/inline-relation/ajax-dispatcher.ts +5 -0
  10. package/backend/image-manipulation.ts +1 -1
  11. package/backend/layout-module/sticky-language-header.ts +186 -0
  12. package/backend/live-search/element/result/item/item.ts +2 -0
  13. package/backend/live-search/element/result/result-detail-container.ts +14 -1
  14. package/backend/module-docheader.ts +58 -0
  15. package/backend/page-wizard/steps/position-step.ts +5 -0
  16. package/backend/settings/type/stringlist.ts +7 -0
  17. package/backend/tests/settings/type/stringlist-test.ts +90 -0
  18. package/backend/toolbar/system-information-menu.ts +1 -0
  19. package/backend/tree/page-position-select.ts +25 -0
  20. package/backend/wizard/finisher/event-finisher.ts +62 -0
  21. package/backend/wizard/wizard.ts +99 -3
  22. package/dashboard/dashboard-wizard.ts +86 -0
  23. package/dashboard/dashboard.ts +28 -76
  24. package/dashboard/wizard/finisher/dashboard-wizard-submission-service.ts +43 -0
  25. package/dashboard/wizard/steps/preset-step.ts +164 -0
  26. package/dashboard/wizard/steps/title-step.ts +90 -0
  27. package/form/backend/form-editor/core.ts +22 -9
  28. package/form/backend/form-editor/inspector-component.ts +61 -38
  29. package/form/backend/form-editor/stage-component.ts +2 -2
  30. package/form/backend/form-wizard/form-wizard.ts +1 -0
  31. package/form/backend/form-wizard/steps/settings-step.ts +0 -10
  32. package/install/router.ts +1 -0
  33. package/package.json +2 -2
  34. package/redirects/event-handler.ts +3 -3
  35. package/scheduler/scheduler.ts +2 -2
  36. package/workspaces/renderable/record-table.ts +1 -1
  37. package/workspaces/workspace-state.ts +1 -1
@@ -0,0 +1,164 @@
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 { live } from 'lit/directives/live.js';
16
+ import { Task, TaskStatus } from '@lit/task';
17
+ import type { WizardStepInterface } from '@typo3/backend/wizard/steps/wizard-step-interface';
18
+ import type { WizardStepValueInterface } from '@typo3/backend/wizard/steps/wizard-step-value-interface';
19
+ import type { WizardStepSummaryInterface } from '@typo3/backend/wizard/steps/wizard-step-summary-interface';
20
+ import type { WizardStepAfterRenderInterface } from '@typo3/backend/wizard/steps/wizard-step-after-render-interface';
21
+ import type { SummaryItem } from '@typo3/backend/wizard/steps/summary-item-interface';
22
+ import AjaxRequest from '@typo3/core/ajax/ajax-request';
23
+ import type { DashboardWizardContext } from '@typo3/dashboard/dashboard-wizard';
24
+ import type { DashboardPresetInterface } from '@typo3/dashboard/dashboard';
25
+ import labels from '~labels/dashboard.messages';
26
+
27
+ export class PresetStep implements WizardStepInterface, WizardStepValueInterface, WizardStepSummaryInterface, WizardStepAfterRenderInterface {
28
+ readonly key = 'preset';
29
+ readonly title = labels.get('dashboard.wizard.preset.title');
30
+ readonly autoAdvance = true;
31
+
32
+ private task: Task<[], DashboardPresetInterface[]>;
33
+ private selectedPreset: string | null = null;
34
+ private hasDispatchedAutoAdvance: boolean = false;
35
+
36
+ constructor(private readonly context: DashboardWizardContext) {
37
+ this.initPresetsTask();
38
+ }
39
+
40
+ public isComplete(): boolean {
41
+ return this.getValue() !== null;
42
+ }
43
+
44
+ public render(): TemplateResult {
45
+ if (this.task.status === TaskStatus.INITIAL) {
46
+ this.task.run();
47
+ }
48
+
49
+ return this.task.render({
50
+ complete: (presets: DashboardPresetInterface[]) => {
51
+ let shouldAutoAdvance = false;
52
+
53
+ // Initialize from store, otherwise preselect the first preset
54
+ if (this.getValue() === null) {
55
+ const storedValue = this.context.getStoreData(this.key);
56
+ if (storedValue != null) {
57
+ this.setValue(storedValue);
58
+ } else if (presets.length > 0) {
59
+ this.setValue(presets[0].identifier);
60
+ shouldAutoAdvance = presets.length === 1;
61
+ }
62
+ }
63
+
64
+ if (shouldAutoAdvance && !this.hasDispatchedAutoAdvance) {
65
+ this.hasDispatchedAutoAdvance = true;
66
+ this.context.dispatchAutoAdvance();
67
+ return this.context.wizard.renderLoader();
68
+ }
69
+
70
+ return html`
71
+ <div class="dashboard-wizard-preset">
72
+ <h2 class="h4">${labels.get('dashboard.wizard.preset.headline')}</h2>
73
+ <div class="form-check-card-container">
74
+ ${presets.map((preset: DashboardPresetInterface) => html`
75
+ <div class="form-check form-check-type-card">
76
+ <input
77
+ class="form-check-input"
78
+ type="radio"
79
+ name="preset"
80
+ id="preset-${preset.identifier}"
81
+ value="${preset.identifier}"
82
+ .checked="${live(this.getValue() === preset.identifier)}"
83
+ @change="${() => this.setValue(preset.identifier)}"
84
+ >
85
+ <label class="form-check-label" for="preset-${preset.identifier}">
86
+ <span class="form-check-label-header form-check-label-header-inherit">
87
+ <typo3-backend-icon identifier="${preset.icon}" size="small"></typo3-backend-icon>
88
+ ${preset.title}
89
+ </span>
90
+ <span class="form-check-label-body">${preset.description}</span>
91
+ </label>
92
+ </div>
93
+ `)}
94
+ </div>
95
+ </div>
96
+ `;
97
+ },
98
+ error: (error: unknown) => this.context.wizard.renderError(labels.get('dashboard.wizard.preset.loadError'), error),
99
+ pending: () => this.context.wizard.renderLoader()
100
+ });
101
+ }
102
+
103
+ public reset(): void {
104
+ this.selectedPreset = null;
105
+ this.context.clearStoreData(this.key);
106
+ this.initPresetsTask();
107
+ }
108
+
109
+ public getValue(): string {
110
+ return this.selectedPreset;
111
+ }
112
+
113
+ public setValue(value: string): void {
114
+ this.selectedPreset = value;
115
+ this.context.wizard.requestUpdate();
116
+ }
117
+
118
+ public beforeAdvance(): void {
119
+ this.context.setStoreData(this.key, this.getValue());
120
+ }
121
+
122
+ public getSummaryData(): SummaryItem[] {
123
+ const selectedIdentifier = this.context.getStoreData(this.key);
124
+ if (!selectedIdentifier || !this.task.value) {
125
+ return [];
126
+ }
127
+
128
+ const selectedPreset = this.task.value.find((preset: DashboardPresetInterface) => preset.identifier === selectedIdentifier);
129
+ if (!selectedPreset) {
130
+ return [];
131
+ }
132
+
133
+ return [{
134
+ label: this.title,
135
+ value: html`
136
+ <typo3-backend-icon identifier="${selectedPreset.icon}" size="small" class="me-1"></typo3-backend-icon>
137
+ ${selectedPreset.title}
138
+ `
139
+ }];
140
+ }
141
+
142
+ public async afterRender(): Promise<void> {
143
+ // Presets load asynchronously; signal readiness so the wizard moves focus
144
+ // (onto the selected preset) only once they are rendered.
145
+ try {
146
+ await this.task.taskComplete;
147
+ } catch {
148
+ // ignore — the error state is rendered by the step
149
+ }
150
+ }
151
+
152
+ private initPresetsTask(): void {
153
+ this.task = new Task(this.context.wizard, {
154
+ task: async (): Promise<DashboardPresetInterface[]> => {
155
+ const response = await new AjaxRequest(TYPO3.settings.ajaxUrls.dashboard_presets_get).get({ cache: 'no-cache' });
156
+ const data = await response.resolve();
157
+ return Object.values(data as Record<string, DashboardPresetInterface>).filter((preset: DashboardPresetInterface) => preset.showInWizard);
158
+ },
159
+ autoRun: false
160
+ });
161
+ }
162
+ }
163
+
164
+ export default PresetStep;
@@ -0,0 +1,90 @@
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 { live } from 'lit/directives/live.js';
16
+ import type { WizardStepInterface } from '@typo3/backend/wizard/steps/wizard-step-interface';
17
+ import type { WizardStepValueInterface } from '@typo3/backend/wizard/steps/wizard-step-value-interface';
18
+ import type { WizardStepSummaryInterface } from '@typo3/backend/wizard/steps/wizard-step-summary-interface';
19
+ import type { SummaryItem } from '@typo3/backend/wizard/steps/summary-item-interface';
20
+ import type { DashboardWizardContext } from '@typo3/dashboard/dashboard-wizard';
21
+ import labels from '~labels/dashboard.messages';
22
+
23
+ export class TitleStep implements WizardStepInterface, WizardStepValueInterface, WizardStepSummaryInterface {
24
+ readonly key = 'title';
25
+ readonly title = labels.get('dashboard.wizard.title.title');
26
+ readonly autoAdvance = false;
27
+
28
+ private titleValue: string = '';
29
+
30
+ constructor(private readonly context: DashboardWizardContext) {
31
+ }
32
+
33
+ public isComplete(): boolean {
34
+ return this.getValue().trim() !== '';
35
+ }
36
+
37
+ public render(): TemplateResult {
38
+ if (this.titleValue === '') {
39
+ this.titleValue = this.context.getStoreData(this.key) ?? '';
40
+ }
41
+
42
+ return html`
43
+ <div class="dashboard-wizard-title">
44
+ <div class="form-group">
45
+ <label class="form-label" for="dashboard-wizard-title-input">${labels.get('dashboard.title')}</label>
46
+ <input
47
+ type="text"
48
+ id="dashboard-wizard-title-input"
49
+ class="form-control"
50
+ required="required"
51
+ .value="${live(this.titleValue)}"
52
+ @input="${(event: InputEvent) => this.setValue((event.target as HTMLInputElement).value)}"
53
+ >
54
+ </div>
55
+ </div>
56
+ `;
57
+ }
58
+
59
+ public reset(): void {
60
+ this.titleValue = '';
61
+ this.context.clearStoreData(this.key);
62
+ }
63
+
64
+ public getValue(): string {
65
+ return this.titleValue;
66
+ }
67
+
68
+ public setValue(value: string): void {
69
+ this.titleValue = value;
70
+ this.context.wizard.requestUpdate();
71
+ }
72
+
73
+ public beforeAdvance(): void {
74
+ this.context.setStoreData(this.key, this.getValue());
75
+ }
76
+
77
+ public getSummaryData(): SummaryItem[] {
78
+ const title = this.context.getStoreData(this.key);
79
+ if (!title) {
80
+ return [];
81
+ }
82
+
83
+ return [{
84
+ label: this.title,
85
+ value: title
86
+ }];
87
+ }
88
+ }
89
+
90
+ export default TitleStep;
@@ -720,17 +720,30 @@ function extendModel<D extends object, T extends ModelData<D>>(
720
720
  modelToExtend.set(pathPrefix, modelExtension, disablePublishersOnSet);
721
721
  } else {
722
722
  const _modelExtension = { ...modelExtension } as Record<string, T | [] | Record<string, never>>;
723
- for (const key of Object.keys(_modelExtension)) {
724
- const path = (pathPrefix === '') ? key : pathPrefix + '.' + key;
725
723
 
726
- modelToExtend.on(path, 'core/formElement/somePropertyChanged');
724
+ // A "leaf map" is an object whose values are all scalars (e.g. select
725
+ // options or finisher recipients keyed by their value). Its keys may
726
+ // contain dots (e.g. email addresses used as recipient keys) and must
727
+ // therefore be stored as a whole, otherwise Model.set() would split the
728
+ // key into a nested object path and thus corrupt the data.
729
+ const isLeafMap = pathPrefix !== '' && Object.values(_modelExtension).every(
730
+ (value) => value === null || typeof value !== 'object'
731
+ );
727
732
 
728
- if (_modelExtension[key] !== null && (typeof (_modelExtension[key]) === 'object' || Array.isArray(_modelExtension[key]))) {
729
- extendModel(modelToExtend, _modelExtension[key], path, disablePublishersOnSet);
730
- } else if (pathPrefix === 'properties.options') {
731
- modelToExtend.set(pathPrefix, modelExtension, disablePublishersOnSet);
732
- } else {
733
- modelToExtend.set(path, _modelExtension[key], disablePublishersOnSet);
733
+ if (isLeafMap) {
734
+ modelToExtend.on(pathPrefix, 'core/formElement/somePropertyChanged');
735
+ modelToExtend.set(pathPrefix, modelExtension, disablePublishersOnSet);
736
+ } else {
737
+ for (const key of Object.keys(_modelExtension)) {
738
+ const path = (pathPrefix === '') ? key : pathPrefix + '.' + key;
739
+
740
+ modelToExtend.on(path, 'core/formElement/somePropertyChanged');
741
+
742
+ if (_modelExtension[key] !== null && (typeof (_modelExtension[key]) === 'object' || Array.isArray(_modelExtension[key]))) {
743
+ extendModel(modelToExtend, _modelExtension[key], path, disablePublishersOnSet);
744
+ } else {
745
+ modelToExtend.set(path, _modelExtension[key], disablePublishersOnSet);
746
+ }
734
747
  }
735
748
  }
736
749
  }
@@ -22,6 +22,7 @@ import Icons from '@typo3/backend/icons';
22
22
  import Modal from '@typo3/backend/modal';
23
23
  import { MessageUtility } from '@typo3/backend/utility/message-utility';
24
24
  import Sortable from 'sortablejs';
25
+ import dompurify from 'dompurify';
25
26
  import {
26
27
  type PropertyGridEditorEntry,
27
28
  PropertyGridEditorUpdateEvent
@@ -158,6 +159,37 @@ function getFormElementDefinition<T extends keyof FormElementDefinition>(
158
159
  return getFormEditorApp().getFormElementDefinition(formElement, formElementDefinitionKey);
159
160
  }
160
161
 
162
+ const DOMPURIFY_LABEL_CONFIG = {
163
+ ALLOWED_TAGS: ['abbr', 'b', 'br', 'code', 'em', 'i', 'kbd', 'span', 'strong', 'u'],
164
+ ALLOWED_ATTR: ['class', 'title', 'role'],
165
+ };
166
+
167
+ const DOMPURIFY_DESCRIPTION_CONFIG = {
168
+ ALLOWED_TAGS: ['a', 'abbr', 'blockquote', 'b', 'br', 'code', 'em', 'i', 'kbd', 'li', 'p', 'pre', 'span', 'strong', 'u', 'ul', 'ol'],
169
+ ALLOWED_ATTR: ['class', 'href', 'title', 'target', 'role', 'rel'],
170
+ };
171
+
172
+ /**
173
+ * Safely sets sanitized HTML as label content on a DOM element.
174
+ * Only phrasing content (inline elements) is allowed, as required by the HTML
175
+ * specification for <label> elements.
176
+ */
177
+ function setLabelHtmlContent(element: HTMLElement | null | undefined, html: string): void {
178
+ if (element) {
179
+ element.innerHTML = dompurify.sanitize(html, DOMPURIFY_LABEL_CONFIG);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Safely sets sanitized HTML as description content on a DOM element.
185
+ * Allows limited block-level markup (e.g. <p>, <ul>) in addition to inline elements.
186
+ */
187
+ function setHtmlContent(element: HTMLElement | null | undefined, html: string): void {
188
+ if (element) {
189
+ element.innerHTML = dompurify.sanitize(html, DOMPURIFY_DESCRIPTION_CONFIG);
190
+ }
191
+ }
192
+
161
193
  /**
162
194
  * @publish view/inspector/editor/insert/perform
163
195
  */
@@ -853,7 +885,7 @@ export function renderCollectionElementSelectionEditor(
853
885
  collectionContainer?.replaceChildren();
854
886
 
855
887
  const labelEl = getHelper().getTemplatePropertyElement('label', editorHtml);
856
- if (labelEl) { labelEl.append(document.createTextNode(editorConfiguration.label)); }
888
+ setLabelHtmlContent(labelEl, editorConfiguration.label);
857
889
  const selectElement = getHelper().getTemplatePropertyElement('selectOptions', editorHtml) as HTMLSelectElement | null;
858
890
  const hasAlreadySelectedCollectionElements = (
859
891
  !getUtility().isUndefinedOrNull(alreadySelectedCollectionElements) &&
@@ -897,7 +929,7 @@ export function renderCollectionElementSelectionEditor(
897
929
  selectGroup?.remove();
898
930
  const labelNoSelect = getHelper().getTemplatePropertyElement('label-no-select', editorHtml);
899
931
  if (hasAlreadySelectedCollectionElements) {
900
- if (labelNoSelect) { labelNoSelect.textContent = editorConfiguration.label; }
932
+ setLabelHtmlContent(labelNoSelect, editorConfiguration.label);
901
933
  } else {
902
934
  labelNoSelect?.remove();
903
935
  }
@@ -1047,7 +1079,7 @@ export function renderCollectionElementHeaderEditor(
1047
1079
  const panelTitle = getHelper().getTemplatePropertyElement('panel-title', editorHtml);
1048
1080
  if (panelTitle) {
1049
1081
  panelTitle.removeAttribute('data-template-property');
1050
- panelTitle.append(document.createTextNode(editorConfiguration.label));
1082
+ setLabelHtmlContent(panelTitle, editorConfiguration.label);
1051
1083
  }
1052
1084
  }
1053
1085
  }
@@ -1075,7 +1107,7 @@ export function renderFileMaxSizeEditor(
1075
1107
  if (editorConfiguration.label) {
1076
1108
  const element = getHelper().getTemplatePropertyElement('label', editorHtml);
1077
1109
  const maximumFileSize = element?.getAttribute(getHelper().getDomElementDataAttribute('maximumFileSize'));
1078
- element?.append(document.createTextNode(editorConfiguration.label.replace('{0}', maximumFileSize ?? '')));
1110
+ setLabelHtmlContent(element, editorConfiguration.label.replace('{0}', maximumFileSize ?? ''));
1079
1111
  }
1080
1112
  }
1081
1113
 
@@ -1106,8 +1138,7 @@ export function renderTextEditor(
1106
1138
  1475421056
1107
1139
  );
1108
1140
 
1109
- getHelper().getTemplatePropertyElement('label', editorHtml)
1110
- ?.append(document.createTextNode(editorConfiguration.label));
1141
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1111
1142
  renderDescription(editorConfiguration, editorHtml);
1112
1143
 
1113
1144
  if (getUtility().isNonEmptyString(editorConfiguration.placeholder)) {
@@ -1194,8 +1225,7 @@ export function renderValidationErrorMessageEditor(
1194
1225
  1489874124
1195
1226
  );
1196
1227
 
1197
- getHelper().getTemplatePropertyElement('label', editorHtml)
1198
- ?.append(document.createTextNode(editorConfiguration.label));
1228
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1199
1229
  renderDescription(editorConfiguration, editorHtml);
1200
1230
 
1201
1231
  const propertyPath = getFormEditorApp().buildPropertyPath(editorConfiguration.propertyPath);
@@ -1254,8 +1284,7 @@ export function renderCountrySelectEditor(
1254
1284
  collectionName
1255
1285
  );
1256
1286
 
1257
- getHelper().getTemplatePropertyElement('label', editorHtml)
1258
- ?.append(document.createTextNode(editorConfiguration.label));
1287
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1259
1288
  renderDescription(editorConfiguration, editorHtml);
1260
1289
 
1261
1290
  const selectElement = getHelper().getTemplatePropertyElement('selectOptions', editorHtml) as HTMLSelectElement | null;
@@ -1326,8 +1355,7 @@ export function renderSingleSelectEditor(
1326
1355
  collectionName
1327
1356
  );
1328
1357
 
1329
- getHelper().getTemplatePropertyElement('label', editorHtml)
1330
- ?.append(document.createTextNode(editorConfiguration.label));
1358
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1331
1359
  renderDescription(editorConfiguration, editorHtml);
1332
1360
 
1333
1361
  const selectElement = getHelper().getTemplatePropertyElement('selectOptions', editorHtml) as HTMLSelectElement | null;
@@ -1386,8 +1414,7 @@ export function renderMultiSelectEditor(
1386
1414
  collectionName
1387
1415
  );
1388
1416
 
1389
- getHelper().getTemplatePropertyElement('label', editorHtml)
1390
- ?.append(document.createTextNode(editorConfiguration.label));
1417
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1391
1418
  renderDescription(editorConfiguration, editorHtml);
1392
1419
 
1393
1420
  const selectElement = getHelper().getTemplatePropertyElement('selectOptions', editorHtml) as HTMLSelectElement | null;
@@ -1457,8 +1484,7 @@ export function renderGridColumnViewPortConfigurationEditor(
1457
1484
  return;
1458
1485
  }
1459
1486
 
1460
- getHelper().getTemplatePropertyElement('label', editorHtml)
1461
- ?.append(document.createTextNode(editorConfiguration.label));
1487
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1462
1488
 
1463
1489
  const viewportButtonSel = getHelper().getDomElementDataIdentifierSelector('viewportButton');
1464
1490
  const viewportButtonTemplate = editorHtml.querySelector(viewportButtonSel)?.cloneNode(true) as HTMLElement | null;
@@ -1480,15 +1506,15 @@ export function renderGridColumnViewPortConfigurationEditor(
1480
1506
 
1481
1507
  const labelEl = getHelper().getTemplatePropertyElement('numbersOfColumnsToUse-label', numbersOfColumnsTemplateClone);
1482
1508
  if (labelEl) {
1483
- labelEl.append(document.createTextNode(
1509
+ setLabelHtmlContent(labelEl,
1484
1510
  editorConfiguration.configurationOptions.numbersOfColumnsToUse.label
1485
1511
  .replace('{@viewPortLabel}', element.dataset.viewPortLabel ?? '')
1486
- ));
1512
+ );
1487
1513
  }
1488
1514
 
1489
1515
  const descEl = getHelper().getTemplatePropertyElement('numbersOfColumnsToUse-description', numbersOfColumnsTemplateClone);
1490
1516
  if (descEl) {
1491
- descEl.append(document.createTextNode(editorConfiguration.configurationOptions.numbersOfColumnsToUse.description));
1517
+ setHtmlContent(descEl, editorConfiguration.configurationOptions.numbersOfColumnsToUse.description);
1492
1518
  }
1493
1519
 
1494
1520
  const propertyPath = editorConfiguration.configurationOptions.numbersOfColumnsToUse.propertyPath
@@ -1579,8 +1605,7 @@ export function renderPropertyGridEditor(
1579
1605
  1475419232
1580
1606
  );
1581
1607
 
1582
- getHelper().getTemplatePropertyElement('label', editorHtml)
1583
- ?.append(document.createTextNode(editorConfiguration.label));
1608
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1584
1609
  renderDescription(editorConfiguration, editorHtml);
1585
1610
 
1586
1611
  const propertyPathPrefix = (() => {
@@ -1740,8 +1765,7 @@ export function renderRequiredValidatorEditor(
1740
1765
  );
1741
1766
 
1742
1767
  const validatorIdentifier = editorConfiguration.validatorIdentifier;
1743
- getHelper().getTemplatePropertyElement('label', editorHtml)
1744
- ?.append(document.createTextNode(editorConfiguration.label));
1768
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1745
1769
 
1746
1770
  let propertyValue: string;
1747
1771
  let propertyPath: string;
@@ -1767,11 +1791,15 @@ export function renderRequiredValidatorEditor(
1767
1791
  const validationErrorMessageTemplateClone = validationErrorMessageTemplate?.cloneNode(true) as HTMLElement | null;
1768
1792
  getEditorWrapperDomElement(editorHtml)?.after(validationErrorMessageTemplateClone);
1769
1793
 
1770
- getHelper().getTemplatePropertyElement('validationErrorMessage-label', validationErrorMessageTemplateClone)
1771
- ?.append(document.createTextNode(editorConfiguration.configurationOptions.validationErrorMessage.label));
1794
+ setLabelHtmlContent(
1795
+ getHelper().getTemplatePropertyElement('validationErrorMessage-label', validationErrorMessageTemplateClone),
1796
+ editorConfiguration.configurationOptions.validationErrorMessage.label
1797
+ );
1772
1798
 
1773
- getHelper().getTemplatePropertyElement('validationErrorMessage-description', validationErrorMessageTemplateClone)
1774
- ?.append(document.createTextNode(editorConfiguration.configurationOptions.validationErrorMessage.description));
1799
+ setHtmlContent(
1800
+ getHelper().getTemplatePropertyElement('validationErrorMessage-description', validationErrorMessageTemplateClone),
1801
+ editorConfiguration.configurationOptions.validationErrorMessage.description
1802
+ );
1775
1803
 
1776
1804
  propertyData = getCurrentlySelectedFormElement().get(validationErrorMessagePropertyPath);
1777
1805
  if (getUtility().isUndefinedOrNull(propertyData)) {
@@ -1863,8 +1891,7 @@ export function renderCheckboxEditor(
1863
1891
  1476218674
1864
1892
  );
1865
1893
 
1866
- getHelper().getTemplatePropertyElement('label', editorHtml)
1867
- ?.append(document.createTextNode(editorConfiguration.label));
1894
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1868
1895
  renderDescription(editorConfiguration, editorHtml);
1869
1896
 
1870
1897
  const propertyPath = getFormEditorApp()
@@ -1927,8 +1954,7 @@ export function renderTextareaEditor(
1927
1954
  const propertyPath = getFormEditorApp()
1928
1955
  .buildPropertyPath(editorConfiguration.propertyPath, collectionElementIdentifier, collectionName);
1929
1956
 
1930
- getHelper().getTemplatePropertyElement('label', editorHtml)
1931
- ?.append(document.createTextNode(editorConfiguration.label));
1957
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
1932
1958
  renderDescription(editorConfiguration, editorHtml);
1933
1959
 
1934
1960
  const propertyData = getCurrentlySelectedFormElement().get(propertyPath);
@@ -2023,10 +2049,8 @@ export function renderTypo3WinBrowserEditor(
2023
2049
  1477300590
2024
2050
  );
2025
2051
 
2026
- getHelper().getTemplatePropertyElement('label', editorHtml)
2027
- ?.append(document.createTextNode(editorConfiguration.label));
2028
- getHelper().getTemplatePropertyElement('buttonLabel', editorHtml)
2029
- ?.append(document.createTextNode(editorConfiguration.buttonLabel));
2052
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label);
2053
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('buttonLabel', editorHtml), editorConfiguration.buttonLabel);
2030
2054
  renderDescription(editorConfiguration, editorHtml);
2031
2055
 
2032
2056
  const formEl = editorHtml.querySelector('form');
@@ -2222,8 +2246,7 @@ export function renderDateEditor(
2222
2246
  collectionName
2223
2247
  );
2224
2248
 
2225
- getHelper().getTemplatePropertyElement('label', editorHtml)
2226
- ?.append(document.createTextNode(editorConfiguration.label || ''));
2249
+ setLabelHtmlContent(getHelper().getTemplatePropertyElement('label', editorHtml), editorConfiguration.label || '');
2227
2250
  renderDescription(editorConfiguration, editorHtml);
2228
2251
 
2229
2252
  const editorElement = editorHtml.querySelector('typo3-form-date-editor');
@@ -2266,7 +2289,7 @@ export function renderDescription(
2266
2289
  ): void {
2267
2290
  const descEl = getHelper().getTemplatePropertyElement('description', editorHtml);
2268
2291
  if (getUtility().isNonEmptyString(editorConfiguration.description)) {
2269
- if (descEl) { descEl.textContent = editorConfiguration.description; }
2292
+ setHtmlContent(descEl, editorConfiguration.description);
2270
2293
  } else {
2271
2294
  descEl?.remove();
2272
2295
  }
@@ -273,8 +273,8 @@ function renderNestedSortableListItem(formElement: FormElement): HTMLElement {
273
273
  const childFormElements = formElement.get('renderables');
274
274
  const hasChildren = Array.isArray(childFormElements) && childFormElements.length > 0;
275
275
 
276
- // Show "Create new element" placeholder when the container (page or composite) is empty.
277
- if (!hasChildren) {
276
+ // Show "Create new element" placeholder only for composite containers (not SummaryPage).
277
+ if (!hasChildren && isCompositeFormElement) {
278
278
  childList.append(createNewElementPlaceholder(formElement, 'inside'));
279
279
  }
280
280
 
@@ -107,6 +107,7 @@ export class FormWizard extends LitElement {
107
107
  <typo3-backend-wizard .steps="${this.steps}"
108
108
  .submissionService="${this.submissionService}"
109
109
  confirm-button-label="${formManagerLabels.get('formManager.newFormWizard.step1.title')}"
110
+ skip-summary
110
111
  ></typo3-backend-wizard>
111
112
  `;
112
113
  }
@@ -158,7 +158,6 @@ export class SettingsStep implements WizardStepInterface, WizardStepValueInterfa
158
158
  }
159
159
 
160
160
  private renderFormNameInput(): TemplateResult {
161
- this.focusInput('#new-form-name');
162
161
  return html `
163
162
  <div class="form-group">
164
163
  <label class="form-label" for="new-form-name">${formManagerLabels.get('formManager.form_name')}</label>
@@ -234,15 +233,6 @@ export class SettingsStep implements WizardStepInterface, WizardStepValueInterfa
234
233
  template: (currentTemplates[0]?.value ?? '')
235
234
  });
236
235
  }
237
-
238
- private focusInput(selector: string): void {
239
- this.context.wizard.updateComplete.then(() => {
240
- const input = this.context.wizard.renderRoot.querySelector<HTMLInputElement>(selector);
241
- if (input) {
242
- input.focus();
243
- }
244
- });
245
- }
246
236
  }
247
237
 
248
238
  export default SettingsStep;
package/install/router.ts CHANGED
@@ -25,6 +25,7 @@ import RegularEvent from '@typo3/core/event/regular-event';
25
25
  import '@typo3/backend/element/progress-bar-element';
26
26
  import { ScaffoldState } from '@typo3/backend/viewport/scaffold-state';
27
27
  import '@typo3/backend/element/sidebar-toggle-element';
28
+ import 'bootstrap'; // required for panels in `handleAjaxError()`
28
29
 
29
30
  class Router {
30
31
  private readonly rootSelector: string = '.t3js-body';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typo3-typescript-sources",
3
- "version": "14.3.4",
3
+ "version": "14.3.6",
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.3.4",
31
+ "sourceTag": "v14.3.6",
32
32
  "sourcePath": "Build/Sources/TypeScript"
33
33
  }
34
34
  }
@@ -89,9 +89,9 @@ class EventHandler {
89
89
  }
90
90
 
91
91
  private revert(correlationIds: string[]): Promise<AjaxResponse> {
92
- const request = new AjaxRequest(TYPO3.settings.ajaxUrls.redirects_revert_correlation).withQueryArguments({
93
- correlation_ids: correlationIds
94
- }).get();
92
+ const request = new AjaxRequest(TYPO3.settings.ajaxUrls.redirects_revert_correlation).post({
93
+ correlation_ids: correlationIds,
94
+ });
95
95
 
96
96
  request.then(async (response: AjaxResponse): Promise<void> => {
97
97
  const json = await response.resolve();
@@ -90,14 +90,14 @@ class Scheduler {
90
90
  // Schedule selected tasks for next cron run
91
91
  const goCron: HTMLInputElement = document.createElement('input');
92
92
  goCron.setAttribute('type', 'hidden');
93
- goCron.setAttribute('name', 'scheduleCron');
93
+ goCron.setAttribute('name', 'action[scheduleCron]');
94
94
  goCron.setAttribute('value', taskIds.join(','));
95
95
  form.append(goCron);
96
96
  } else {
97
97
  // Execute selected tasks directly
98
98
  const executeTasks: HTMLInputElement = document.createElement('input');
99
99
  executeTasks.setAttribute('type', 'hidden');
100
- executeTasks.setAttribute('name', 'execute');
100
+ executeTasks.setAttribute('name', 'action[execute]');
101
101
  executeTasks.setAttribute('value', taskIds.join(','));
102
102
  form.append(executeTasks);
103
103
  }
@@ -278,7 +278,7 @@ export class RecordTableElement extends LitElement {
278
278
  private renderElementActions(data: RecordData): TemplateResult[] {
279
279
  return [
280
280
  this.getAction(
281
- data.allowedAction_view,
281
+ (data.allowedAction_view && data.previewUrl !== ''),
282
282
  'preview',
283
283
  'actions-version-workspace-preview',
284
284
  {
@@ -30,7 +30,7 @@ export interface Workspace {
30
30
  }
31
31
 
32
32
  interface WorkspaceInfoResponse {
33
- current: Workspace;
33
+ current: Workspace | null;
34
34
  workspaces: Workspace[];
35
35
  }
36
36