From 5e856cc6d288ac7792964df6fe92c97ddec2d950 Mon Sep 17 00:00:00 2001 From: Andreas Kienast <a.fernandez@scripting-base.de> Date: Mon, 26 Feb 2024 09:12:28 +0100 Subject: [PATCH] [BUGFIX] Streamline `typo3-spinner-icon` element with SVG asset The Lit element `typo3-backend-spinner` uses a hard-coded SVG string, which has to be the same as the `spinner-circle` asset file. However, the SVG string was outdated as it neither respected the `currentColor`, nor respected the configured icon size. Both issues are fixed in this commit. Since `currentColor` is now taken into account, the variant styling has been removed. Resolves: #103198 Releases: main, 12.4 Change-Id: I24eababb8716076eb4db55a6bcd3eb43e98ea1fd Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83164 Tested-by: Andreas Kienast <a.fernandez@scripting-base.de> Tested-by: core-ci <typo3@b13.com> Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de> --- .../TypeScript/backend/clipboard-panel.ts | 2 +- .../backend/element/spinner-element.ts | 25 +++++-------------- .../TypeScript/backend/image-manipulation.ts | 2 +- Build/Sources/TypeScript/backend/modal.ts | 2 +- .../pagetsconfig/pagetsconfig-includes.ts | 2 +- Build/Sources/TypeScript/install/router.ts | 2 +- .../tstemplate/template-analyzer.ts | 2 +- .../Public/JavaScript/clipboard-panel.js | 2 +- .../JavaScript/element/spinner-element.js | 14 ++++------- .../Public/JavaScript/image-manipulation.js | 2 +- .../Resources/Public/JavaScript/modal.js | 2 +- .../pagetsconfig/pagetsconfig-includes.js | 2 +- .../Resources/Public/JavaScript/router.js | 2 +- .../Public/JavaScript/template-analyzer.js | 2 +- 14 files changed, 23 insertions(+), 40 deletions(-) diff --git a/Build/Sources/TypeScript/backend/clipboard-panel.ts b/Build/Sources/TypeScript/backend/clipboard-panel.ts index bf2134300c41..667961bee420 100644 --- a/Build/Sources/TypeScript/backend/clipboard-panel.ts +++ b/Build/Sources/TypeScript/backend/clipboard-panel.ts @@ -71,7 +71,7 @@ export class ClipboardPanel extends LitElement { return html` <div class="panel panel-default"> <div class="panel-loader"> - <typo3-backend-spinner size="small" variant="dark"></typo3-backend-spinner> + <typo3-backend-spinner size="small"></typo3-backend-spinner> </div> </div> `; diff --git a/Build/Sources/TypeScript/backend/element/spinner-element.ts b/Build/Sources/TypeScript/backend/element/spinner-element.ts index 726f12b0d9cc..1e32493dd851 100644 --- a/Build/Sources/TypeScript/backend/element/spinner-element.ts +++ b/Build/Sources/TypeScript/backend/element/spinner-element.ts @@ -11,45 +11,32 @@ * The TYPO3 project - inspiring people to share! */ -import { css, html, LitElement, TemplateResult } from 'lit'; +import { html, LitElement, TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators'; import { Sizes } from '../enum/icon-types'; import { IconStyles } from '@typo3/backend/icons'; -enum Variant { - light = 'light', - dark = 'dark' -} - /** * Module: @typo3/backend/element/spinner-element * * @example - * <typo3-backend-spinner size="small" variant="dark"></typo3-backend-spinner> + * <typo3-backend-spinner size="small"></typo3-backend-spinner> * + attribute size can be one of small, default, large or mega */ @customElement('typo3-backend-spinner') export class SpinnerElement extends LitElement { - public static styles = [ - ...IconStyles.getStyles(), - css` - :host([variant=dark]) svg { fill: #212121; } - :host([variant=light]) svg { fill: #fff; } - ` - ]; + static styles = IconStyles.getStyles(); @property({ type: String }) size: Sizes = Sizes.default; - @property({ type: String }) variant: Variant = Variant.dark; protected render(): TemplateResult { return html` <div class="icon-wrapper"> - <span class="icon icon-size-small icon-state-default icon-spin"> + <span class="icon icon-size-${this.size} icon-state-default icon-spin"> <span class="icon-markup"> <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 16 16"> - <g class="icon-color"> - <path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" opacity=".3"/> - <path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z"/> + <g fill="currentColor"> + <path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" opacity=".3"/><path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z"/> </g> </svg> </span> diff --git a/Build/Sources/TypeScript/backend/image-manipulation.ts b/Build/Sources/TypeScript/backend/image-manipulation.ts index e85cdea24bd5..06dc438da9d2 100644 --- a/Build/Sources/TypeScript/backend/image-manipulation.ts +++ b/Build/Sources/TypeScript/backend/image-manipulation.ts @@ -232,7 +232,7 @@ class ImageManipulation { text: buttonSaveText, }, ], - content: html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`, + content: html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`, size: Modal.sizes.full, style: Modal.styles.dark, title: modalTitle, diff --git a/Build/Sources/TypeScript/backend/modal.ts b/Build/Sources/TypeScript/backend/modal.ts index 95c2e410a673..df4f3d8ebe05 100644 --- a/Build/Sources/TypeScript/backend/modal.ts +++ b/Build/Sources/TypeScript/backend/modal.ts @@ -217,7 +217,7 @@ export class ModalElement extends LitElement { this.templateResultContent = html`<p><strong>Oops, received a ${response.response.status} response from </strong> <span class="text-break">${this.content}</span>.</p>`; } }); - return html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`; + return html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`; } return this.templateResultContent as TemplateResult; diff --git a/Build/Sources/TypeScript/backend/pagetsconfig/pagetsconfig-includes.ts b/Build/Sources/TypeScript/backend/pagetsconfig/pagetsconfig-includes.ts index 63d54180e595..4df62887a10d 100644 --- a/Build/Sources/TypeScript/backend/pagetsconfig/pagetsconfig-includes.ts +++ b/Build/Sources/TypeScript/backend/pagetsconfig/pagetsconfig-includes.ts @@ -43,7 +43,7 @@ class PageTsConfigIncludes { const size = Modal.sizes.large; const content = html`${until( this.fetchModalContent(url), - html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>` + html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>` )}`; Modal.advanced({ type, title, size, content }); }); diff --git a/Build/Sources/TypeScript/install/router.ts b/Build/Sources/TypeScript/install/router.ts index 5ddfc71b6837..4003949ad6f2 100644 --- a/Build/Sources/TypeScript/install/router.ts +++ b/Build/Sources/TypeScript/install/router.ts @@ -83,7 +83,7 @@ class Router { type: Modal.types.default, title: modalTitle, size: modalSize, - content: html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`, + content: html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`, additionalCssClasses: ['install-tool-modal'], staticBackdrop: true, callback: (currentModal: ModalElement): void => { diff --git a/Build/Sources/TypeScript/tstemplate/template-analyzer.ts b/Build/Sources/TypeScript/tstemplate/template-analyzer.ts index 7a97ec41c71a..3660a88af68c 100644 --- a/Build/Sources/TypeScript/tstemplate/template-analyzer.ts +++ b/Build/Sources/TypeScript/tstemplate/template-analyzer.ts @@ -43,7 +43,7 @@ class TemplateAnalyzer { const size = Modal.sizes.large; const content = html`${until( this.fetchModalContent(url), - html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>` + html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>` )}`; Modal.advanced({ type, title, size, content }); }); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/clipboard-panel.js b/typo3/sysext/backend/Resources/Public/JavaScript/clipboard-panel.js index 978ce041dcce..850819bae3a2 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/clipboard-panel.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/clipboard-panel.js @@ -13,7 +13,7 @@ var ClipboardPanel_1,CopyMode,__decorate=function(t,e,o,a){var i,n=arguments.length,l=n<3?e:null===a?a=Object.getOwnPropertyDescriptor(e,o):a;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(t,e,o,a);else for(var r=t.length-1;r>=0;r--)(i=t[r])&&(l=(n<3?i(l):n>3?i(e,o,l):i(e,o))||l);return n>3&&l&&Object.defineProperty(e,o,l),l};import{html,LitElement,nothing}from"lit";import{customElement,property}from"lit/decorators.js";import{until}from"lit/directives/until.js";import{unsafeHTML}from"lit/directives/unsafe-html.js";import{classMap}from"lit/directives/class-map.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import Notification from"@typo3/backend/notification.js";import"@typo3/backend/element/spinner-element.js";import"@typo3/backend/element/icon-element.js";!function(t){t.cut="cut",t.copy="copy"}(CopyMode||(CopyMode={}));let ClipboardPanel=ClipboardPanel_1=class extends LitElement{constructor(){super(...arguments),this.returnUrl="",this.table=""}static renderLoader(){return html` <div class="panel panel-default"> <div class="panel-loader"> - <typo3-backend-spinner size="small" variant="dark"></typo3-backend-spinner> + <typo3-backend-spinner size="small"></typo3-backend-spinner> </div> </div> `}createRenderRoot(){return this}render(){return html` diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/element/spinner-element.js b/typo3/sysext/backend/Resources/Public/JavaScript/element/spinner-element.js index 8e919f5e9f0f..67a9709faf4c 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/element/spinner-element.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/element/spinner-element.js @@ -10,20 +10,16 @@ * * The TYPO3 project - inspiring people to share! */ -var Variant,__decorate=function(e,t,r,n){var i,o=arguments.length,s=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,n);else for(var a=e.length-1;a>=0;a--)(i=e[a])&&(s=(o<3?i(s):o>3?i(t,r,s):i(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s};import{css,html,LitElement}from"lit";import{customElement,property}from"lit/decorators.js";import{Sizes}from"@typo3/backend/enum/icon-types.js";import{IconStyles}from"@typo3/backend/icons.js";!function(e){e.light="light",e.dark="dark"}(Variant||(Variant={}));let SpinnerElement=class extends LitElement{constructor(){super(...arguments),this.size=Sizes.default,this.variant=Variant.dark}render(){return html` +var __decorate=function(e,t,n,r){var o,s=arguments.length,i=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,r);else for(var c=e.length-1;c>=0;c--)(o=e[c])&&(i=(s<3?o(i):s>3?o(t,n,i):o(t,n))||i);return s>3&&i&&Object.defineProperty(t,n,i),i};import{html,LitElement}from"lit";import{customElement,property}from"lit/decorators.js";import{Sizes}from"@typo3/backend/enum/icon-types.js";import{IconStyles}from"@typo3/backend/icons.js";let SpinnerElement=class extends LitElement{constructor(){super(...arguments),this.size=Sizes.default}render(){return html` <div class="icon-wrapper"> - <span class="icon icon-size-small icon-state-default icon-spin"> + <span class="icon icon-size-${this.size} icon-state-default icon-spin"> <span class="icon-markup"> <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 16 16"> - <g class="icon-color"> - <path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" opacity=".3"/> - <path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z"/> + <g fill="currentColor"> + <path d="M8 15c-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7 0 3.859-3.141 7-7 7zM8 3C5.243 3 3 5.243 3 8s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" opacity=".3"/><path d="M14 9a1 1 0 0 1-1-1c0-2.757-2.243-5-5-5a1 1 0 0 1 0-2c3.859 0 7 3.14 7 7a1 1 0 0 1-1 1z"/> </g> </svg> </span> </span> </div> - `}};SpinnerElement.styles=[...IconStyles.getStyles(),css` - :host([variant=dark]) svg { fill: #212121; } - :host([variant=light]) svg { fill: #fff; } - `],__decorate([property({type:String})],SpinnerElement.prototype,"size",void 0),__decorate([property({type:String})],SpinnerElement.prototype,"variant",void 0),SpinnerElement=__decorate([customElement("typo3-backend-spinner")],SpinnerElement);export{SpinnerElement}; \ No newline at end of file + `}};SpinnerElement.styles=IconStyles.getStyles(),__decorate([property({type:String})],SpinnerElement.prototype,"size",void 0),SpinnerElement=__decorate([customElement("typo3-backend-spinner")],SpinnerElement);export{SpinnerElement}; \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/image-manipulation.js b/typo3/sysext/backend/Resources/Public/JavaScript/image-manipulation.js index f1930e47d315..9ff542dc936a 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/image-manipulation.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/image-manipulation.js @@ -10,7 +10,7 @@ * * The TYPO3 project - inspiring people to share! */ -import{html}from"lit";import{unsafeHTML}from"lit/directives/unsafe-html.js";import{styleMap}from"lit/directives/style-map.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import RegularEvent from"@typo3/core/event/regular-event.js";import FormEngineValidation from"@typo3/backend/form-engine-validation.js";import Cropper from"cropperjs";import{default as Modal}from"@typo3/backend/modal.js";import"@typo3/backend/element/spinner-element.js";import{renderNodes}from"@typo3/core/lit-helper.js";import{Offset}from"@typo3/backend/element/draggable-resizable-element.js";class ImageManipulation{constructor(){this.initialized=!1,this.triggerListener=null,this.cropImageSelector="#t3js-crop-image",this.coverAreaSelector=".t3js-cropper-cover-area",this.cropInfoSelector=".t3js-cropper-info-crop",this.focusAreaSelector="#t3js-cropper-focus-area",this.defaultFocusArea={height:1/3,width:1/3,x:0,y:0},this.defaultOpts={autoCrop:!0,autoCropArea:.7,dragMode:"crop",guides:!0,responsive:!0,viewMode:1,zoomable:!1,checkCrossOrigin:!1},this.cropBuiltHandler=()=>{this.initialized=!0;const t=this.cropper.getImageData(),e=this.currentModal.querySelector(this.cropImageSelector);this.currentModal.querySelector(".cropper-canvas img")?.classList.remove("cropper-hide"),this.imageOriginalSizeFactor=parseInt(e.dataset.originalWidth,10)/t.naturalWidth,this.cropVariantTriggers.forEach((e=>{const r=e.dataset.cropVariantId,a=this.convertRelativeToAbsoluteCropArea(this.data[r].cropArea,t),i=Object.assign({},this.data[r],{cropArea:a});this.updatePreviewThumbnail(i,e)})),this.currentCropVariant.cropArea=this.convertRelativeToAbsoluteCropArea(this.currentCropVariant.cropArea,t),this.cropBox=this.currentModal.querySelector(".cropper-crop-box"),this.setCropArea(this.currentCropVariant.cropArea),this.currentCropVariant.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.currentCropVariant.focusArea&&(ImageManipulation.isEmptyObject(this.currentCropVariant.focusArea)&&(this.currentCropVariant.focusArea=Object.assign({},this.defaultFocusArea)),this.focusAreaEl?.remove(),this.initFocusArea(this.cropBox)),this.currentCropVariant.selectedRatio&&this.currentModal.querySelector(`[data-bs-option='${this.currentCropVariant.selectedRatio}']`)?.classList.add("active")},this.cropMoveHandler=t=>{if(!this.initialized)return;let e=Math.ceil(t.detail.width),r=Math.ceil(t.detail.height);(e<15||r<15)&&(e=Math.max(15,r),r=Math.max(15,e),this.cropper.setData({width:e,height:r})),this.currentCropVariant.cropArea=Object.assign({},this.currentCropVariant.cropArea,{width:Math.floor(e),height:Math.floor(r),x:Math.floor(t.detail.x),y:Math.floor(t.detail.y)}),this.focusAreaEl&&this.currentCropVariant?.focusArea&&(this.focusAreaEl.offset=this.convertAreaToOffset(this.currentCropVariant.focusArea,this.cropBox)),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger),this.updateCropVariantData(this.currentCropVariant);const a=Math.round(this.currentCropVariant.cropArea.width*this.imageOriginalSizeFactor),i=Math.round(this.currentCropVariant.cropArea.height*this.imageOriginalSizeFactor);this.cropInfo.innerText=`${a}×${i} px`}}static wait(t,e){window.setTimeout(t,e)}static toCssPercent(t){return 100*t+"%"}static serializeCropVariants(t){return JSON.stringify(t,((t,e)=>"id"===t||"title"===t||"allowedAspectRatios"===t||"coverAreas"===t?void 0:e))}static isEmptyObject(t){return!t||"object"!=typeof t||0===Object.keys(t).length||"{}"===JSON.stringify(t)}static resolvePointerEventNames(){const t="undefined"!=typeof window&&void 0!==window.document,e=!(!t||!window.document.documentElement)&&"ontouchstart"in window.document.documentElement,r=!!t&&"PointerEvent"in window,a=e?["touchmove"]:["mousemove"],i=e?["touchstart"]:["mousedown"],o=e?["touchend","touchcancel"]:["mouseup"];return{touchStart:i,touchMove:a,touchEnd:o,pointerDown:r?["pointerdown"]:i,pointerMove:r?["pointermove"]:a,pointerUp:r?["pointerup","pointercancel"]:o}}initializeTrigger(){this.triggerListener||(this.triggerListener=new RegularEvent("click",((t,e)=>{t.preventDefault(),this.trigger=e,this.show()})),this.triggerListener.delegateTo(document,".t3js-image-manipulation-trigger"))}async initializeCropperModal(){const t=this.currentModal.querySelector(this.cropImageSelector);await new Promise((e=>{t.complete?e():t.addEventListener("load",(()=>e()))})),this.init()}show(){const t=this.trigger.dataset,e=t.modalTitle,r=t.buttonPreviewText,a=t.buttonDismissText,i=t.buttonSaveText,o=t.url,s=JSON.parse(t.payload);this.currentModal=Modal.advanced({additionalCssClasses:["modal-image-manipulation","cropper"],buttons:[{btnClass:"btn-default float-start",name:"preview",icon:"actions-view",text:r},{btnClass:"btn-default",name:"dismiss",icon:"actions-close",text:a},{btnClass:"btn-primary",name:"save",icon:"actions-document-save",text:i}],content:html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`,size:Modal.sizes.full,style:Modal.styles.dark,title:e,staticBackdrop:!0}),this.currentModal.addEventListener("typo3-modal-shown",(()=>{new AjaxRequest(o).post(s).then((async t=>{const e=await t.resolve();this.currentModal.templateResultContent=html`${unsafeHTML(e)}`,this.currentModal.updateComplete.then((()=>this.initializeCropperModal()))}))})),this.currentModal.addEventListener("typo3-modal-hide",(()=>{this.destroy()}))}init(){const t=this.currentModal.querySelector(this.cropImageSelector),e=this.trigger.dataset.cropVariants;if(!e)throw new TypeError("ImageManipulation: No cropVariants data found for image");this.data=ImageManipulation.isEmptyObject(this.data)?JSON.parse(e):this.data,this.cropVariantTriggers=this.currentModal.querySelectorAll(".t3js-crop-variant-trigger"),this.activeCropVariantTrigger=this.currentModal.querySelector(".t3js-crop-variant-trigger.is-active"),this.cropInfo=this.currentModal.querySelector(this.cropInfoSelector),this.currentCropVariant=this.data[this.activeCropVariantTrigger.dataset.cropVariantId],this.cropVariantTriggers.forEach((t=>t.addEventListener("click",(t=>{if(t.currentTarget.classList.contains("is-active"))return t.stopPropagation(),void t.preventDefault();this.activeCropVariantTrigger.classList.remove("is-active"),t.currentTarget.classList.add("is-active"),this.activeCropVariantTrigger=t.currentTarget;const e=this.data[this.activeCropVariantTrigger.dataset.cropVariantId],r=this.cropper.getImageData();e.cropArea=this.convertRelativeToAbsoluteCropArea(e.cropArea,r),this.currentCropVariant=Object.assign({},e),this.update(e)})))),new RegularEvent("click",((t,e)=>{const r=e.dataset.bsOption,a=Object.assign({},this.currentCropVariant),i=a.allowedAspectRatios[r];this.setAspectRatio(i),this.setCropArea(a.cropArea),this.currentCropVariant=Object.assign({},a,{selectedRatio:r}),this.update(this.currentCropVariant)})).delegateTo(this.currentModal,"label[data-method=setAspectRatio]"),new RegularEvent("click",(()=>this.save(this.data))).delegateTo(this.currentModal,"button[name=save]"),this.trigger.dataset.previewUrl?new RegularEvent("click",(()=>this.openPreview(this.data))).delegateTo(this.currentModal,"button[name=preview]"):this.currentModal.querySelectorAll("button[name=preview]").forEach((t=>t.style.display="none")),new RegularEvent("click",(()=>this.currentModal.hideModal())).delegateTo(this.currentModal,"button[name=dismiss]"),new RegularEvent("click",((t,e)=>{const r=this.cropper.getImageData(),a=e.dataset.cropVariant;if(t.preventDefault(),t.stopPropagation(),!a)throw new TypeError("TYPO3 Cropper: No cropVariant data attribute found on reset element.");const i=JSON.parse(a),o=this.convertRelativeToAbsoluteCropArea(i.cropArea,r);this.currentCropVariant=Object.assign({},i,{cropArea:o}),this.update(this.currentCropVariant)})).delegateTo(this.currentModal,"button[name=reset]"),ImageManipulation.isEmptyObject(this.currentCropVariant.cropArea)&&(this.defaultOpts=Object.assign({autoCropArea:1},this.defaultOpts)),this.cropper=new Cropper(t,Object.assign({},this.defaultOpts,{ready:()=>{this.cropBuiltHandler(),this.update(this.currentCropVariant)},crop:this.cropMoveHandler.bind(this),data:this.currentCropVariant.cropArea}))}update(t){const e=Object.assign({},t),r=t.allowedAspectRatios[t.selectedRatio];this.currentModal.querySelector("[data-bs-option].active")?.classList.remove("active"),this.currentModal.querySelector(`[data-bs-option="${t.selectedRatio}"]`)?.classList.add("active"),this.setAspectRatio(r),this.setCropArea(e.cropArea),this.currentCropVariant=Object.assign({},e,t),this.cropBox?.querySelector(this.coverAreaSelector)?.remove(),this.cropBox?.querySelectorAll(this.focusAreaSelector)?.length>0&&this.focusAreaEl.remove(),t.focusArea&&(ImageManipulation.isEmptyObject(t.focusArea)&&(this.currentCropVariant.focusArea=Object.assign({},this.defaultFocusArea)),this.focusAreaEl?.remove(),this.initFocusArea(this.cropBox)),t.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger)}initFocusArea(t){this.focusAreaEl=document.createElement("typo3-backend-draggable-resizable"),this.focusAreaEl.window=this.currentModal.ownerDocument.defaultView,this.focusAreaEl.offset=this.convertAreaToOffset(this.currentCropVariant.focusArea,t),this.focusAreaEl.container=t,this.focusAreaEl.pointerEventNames=ImageManipulation.resolvePointerEventNames(),this.focusAreaEl.addEventListener("draggable-resizable-started",(()=>{this.cropper.disable()})),this.focusAreaEl.addEventListener("draggable-resizable-updated",(()=>{const e=this.currentCropVariant.coverAreas,r=this.convertOffsetToArea(this.focusAreaEl.offset,t),a=this.focusAreaEl.querySelector(this.focusAreaSelector);this.checkFocusAndCoverAreasCollision(r,e)?a.classList.add("has-nodrop"):a.classList.remove("has-nodrop")})),this.focusAreaEl.addEventListener("draggable-resizable-finished",(e=>{const r=this.currentCropVariant.coverAreas,a=this.convertOffsetToArea(this.focusAreaEl.offset,t);this.checkFocusAndCoverAreasCollision(a,r)?this.focusAreaEl.revert(e.detail.originOffset):this.scaleAndMoveFocusArea(a);this.focusAreaEl.querySelector(this.focusAreaSelector).classList.remove("has-nodrop"),this.cropper.enable()})),t.appendChild(this.focusAreaEl),this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea)}initCoverAreas(t,e){e.forEach((e=>{const r={height:ImageManipulation.toCssPercent(e.height),left:ImageManipulation.toCssPercent(e.x),top:ImageManipulation.toCssPercent(e.y),width:ImageManipulation.toCssPercent(e.width)},a=html` +import{html}from"lit";import{unsafeHTML}from"lit/directives/unsafe-html.js";import{styleMap}from"lit/directives/style-map.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import RegularEvent from"@typo3/core/event/regular-event.js";import FormEngineValidation from"@typo3/backend/form-engine-validation.js";import Cropper from"cropperjs";import{default as Modal}from"@typo3/backend/modal.js";import"@typo3/backend/element/spinner-element.js";import{renderNodes}from"@typo3/core/lit-helper.js";import{Offset}from"@typo3/backend/element/draggable-resizable-element.js";class ImageManipulation{constructor(){this.initialized=!1,this.triggerListener=null,this.cropImageSelector="#t3js-crop-image",this.coverAreaSelector=".t3js-cropper-cover-area",this.cropInfoSelector=".t3js-cropper-info-crop",this.focusAreaSelector="#t3js-cropper-focus-area",this.defaultFocusArea={height:1/3,width:1/3,x:0,y:0},this.defaultOpts={autoCrop:!0,autoCropArea:.7,dragMode:"crop",guides:!0,responsive:!0,viewMode:1,zoomable:!1,checkCrossOrigin:!1},this.cropBuiltHandler=()=>{this.initialized=!0;const t=this.cropper.getImageData(),e=this.currentModal.querySelector(this.cropImageSelector);this.currentModal.querySelector(".cropper-canvas img")?.classList.remove("cropper-hide"),this.imageOriginalSizeFactor=parseInt(e.dataset.originalWidth,10)/t.naturalWidth,this.cropVariantTriggers.forEach((e=>{const r=e.dataset.cropVariantId,a=this.convertRelativeToAbsoluteCropArea(this.data[r].cropArea,t),i=Object.assign({},this.data[r],{cropArea:a});this.updatePreviewThumbnail(i,e)})),this.currentCropVariant.cropArea=this.convertRelativeToAbsoluteCropArea(this.currentCropVariant.cropArea,t),this.cropBox=this.currentModal.querySelector(".cropper-crop-box"),this.setCropArea(this.currentCropVariant.cropArea),this.currentCropVariant.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.currentCropVariant.focusArea&&(ImageManipulation.isEmptyObject(this.currentCropVariant.focusArea)&&(this.currentCropVariant.focusArea=Object.assign({},this.defaultFocusArea)),this.focusAreaEl?.remove(),this.initFocusArea(this.cropBox)),this.currentCropVariant.selectedRatio&&this.currentModal.querySelector(`[data-bs-option='${this.currentCropVariant.selectedRatio}']`)?.classList.add("active")},this.cropMoveHandler=t=>{if(!this.initialized)return;let e=Math.ceil(t.detail.width),r=Math.ceil(t.detail.height);(e<15||r<15)&&(e=Math.max(15,r),r=Math.max(15,e),this.cropper.setData({width:e,height:r})),this.currentCropVariant.cropArea=Object.assign({},this.currentCropVariant.cropArea,{width:Math.floor(e),height:Math.floor(r),x:Math.floor(t.detail.x),y:Math.floor(t.detail.y)}),this.focusAreaEl&&this.currentCropVariant?.focusArea&&(this.focusAreaEl.offset=this.convertAreaToOffset(this.currentCropVariant.focusArea,this.cropBox)),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger),this.updateCropVariantData(this.currentCropVariant);const a=Math.round(this.currentCropVariant.cropArea.width*this.imageOriginalSizeFactor),i=Math.round(this.currentCropVariant.cropArea.height*this.imageOriginalSizeFactor);this.cropInfo.innerText=`${a}×${i} px`}}static wait(t,e){window.setTimeout(t,e)}static toCssPercent(t){return 100*t+"%"}static serializeCropVariants(t){return JSON.stringify(t,((t,e)=>"id"===t||"title"===t||"allowedAspectRatios"===t||"coverAreas"===t?void 0:e))}static isEmptyObject(t){return!t||"object"!=typeof t||0===Object.keys(t).length||"{}"===JSON.stringify(t)}static resolvePointerEventNames(){const t="undefined"!=typeof window&&void 0!==window.document,e=!(!t||!window.document.documentElement)&&"ontouchstart"in window.document.documentElement,r=!!t&&"PointerEvent"in window,a=e?["touchmove"]:["mousemove"],i=e?["touchstart"]:["mousedown"],o=e?["touchend","touchcancel"]:["mouseup"];return{touchStart:i,touchMove:a,touchEnd:o,pointerDown:r?["pointerdown"]:i,pointerMove:r?["pointermove"]:a,pointerUp:r?["pointerup","pointercancel"]:o}}initializeTrigger(){this.triggerListener||(this.triggerListener=new RegularEvent("click",((t,e)=>{t.preventDefault(),this.trigger=e,this.show()})),this.triggerListener.delegateTo(document,".t3js-image-manipulation-trigger"))}async initializeCropperModal(){const t=this.currentModal.querySelector(this.cropImageSelector);await new Promise((e=>{t.complete?e():t.addEventListener("load",(()=>e()))})),this.init()}show(){const t=this.trigger.dataset,e=t.modalTitle,r=t.buttonPreviewText,a=t.buttonDismissText,i=t.buttonSaveText,o=t.url,s=JSON.parse(t.payload);this.currentModal=Modal.advanced({additionalCssClasses:["modal-image-manipulation","cropper"],buttons:[{btnClass:"btn-default float-start",name:"preview",icon:"actions-view",text:r},{btnClass:"btn-default",name:"dismiss",icon:"actions-close",text:a},{btnClass:"btn-primary",name:"save",icon:"actions-document-save",text:i}],content:html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`,size:Modal.sizes.full,style:Modal.styles.dark,title:e,staticBackdrop:!0}),this.currentModal.addEventListener("typo3-modal-shown",(()=>{new AjaxRequest(o).post(s).then((async t=>{const e=await t.resolve();this.currentModal.templateResultContent=html`${unsafeHTML(e)}`,this.currentModal.updateComplete.then((()=>this.initializeCropperModal()))}))})),this.currentModal.addEventListener("typo3-modal-hide",(()=>{this.destroy()}))}init(){const t=this.currentModal.querySelector(this.cropImageSelector),e=this.trigger.dataset.cropVariants;if(!e)throw new TypeError("ImageManipulation: No cropVariants data found for image");this.data=ImageManipulation.isEmptyObject(this.data)?JSON.parse(e):this.data,this.cropVariantTriggers=this.currentModal.querySelectorAll(".t3js-crop-variant-trigger"),this.activeCropVariantTrigger=this.currentModal.querySelector(".t3js-crop-variant-trigger.is-active"),this.cropInfo=this.currentModal.querySelector(this.cropInfoSelector),this.currentCropVariant=this.data[this.activeCropVariantTrigger.dataset.cropVariantId],this.cropVariantTriggers.forEach((t=>t.addEventListener("click",(t=>{if(t.currentTarget.classList.contains("is-active"))return t.stopPropagation(),void t.preventDefault();this.activeCropVariantTrigger.classList.remove("is-active"),t.currentTarget.classList.add("is-active"),this.activeCropVariantTrigger=t.currentTarget;const e=this.data[this.activeCropVariantTrigger.dataset.cropVariantId],r=this.cropper.getImageData();e.cropArea=this.convertRelativeToAbsoluteCropArea(e.cropArea,r),this.currentCropVariant=Object.assign({},e),this.update(e)})))),new RegularEvent("click",((t,e)=>{const r=e.dataset.bsOption,a=Object.assign({},this.currentCropVariant),i=a.allowedAspectRatios[r];this.setAspectRatio(i),this.setCropArea(a.cropArea),this.currentCropVariant=Object.assign({},a,{selectedRatio:r}),this.update(this.currentCropVariant)})).delegateTo(this.currentModal,"label[data-method=setAspectRatio]"),new RegularEvent("click",(()=>this.save(this.data))).delegateTo(this.currentModal,"button[name=save]"),this.trigger.dataset.previewUrl?new RegularEvent("click",(()=>this.openPreview(this.data))).delegateTo(this.currentModal,"button[name=preview]"):this.currentModal.querySelectorAll("button[name=preview]").forEach((t=>t.style.display="none")),new RegularEvent("click",(()=>this.currentModal.hideModal())).delegateTo(this.currentModal,"button[name=dismiss]"),new RegularEvent("click",((t,e)=>{const r=this.cropper.getImageData(),a=e.dataset.cropVariant;if(t.preventDefault(),t.stopPropagation(),!a)throw new TypeError("TYPO3 Cropper: No cropVariant data attribute found on reset element.");const i=JSON.parse(a),o=this.convertRelativeToAbsoluteCropArea(i.cropArea,r);this.currentCropVariant=Object.assign({},i,{cropArea:o}),this.update(this.currentCropVariant)})).delegateTo(this.currentModal,"button[name=reset]"),ImageManipulation.isEmptyObject(this.currentCropVariant.cropArea)&&(this.defaultOpts=Object.assign({autoCropArea:1},this.defaultOpts)),this.cropper=new Cropper(t,Object.assign({},this.defaultOpts,{ready:()=>{this.cropBuiltHandler(),this.update(this.currentCropVariant)},crop:this.cropMoveHandler.bind(this),data:this.currentCropVariant.cropArea}))}update(t){const e=Object.assign({},t),r=t.allowedAspectRatios[t.selectedRatio];this.currentModal.querySelector("[data-bs-option].active")?.classList.remove("active"),this.currentModal.querySelector(`[data-bs-option="${t.selectedRatio}"]`)?.classList.add("active"),this.setAspectRatio(r),this.setCropArea(e.cropArea),this.currentCropVariant=Object.assign({},e,t),this.cropBox?.querySelector(this.coverAreaSelector)?.remove(),this.cropBox?.querySelectorAll(this.focusAreaSelector)?.length>0&&this.focusAreaEl.remove(),t.focusArea&&(ImageManipulation.isEmptyObject(t.focusArea)&&(this.currentCropVariant.focusArea=Object.assign({},this.defaultFocusArea)),this.focusAreaEl?.remove(),this.initFocusArea(this.cropBox)),t.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger)}initFocusArea(t){this.focusAreaEl=document.createElement("typo3-backend-draggable-resizable"),this.focusAreaEl.window=this.currentModal.ownerDocument.defaultView,this.focusAreaEl.offset=this.convertAreaToOffset(this.currentCropVariant.focusArea,t),this.focusAreaEl.container=t,this.focusAreaEl.pointerEventNames=ImageManipulation.resolvePointerEventNames(),this.focusAreaEl.addEventListener("draggable-resizable-started",(()=>{this.cropper.disable()})),this.focusAreaEl.addEventListener("draggable-resizable-updated",(()=>{const e=this.currentCropVariant.coverAreas,r=this.convertOffsetToArea(this.focusAreaEl.offset,t),a=this.focusAreaEl.querySelector(this.focusAreaSelector);this.checkFocusAndCoverAreasCollision(r,e)?a.classList.add("has-nodrop"):a.classList.remove("has-nodrop")})),this.focusAreaEl.addEventListener("draggable-resizable-finished",(e=>{const r=this.currentCropVariant.coverAreas,a=this.convertOffsetToArea(this.focusAreaEl.offset,t);this.checkFocusAndCoverAreasCollision(a,r)?this.focusAreaEl.revert(e.detail.originOffset):this.scaleAndMoveFocusArea(a);this.focusAreaEl.querySelector(this.focusAreaSelector).classList.remove("has-nodrop"),this.cropper.enable()})),t.appendChild(this.focusAreaEl),this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea)}initCoverAreas(t,e){e.forEach((e=>{const r={height:ImageManipulation.toCssPercent(e.height),left:ImageManipulation.toCssPercent(e.x),top:ImageManipulation.toCssPercent(e.y),width:ImageManipulation.toCssPercent(e.width)},a=html` <div class="cropper-cover-area t3js-cropper-cover-area" style=${styleMap(r)}></div> `;this.renderElements(a,t)}))}updatePreviewThumbnail(t,e){const r=e.querySelector(".t3js-cropper-preview-thumbnail-crop-area"),a=e.querySelector(".t3js-cropper-preview-thumbnail-crop-image"),i=e.querySelector(".t3js-cropper-preview-thumbnail-focus-area"),o=this.cropper.getImageData();Object.assign(r.style,{height:ImageManipulation.toCssPercent(t.cropArea.height/o.naturalHeight),left:ImageManipulation.toCssPercent(t.cropArea.x/o.naturalWidth),top:ImageManipulation.toCssPercent(t.cropArea.y/o.naturalHeight),width:ImageManipulation.toCssPercent(t.cropArea.width/o.naturalWidth)}),t.focusArea&&Object.assign(i.style,{height:ImageManipulation.toCssPercent(t.focusArea.height),left:ImageManipulation.toCssPercent(t.focusArea.x),top:ImageManipulation.toCssPercent(t.focusArea.y),width:ImageManipulation.toCssPercent(t.focusArea.width)});const s=getComputedStyle(r),n={width:s.getPropertyValue("width"),height:s.getPropertyValue("height"),left:s.getPropertyValue("left"),top:s.getPropertyValue("top")};Object.assign(a.style,{height:parseFloat(n.height)*(1/(t.cropArea.height/o.naturalHeight))+"px",margin:-1*parseFloat(n.left)+"px",marginTop:-1*parseFloat(n.top)+"px",width:parseFloat(n.width)*(1/(t.cropArea.width/o.naturalWidth))+"px"})}scaleAndMoveFocusArea(t){this.currentCropVariant.focusArea=t,this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger),this.updateCropVariantData(this.currentCropVariant)}updateCropVariantData(t){const e=this.cropper.getImageData(),r=this.convertAbsoluteToRelativeCropArea(t.cropArea,e);this.data[t.id]=Object.assign({},t,{cropArea:r})}setAspectRatio(t){this.cropper.setAspectRatio(t.value)}setCropArea(t){const e=this.currentCropVariant.allowedAspectRatios[this.currentCropVariant.selectedRatio];0===e.value?this.cropper.setData({height:t.height,width:t.width,x:t.x,y:t.y}):this.cropper.setData({height:t.height,width:t.height*e.value,x:t.x,y:t.y})}checkFocusAndCoverAreasCollision(t,e){return!!e&&e.some((e=>t.x<e.x+e.width&&e.x<t.x+t.width&&t.y<e.y+e.height&&e.y<t.height+t.y))}convertAbsoluteToRelativeCropArea(t,e){const{height:r,width:a,x:i,y:o}=t;return{height:r/e.naturalHeight,width:a/e.naturalWidth,x:i/e.naturalWidth,y:o/e.naturalHeight}}convertRelativeToAbsoluteCropArea(t,e){const{height:r,width:a,x:i,y:o}=t;return{height:r*e.naturalHeight,width:a*e.naturalWidth,x:i*e.naturalWidth,y:o*e.naturalHeight}}setPreviewImages(t){const e=this.cropper.image,r=this.cropper.getImageData();Object.keys(t).forEach((a=>{const i=t[a],o=this.convertRelativeToAbsoluteCropArea(i.cropArea,r),s=this.trigger.closest(".form-group").querySelector(`.t3js-image-manipulation-preview[data-crop-variant-id="${a}"]`),n=this.trigger.closest(".form-group").querySelector(`.t3js-image-manipulation-selected-ratio[data-crop-variant-id="${a}"]`);if(!(s instanceof HTMLElement))return;let c=s.getBoundingClientRect().width,h=parseInt(s.dataset.previewHeight,10);const l=o.width/o.height,p=c/l;p>h?c=h*l:h=p,c>o.width&&(c=o.width,h=o.height);const u=c/o.width,d={height:r.naturalHeight*u+"px",left:-o.x*u+"px",top:-o.y*u+"px",width:r.naturalWidth*u+"px"},g=html` <span class="thumbnail thumbnail-status"> diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/modal.js index 0175e4712837..b3dcbb469163 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/modal.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/modal.js @@ -41,7 +41,7 @@ var Identifiers,__decorate=function(t,e,a,o){var s,n=arguments.length,i=n<3?e:nu </div> </div> </div> - `}_buttonClick(t,e){const a=t.currentTarget;e.action?(this.activeButton=e,e.action.execute(a).then((()=>this.bootstrapModal.hide()))):e.trigger&&e.trigger(t,this),a.dispatchEvent(new CustomEvent("button.clicked",{bubbles:!0}))}renderAjaxBody(){return null===this.templateResultContent?(new AjaxRequest(this.content).get().then((async t=>{const e=await t.raw().text();this.templateResultContent=html`${unsafeHTML(e)}`,this.updateComplete.then((()=>{this.ajaxCallback&&this.ajaxCallback(this),this.dispatchEvent(new CustomEvent("modal-loaded"))}))})).catch((async t=>{const e=await t.raw().text();this.templateResultContent=e?html`${unsafeHTML(e)}`:html`<p><strong>Oops, received a ${t.response.status} response from </strong> <span class="text-break">${this.content}</span>.</p>`})),html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`):this.templateResultContent}renderModalBody(){if(this.type===Types.ajax)return this.renderAjaxBody();if(this.type===Types.iframe){const t=t=>{const e=t.currentTarget;this.modalTitle=e.contentDocument.title,e.contentDocument.body.classList.add("with-overflow")};return html` + `}_buttonClick(t,e){const a=t.currentTarget;e.action?(this.activeButton=e,e.action.execute(a).then((()=>this.bootstrapModal.hide()))):e.trigger&&e.trigger(t,this),a.dispatchEvent(new CustomEvent("button.clicked",{bubbles:!0}))}renderAjaxBody(){return null===this.templateResultContent?(new AjaxRequest(this.content).get().then((async t=>{const e=await t.raw().text();this.templateResultContent=html`${unsafeHTML(e)}`,this.updateComplete.then((()=>{this.ajaxCallback&&this.ajaxCallback(this),this.dispatchEvent(new CustomEvent("modal-loaded"))}))})).catch((async t=>{const e=await t.raw().text();this.templateResultContent=e?html`${unsafeHTML(e)}`:html`<p><strong>Oops, received a ${t.response.status} response from </strong> <span class="text-break">${this.content}</span>.</p>`})),html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`):this.templateResultContent}renderModalBody(){if(this.type===Types.ajax)return this.renderAjaxBody();if(this.type===Types.iframe){const t=t=>{const e=t.currentTarget;this.modalTitle=e.contentDocument.title,e.contentDocument.body.classList.add("with-overflow")};return html` <iframe src="${this.content}" name="modal_frame" class="modal-iframe t3js-modal-iframe" @load=${t}></iframe> `}return this.type===Types.template?this.templateResultContent:html`<p>${this.content}</p>`}renderModalButton(t){const e={btn:!0,[t.btnClass||"btn-default"]:!0,"t3js-active":t.active,disabled:this.activeButton&&this.activeButton!==t};return html` <button class=${classMap(e)} diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/pagetsconfig/pagetsconfig-includes.js b/typo3/sysext/backend/Resources/Public/JavaScript/pagetsconfig/pagetsconfig-includes.js index 47e8262775a3..06b5bd4ebaf3 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/pagetsconfig/pagetsconfig-includes.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/pagetsconfig/pagetsconfig-includes.js @@ -10,7 +10,7 @@ * * The TYPO3 project - inspiring people to share! */ -import DocumentService from"@typo3/core/document-service.js";import{default as Modal}from"@typo3/backend/modal.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import{html}from"lit";import{until}from"lit/directives/until.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";class PageTsConfigIncludes{constructor(){this.registerEventListeners()}async registerEventListeners(){await DocumentService.ready(),document.querySelectorAll(".t3js-pagetsconfig-includes-modal").forEach((e=>{e.addEventListener("click",(t=>{t.preventDefault();const o=Modal.types.default,r=e.dataset.modalTitle||e.textContent.trim(),a=e.getAttribute("href"),l=Modal.sizes.large,i=html`${until(this.fetchModalContent(a),html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`)}`;Modal.advanced({type:o,title:r,size:l,content:i})}))}))}async fetchModalContent(e){topLevelModuleImport("@typo3/t3editor/element/code-mirror-element.js");const t=await new AjaxRequest(e).get(),o=await t.resolve();return html` +import DocumentService from"@typo3/core/document-service.js";import{default as Modal}from"@typo3/backend/modal.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import{html}from"lit";import{until}from"lit/directives/until.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";class PageTsConfigIncludes{constructor(){this.registerEventListeners()}async registerEventListeners(){await DocumentService.ready(),document.querySelectorAll(".t3js-pagetsconfig-includes-modal").forEach((e=>{e.addEventListener("click",(t=>{t.preventDefault();const o=Modal.types.default,r=e.dataset.modalTitle||e.textContent.trim(),a=e.getAttribute("href"),l=Modal.sizes.large,i=html`${until(this.fetchModalContent(a),html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`)}`;Modal.advanced({type:o,title:r,size:l,content:i})}))}))}async fetchModalContent(e){topLevelModuleImport("@typo3/t3editor/element/code-mirror-element.js");const t=await new AjaxRequest(e).get(),o=await t.resolve();return html` <typo3-t3editor-codemirror .mode="${{name:"@typo3/t3editor/language/typoscript.js",flags:2,exportName:"typoscript",items:[{type:"invoke",args:[]}]}}" nolazyload readonly class="flex-grow-1 mh-100"> <textarea readonly disabled class="form-control">${o}</textarea> </typo3-t3editor-codemirror> diff --git a/typo3/sysext/install/Resources/Public/JavaScript/router.js b/typo3/sysext/install/Resources/Public/JavaScript/router.js index cc932cbc9e8c..0fa98e16a1dc 100644 --- a/typo3/sysext/install/Resources/Public/JavaScript/router.js +++ b/typo3/sysext/install/Resources/Public/JavaScript/router.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import $ from"jquery";import{html}from"lit";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import{default as Modal}from"@typo3/backend/modal.js";import InfoBox from"@typo3/install/renderable/info-box.js";import ProgressBar from"@typo3/install/renderable/progress-bar.js";import Severity from"@typo3/install/renderable/severity.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import"@typo3/backend/element/spinner-element.js";class Router{constructor(){this.rootSelector=".t3js-body",this.contentSelector=".t3js-module-body",this.scaffoldSelector=".t3js-scaffold",this.scaffoldContentOverlaySelector=".t3js-scaffold-content-overlay",this.scaffoldMenuToggleSelector=".t3js-topbar-button-modulemenu"}setContent(e){this.rootContainer.querySelector(this.contentSelector).innerHTML=e}initialize(){this.rootContainer=document.querySelector(this.rootSelector),this.context=this.rootContainer.dataset.context??"",this.controller=this.rootContainer.dataset.controller??"",this.registerInstallToolRoutes(),$(document).on("click",".t3js-login-lockInstallTool",(e=>{e.preventDefault(),this.logout()})),$(document).on("click",".t3js-login-login",(e=>{e.preventDefault(),this.login()})),$(document).on("keydown","#t3-install-form-password",(e=>{"Enter"===e.key&&(e.preventDefault(),$(".t3js-login-login").trigger("click"))})),$(document).on("click",".card .btn",(e=>{e.preventDefault();const t=$(e.currentTarget),o=t.data("import"),n=t.data("inline");if(void 0!==n&&1===parseInt(n,10))import(o).then((({default:e})=>{e.initialize(t)}));else{const e=t.closest(".card").find(".card-title").html(),n=t.data("modalSize")||Modal.sizes.large;Modal.advanced({type:Modal.types.default,title:e,size:n,content:html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`,additionalCssClasses:["install-tool-modal"],staticBackdrop:!0,callback:e=>{import(o).then((({default:t})=>{window.location!==window.parent.location?topLevelModuleImport("jquery").then((({default:o})=>{t.initialize(o(e))})):t.initialize($(e))}))}})}})),"backend"===this.context?this.executeSilentConfigurationUpdate():this.preAccessCheck()}registerInstallToolRoutes(){void 0===TYPO3.settings&&(TYPO3.settings={ajaxUrls:{icons:window.location.origin+window.location.pathname+"?install[controller]=icon&install[action]=getIcon",icons_cache:window.location.origin+window.location.pathname+"?install[controller]=icon&install[action]=getCacheIdentifier"}})}getUrl(e,t,o){const n=new URL(location.href,window.origin);if(n.searchParams.set("install[controller]",t??this.controller),n.searchParams.set("install[context]",this.context),void 0!==e&&n.searchParams.set("install[action]",e),void 0!==o)for(const[e,t]of Object.entries(o))n.searchParams.set(e,t);return n.toString()}executeSilentConfigurationUpdate(){this.updateLoadingInfo("Checking session and executing silent configuration update"),new AjaxRequest(this.getUrl("executeSilentConfigurationUpdate","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentTemplateFileUpdate():this.executeSilentConfigurationUpdate()}),(e=>{this.handleAjaxError(e)}))}executeSilentTemplateFileUpdate(){this.updateLoadingInfo("Checking session and executing silent template file update"),new AjaxRequest(this.getUrl("executeSilentTemplateFileUpdate","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentExtensionConfigurationSynchronization():this.executeSilentTemplateFileUpdate()}),(e=>{this.handleAjaxError(e)}))}executeSilentExtensionConfigurationSynchronization(){this.updateLoadingInfo("Executing silent extension configuration synchronization"),new AjaxRequest(this.getUrl("executeSilentExtensionConfigurationSynchronization","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.loadMainLayout():this.setContent(InfoBox.render(Severity.error,"Something went wrong","").html())}),(e=>{this.handleAjaxError(e)}))}loadMainLayout(){this.updateLoadingInfo("Loading main layout"),new AjaxRequest(this.getUrl("mainLayout","layout",{"install[module]":this.controller})).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&"undefined"!==t.html&&t.html.length>0?(this.rootContainer.innerHTML=t.html,"backend"!==this.context&&(this.rootContainer.querySelector('[data-installroute-controller="'+this.controller+'"]').classList.add("modulemenu-action-active"),this.registerScaffoldEvents()),this.loadCards()):this.rootContainer.innerHTML=InfoBox.render(Severity.error,"Something went wrong","").html()}),(e=>{this.handleAjaxError(e)}))}async handleAjaxError(e,t){if(403===e.response.status)"backend"===this.context?this.rootContainer.innerHTML=InfoBox.render(Severity.error,"The install tool session expired. Please reload the backend and try again.").html():this.checkEnableInstallToolFile();else{const o='<div class="t3js-infobox callout callout-sm callout-danger"><h4 class="callout-title">Something went wrong</h4><div class="callout-body"><p>Please use <b><a href="'+this.getUrl(void 0,"upgrade")+'">Check for broken extensions</a></b> to see if a loaded extension breaks this part of the install tool and unload it.</p><p>The box below may additionally reveal further details on what went wrong depending on your debug settings. It may help to temporarily switch to debug mode using <b>Settings > Configuration Presets > Debug settings.</b></p><p>If this error happens at an early state and no full exception back trace is shown, it may also help to manually increase debugging output in <strong>%config-dir%/system/settings.php</strong>:<code>[\'BE\'][\'debug\'] => true</code>, <code>[\'SYS\'][\'devIPmask\'] => \'*\'</code>, <code>[\'SYS\'][\'displayErrors\'] => 1</code>,<code>[\'SYS\'][\'exceptionalErrors\'] => 12290</code></p></div></div><div class="panel-group" role="tablist" aria-multiselectable="true"><div class="panel panel-default searchhit"><div class="panel-heading" role="tab" id="heading-error"><h3 class="panel-title"><a role="button" data-bs-toggle="collapse" data-bs-parent="#accordion" href="#collapse-error" aria-expanded="true" aria-controls="collapse-error" class="collapsed"><span class="caret"></span><strong>Ajax error</strong></a></h3></div><div id="collapse-error" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-error"><div class="panel-body">'+await e.response.text()+"</div></div></div></div>";void 0!==t?$(t).empty().html(o):this.rootContainer.innerHTML=o}}checkEnableInstallToolFile(){new AjaxRequest(this.getUrl("checkEnableInstallToolFile")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkLogin():this.showEnableInstallTool()}),(e=>{this.handleAjaxError(e)}))}showEnableInstallTool(){new AjaxRequest(this.getUrl("showEnableInstallToolFile")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&(this.rootContainer.innerHTML=t.html)}),(e=>{this.handleAjaxError(e)}))}checkLogin(){new AjaxRequest(this.getUrl("checkLogin")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.loadMainLayout():this.showLogin()}),(e=>{this.handleAjaxError(e)}))}showLogin(){new AjaxRequest(this.getUrl("showLogin")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&(this.rootContainer.innerHTML=t.html)}),(e=>{this.handleAjaxError(e)}))}login(){const e=$(".t3js-login-output"),t=ProgressBar.render(Severity.loading,"Loading...","");e.empty().append(t),new AjaxRequest(this.getUrl()).post({install:{action:"login",token:$("[data-login-token]").data("login-token"),password:$(".t3-install-form-input-text").val()}}).then((async t=>{const o=await t.resolve();!0===o.success?this.executeSilentConfigurationUpdate():o.status.forEach((t=>{const o=InfoBox.render(t.severity,t.title,t.message);e.empty().append(o)}))}),(e=>{this.handleAjaxError(e)}))}logout(){new AjaxRequest(this.getUrl("logout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success&&this.showEnableInstallTool()}),(e=>{this.handleAjaxError(e)}))}loadCards(){new AjaxRequest(this.getUrl("cards")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&"undefined"!==t.html&&t.html.length>0?this.setContent(t.html):this.setContent(InfoBox.render(Severity.error,"Something went wrong","").html())}),(e=>{this.handleAjaxError(e)}))}registerScaffoldEvents(){localStorage.getItem("typo3-install-modulesCollapsed")||localStorage.setItem("typo3-install-modulesCollapsed","false"),this.toggleMenu("true"===localStorage.getItem("typo3-install-modulesCollapsed")),document.querySelector(this.scaffoldMenuToggleSelector).addEventListener("click",(e=>{e.preventDefault(),this.toggleMenu()})),document.querySelector(this.scaffoldContentOverlaySelector).addEventListener("click",(e=>{e.preventDefault(),this.toggleMenu(!0)})),document.querySelectorAll("[data-installroute-controller]").forEach((e=>{e.addEventListener("click",(()=>{window.innerWidth<768&&localStorage.setItem("typo3-install-modulesCollapsed","true")}))}))}toggleMenu(e){const t=document.querySelector(this.scaffoldSelector),o="scaffold-modulemenu-expanded";void 0===e&&(e=t.classList.contains(o)),t.classList.toggle(o,!e),localStorage.setItem("typo3-install-modulesCollapsed",e?"true":"false")}updateLoadingInfo(e){const t=this.rootContainer.querySelector("#t3js-ui-block-detail");void 0!==t&&t instanceof HTMLElement&&(t.innerText=e)}preAccessCheck(){this.updateLoadingInfo("Execute pre access check"),new AjaxRequest(this.getUrl("preAccessCheck","layout")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();t.installToolLocked?this.checkEnableInstallToolFile():t.isAuthorized?this.executeSilentConfigurationUpdate():this.showLogin()}),(e=>{this.handleAjaxError(e)}))}}export default new Router; \ No newline at end of file +import $ from"jquery";import{html}from"lit";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import{default as Modal}from"@typo3/backend/modal.js";import InfoBox from"@typo3/install/renderable/info-box.js";import ProgressBar from"@typo3/install/renderable/progress-bar.js";import Severity from"@typo3/install/renderable/severity.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import"@typo3/backend/element/spinner-element.js";class Router{constructor(){this.rootSelector=".t3js-body",this.contentSelector=".t3js-module-body",this.scaffoldSelector=".t3js-scaffold",this.scaffoldContentOverlaySelector=".t3js-scaffold-content-overlay",this.scaffoldMenuToggleSelector=".t3js-topbar-button-modulemenu"}setContent(e){this.rootContainer.querySelector(this.contentSelector).innerHTML=e}initialize(){this.rootContainer=document.querySelector(this.rootSelector),this.context=this.rootContainer.dataset.context??"",this.controller=this.rootContainer.dataset.controller??"",this.registerInstallToolRoutes(),$(document).on("click",".t3js-login-lockInstallTool",(e=>{e.preventDefault(),this.logout()})),$(document).on("click",".t3js-login-login",(e=>{e.preventDefault(),this.login()})),$(document).on("keydown","#t3-install-form-password",(e=>{"Enter"===e.key&&(e.preventDefault(),$(".t3js-login-login").trigger("click"))})),$(document).on("click",".card .btn",(e=>{e.preventDefault();const t=$(e.currentTarget),o=t.data("import"),n=t.data("inline");if(void 0!==n&&1===parseInt(n,10))import(o).then((({default:e})=>{e.initialize(t)}));else{const e=t.closest(".card").find(".card-title").html(),n=t.data("modalSize")||Modal.sizes.large;Modal.advanced({type:Modal.types.default,title:e,size:n,content:html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`,additionalCssClasses:["install-tool-modal"],staticBackdrop:!0,callback:e=>{import(o).then((({default:t})=>{window.location!==window.parent.location?topLevelModuleImport("jquery").then((({default:o})=>{t.initialize(o(e))})):t.initialize($(e))}))}})}})),"backend"===this.context?this.executeSilentConfigurationUpdate():this.preAccessCheck()}registerInstallToolRoutes(){void 0===TYPO3.settings&&(TYPO3.settings={ajaxUrls:{icons:window.location.origin+window.location.pathname+"?install[controller]=icon&install[action]=getIcon",icons_cache:window.location.origin+window.location.pathname+"?install[controller]=icon&install[action]=getCacheIdentifier"}})}getUrl(e,t,o){const n=new URL(location.href,window.origin);if(n.searchParams.set("install[controller]",t??this.controller),n.searchParams.set("install[context]",this.context),void 0!==e&&n.searchParams.set("install[action]",e),void 0!==o)for(const[e,t]of Object.entries(o))n.searchParams.set(e,t);return n.toString()}executeSilentConfigurationUpdate(){this.updateLoadingInfo("Checking session and executing silent configuration update"),new AjaxRequest(this.getUrl("executeSilentConfigurationUpdate","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentTemplateFileUpdate():this.executeSilentConfigurationUpdate()}),(e=>{this.handleAjaxError(e)}))}executeSilentTemplateFileUpdate(){this.updateLoadingInfo("Checking session and executing silent template file update"),new AjaxRequest(this.getUrl("executeSilentTemplateFileUpdate","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentExtensionConfigurationSynchronization():this.executeSilentTemplateFileUpdate()}),(e=>{this.handleAjaxError(e)}))}executeSilentExtensionConfigurationSynchronization(){this.updateLoadingInfo("Executing silent extension configuration synchronization"),new AjaxRequest(this.getUrl("executeSilentExtensionConfigurationSynchronization","layout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.loadMainLayout():this.setContent(InfoBox.render(Severity.error,"Something went wrong","").html())}),(e=>{this.handleAjaxError(e)}))}loadMainLayout(){this.updateLoadingInfo("Loading main layout"),new AjaxRequest(this.getUrl("mainLayout","layout",{"install[module]":this.controller})).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&"undefined"!==t.html&&t.html.length>0?(this.rootContainer.innerHTML=t.html,"backend"!==this.context&&(this.rootContainer.querySelector('[data-installroute-controller="'+this.controller+'"]').classList.add("modulemenu-action-active"),this.registerScaffoldEvents()),this.loadCards()):this.rootContainer.innerHTML=InfoBox.render(Severity.error,"Something went wrong","").html()}),(e=>{this.handleAjaxError(e)}))}async handleAjaxError(e,t){if(403===e.response.status)"backend"===this.context?this.rootContainer.innerHTML=InfoBox.render(Severity.error,"The install tool session expired. Please reload the backend and try again.").html():this.checkEnableInstallToolFile();else{const o='<div class="t3js-infobox callout callout-sm callout-danger"><h4 class="callout-title">Something went wrong</h4><div class="callout-body"><p>Please use <b><a href="'+this.getUrl(void 0,"upgrade")+'">Check for broken extensions</a></b> to see if a loaded extension breaks this part of the install tool and unload it.</p><p>The box below may additionally reveal further details on what went wrong depending on your debug settings. It may help to temporarily switch to debug mode using <b>Settings > Configuration Presets > Debug settings.</b></p><p>If this error happens at an early state and no full exception back trace is shown, it may also help to manually increase debugging output in <strong>%config-dir%/system/settings.php</strong>:<code>[\'BE\'][\'debug\'] => true</code>, <code>[\'SYS\'][\'devIPmask\'] => \'*\'</code>, <code>[\'SYS\'][\'displayErrors\'] => 1</code>,<code>[\'SYS\'][\'exceptionalErrors\'] => 12290</code></p></div></div><div class="panel-group" role="tablist" aria-multiselectable="true"><div class="panel panel-default searchhit"><div class="panel-heading" role="tab" id="heading-error"><h3 class="panel-title"><a role="button" data-bs-toggle="collapse" data-bs-parent="#accordion" href="#collapse-error" aria-expanded="true" aria-controls="collapse-error" class="collapsed"><span class="caret"></span><strong>Ajax error</strong></a></h3></div><div id="collapse-error" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-error"><div class="panel-body">'+await e.response.text()+"</div></div></div></div>";void 0!==t?$(t).empty().html(o):this.rootContainer.innerHTML=o}}checkEnableInstallToolFile(){new AjaxRequest(this.getUrl("checkEnableInstallToolFile")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkLogin():this.showEnableInstallTool()}),(e=>{this.handleAjaxError(e)}))}showEnableInstallTool(){new AjaxRequest(this.getUrl("showEnableInstallToolFile")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&(this.rootContainer.innerHTML=t.html)}),(e=>{this.handleAjaxError(e)}))}checkLogin(){new AjaxRequest(this.getUrl("checkLogin")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.loadMainLayout():this.showLogin()}),(e=>{this.handleAjaxError(e)}))}showLogin(){new AjaxRequest(this.getUrl("showLogin")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&(this.rootContainer.innerHTML=t.html)}),(e=>{this.handleAjaxError(e)}))}login(){const e=$(".t3js-login-output"),t=ProgressBar.render(Severity.loading,"Loading...","");e.empty().append(t),new AjaxRequest(this.getUrl()).post({install:{action:"login",token:$("[data-login-token]").data("login-token"),password:$(".t3-install-form-input-text").val()}}).then((async t=>{const o=await t.resolve();!0===o.success?this.executeSilentConfigurationUpdate():o.status.forEach((t=>{const o=InfoBox.render(t.severity,t.title,t.message);e.empty().append(o)}))}),(e=>{this.handleAjaxError(e)}))}logout(){new AjaxRequest(this.getUrl("logout")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success&&this.showEnableInstallTool()}),(e=>{this.handleAjaxError(e)}))}loadCards(){new AjaxRequest(this.getUrl("cards")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();!0===t.success&&"undefined"!==t.html&&t.html.length>0?this.setContent(t.html):this.setContent(InfoBox.render(Severity.error,"Something went wrong","").html())}),(e=>{this.handleAjaxError(e)}))}registerScaffoldEvents(){localStorage.getItem("typo3-install-modulesCollapsed")||localStorage.setItem("typo3-install-modulesCollapsed","false"),this.toggleMenu("true"===localStorage.getItem("typo3-install-modulesCollapsed")),document.querySelector(this.scaffoldMenuToggleSelector).addEventListener("click",(e=>{e.preventDefault(),this.toggleMenu()})),document.querySelector(this.scaffoldContentOverlaySelector).addEventListener("click",(e=>{e.preventDefault(),this.toggleMenu(!0)})),document.querySelectorAll("[data-installroute-controller]").forEach((e=>{e.addEventListener("click",(()=>{window.innerWidth<768&&localStorage.setItem("typo3-install-modulesCollapsed","true")}))}))}toggleMenu(e){const t=document.querySelector(this.scaffoldSelector),o="scaffold-modulemenu-expanded";void 0===e&&(e=t.classList.contains(o)),t.classList.toggle(o,!e),localStorage.setItem("typo3-install-modulesCollapsed",e?"true":"false")}updateLoadingInfo(e){const t=this.rootContainer.querySelector("#t3js-ui-block-detail");void 0!==t&&t instanceof HTMLElement&&(t.innerText=e)}preAccessCheck(){this.updateLoadingInfo("Execute pre access check"),new AjaxRequest(this.getUrl("preAccessCheck","layout")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();t.installToolLocked?this.checkEnableInstallToolFile():t.isAuthorized?this.executeSilentConfigurationUpdate():this.showLogin()}),(e=>{this.handleAjaxError(e)}))}}export default new Router; \ No newline at end of file diff --git a/typo3/sysext/tstemplate/Resources/Public/JavaScript/template-analyzer.js b/typo3/sysext/tstemplate/Resources/Public/JavaScript/template-analyzer.js index 2befe3ae7e0d..ece241dae6b3 100644 --- a/typo3/sysext/tstemplate/Resources/Public/JavaScript/template-analyzer.js +++ b/typo3/sysext/tstemplate/Resources/Public/JavaScript/template-analyzer.js @@ -10,7 +10,7 @@ * * The TYPO3 project - inspiring people to share! */ -import DocumentService from"@typo3/core/document-service.js";import{default as Modal}from"@typo3/backend/modal.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import{html}from"lit";import{until}from"lit/directives/until.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";class TemplateAnalyzer{constructor(){this.registerEventListeners()}async registerEventListeners(){await DocumentService.ready(),document.querySelectorAll(".t3js-typoscript-analyzer-modal").forEach((e=>{e.addEventListener("click",(t=>{t.preventDefault();const o=Modal.types.default,r=e.dataset.modalTitle||e.textContent.trim(),a=e.getAttribute("href"),l=Modal.sizes.large,i=html`${until(this.fetchModalContent(a),html`<div class="modal-loading"><typo3-backend-spinner size="default"></typo3-backend-spinner></div>`)}`;Modal.advanced({type:o,title:r,size:l,content:i})}))}))}async fetchModalContent(e){topLevelModuleImport("@typo3/t3editor/element/code-mirror-element.js");const t=await new AjaxRequest(e).get(),o=await t.resolve();return html` +import DocumentService from"@typo3/core/document-service.js";import{default as Modal}from"@typo3/backend/modal.js";import{topLevelModuleImport}from"@typo3/backend/utility/top-level-module-import.js";import{html}from"lit";import{until}from"lit/directives/until.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";class TemplateAnalyzer{constructor(){this.registerEventListeners()}async registerEventListeners(){await DocumentService.ready(),document.querySelectorAll(".t3js-typoscript-analyzer-modal").forEach((e=>{e.addEventListener("click",(t=>{t.preventDefault();const o=Modal.types.default,r=e.dataset.modalTitle||e.textContent.trim(),a=e.getAttribute("href"),l=Modal.sizes.large,i=html`${until(this.fetchModalContent(a),html`<div class="modal-loading"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`)}`;Modal.advanced({type:o,title:r,size:l,content:i})}))}))}async fetchModalContent(e){topLevelModuleImport("@typo3/t3editor/element/code-mirror-element.js");const t=await new AjaxRequest(e).get(),o=await t.resolve();return html` <typo3-t3editor-codemirror .mode="${{name:"@typo3/t3editor/language/typoscript.js",flags:2,exportName:"typoscript",items:[{type:"invoke",args:[]}]}}" nolazyload readonly class="flex-grow-1 mh-100"> <textarea readonly disabled class="form-control">${o}</textarea> </typo3-t3editor-codemirror> -- GitLab