typo3-typescript-sources 14.3.7 → 14.3.8
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/.last-synced-tag +1 -1
- package/README.md +1 -1
- package/backend/column-selector-button.ts +5 -2
- package/backend/drag-uploader.ts +78 -86
- package/backend/form-engine/element/online-media-form-element.ts +3 -3
- package/backend/form-engine.ts +8 -12
- package/backend/layout-module/column-restriction.ts +61 -0
- package/backend/layout-module/drag-drop.ts +12 -0
- package/backend/layout-module/paste.ts +7 -0
- package/backend/login-refresh.ts +1 -9
- package/backend/modal.ts +38 -5
- package/backend/notification.ts +90 -6
- package/backend/online-media.ts +1 -3
- package/backend/record-download-button.ts +4 -5
- package/backend/recordlist.ts +5 -0
- package/backend/tests/fixtures/modal-autofocus.html +1 -0
- package/backend/tests/modal-test.ts +91 -0
- package/backend/tests/notification-test.ts +176 -0
- package/backend/tree/page-tree-element.ts +8 -1
- package/dashboard/dashboard.ts +3 -8
- package/filelist/file-list-rename-handler.ts +3 -6
- package/filelist/file-list-replace-handler.ts +3 -6
- package/filelist/file-list.ts +3 -4
- package/form/backend/form-editor/modals-component.ts +114 -5
- package/package.json +2 -2
- package/rte-ckeditor/legacy/ckeditor5-bundle.ts +0 -1
- package/rte-ckeditor/plugin/typo3-link.ts +7 -9
- package/rte-ckeditor/rte-link-browser.ts +1 -1
- package/scheduler/scheduler-add-group.ts +4 -12
- package/workspaces/preview.ts +39 -0
- package/workspaces/renderable/record-table.ts +94 -77
|
@@ -54,6 +54,13 @@ const defaultConfiguration: Partial<HelperConfiguration> = {
|
|
|
54
54
|
rowItem: 'rowItem',
|
|
55
55
|
rowLink: 'rowLink',
|
|
56
56
|
rowsContainer: 'rowsContainer',
|
|
57
|
+
elementIdentifier: 'elementIdentifier',
|
|
58
|
+
elementType: 'elementType',
|
|
59
|
+
validationErrors: 'validationErrors',
|
|
60
|
+
validationErrorGroup: 'validationErrorGroup',
|
|
61
|
+
validationErrorGroupLabel: 'validationErrorGroupLabel',
|
|
62
|
+
validationErrorGroupItems: 'validationErrorGroupItems',
|
|
63
|
+
validationError: 'validationError',
|
|
57
64
|
templateInsertElements: 'Modal-InsertElements',
|
|
58
65
|
templateInsertPages: 'Modal-InsertPages',
|
|
59
66
|
templateValidationErrors: 'Modal-ValidationErrors'
|
|
@@ -206,7 +213,7 @@ function _validationErrorsModalSetup(
|
|
|
206
213
|
modalContent: DocumentFragment,
|
|
207
214
|
validationResults: ValidationResultsRecursive
|
|
208
215
|
): void {
|
|
209
|
-
let formElement, newRowItem;
|
|
216
|
+
let formElement: FormElement, newRowItem: HTMLElement | null;
|
|
210
217
|
|
|
211
218
|
assert(
|
|
212
219
|
Array.isArray(validationResults),
|
|
@@ -235,13 +242,70 @@ function _validationErrorsModalSetup(
|
|
|
235
242
|
formElement = getFormEditorApp()
|
|
236
243
|
.getFormElementByIdentifierPath(validationResults[i].formElementIdentifierPath);
|
|
237
244
|
newRowItem = rowItemTemplate?.cloneNode(true) as HTMLElement | null;
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
if (newRowItem) {
|
|
246
|
+
const rowLink = newRowItem;
|
|
240
247
|
rowLink.setAttribute(
|
|
241
248
|
getHelper().getDomElementDataAttribute('elementIdentifier'),
|
|
242
249
|
validationResults[i].formElementIdentifierPath
|
|
243
250
|
);
|
|
244
|
-
rowLink.
|
|
251
|
+
const rowLinkTitle = rowLink.querySelector('.form-editor-validation-error-title');
|
|
252
|
+
rowLinkTitle?.replaceChildren(_buildTitleByFormElement(formElement));
|
|
253
|
+
rowLink.querySelector(
|
|
254
|
+
getHelper().getDomElementDataIdentifierSelector('elementIdentifier')
|
|
255
|
+
)?.replaceChildren(document.createTextNode(formElement.get('identifier')));
|
|
256
|
+
rowLink.querySelector(
|
|
257
|
+
getHelper().getDomElementDataIdentifierSelector('elementType')
|
|
258
|
+
)?.replaceChildren(document.createTextNode(
|
|
259
|
+
getFormElementDefinition(formElement, 'label') || formElement.get('type')
|
|
260
|
+
));
|
|
261
|
+
const validationErrors = rowLink.querySelector(
|
|
262
|
+
getHelper().getDomElementDataIdentifierSelector('validationErrors')
|
|
263
|
+
);
|
|
264
|
+
const validationErrorGroupTemplate = validationErrors?.querySelector(
|
|
265
|
+
getHelper().getDomElementDataIdentifierSelector('validationErrorGroup')
|
|
266
|
+
);
|
|
267
|
+
const validationErrorTemplate = validationErrors?.querySelector(
|
|
268
|
+
getHelper().getDomElementDataIdentifierSelector('validationError')
|
|
269
|
+
);
|
|
270
|
+
validationErrors?.replaceChildren();
|
|
271
|
+
const validationErrorGroups = new Map<string, HTMLElement>();
|
|
272
|
+
validationResults[i].validationResults.forEach((propertyValidationResult) => {
|
|
273
|
+
const propertyLabel = _getPropertyLabelByPath(formElement, propertyValidationResult.propertyPath);
|
|
274
|
+
const finisherIndex = _getFinisherIndexByPropertyPath(propertyValidationResult.propertyPath);
|
|
275
|
+
const validatorIdentifier = _getValidatorIdentifierByPropertyPath(formElement, propertyValidationResult.propertyPath);
|
|
276
|
+
const groupLabel = finisherIndex
|
|
277
|
+
? _getFinisherLabelByIdentifier(formElement, finisherIndex)
|
|
278
|
+
: validatorIdentifier;
|
|
279
|
+
const groupKey = finisherIndex
|
|
280
|
+
? `finisher-${finisherIndex}`
|
|
281
|
+
: `validator-${validatorIdentifier}`;
|
|
282
|
+
const validationMessage = propertyValidationResult.validationResults.join(' ');
|
|
283
|
+
if (groupLabel && validationErrorGroupTemplate) {
|
|
284
|
+
let validationErrorGroup = validationErrorGroups.get(groupKey);
|
|
285
|
+
if (!validationErrorGroup) {
|
|
286
|
+
validationErrorGroup = validationErrorGroupTemplate.cloneNode(true) as HTMLElement;
|
|
287
|
+
validationErrorGroup.querySelector(
|
|
288
|
+
getHelper().getDomElementDataIdentifierSelector('validationErrorGroupLabel')
|
|
289
|
+
)?.replaceChildren(document.createTextNode(groupLabel));
|
|
290
|
+
validationErrorGroup.querySelector(
|
|
291
|
+
getHelper().getDomElementDataIdentifierSelector('validationErrorGroupItems')
|
|
292
|
+
)?.replaceChildren();
|
|
293
|
+
validationErrorGroups.set(groupKey, validationErrorGroup);
|
|
294
|
+
validationErrors?.append(validationErrorGroup);
|
|
295
|
+
}
|
|
296
|
+
const validationError = validationErrorTemplate?.cloneNode() as HTMLElement | undefined;
|
|
297
|
+
validationError?.replaceChildren(document.createTextNode(
|
|
298
|
+
propertyLabel ? `${propertyLabel}: ${validationMessage}` : validationMessage
|
|
299
|
+
));
|
|
300
|
+
validationErrorGroup.querySelector(
|
|
301
|
+
getHelper().getDomElementDataIdentifierSelector('validationErrorGroupItems')
|
|
302
|
+
)?.append(validationError);
|
|
303
|
+
} else {
|
|
304
|
+
const validationError = validationErrorTemplate?.cloneNode() as HTMLElement | undefined;
|
|
305
|
+
validationError?.replaceChildren(document.createTextNode(propertyLabel ? `${propertyLabel}: ${validationMessage}` : validationMessage));
|
|
306
|
+
validationErrors?.append(validationError);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
245
309
|
}
|
|
246
310
|
const rowsContainer = modalContent.querySelector(getHelper().getDomElementDataIdentifierSelector('rowsContainer'));
|
|
247
311
|
if (rowsContainer && newRowItem) {
|
|
@@ -251,7 +315,8 @@ function _validationErrorsModalSetup(
|
|
|
251
315
|
}
|
|
252
316
|
|
|
253
317
|
modalContent.querySelectorAll<HTMLAnchorElement>('a').forEach((a) => {
|
|
254
|
-
a.addEventListener('click', function() {
|
|
318
|
+
a.addEventListener('click', function(event: MouseEvent) {
|
|
319
|
+
event.preventDefault();
|
|
255
320
|
getPublisherSubscriber().publish('view/modal/validationErrors/element/clicked', [
|
|
256
321
|
a.getAttribute(getHelper().getDomElementDataAttribute('elementIdentifier'))
|
|
257
322
|
]);
|
|
@@ -272,6 +337,50 @@ function _buildTitleByFormElement(formElement: FormElement): HTMLElement {
|
|
|
272
337
|
return span;
|
|
273
338
|
}
|
|
274
339
|
|
|
340
|
+
function _getPropertyLabelByPath(formElement: FormElement, propertyPath: string): string {
|
|
341
|
+
return _getEditorConfigurationByPropertyPath(formElement, propertyPath)?.label || '';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function _getValidatorIdentifierByPropertyPath(formElement: FormElement, propertyPath: string): string {
|
|
345
|
+
const editorConfiguration = _getEditorConfigurationByPropertyPath(formElement, propertyPath);
|
|
346
|
+
const validatorIdentifier = editorConfiguration?.propertyValidators?.find((identifier) => identifier !== 'NotEmpty');
|
|
347
|
+
return validatorIdentifier || '';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function _getEditorConfigurationByPropertyPath(
|
|
351
|
+
formElement: FormElement,
|
|
352
|
+
propertyPath: string
|
|
353
|
+
): FormElementDefinition['editors'][number] | undefined {
|
|
354
|
+
const formElementDefinition = getFormElementDefinition(formElement, undefined) as FormElementDefinition;
|
|
355
|
+
const editors = [
|
|
356
|
+
...(formElementDefinition.editors || []),
|
|
357
|
+
...Object.values(formElementDefinition.propertyCollections || {}).flatMap((collection) =>
|
|
358
|
+
collection.flatMap((collectionElement) => collectionElement.editors || [])
|
|
359
|
+
)
|
|
360
|
+
];
|
|
361
|
+
return editors.find((editorConfiguration) =>
|
|
362
|
+
editorConfiguration.propertyPath === propertyPath
|
|
363
|
+
|| propertyPath.endsWith(`.${editorConfiguration.propertyPath}`)
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function _getFinisherIndexByPropertyPath(propertyPath: string): string {
|
|
368
|
+
const match = propertyPath.match(/^finishers\.(\d+)\./);
|
|
369
|
+
if (!match) {
|
|
370
|
+
return '';
|
|
371
|
+
}
|
|
372
|
+
return match[1];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function _getFinisherLabelByIdentifier(formElement: FormElement, finisherIndex: string): string {
|
|
376
|
+
const finishers = formElement.get('finishers') as Array<{ identifier: string }> | undefined;
|
|
377
|
+
const finisherIdentifier = finishers?.[Number(finisherIndex)]?.identifier;
|
|
378
|
+
if (!finisherIdentifier) {
|
|
379
|
+
return '';
|
|
380
|
+
}
|
|
381
|
+
return getFormEditorApp().getFormEditorDefinition('finishers', finisherIdentifier)?.label || '';
|
|
382
|
+
}
|
|
383
|
+
|
|
275
384
|
/* *************************************************************
|
|
276
385
|
* Public Methods
|
|
277
386
|
* ************************************************************/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typo3-typescript-sources",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.8",
|
|
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.
|
|
31
|
+
"sourceTag": "v14.3.8",
|
|
32
32
|
"sourcePath": "Build/Sources/TypeScript"
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -12,6 +12,5 @@ export * as Widget from '@ckeditor/ckeditor5-widget';
|
|
|
12
12
|
|
|
13
13
|
// single or prefixed exports
|
|
14
14
|
export { WordCount } from '@ckeditor/ckeditor5-word-count';
|
|
15
|
-
export { LinkUtils } from '@ckeditor/ckeditor5-link';
|
|
16
15
|
|
|
17
16
|
console.warn('The module "@typo3/ckeditor5-bundle.js" has been marked as deprecated. Please use "@ckeditor/ckeditor5-*" modules directly.');
|
|
@@ -5,11 +5,9 @@ import * as Typing from '@ckeditor/ckeditor5-typing';
|
|
|
5
5
|
import * as Widget from '@ckeditor/ckeditor5-widget';
|
|
6
6
|
import * as Utils from '@ckeditor/ckeditor5-utils';
|
|
7
7
|
import * as Link from '@ckeditor/ckeditor5-link';
|
|
8
|
-
import { LinkUtils } from '@ckeditor/ckeditor5-link';
|
|
9
8
|
import { default as modalObject, type ModalElement } from '@typo3/backend/modal';
|
|
10
9
|
import type { ViewAttributeElement, ViewElement, ModelSchema, ModelWriter } from '@ckeditor/ckeditor5-engine';
|
|
11
|
-
import type { GeneralHtmlSupport, DataFilter } from '@ckeditor/ckeditor5-html-support';
|
|
12
|
-
import type { GHSViewAttributes } from '@ckeditor/ckeditor5-html-support/src/utils';
|
|
10
|
+
import type { GeneralHtmlSupport, DataFilter, GHSViewAttributes } from '@ckeditor/ckeditor5-html-support';
|
|
13
11
|
import { IconLink, IconPencil, IconUnlink } from '@ckeditor/ckeditor5-icons';
|
|
14
12
|
|
|
15
13
|
export const LINK_ALLOWED_ATTRIBUTES = ['href', 'title', 'class', 'target', 'rel', 'download'];
|
|
@@ -54,7 +52,7 @@ export class Typo3LinkCommand extends Core.Command {
|
|
|
54
52
|
|
|
55
53
|
// A check for any integration that allows linking elements (e.g. `LinkImage`).
|
|
56
54
|
// Currently, the selection reads attributes from text nodes only. See #7429 and #7465.
|
|
57
|
-
const sourceSelection =
|
|
55
|
+
const sourceSelection = Link.isLinkableElement(selectedElement, model.schema) ? selectedElement : selection;
|
|
58
56
|
if (sourceSelection === selectedElement) {
|
|
59
57
|
this.value = selectedElement.getAttribute('linkHref') as string;
|
|
60
58
|
this.isEnabled = model.schema.checkAttribute(selectedElement, 'linkHref');
|
|
@@ -227,7 +225,7 @@ export class Typo3UnlinkCommand extends Core.Command {
|
|
|
227
225
|
const selection = model.document.selection;
|
|
228
226
|
const selectedElement = selection.getSelectedElement();
|
|
229
227
|
|
|
230
|
-
if (
|
|
228
|
+
if (Link.isLinkableElement(selectedElement, model.schema)) {
|
|
231
229
|
this.isEnabled = model.schema.checkAttribute(selectedElement, 'linkHref');
|
|
232
230
|
} else {
|
|
233
231
|
this.isEnabled = model.schema.checkAttributeInSelection(selection, 'linkHref');
|
|
@@ -457,7 +455,7 @@ export class Typo3LinkUI extends Core.Plugin {
|
|
|
457
455
|
const t = editor.t;
|
|
458
456
|
|
|
459
457
|
// Handle the `Ctrl+K` keystroke and show the panel.
|
|
460
|
-
editor.keystrokes.set(
|
|
458
|
+
editor.keystrokes.set(Link._LINK_KEYSTROKE, (keyEvtData, cancel) => {
|
|
461
459
|
// Prevent focusing the search bar in FF, Chrome and Edge. See https://github.com/ckeditor/ckeditor5/issues/4811.
|
|
462
460
|
cancel();
|
|
463
461
|
if (linkCommand.isEnabled) {
|
|
@@ -471,7 +469,7 @@ export class Typo3LinkUI extends Core.Plugin {
|
|
|
471
469
|
linkButton.isEnabled = true;
|
|
472
470
|
linkButton.label = t('Link');
|
|
473
471
|
linkButton.icon = IconLink;
|
|
474
|
-
linkButton.keystroke =
|
|
472
|
+
linkButton.keystroke = Link._LINK_KEYSTROKE;
|
|
475
473
|
linkButton.tooltip = true;
|
|
476
474
|
linkButton.isToggleable = true;
|
|
477
475
|
linkButton.bind('isEnabled').to(linkCommand, 'isEnabled');
|
|
@@ -486,7 +484,7 @@ export class Typo3LinkUI extends Core.Plugin {
|
|
|
486
484
|
const linkCommand = editor.commands.get('link');
|
|
487
485
|
button.bind('isEnabled').to(linkCommand, 'value', href => !!href);
|
|
488
486
|
button.bind('href').to(linkCommand, 'value', href => {
|
|
489
|
-
return href &&
|
|
487
|
+
return href && Link._ensureSafeLinkUrl(href, allowedProtocols);
|
|
490
488
|
});
|
|
491
489
|
|
|
492
490
|
button.icon = undefined;
|
|
@@ -753,7 +751,7 @@ export class Typo3LinkUI extends Core.Plugin {
|
|
|
753
751
|
}
|
|
754
752
|
|
|
755
753
|
private findLinkElementAncestor(position: any) {
|
|
756
|
-
return position.getAncestors().find((ancestor: any) =>
|
|
754
|
+
return position.getAncestors().find((ancestor: any) => Link.isLinkElement(ancestor));
|
|
757
755
|
}
|
|
758
756
|
|
|
759
757
|
/**
|
|
@@ -16,7 +16,7 @@ import Modal from '@typo3/backend/modal';
|
|
|
16
16
|
import RegularEvent from '@typo3/core/event/regular-event';
|
|
17
17
|
import { type Typo3LinkDict, LINK_ALLOWED_ATTRIBUTES, addLinkPrefix } from '@typo3/rte-ckeditor/plugin/typo3-link';
|
|
18
18
|
import type * as Core from '@ckeditor/ckeditor5-core';
|
|
19
|
-
import type { ModelPosition } from '@ckeditor/ckeditor5-engine
|
|
19
|
+
import type { ModelPosition } from '@ckeditor/ckeditor5-engine';
|
|
20
20
|
import '@typo3/backend/element/combobox-element';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -37,7 +37,7 @@ class SchedulerAddGroups {
|
|
|
37
37
|
element.addEventListener('click', (button: MouseEvent) => {
|
|
38
38
|
button.preventDefault();
|
|
39
39
|
const content: TemplateResult = html`
|
|
40
|
-
<form name="scheduler-create-group" @submit=${this.createGroup}>
|
|
40
|
+
<form id="scheduler-create-group" name="scheduler-create-group" @submit=${this.createGroup}>
|
|
41
41
|
<label class="form-label" for="actionCreateGroup">Group name</label>
|
|
42
42
|
<input class="form-control" id="actionCreateGroup" required="" name="action[createGroup]" autofocus type="text">
|
|
43
43
|
<div class="form-text mt-3">
|
|
@@ -46,7 +46,7 @@ class SchedulerAddGroups {
|
|
|
46
46
|
</form>
|
|
47
47
|
`;
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Modal.advanced({
|
|
50
50
|
content: content,
|
|
51
51
|
title: labels.get('function.group.add'),
|
|
52
52
|
size: Modal.sizes.small,
|
|
@@ -57,21 +57,13 @@ class SchedulerAddGroups {
|
|
|
57
57
|
btnClass: 'btn-default',
|
|
58
58
|
name: 'cancel'
|
|
59
59
|
},{
|
|
60
|
-
trigger: (): void => {
|
|
61
|
-
const form: HTMLFormElement = Modal.currentModal.querySelector('form[name="scheduler-create-group"]');
|
|
62
|
-
form.requestSubmit();
|
|
63
|
-
},
|
|
64
60
|
text: labels.get('button.group.modalOk'),
|
|
65
61
|
btnClass: 'btn-primary',
|
|
66
|
-
name: 'ok'
|
|
62
|
+
name: 'ok',
|
|
63
|
+
form: 'scheduler-create-group'
|
|
67
64
|
}
|
|
68
65
|
]
|
|
69
66
|
});
|
|
70
|
-
|
|
71
|
-
modal.addEventListener('typo3-modal-shown', (): void => {
|
|
72
|
-
const input: HTMLInputElement = Modal.currentModal.querySelector('input[name="action[createGroup]"]');
|
|
73
|
-
input.focus();
|
|
74
|
-
});
|
|
75
67
|
});
|
|
76
68
|
}
|
|
77
69
|
}
|
package/workspaces/preview.ts
CHANGED
|
@@ -30,6 +30,7 @@ enum Identifiers {
|
|
|
30
30
|
workspaceView = '.t3js-workspace-view-workspace',
|
|
31
31
|
sendToStageAction = '[data-action="send-to-stage"]',
|
|
32
32
|
discardAction = '[data-action="discard"]',
|
|
33
|
+
publishAction = '[data-action="publish"]',
|
|
33
34
|
stageButtonsContainer = '.t3js-stage-buttons',
|
|
34
35
|
previewModeContainer = '.t3js-preview-mode',
|
|
35
36
|
activePreviewMode = '.t3js-active-preview-mode',
|
|
@@ -78,6 +79,7 @@ class Preview extends Workspaces {
|
|
|
78
79
|
this.resizeViews();
|
|
79
80
|
}, 50).bindTo(window);
|
|
80
81
|
new RegularEvent('click', this.renderDiscardWindow.bind(this)).delegateTo(document, Identifiers.discardAction);
|
|
82
|
+
new RegularEvent('click', this.renderPublishWindow.bind(this)).delegateTo(document, Identifiers.publishAction);
|
|
81
83
|
new RegularEvent('click', this.renderSendPageToStageWindow.bind(this)).delegateTo(document, Identifiers.sendToStageAction);
|
|
82
84
|
new RegularEvent('click', () => {
|
|
83
85
|
window.top.document.querySelectorAll('.t3js-workspace-recipient:not([disabled])').forEach((element: HTMLInputElement) => {
|
|
@@ -166,6 +168,43 @@ class Preview extends Workspaces {
|
|
|
166
168
|
});
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
private renderPublishWindow(): void {
|
|
172
|
+
const modal = Modal.confirm(
|
|
173
|
+
labels.get('window.publishAll.title'),
|
|
174
|
+
labels.get('window.publishAll.message'),
|
|
175
|
+
SeverityEnum.warning,
|
|
176
|
+
[
|
|
177
|
+
{
|
|
178
|
+
text: labels.get('cancel'),
|
|
179
|
+
active: true,
|
|
180
|
+
btnClass: 'btn-default',
|
|
181
|
+
name: 'cancel',
|
|
182
|
+
trigger: (): void => {
|
|
183
|
+
modal.hideModal();
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
text: labels.get('ok'),
|
|
188
|
+
btnClass: 'btn-warning',
|
|
189
|
+
name: 'ok',
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
);
|
|
193
|
+
modal.addEventListener('button.clicked', (e: Event): void => {
|
|
194
|
+
if ((e.target as HTMLButtonElement).name === 'ok') {
|
|
195
|
+
this.sendRemoteRequest([
|
|
196
|
+
this.generateRemotePayloadBody('publishPageDirectly', [TYPO3.settings.Workspaces.id]),
|
|
197
|
+
this.generateRemotePayloadBody('updateStageChangeButtons', [TYPO3.settings.Workspaces.id]),
|
|
198
|
+
], Identifiers.topbar).then(async (response: AjaxResponse): Promise<void> => {
|
|
199
|
+
modal.hideModal();
|
|
200
|
+
this.renderStageButtons((await response.resolve())[1].result);
|
|
201
|
+
// Reloading live view IFRAME
|
|
202
|
+
this.elements.workspaceView.setAttribute('src', this.elements.workspaceView.getAttribute('src'));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
169
208
|
/**
|
|
170
209
|
* Renders the "send page to stage" window
|
|
171
210
|
*/
|
|
@@ -32,6 +32,15 @@ export type AdditionalColumnValue = {
|
|
|
32
32
|
url?: string
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
export type ActionData = {
|
|
36
|
+
group?: string,
|
|
37
|
+
enabled?: boolean,
|
|
38
|
+
visible?: boolean,
|
|
39
|
+
icon?: string,
|
|
40
|
+
title?: string,
|
|
41
|
+
url?: string
|
|
42
|
+
};
|
|
43
|
+
|
|
35
44
|
export type RecordData = {
|
|
36
45
|
table: string,
|
|
37
46
|
id: string,
|
|
@@ -76,7 +85,8 @@ export type RecordData = {
|
|
|
76
85
|
status: string,
|
|
77
86
|
messages: string
|
|
78
87
|
},
|
|
79
|
-
additional?: Record<string, AdditionalColumnValue
|
|
88
|
+
additional?: Record<string, AdditionalColumnValue>,
|
|
89
|
+
actions?: Record<string, ActionData>
|
|
80
90
|
};
|
|
81
91
|
|
|
82
92
|
@customElement('typo3-workspaces-record-table')
|
|
@@ -280,10 +290,7 @@ export class RecordTableElement extends LitElement {
|
|
|
280
290
|
` : nothing}</td>
|
|
281
291
|
<td>${data.label_Stage}</td>
|
|
282
292
|
${Object.keys(this.additionalColumns).map((identifier: string) => this.renderAdditionalColumn(data, identifier))}
|
|
283
|
-
<td class="col-control nowrap">
|
|
284
|
-
<div class="btn-group">${this.renderElementActions(data)}</div>
|
|
285
|
-
<div class="btn-group">${this.renderVersioningActions(data)}</div>
|
|
286
|
-
</td>
|
|
293
|
+
<td class="col-control nowrap">${this.renderActions(data)}</td>
|
|
287
294
|
</tr>
|
|
288
295
|
`;
|
|
289
296
|
}
|
|
@@ -308,42 +315,94 @@ export class RecordTableElement extends LitElement {
|
|
|
308
315
|
`;
|
|
309
316
|
}
|
|
310
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The action buttons of a row, rendered as one button group per group declared by the server.
|
|
320
|
+
* Which actions exist is decided there, the module only knows how to present its own ones.
|
|
321
|
+
*/
|
|
322
|
+
protected renderActions(data: RecordData): TemplateResult[] {
|
|
323
|
+
const groups = new Map<string, TemplateResult[]>();
|
|
324
|
+
|
|
325
|
+
for (const [ identifier, action ] of Object.entries(data.actions ?? {})) {
|
|
326
|
+
if (action.visible === false) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const group = action.group ?? 'custom';
|
|
330
|
+
if (!groups.has(group)) {
|
|
331
|
+
groups.set(group, []);
|
|
332
|
+
}
|
|
333
|
+
groups.get(group).push(this.renderAction(identifier, action, data));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return Array.from(groups.values()).map((actions: TemplateResult[]) => html`
|
|
337
|
+
<div class="btn-group">${actions}</div>
|
|
338
|
+
`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
protected renderAction(identifier: string, action: ActionData, data: RecordData): TemplateResult {
|
|
342
|
+
const enabled = action.enabled !== false;
|
|
343
|
+
|
|
344
|
+
if (identifier === 'qrcode') {
|
|
345
|
+
return this.getQrCodeAction(data, enabled);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const defaults = this.getActionDefaults(identifier, data, enabled);
|
|
349
|
+
const attributes: Record<string, string> = { ...defaults.attributes };
|
|
350
|
+
const title = action.title || defaults.title;
|
|
351
|
+
if (title !== '') {
|
|
352
|
+
attributes.title = title;
|
|
353
|
+
}
|
|
354
|
+
const icon = action.icon || defaults.icon;
|
|
355
|
+
|
|
356
|
+
if (action.url && enabled) {
|
|
357
|
+
return html`
|
|
358
|
+
<a class="btn btn-default" href=${action.url} title=${ifDefined(attributes.title)}>
|
|
359
|
+
<typo3-backend-icon identifier=${IconHelper.getIconIdentifier(icon || 'empty-empty')} size="small"></typo3-backend-icon>
|
|
360
|
+
</a>
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return this.getAction(enabled, identifier, icon, attributes);
|
|
365
|
+
}
|
|
366
|
+
|
|
311
367
|
protected renderIndent(level: number) {
|
|
312
368
|
return html`<span class="indent indent-inline-block" style="--indent-level: ${level}"></span>`;
|
|
313
369
|
}
|
|
314
370
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Icon, title and attributes of the actions the module handles itself. Actions of third party
|
|
373
|
+
* extensions are unknown here and are presented with what their payload provides.
|
|
374
|
+
*/
|
|
375
|
+
private getActionDefaults(identifier: string, data: RecordData, enabled: boolean): { icon: string, title: string, attributes: Record<string, string> } {
|
|
376
|
+
switch (identifier) {
|
|
377
|
+
case 'preview':
|
|
378
|
+
return { icon: 'actions-version-workspace-preview', title: labels.get('tooltip.viewElementAction'), attributes: {} };
|
|
379
|
+
case 'open':
|
|
380
|
+
return { icon: 'actions-open', title: labels.get('tooltip.editElementAction'), attributes: {} };
|
|
381
|
+
case 'version':
|
|
382
|
+
return { icon: 'actions-version-page-open', title: labels.get('tooltip.openPage'), attributes: {} };
|
|
383
|
+
case 'expand':
|
|
384
|
+
return {
|
|
385
|
+
icon: data.expanded ? 'actions-caret-down' : 'actions-caret-right',
|
|
386
|
+
title: labels.get('tooltip.expand'),
|
|
387
|
+
attributes: {
|
|
388
|
+
'data-bs-target': '[data-collection="' + data.Workspaces_CollectionCurrent + '"]',
|
|
389
|
+
'aria-expanded': !enabled || data.expanded ? 'true' : 'false',
|
|
390
|
+
'data-bs-toggle': 'collapse',
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
case 'changes':
|
|
394
|
+
return { icon: 'actions-document-info', title: labels.get('tooltip.showChanges'), attributes: {} };
|
|
395
|
+
case 'publish':
|
|
396
|
+
return { icon: 'actions-version-swap-version', title: labels.get('tooltip.publish'), attributes: {} };
|
|
397
|
+
case 'remove':
|
|
398
|
+
return { icon: 'actions-delete', title: labels.get('tooltip.discardVersion'), attributes: {} };
|
|
399
|
+
default:
|
|
400
|
+
return { icon: '', title: '', attributes: {} };
|
|
401
|
+
}
|
|
343
402
|
}
|
|
344
403
|
|
|
345
|
-
private getQrCodeAction(data: RecordData): TemplateResult {
|
|
346
|
-
if (!data.previewUrl) {
|
|
404
|
+
private getQrCodeAction(data: RecordData, enabled: boolean): TemplateResult {
|
|
405
|
+
if (!enabled || !data.previewUrl) {
|
|
347
406
|
return html`
|
|
348
407
|
<button type="button" class="btn btn-default" disabled>
|
|
349
408
|
<typo3-backend-icon identifier="empty-empty" size="small"></typo3-backend-icon>
|
|
@@ -361,48 +420,6 @@ export class RecordTableElement extends LitElement {
|
|
|
361
420
|
`;
|
|
362
421
|
}
|
|
363
422
|
|
|
364
|
-
private renderVersioningActions(data: RecordData): TemplateResult[] {
|
|
365
|
-
const hasSubitems = data.Workspaces_CollectionChildren > 0 && data.Workspaces_CollectionCurrent !== '';
|
|
366
|
-
|
|
367
|
-
return [
|
|
368
|
-
this.getAction(
|
|
369
|
-
hasSubitems,
|
|
370
|
-
'expand',
|
|
371
|
-
(data.expanded ? 'actions-caret-down' : 'actions-caret-right'),
|
|
372
|
-
{
|
|
373
|
-
'title': labels.get('tooltip.expand'),
|
|
374
|
-
'data-bs-target': '[data-collection="' + data.Workspaces_CollectionCurrent + '"]',
|
|
375
|
-
'aria-expanded': !hasSubitems || data.expanded ? 'true' : 'false',
|
|
376
|
-
'data-bs-toggle': 'collapse',
|
|
377
|
-
}
|
|
378
|
-
),
|
|
379
|
-
this.getAction(
|
|
380
|
-
data.hasChanges,
|
|
381
|
-
'changes',
|
|
382
|
-
'actions-document-info',
|
|
383
|
-
{
|
|
384
|
-
'title': labels.get('tooltip.showChanges')
|
|
385
|
-
}
|
|
386
|
-
),
|
|
387
|
-
this.getAction(
|
|
388
|
-
data.allowedAction_publish && data.Workspaces_CollectionParent === '',
|
|
389
|
-
'publish',
|
|
390
|
-
'actions-version-swap-version',
|
|
391
|
-
{
|
|
392
|
-
'title': labels.get('tooltip.publish')
|
|
393
|
-
}
|
|
394
|
-
),
|
|
395
|
-
this.getAction(
|
|
396
|
-
data.allowedAction_delete,
|
|
397
|
-
'remove',
|
|
398
|
-
'actions-delete',
|
|
399
|
-
{
|
|
400
|
-
'title': labels.get('tooltip.discardVersion')
|
|
401
|
-
}
|
|
402
|
-
)
|
|
403
|
-
];
|
|
404
|
-
}
|
|
405
|
-
|
|
406
423
|
/**
|
|
407
424
|
* Renders the action button based on the user's permission.
|
|
408
425
|
*
|