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
package/.last-synced-tag
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v14.3.
|
|
1
|
+
v14.3.8
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install typo3-typescript-sources
|
|
|
16
16
|
|
|
17
17
|
- **Original Repository:** [TYPO3/typo3](https://github.com/TYPO3/typo3)
|
|
18
18
|
- **Source Path:** `Build/Sources/TypeScript/`
|
|
19
|
-
- **Current TYPO3 Version:** `v14.3.
|
|
19
|
+
- **Current TYPO3 Version:** `v14.3.8`
|
|
20
20
|
|
|
21
21
|
## 🌿 Branches
|
|
22
22
|
|
|
@@ -160,7 +160,7 @@ export class ColumnSelectorButton extends PseudoButtonLitElement {
|
|
|
160
160
|
text: this.buttonOk,
|
|
161
161
|
btnClass: 'btn-primary',
|
|
162
162
|
name: 'update',
|
|
163
|
-
|
|
163
|
+
form: 'columnSelectorForm',
|
|
164
164
|
}
|
|
165
165
|
],
|
|
166
166
|
ajaxCallback: (): void => this.handleModalContentLoaded(modal)
|
|
@@ -198,7 +198,10 @@ export class ColumnSelectorButton extends PseudoButtonLitElement {
|
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
// Prevent the form from being submitted as the form data will be send via an ajax request
|
|
201
|
-
form.addEventListener('submit', (e: Event): void => {
|
|
201
|
+
form.addEventListener('submit', (e: Event): void => {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
this.processSelection(currentModal);
|
|
204
|
+
});
|
|
202
205
|
|
|
203
206
|
const columns: NodeListOf<HTMLInputElement> = currentModal.querySelectorAll(Selectors.columnsSelector);
|
|
204
207
|
const columnsFilter: HTMLInputElement = currentModal.querySelector(Selectors.columnsFilterSelector);
|
package/backend/drag-uploader.ts
CHANGED
|
@@ -371,7 +371,21 @@ export default class DragUploader {
|
|
|
371
371
|
* @param {FileList} files
|
|
372
372
|
*/
|
|
373
373
|
public processFiles(files: FileList): void {
|
|
374
|
-
|
|
374
|
+
const validFiles: File[] = [];
|
|
375
|
+
for (const file of Array.from(files)) {
|
|
376
|
+
const error = this.validateFile(file);
|
|
377
|
+
if (error === null) {
|
|
378
|
+
validFiles.push(file);
|
|
379
|
+
} else {
|
|
380
|
+
Notification.error(coreLabels.get('file_upload.notUploaded.title'), error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
this.fileInput.value = '';
|
|
384
|
+
if (validFiles.length === 0) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.queueLength = validFiles.length;
|
|
375
389
|
|
|
376
390
|
if (this.fileList.parentElement.hasAttribute('hidden')) {
|
|
377
391
|
// Show the filelist (table)
|
|
@@ -385,11 +399,11 @@ export default class DragUploader {
|
|
|
385
399
|
this.overallProgressBar = document.createElement('typo3-backend-progress-bar');
|
|
386
400
|
document.body.appendChild(this.overallProgressBar);
|
|
387
401
|
this.overallProgressBar.start();
|
|
388
|
-
this.percentagePerFile = 1 /
|
|
402
|
+
this.percentagePerFile = 1 / validFiles.length;
|
|
389
403
|
|
|
390
404
|
// Check for each file if is already exist before adding it to the queue
|
|
391
405
|
const ajaxCalls: Promise<void>[] = [];
|
|
392
|
-
|
|
406
|
+
validFiles.forEach((file: File) => {
|
|
393
407
|
const request = new AjaxRequest(TYPO3.settings.ajaxUrls.file_exists).withQueryArguments({
|
|
394
408
|
fileName: file.name,
|
|
395
409
|
fileTarget: this.target,
|
|
@@ -418,8 +432,6 @@ export default class DragUploader {
|
|
|
418
432
|
this.overallProgressBar.done();
|
|
419
433
|
}
|
|
420
434
|
});
|
|
421
|
-
|
|
422
|
-
this.fileInput.value = '';
|
|
423
435
|
}
|
|
424
436
|
|
|
425
437
|
public fileInDropzone = (): void => {
|
|
@@ -631,6 +643,37 @@ export default class DragUploader {
|
|
|
631
643
|
document.dispatchEvent(new CustomEvent(uploadFinishedEventName));
|
|
632
644
|
});
|
|
633
645
|
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* @returns the reason why the file cannot be uploaded, null if it can be uploaded
|
|
649
|
+
*/
|
|
650
|
+
private validateFile(file: File): string | null {
|
|
651
|
+
const extension = file.name.split('.').pop().toLowerCase();
|
|
652
|
+
if (this.maxFileSize > 0 && file.size > this.maxFileSize) {
|
|
653
|
+
return coreLabels.get('file_upload.maxFileSizeExceeded', {
|
|
654
|
+
'0': file.name,
|
|
655
|
+
'1': FormatUtility.fileSizeAsString(this.maxFileSize),
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
if (this.fileDenyPattern && file.name.match(this.fileDenyPattern)) {
|
|
659
|
+
return coreLabels.get('file_upload.fileNotAllowed', {
|
|
660
|
+
'0': file.name,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
if (this.filesExtensionsAllowed && !this.filesExtensionsAllowed.split(',').includes(extension)) {
|
|
664
|
+
return coreLabels.get('file_upload.notUploaded.extensionExpected', {
|
|
665
|
+
'0': file.name,
|
|
666
|
+
'1': this.filesExtensionsAllowed.split(',').join(', '),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (this.filesExtensionsDisallowed && this.filesExtensionsDisallowed.split(',').includes(extension)) {
|
|
670
|
+
return coreLabels.get('file_upload.notUploaded.extensionDisallowed', {
|
|
671
|
+
'0': file.name,
|
|
672
|
+
'1': extension,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
634
677
|
}
|
|
635
678
|
|
|
636
679
|
class FileQueueItem {
|
|
@@ -697,72 +740,41 @@ class FileQueueItem {
|
|
|
697
740
|
// set dummy file icon
|
|
698
741
|
this.iconCol.innerHTML = '<typo3-backend-icon identifier="mimetypes-other-other" />';
|
|
699
742
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
'0': this.dragUploader.filesExtensionsAllowed,
|
|
720
|
-
}));
|
|
721
|
-
this.progressBar.value = 100;
|
|
722
|
-
this.progressBar.severity = SeverityEnum.error;
|
|
723
|
-
} else if (this.isProhibitedByDisallowedExtensionsList()) {
|
|
724
|
-
this.updateMessage(coreLabels.get('file_upload.fileExtensionDisallowed', {
|
|
725
|
-
'0': this.dragUploader.filesExtensionsDisallowed,
|
|
726
|
-
}));
|
|
727
|
-
this.progressBar.value = 100;
|
|
728
|
-
this.progressBar.severity = SeverityEnum.error;
|
|
729
|
-
} else {
|
|
730
|
-
this.updateMessage('- ' + FormatUtility.fileSizeAsString(this.file.size));
|
|
731
|
-
|
|
732
|
-
const formData = new FormData();
|
|
733
|
-
formData.append('data[upload][1][target]', this.dragUploader.target);
|
|
734
|
-
formData.append('data[upload][1][data]', '1');
|
|
735
|
-
formData.append('overwriteExistingFiles', this.override);
|
|
736
|
-
formData.append('redirect', '');
|
|
737
|
-
formData.append('upload_1', this.file);
|
|
738
|
-
|
|
739
|
-
// We use XMLHttpRequest as we need the `progress` event which isn't supported by fetch()
|
|
740
|
-
const xhr = new XMLHttpRequest();
|
|
741
|
-
xhr.onreadystatechange = (): void => {
|
|
742
|
-
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
743
|
-
if (xhr.status === 200) {
|
|
744
|
-
try {
|
|
745
|
-
const response = JSON.parse(xhr.responseText);
|
|
746
|
-
if (!response.hasErrors) {
|
|
747
|
-
this.uploadSuccess(response);
|
|
748
|
-
} else {
|
|
749
|
-
this.uploadError(xhr);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
catch {
|
|
753
|
-
// In case JSON can not be parsed, the upload failed due to server errors,
|
|
754
|
-
// e.g. "POST Content-Length exceeds limit". Just handle as upload error.
|
|
743
|
+
this.updateMessage('- ' + FormatUtility.fileSizeAsString(this.file.size));
|
|
744
|
+
|
|
745
|
+
const formData = new FormData();
|
|
746
|
+
formData.append('data[upload][1][target]', this.dragUploader.target);
|
|
747
|
+
formData.append('data[upload][1][data]', '1');
|
|
748
|
+
formData.append('overwriteExistingFiles', this.override);
|
|
749
|
+
formData.append('redirect', '');
|
|
750
|
+
formData.append('upload_1', this.file);
|
|
751
|
+
|
|
752
|
+
// We use XMLHttpRequest as we need the `progress` event which isn't supported by fetch()
|
|
753
|
+
const xhr = new XMLHttpRequest();
|
|
754
|
+
xhr.onreadystatechange = (): void => {
|
|
755
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
756
|
+
if (xhr.status === 200) {
|
|
757
|
+
try {
|
|
758
|
+
const response = JSON.parse(xhr.responseText);
|
|
759
|
+
if (!response.hasErrors) {
|
|
760
|
+
this.uploadSuccess(response);
|
|
761
|
+
} else {
|
|
755
762
|
this.uploadError(xhr);
|
|
756
763
|
}
|
|
757
|
-
}
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
// In case JSON can not be parsed, the upload failed due to server errors,
|
|
767
|
+
// e.g. "POST Content-Length exceeds limit". Just handle as upload error.
|
|
758
768
|
this.uploadError(xhr);
|
|
759
769
|
}
|
|
770
|
+
} else {
|
|
771
|
+
this.uploadError(xhr);
|
|
760
772
|
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
xhr.upload.addEventListener('progress', (e: ProgressEvent) => this.updateProgress(e));
|
|
776
|
+
xhr.open('POST', TYPO3.settings.ajaxUrls.file_process);
|
|
777
|
+
xhr.send(formData);
|
|
766
778
|
}
|
|
767
779
|
|
|
768
780
|
/**
|
|
@@ -925,26 +937,6 @@ class FileQueueItem {
|
|
|
925
937
|
this.row.append(document.createElement('td'));
|
|
926
938
|
}
|
|
927
939
|
}
|
|
928
|
-
|
|
929
|
-
public isProhibitedByAllowedExtensionsList(): boolean {
|
|
930
|
-
if (!this.dragUploader.filesExtensionsAllowed) {
|
|
931
|
-
return false;
|
|
932
|
-
}
|
|
933
|
-
const extension = this.file.name.split('.').pop();
|
|
934
|
-
const allowed = this.dragUploader.filesExtensionsAllowed.split(',');
|
|
935
|
-
|
|
936
|
-
return !allowed.includes(extension.toLowerCase());
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
public isProhibitedByDisallowedExtensionsList(): boolean {
|
|
940
|
-
if (!this.dragUploader.filesExtensionsDisallowed) {
|
|
941
|
-
return false;
|
|
942
|
-
}
|
|
943
|
-
const extension = this.file.name.split('.').pop();
|
|
944
|
-
const disallowed = this.dragUploader.filesExtensionsDisallowed.split(',');
|
|
945
|
-
|
|
946
|
-
return disallowed.includes(extension.toLowerCase());
|
|
947
|
-
}
|
|
948
940
|
}
|
|
949
941
|
|
|
950
942
|
DragUploader.init();
|
|
@@ -30,14 +30,14 @@ export class OnlineMediaFormElement extends LitElement {
|
|
|
30
30
|
|
|
31
31
|
protected override render(): TemplateResult {
|
|
32
32
|
return html`
|
|
33
|
-
<form @submit="${ this.dispatchSubmitEvent }">
|
|
33
|
+
<form id="online-media-form" @submit="${ this.dispatchSubmitEvent }">
|
|
34
34
|
<div class="form-control-wrap">
|
|
35
|
-
<input type="text" class="form-control" name="online-media-url" placeholder="${this.placeholder}" required>
|
|
35
|
+
<input type="text" class="form-control" name="online-media-url" placeholder="${this.placeholder}" required autofocus>
|
|
36
36
|
<div class="form-text">
|
|
37
37
|
${this.allowedExtensionsHelpText}<br>
|
|
38
38
|
<ul class="badge-list">
|
|
39
39
|
${this.allowedExtensions.split(',').map((ext: string) => html`
|
|
40
|
-
<li><span class="badge badge-secondary">${ext.trim().
|
|
40
|
+
<li><span class="badge badge-secondary">${ext.trim().toLowerCase()}</span></li>
|
|
41
41
|
`)}
|
|
42
42
|
</ul>
|
|
43
43
|
</div>
|
package/backend/form-engine.ts
CHANGED
|
@@ -1033,8 +1033,12 @@ export default (function() {
|
|
|
1033
1033
|
};
|
|
1034
1034
|
|
|
1035
1035
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1036
|
+
* Releases the doc header button bar, which is rendered "inert" as long as the
|
|
1037
|
+
* ui block is shown, to prevent unintended operations while FormEngine is loading.
|
|
1038
|
+
* Called after FormEngine initialization is complete.
|
|
1039
|
+
*
|
|
1040
|
+
* The block is applied to the button bar as a whole, never to the single buttons,
|
|
1041
|
+
* so that the disabled state of individual buttons remains untouched.
|
|
1038
1042
|
*/
|
|
1039
1043
|
FormEngine.enableDocHeaderButtons = function(): void {
|
|
1040
1044
|
const docHeaderBar = document.querySelector('.t3js-module-docheader-buttons');
|
|
@@ -1042,16 +1046,8 @@ export default (function() {
|
|
|
1042
1046
|
return;
|
|
1043
1047
|
}
|
|
1044
1048
|
|
|
1045
|
-
|
|
1046
|
-
docHeaderBar.
|
|
1047
|
-
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
|
|
1048
|
-
element.disabled = false;
|
|
1049
|
-
} else if (element instanceof HTMLAnchorElement) {
|
|
1050
|
-
// For anchor elements - remove TYPO3 disabled state
|
|
1051
|
-
element.classList.remove('disabled');
|
|
1052
|
-
element.removeAttribute('aria-disabled');
|
|
1053
|
-
}
|
|
1054
|
-
});
|
|
1049
|
+
docHeaderBar.removeAttribute('inert');
|
|
1050
|
+
docHeaderBar.removeAttribute('aria-busy');
|
|
1055
1051
|
};
|
|
1056
1052
|
|
|
1057
1053
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
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/column-restriction
|
|
16
|
+
*
|
|
17
|
+
* Client-side mirror of the server-side backend layout colPos restriction
|
|
18
|
+
* (\TYPO3\CMS\Backend\View\BackendLayoutView::isCTypeAllowedInColPosByPage()).
|
|
19
|
+
* Used by drag-drop and paste in the page layout module to avoid offering
|
|
20
|
+
* actions that the DataHandler would reject anyway.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function toList(value: string | undefined): string[] {
|
|
26
|
+
return (value ?? '')
|
|
27
|
+
.split(',')
|
|
28
|
+
.map((item: string): string => item.trim())
|
|
29
|
+
.filter((item: string): boolean => item !== '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Evaluates whether the given CType is allowed in the column described by the
|
|
34
|
+
* "data-allowed-content-types" / "data-disallowed-content-types" attributes.
|
|
35
|
+
* Mirrors the server-side logic: disallowed wins, an empty allow list allows all.
|
|
36
|
+
*/
|
|
37
|
+
export function isContentTypeAllowedInColumn(cType: string, columnElement: HTMLElement | null): boolean {
|
|
38
|
+
if (columnElement === null) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const disallowed = toList(columnElement.dataset.disallowedContentTypes);
|
|
43
|
+
if (disallowed.includes(cType)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const allowed = toList(columnElement.dataset.allowedContentTypes);
|
|
48
|
+
if (allowed.length > 0 && !allowed.includes(cType)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves the column of the given element (nearest ancestor carrying a colPos)
|
|
57
|
+
* and evaluates whether the CType is allowed there.
|
|
58
|
+
*/
|
|
59
|
+
export function isContentTypeAllowedForTarget(cType: string, target: HTMLElement): boolean {
|
|
60
|
+
return isContentTypeAllowedInColumn(cType, target.closest('[data-colpos]'));
|
|
61
|
+
}
|
|
@@ -22,6 +22,7 @@ import RegularEvent from '@typo3/core/event/regular-event';
|
|
|
22
22
|
import { DataTransferTypes } from '@typo3/backend/enum/data-transfer-types';
|
|
23
23
|
import BroadcastService from '@typo3/backend/broadcast-service';
|
|
24
24
|
import { BroadcastMessage } from '@typo3/backend/broadcast-message';
|
|
25
|
+
import { isContentTypeAllowedInColumn } from '@typo3/backend/layout-module/column-restriction';
|
|
25
26
|
import type ResponseInterface from '../ajax-data-handler/response-interface';
|
|
26
27
|
import type { DragDropThumbnail, DragTooltipMetadata } from '@typo3/backend/drag-tooltip';
|
|
27
28
|
import DragDropUtility from '@typo3/backend/utility/drag-drop-utility';
|
|
@@ -36,6 +37,7 @@ export type ContentElementDragDropData = {
|
|
|
36
37
|
pid: number;
|
|
37
38
|
uid: number;
|
|
38
39
|
language: number;
|
|
40
|
+
cType: string;
|
|
39
41
|
content: string;
|
|
40
42
|
moveElementUrl: string;
|
|
41
43
|
};
|
|
@@ -54,6 +56,8 @@ enum Classes {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
class DragDrop {
|
|
59
|
+
private draggedContentType: string = '';
|
|
60
|
+
|
|
57
61
|
constructor() {
|
|
58
62
|
DocumentService.ready().then((): void => {
|
|
59
63
|
this.initialize();
|
|
@@ -116,10 +120,13 @@ class DragDrop {
|
|
|
116
120
|
protected onDragStart(event: DragEvent, target: HTMLElement): void {
|
|
117
121
|
const content = target.closest(Identifiers.content) as HTMLElement;
|
|
118
122
|
|
|
123
|
+
this.draggedContentType = content.dataset.ctype ?? '';
|
|
124
|
+
|
|
119
125
|
event.dataTransfer.setData(DataTransferTypes.content, JSON.stringify({
|
|
120
126
|
pid: this.getCurrentPageId(),
|
|
121
127
|
uid: parseInt(content.dataset.uid, 10),
|
|
122
128
|
language: parseInt(content.dataset.languageUid, 10),
|
|
129
|
+
cType: this.draggedContentType,
|
|
123
130
|
content: content.outerHTML,
|
|
124
131
|
moveElementUrl: content.dataset.moveElementUrl,
|
|
125
132
|
} as ContentElementDragDropData));
|
|
@@ -133,6 +140,7 @@ class DragDrop {
|
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
protected onDragEnd(): void {
|
|
143
|
+
this.draggedContentType = '';
|
|
136
144
|
this.hideDropZones();
|
|
137
145
|
}
|
|
138
146
|
|
|
@@ -176,6 +184,7 @@ class DragDrop {
|
|
|
176
184
|
|
|
177
185
|
// the dragged elements language uid
|
|
178
186
|
let language = contentElementDragDropData.language;
|
|
187
|
+
// Keep in sync with LanguageMarker::ALL_LANGUAGES on the PHP side.
|
|
179
188
|
if (language !== -1) {
|
|
180
189
|
// new elements language must be the same as the column the element is dropped in if element is not -1
|
|
181
190
|
language = parseInt((dropContainer.closest('[data-language-uid]') as HTMLElement).dataset.languageUid, 10);
|
|
@@ -335,6 +344,9 @@ class DragDrop {
|
|
|
335
344
|
|
|
336
345
|
protected showDropZones(): void {
|
|
337
346
|
document.querySelectorAll(Identifiers.dropZone).forEach((element: HTMLElement): void => {
|
|
347
|
+
if (!isContentTypeAllowedInColumn(this.draggedContentType, element.closest('[data-colpos]'))) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
338
350
|
element.hidden = false;
|
|
339
351
|
const addContentButton = element.parentElement.querySelector(Identifiers.addContent) as HTMLElement;
|
|
340
352
|
if (addContentButton !== null) {
|
|
@@ -24,18 +24,21 @@ import Severity from '../severity';
|
|
|
24
24
|
import '@typo3/backend/element/icon-element';
|
|
25
25
|
import { SeverityEnum } from '../enum/severity';
|
|
26
26
|
import RegularEvent from '@typo3/core/event/regular-event';
|
|
27
|
+
import { isContentTypeAllowedForTarget } from '@typo3/backend/layout-module/column-restriction';
|
|
27
28
|
import type ResponseInterface from '../ajax-data-handler/response-interface';
|
|
28
29
|
import layoutLabels from '~labels/backend.layout';
|
|
29
30
|
|
|
30
31
|
type PasteOptions = {
|
|
31
32
|
itemOnClipboardUid: number;
|
|
32
33
|
itemOnClipboardTitle: string;
|
|
34
|
+
itemOnClipboardCType: string;
|
|
33
35
|
copyMode: string;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
class Paste {
|
|
37
39
|
private readonly itemOnClipboardUid: number = 0;
|
|
38
40
|
private readonly itemOnClipboardTitle: string = '';
|
|
41
|
+
private readonly itemOnClipboardCType: string = '';
|
|
39
42
|
private readonly copyMode: string = '';
|
|
40
43
|
private readonly elementIdentifier: string = '.t3js-page-ce';
|
|
41
44
|
private pasteAfterLinkTemplate: string = '';
|
|
@@ -47,6 +50,7 @@ class Paste {
|
|
|
47
50
|
constructor(args: PasteOptions) {
|
|
48
51
|
this.itemOnClipboardUid = args.itemOnClipboardUid;
|
|
49
52
|
this.itemOnClipboardTitle = args.itemOnClipboardTitle;
|
|
53
|
+
this.itemOnClipboardCType = args.itemOnClipboardCType;
|
|
50
54
|
this.copyMode = args.copyMode;
|
|
51
55
|
|
|
52
56
|
DocumentService.ready().then((): void => {
|
|
@@ -96,6 +100,9 @@ class Paste {
|
|
|
96
100
|
private activatePasteIcons(): void {
|
|
97
101
|
if (this.pasteAfterLinkTemplate && this.pasteIntoLinkTemplate) {
|
|
98
102
|
document.querySelectorAll('.t3js-page-new-ce').forEach((el: HTMLElement): void => {
|
|
103
|
+
if (!isContentTypeAllowedForTarget(this.itemOnClipboardCType, el)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
99
106
|
const template = el.parentElement.dataset.page ? this.pasteIntoLinkTemplate : this.pasteAfterLinkTemplate;
|
|
100
107
|
el.append(document.createRange().createContextualFragment(template));
|
|
101
108
|
});
|
package/backend/login-refresh.ts
CHANGED
|
@@ -284,15 +284,7 @@ class LoginRefresh {
|
|
|
284
284
|
active: false,
|
|
285
285
|
btnClass: 'btn-primary',
|
|
286
286
|
name: 'refreshSession',
|
|
287
|
-
|
|
288
|
-
modal.querySelector('form').requestSubmit();
|
|
289
|
-
const response = await new AjaxRequest(TYPO3.settings.ajaxUrls.login_refresh).get();
|
|
290
|
-
const data = await response.resolve();
|
|
291
|
-
modal.hideModal();
|
|
292
|
-
if (!data.refresh.success) {
|
|
293
|
-
modal.dispatchEvent(new Event('show-login-form'));
|
|
294
|
-
}
|
|
295
|
-
}
|
|
287
|
+
form: 'beLoginRefresh',
|
|
296
288
|
}
|
|
297
289
|
],
|
|
298
290
|
content: html`
|
package/backend/modal.ts
CHANGED
|
@@ -318,6 +318,7 @@ export class ModalElement extends LitElement {
|
|
|
318
318
|
${this.buttons.map(button => this.renderModalButton(button))}
|
|
319
319
|
</div>
|
|
320
320
|
`}
|
|
321
|
+
<div class="alert-container"></div>
|
|
321
322
|
</dialog>
|
|
322
323
|
`;
|
|
323
324
|
}
|
|
@@ -389,6 +390,8 @@ export class ModalElement extends LitElement {
|
|
|
389
390
|
if (this.ajaxCallback) {
|
|
390
391
|
this.ajaxCallback(this);
|
|
391
392
|
}
|
|
393
|
+
// Native autofocus only covers content present when the dialog opens
|
|
394
|
+
this.querySelector<HTMLElement>(`${Identifiers.body} [autofocus]`)?.focus();
|
|
392
395
|
this.dispatchEvent(new CustomEvent('modal-loaded'));
|
|
393
396
|
});
|
|
394
397
|
})
|
|
@@ -944,11 +947,10 @@ class Modal {
|
|
|
944
947
|
}
|
|
945
948
|
|
|
946
949
|
currentModal.addEventListener('typo3-modal-shown', (): void => {
|
|
947
|
-
//
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
950
|
+
// Prefer an autofocus field over the active button
|
|
951
|
+
const focusTarget = currentModal.querySelector<HTMLElement>(`${Identifiers.body} [autofocus]`)
|
|
952
|
+
?? currentModal.querySelector<HTMLElement>(`${Identifiers.footer} .t3js-active`);
|
|
953
|
+
focusTarget?.focus();
|
|
952
954
|
});
|
|
953
955
|
|
|
954
956
|
// Remove modal from Modal.instances when hidden
|
|
@@ -957,10 +959,15 @@ class Modal {
|
|
|
957
959
|
const lastIndex = this.instances.length - 1;
|
|
958
960
|
this.instances.splice(lastIndex, 1);
|
|
959
961
|
this.currentModal = this.instances[lastIndex - 1];
|
|
962
|
+
remountAlertContainer(this.currentModal);
|
|
960
963
|
}
|
|
961
964
|
});
|
|
962
965
|
|
|
963
966
|
currentModal.addEventListener('typo3-modal-hidden', (): void => {
|
|
967
|
+
// A notification shown while the modal closes is placed in it, and must not be removed along with it
|
|
968
|
+
if (currentModal.querySelector('#alert-container') !== null) {
|
|
969
|
+
remountAlertContainer(this.currentModal);
|
|
970
|
+
}
|
|
964
971
|
currentModal.remove();
|
|
965
972
|
});
|
|
966
973
|
|
|
@@ -970,12 +977,38 @@ class Modal {
|
|
|
970
977
|
this.instances.push(currentModal);
|
|
971
978
|
});
|
|
972
979
|
|
|
980
|
+
currentModal.addEventListener('typo3-modal-shown', (): void => {
|
|
981
|
+
remountAlertContainer(currentModal);
|
|
982
|
+
});
|
|
983
|
+
|
|
973
984
|
document.body.appendChild(currentModal);
|
|
974
985
|
|
|
975
986
|
return currentModal;
|
|
976
987
|
}
|
|
977
988
|
}
|
|
978
989
|
|
|
990
|
+
const remountAlertContainer = (modal: ModalElement | undefined) => {
|
|
991
|
+
const alertContainer = document.querySelector<HTMLDivElement>('#alert-container');
|
|
992
|
+
if (alertContainer) {
|
|
993
|
+
const isOpen = alertContainer.matches(':popover-open');
|
|
994
|
+
let target: HTMLElement = document.body;
|
|
995
|
+
if (modal) {
|
|
996
|
+
target = modal.querySelector('.alert-container') ?? target;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if ('moveBefore' in target) {
|
|
1000
|
+
target.moveBefore(alertContainer, null);
|
|
1001
|
+
} else {
|
|
1002
|
+
(target as HTMLElement).appendChild(alertContainer);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (isOpen) {
|
|
1006
|
+
alertContainer.hidePopover();
|
|
1007
|
+
alertContainer.showPopover();
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
979
1012
|
let modalObject: Modal = null;
|
|
980
1013
|
try {
|
|
981
1014
|
if (parent && parent.window.TYPO3 && parent.window.TYPO3.Modal) {
|