From 9199f2540db48dab3ddd2d0fa9434baf58744c1a Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Mon, 13 Apr 2020 19:27:49 +0200
Subject: [PATCH] [TASK] Reduce inline JavaScript in ext:filelist

This change aims to reduce the amount of inline JavaScript by
removing `onchange` or `onclick` events and dynamically created
JavaScript code/settings. There are still a couple of aspects
that need to be solved in a general way - e.g. assigning global
language labels (`TYPO3.lang`) or settings (`TYPO3.settings`).

* allows `Modal` confirmation to emit `ModalResponseEvent`
* extracts inline JavaScript from controllers & templates
* introduces `BroadcastChannel` in `nav_frame` (folder tree)
* uses plain links (`<a href="..."`) where applicable
* updates folder tree when navigating in browser history

Resolves: #91016
Releases: master
Change-Id: Ied6a626e09df07bd1a240b9b6b4250cd6fff4c8a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64154
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../Resources/Public/TypeScript/Modal.ts      | 24 +++++-
 .../Public/TypeScript/ModalInterface.ts       | 19 +++++
 .../Public/TypeScript/CreateFolder.ts         | 73 +++++++++++++++++
 .../Resources/Public/TypeScript/FileList.ts   | 80 +++++++++++++++---
 .../Public/TypeScript/FileReplace.ts          | 34 ++++++++
 .../FileSystemNavigationFrameController.php   |  1 +
 .../Resources/Public/JavaScript/LegacyTree.js | 11 ++-
 .../Resources/Public/JavaScript/Modal.js      |  2 +-
 .../Public/JavaScript/ModalInterface.js       | 13 +++
 .../File/CreateFolderController.php           | 66 ++++++---------
 .../Controller/File/EditFileController.php    | 42 ++++------
 .../Controller/File/RenameFileController.php  |  6 --
 .../Controller/File/ReplaceFileController.php |  5 +-
 .../Classes/Controller/FileListController.php | 34 +++-----
 typo3/sysext/filelist/Classes/FileList.php    | 82 +++++++++++--------
 .../Resources/Private/Layouts/Default.html    |  7 +-
 .../Private/Templates/File/CreateFolder.html  |  8 +-
 .../Private/Templates/File/ReplaceFile.html   | 19 ++---
 .../Public/JavaScript/CreateFolder.js         | 13 +++
 .../Resources/Public/JavaScript/FileList.js   |  2 +-
 .../Public/JavaScript/FileReplace.js          | 13 +++
 21 files changed, 388 insertions(+), 166 deletions(-)
 create mode 100644 Build/Sources/TypeScript/backend/Resources/Public/TypeScript/ModalInterface.ts
 create mode 100644 Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/CreateFolder.ts
 create mode 100644 Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileReplace.ts
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/ModalInterface.js
 create mode 100644 typo3/sysext/filelist/Resources/Public/JavaScript/CreateFolder.js
 create mode 100644 typo3/sysext/filelist/Resources/Public/JavaScript/FileReplace.js

diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Modal.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Modal.ts
index e706f36ae226..61ebe1125962 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Modal.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Modal.ts
@@ -15,6 +15,7 @@ import 'bootstrap';
 import * as $ from 'jquery';
 import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
 import {AbstractAction} from './ActionButton/AbstractAction';
+import {ModalResponseEvent} from 'TYPO3/CMS/Backend/ModalInterface';
 import {SeverityEnum} from './Enum/Severity';
 import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
 import SecurityUtility = require('TYPO3/CMS/Core/SecurityUtility');
@@ -123,6 +124,16 @@ class Modal {
 
   private readonly securityUtility: SecurityUtility;
 
+  private static createModalResponseEventFromElement(element: HTMLElement, result: boolean): ModalResponseEvent | null {
+    if (!element.dataset.eventName) {
+      return null;
+    }
+    return new CustomEvent(
+      element.dataset.eventName, {
+        detail: { result, payload: element.dataset.eventPayload || null }
+      });
+  }
+
   constructor(securityUtility: SecurityUtility) {
     this.securityUtility = securityUtility;
     $(document).on('modal-dismiss', this.dismiss);
@@ -377,6 +388,10 @@ class Modal {
             btnClass: 'btn-default',
             trigger: (): void => {
               this.currentModal.trigger('modal-dismiss');
+              const event = Modal.createModalResponseEventFromElement(evt.currentTarget as HTMLElement, false);
+              if (event !== null) {
+                evt.currentTarget.dispatchEvent(event);
+              }
             },
           },
           {
@@ -384,7 +399,14 @@ class Modal {
             btnClass: 'btn-' + Severity.getCssClass(severity),
             trigger: (): void => {
               this.currentModal.trigger('modal-dismiss');
-              evt.target.ownerDocument.location.href = $element.data('href') || $element.attr('href');
+              const event = Modal.createModalResponseEventFromElement(evt.currentTarget as HTMLElement, true);
+              if (event !== null) {
+                evt.currentTarget.dispatchEvent(event);
+              }
+              const href = $element.data('href') || $element.attr('href');
+              if (href && href !== '#') {
+                evt.target.ownerDocument.location.href = href;
+              }
             },
           },
         ],
diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/ModalInterface.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/ModalInterface.ts
new file mode 100644
index 000000000000..9a123380fe22
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/ModalInterface.ts
@@ -0,0 +1,19 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+export interface ModalResponseEvent extends CustomEvent {
+  readonly detail: {
+    result: boolean,
+    payload: any
+  };
+}
diff --git a/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/CreateFolder.ts b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/CreateFolder.ts
new file mode 100644
index 000000000000..15b5f53710d3
--- /dev/null
+++ b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/CreateFolder.ts
@@ -0,0 +1,73 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import * as $ from 'jquery';
+import Modal = require('TYPO3/CMS/Backend/Modal');
+import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
+
+/**
+ * Module: TYPO3/CMS/Filelist/CreateFolder
+ * @exports TYPO3/CMS/Filelist/CreateFolder
+ */
+class CreateFolder {
+  private selfUrl: string;
+  private confirmTitle: string;
+  private confirmText: string;
+  private changed: boolean = false;
+
+  constructor() {
+    $((): void => {
+      const mainElement: HTMLElement = document.querySelector('.filelist-create-folder-main');
+      if (!(mainElement instanceof HTMLElement)) {
+        throw new Error('Main element not found');
+      }
+      this.selfUrl = mainElement.dataset.selfUrl;
+      this.confirmTitle = mainElement.dataset.confirmTitle;
+      this.confirmText = mainElement.dataset.confirmText;
+      this.registerEvents();
+    });
+  }
+
+  public reload(amount: number): void {
+    const url = this.selfUrl.replace(/AMOUNT/, amount.toString());
+    if (!this.changed) {
+      window.location.href = url;
+    } else {
+      const modal = Modal.confirm(this.confirmTitle, this.confirmText);
+      modal.on('confirm.button.cancel', (): void => {
+        modal.trigger('modal-dismiss');
+      });
+      modal.on('confirm.button.ok', (): void => {
+        modal.trigger('modal-dismiss');
+        window.location.href = url;
+      });
+    }
+  }
+
+  private registerEvents(): void {
+    const inputElementSelectors = [
+      'input[type="text"][name^="data[newfolder]"]',
+      'input[type="text"][name^="data[newfile]"]',
+      'input[type="text"][name^="data[newMedia]"]'
+    ];
+    new RegularEvent('change', (): void => {
+      this.changed = true;
+    }).delegateTo(document, inputElementSelectors.join(','));
+    new RegularEvent('change', (e: Event): void => {
+      const amount = parseInt((e.target as HTMLSelectElement).value, 10);
+      this.reload(amount);
+    }).bindTo(document.getElementById('number-of-new-folders'));
+  }
+}
+
+export = new CreateFolder();
diff --git a/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileList.ts b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileList.ts
index 4cb94e4c4dd0..df34f205079f 100644
--- a/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileList.ts
+++ b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileList.ts
@@ -13,31 +13,81 @@
 
 import * as $ from 'jquery';
 import InfoWindow = require('TYPO3/CMS/Backend/InfoWindow');
+import {BroadcastMessage} from 'TYPO3/CMS/Backend/BroadcastMessage';
+import {ModalResponseEvent} from 'TYPO3/CMS/Backend/ModalInterface';
+import broadcastService = require('TYPO3/CMS/Backend/BroadcastService');
+import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
 
 /**
  * Module: TYPO3/CMS/Filelist/Filelist
  * @exports TYPO3/CMS/Filelist/Filelist
  */
 class Filelist {
-  /**
-   * @param identifier
-   */
-  private static openFileInfoPopup(identifier: string): void {
-    InfoWindow.showItem('_FILE', identifier);
+  protected static openInfoPopup(type: string, identifier: string): void {
+    InfoWindow.showItem(type, identifier);
+  }
+
+  private static processTriggers(): void {
+    const mainElement: HTMLElement = document.querySelector('.filelist-main');
+    if (mainElement === null) {
+      return
+    }
+    // emit event for currently shown folder
+    Filelist.emitTreeUpdateRequest(
+      mainElement.dataset.filelistCurrentFolderHash
+    );
+    // update recentIds (for whatever reason)
+    if (top.fsMod && top.fsMod.recentIds instanceof Object) {
+      top.fsMod.recentIds.file = encodeURIComponent(mainElement.dataset.filelistCurrentIdentifier);
+    }
+  }
+
+  private static registerTreeUpdateEvents(): void {
+    // listen potential change of folder
+    new RegularEvent('click', function (this: HTMLElement): void {
+      Filelist.emitTreeUpdateRequest(
+        this.dataset.treeUpdateRequest
+      );
+    }).delegateTo(document.body, '[data-tree-update-request]');
+  }
+
+  private static emitTreeUpdateRequest(identifier: string): void {
+    const message = new BroadcastMessage(
+      'filelist',
+      'treeUpdateRequested',
+      {type: 'folder', identifier: identifier}
+    );
+    broadcastService.post(message);
+  }
+
+  private static submitClipboardFormWithCommand(cmd: string): void {
+    const $form = $('form[name="dblistForm"]');
+    $form.find('input[name="cmd"]').val(cmd);
+    $form.submit();
   }
 
   constructor() {
+    Filelist.processTriggers();
     $((): void => {
+      Filelist.registerTreeUpdateEvents();
+      // file index events
+      $('[data-filelist-show-item-identifier][data-filelist-show-item-type]').click((evt: JQueryEventObject): void => {
+        const $element = $(evt.currentTarget);
+        evt.preventDefault();
+        Filelist.openInfoPopup(
+          $element.data('filelistShowItemType'),
+          $element.data('filelistShowItemIdentifier')
+        );
+      });
+      // file search events
       $('a.btn.filelist-file-info').click((event: JQueryEventObject): void => {
         event.preventDefault();
-        Filelist.openFileInfoPopup($(event.currentTarget).attr('data-identifier'));
+        Filelist.openInfoPopup('_FILE', $(event.currentTarget).attr('data-identifier'));
       });
-
       $('a.filelist-file-references').click((event: JQueryEventObject): void => {
         event.preventDefault();
-        Filelist.openFileInfoPopup($(event.currentTarget).attr('data-identifier'));
+        Filelist.openInfoPopup('_FILE', $(event.currentTarget).attr('data-identifier'));
       });
-
       $('a.btn.filelist-file-copy').click((event: JQueryEventObject): void => {
         event.preventDefault();
         const $element = $(event.currentTarget);
@@ -45,9 +95,19 @@ class Filelist {
         let redirectUrl = (url)
           ? encodeURIComponent(url)
           : encodeURIComponent(top.list_frame.document.location.pathname + top.list_frame.document.location.search);
-
         top.list_frame.location.href = url + '&redirect=' + redirectUrl;
       });
+      // clipboard events
+      $('[data-event-name="filelist:clipboard:cmd"]').on('filelist:clipboard:cmd', (evt: JQueryEventObject) => {
+        const originalEvent = evt.originalEvent as ModalResponseEvent;
+        if (originalEvent.detail.result) {
+          Filelist.submitClipboardFormWithCommand(originalEvent.detail.payload);
+        }
+      });
+      $('[data-filelist-clipboard-cmd]:not([data-filelist-clipboard-cmd=""])').click((evt: JQueryEventObject): void => {
+        const cmd = $(evt.currentTarget).data('filelistClipboardCmd');
+        Filelist.submitClipboardFormWithCommand(cmd);
+      });
     });
   }
 }
diff --git a/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileReplace.ts b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileReplace.ts
new file mode 100644
index 000000000000..13612df28c3a
--- /dev/null
+++ b/Build/Sources/TypeScript/filelist/Resources/Public/TypeScript/FileReplace.ts
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
+
+class FileReplace {
+  constructor() {
+    this.registerEvents();
+  }
+
+  private registerEvents(): void {
+    new RegularEvent('click', function (this: HTMLElement): void {
+      const targetSelector = this.dataset.filelistClickTarget;
+      (document.querySelector(targetSelector) as HTMLElement).click();
+    }).delegateTo(document.body, '[data-filelist-click-target]:not([data-filelist-click-target=""]');
+
+    new RegularEvent('change', function (this: HTMLInputElement): void {
+      const targetSelector = this.dataset.filelistChangeTarget;
+      (document.querySelector(targetSelector) as HTMLInputElement).value = this.value;
+    }).delegateTo(document.body, '[data-filelist-change-target]:not([data-filelist-change-target=""])');
+  }
+}
+
+export = new FileReplace();
diff --git a/typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php b/typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php
index 17911be79543..611a2ad317e2 100644
--- a/typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php
+++ b/typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php
@@ -161,6 +161,7 @@ class FileSystemNavigationFrameController
         // Adding javascript for drag & drop activation and highlighting
         $pageRenderer = $this->moduleTemplate->getPageRenderer();
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
+        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/BroadcastService', 'function(service) { service.listen(); }');
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree', 'function() {
             DragDrop.table = "folders";
             Tree.registerDragDropHandlers();
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js b/typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js
index 389e535dbbd8..5c904b4ddb50 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js
@@ -23,7 +23,6 @@ var Tree, DragDrop;
  * @exports TYPO3/CMS/Backend/LegacyTree
  */
 define(['jquery'], function($) {
-
   DragDrop = {
     dragID: null,
     table: null	// can be "pages" or "folders", needed for doing the changes when dropping
@@ -214,5 +213,15 @@ define(['jquery'], function($) {
     }
   };
 
+  // event listener updating current tree state
+  document.addEventListener('typo3:filelist:treeUpdateRequested', (evt) => {
+    var identifier = evt.detail.payload.identifier;
+    // @todo currentBank is not defined unless item was clicked in tree
+    if (top.fsMod && top.fsMod.currentBank) {
+      identifier += '_' + top.fsMod.currentBank;
+    }
+    Tree.highlightActiveItem('file', identifier);
+  });
+
   return Tree;
 });
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
index 9b50004fc5c7..755df6112dcd 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","jquery","./Enum/Severity","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Core/SecurityUtility","./Icons","./Severity","bootstrap"],(function(t,e,a,n,i,s,l,o){"use strict";var d,r,c,u;!function(t){t.modal=".t3js-modal",t.content=".t3js-modal-content",t.title=".t3js-modal-title",t.close=".t3js-modal-close",t.body=".t3js-modal-body",t.footer=".t3js-modal-footer",t.iframe=".t3js-modal-iframe",t.iconPlaceholder=".t3js-modal-icon-placeholder"}(d||(d={})),function(t){t.small="small",t.default="default",t.medium="medium",t.large="large",t.full="full"}(r||(r={})),function(t){t.default="default",t.light="light",t.dark="dark"}(c||(c={})),function(t){t.default="default",t.ajax="ajax",t.iframe="iframe"}(u||(u={}));class f{constructor(t){this.sizes=r,this.styles=c,this.types=u,this.currentModal=null,this.instances=[],this.$template=a('<div class="t3js-modal modal fade"><div class="modal-dialog"><div class="t3js-modal-content modal-content"><div class="modal-header"><button class="t3js-modal-close close"><span aria-hidden="true"><span class="t3js-modal-icon-placeholder" data-icon="actions-close"></span></span><span class="sr-only"></span></button><h4 class="t3js-modal-title modal-title"></h4></div><div class="t3js-modal-body modal-body"></div><div class="t3js-modal-footer modal-footer"></div></div></div></div>'),this.defaultConfiguration={type:u.default,title:"Information",content:"No content provided, please check your <code>Modal</code> configuration.",severity:n.SeverityEnum.notice,buttons:[],style:c.default,size:r.default,additionalCssClasses:[],callback:a.noop(),ajaxCallback:a.noop(),ajaxTarget:null},this.securityUtility=t,a(document).on("modal-dismiss",this.dismiss),this.initializeMarkupTrigger(document)}dismiss(){this.currentModal&&this.currentModal.modal("hide")}confirm(t,e,i=n.SeverityEnum.warning,s=[],l){return 0===s.length&&s.push({text:a(this).data("button-close-text")||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:a(this).data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(i),name:"ok"}),this.advanced({title:t,content:e,severity:i,buttons:s,additionalCssClasses:l,callback:t=>{t.on("button.clicked",t=>{"cancel"===t.target.getAttribute("name")?a(t.currentTarget).trigger("confirm.button.cancel"):"ok"===t.target.getAttribute("name")&&a(t.currentTarget).trigger("confirm.button.ok")})}})}loadUrl(t,e=n.SeverityEnum.info,a,i,s,l){return this.advanced({type:u.ajax,title:t,severity:e,buttons:a,ajaxCallback:s,ajaxTarget:l,content:i})}show(t,e,a=n.SeverityEnum.info,i,s){return this.advanced({type:u.default,title:t,content:e,severity:a,buttons:i,additionalCssClasses:s})}advanced(t){return t.type="string"==typeof t.type&&t.type in u?t.type:this.defaultConfiguration.type,t.title="string"==typeof t.title?t.title:this.defaultConfiguration.title,t.content="string"==typeof t.content||"object"==typeof t.content?t.content:this.defaultConfiguration.content,t.severity=void 0!==t.severity?t.severity:this.defaultConfiguration.severity,t.buttons=t.buttons||this.defaultConfiguration.buttons,t.size="string"==typeof t.size&&t.size in r?t.size:this.defaultConfiguration.size,t.style="string"==typeof t.style&&t.style in c?t.style:this.defaultConfiguration.style,t.additionalCssClasses=t.additionalCssClasses||this.defaultConfiguration.additionalCssClasses,t.callback="function"==typeof t.callback?t.callback:this.defaultConfiguration.callback,t.ajaxCallback="function"==typeof t.ajaxCallback?t.ajaxCallback:this.defaultConfiguration.ajaxCallback,t.ajaxTarget="string"==typeof t.ajaxTarget?t.ajaxTarget:this.defaultConfiguration.ajaxTarget,this.generate(t)}setButtons(t){const e=this.currentModal.find(d.footer);if(t.length>0){e.empty();for(let n=0;n<t.length;n++){const i=t[n],s=a("<button />",{class:"btn"});s.html("<span>"+this.securityUtility.encodeHtml(i.text,!1)+"</span>"),i.active&&s.addClass("t3js-active"),""!==i.btnClass&&s.addClass(i.btnClass),""!==i.name&&s.attr("name",i.name),i.action?s.on("click",()=>{e.find("button").not(s).addClass("disabled"),i.action.execute(s.get(0)).then(()=>{this.currentModal.modal("hide")})}):i.trigger&&s.on("click",i.trigger),i.dataAttributes&&Object.keys(i.dataAttributes).length>0&&Object.keys(i.dataAttributes).map(t=>{s.attr("data-"+t,i.dataAttributes[t])}),i.icon&&s.prepend('<span class="t3js-modal-icon-placeholder" data-icon="'+i.icon+'"></span>'),e.append(s)}e.show(),e.find("button").on("click",t=>{a(t.currentTarget).trigger("button.clicked")})}else e.hide();return this.currentModal}initializeMarkupTrigger(t){a(t).on("click",".t3js-modal-trigger",t=>{t.preventDefault();const e=a(t.currentTarget),i=e.data("content")||"Are you sure?",s=void 0!==n.SeverityEnum[e.data("severity")]?n.SeverityEnum[e.data("severity")]:n.SeverityEnum.info;let l=e.data("url")||null;if(null!==l){const t=l.includes("?")?"&":"?";l=l+t+a.param({data:e.data()})}this.advanced({type:null!==l?u.ajax:u.default,title:e.data("title")||"Alert",content:null!==l?l:i,severity:s,buttons:[{text:e.data("button-close-text")||TYPO3.lang["button.close"]||"Close",active:!0,btnClass:"btn-default",trigger:()=>{this.currentModal.trigger("modal-dismiss")}},{text:e.data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(s),trigger:()=>{this.currentModal.trigger("modal-dismiss"),t.target.ownerDocument.location.href=e.data("href")||e.attr("href")}}]})})}generate(t){const e=this.$template.clone();if(t.additionalCssClasses.length>0)for(let a of t.additionalCssClasses)e.addClass(a);if(e.addClass("modal-type-"+t.type),e.addClass("modal-severity-"+o.getCssClass(t.severity)),e.addClass("modal-style-"+t.style),e.addClass("modal-size-"+t.size),e.attr("tabindex","-1"),e.find(d.title).text(t.title),e.find(d.close).on("click",()=>{e.modal("hide")}),"ajax"===t.type){const a=t.ajaxTarget?t.ajaxTarget:d.body,n=e.find(a);l.getIcon("spinner-circle",l.sizes.default,null,null,l.markupIdentifiers.inline).then(e=>{n.html('<div class="modal-loading">'+e+"</div>"),new i(t.content).get().then(async e=>{this.currentModal.find(a).empty().append(await e.raw().text()),t.ajaxCallback&&t.ajaxCallback(),this.currentModal.trigger("modal-loaded")})})}else"iframe"===t.type?(e.find(d.body).append(a("<iframe />",{src:t.content,name:"modal_frame",class:"modal-iframe t3js-modal-iframe"})),e.find(d.iframe).on("load",()=>{e.find(d.title).text(e.find(d.iframe).get(0).contentDocument.title)})):("string"==typeof t.content&&(t.content=a("<p />").html(this.securityUtility.encodeHtml(t.content))),e.find(d.body).append(t.content));return e.on("shown.bs.modal",t=>{const e=a(t.currentTarget);e.find(d.footer).find(".t3js-active").first().focus(),e.find(d.iconPlaceholder).each((t,e)=>{l.getIcon(a(e).data("icon"),l.sizes.small,null,null,l.markupIdentifiers.inline).then(t=>{this.currentModal.find(d.iconPlaceholder+"[data-icon="+a(t).data("identifier")+"]").replaceWith(t)})})}),e.on("hidden.bs.modal",t=>{if(this.instances.length>0){const t=this.instances.length-1;this.instances.splice(t,1),this.currentModal=this.instances[t-1]}e.trigger("modal-destroyed"),a(t.currentTarget).remove(),this.instances.length>0&&a("body").addClass("modal-open")}),e.on("show.bs.modal",e=>{this.currentModal=a(e.currentTarget),this.setButtons(t.buttons),this.instances.push(this.currentModal)}),e.on("modal-dismiss",t=>{a(t.currentTarget).modal("hide")}),t.callback&&t.callback(e),e.modal()}}let m=null;try{parent&&parent.window.TYPO3&&parent.window.TYPO3.Modal?(parent.window.TYPO3.Modal.initializeMarkupTrigger(document),m=parent.window.TYPO3.Modal):top&&top.TYPO3.Modal&&(top.TYPO3.Modal.initializeMarkupTrigger(document),m=top.TYPO3.Modal)}catch(t){}return m||(m=new f(new s),TYPO3.Modal=m),m}));
\ No newline at end of file
+define(["require","exports","jquery","./Enum/Severity","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Core/SecurityUtility","./Icons","./Severity","bootstrap"],(function(t,e,a,n,s,i,l,o){"use strict";var d,r,c,u;!function(t){t.modal=".t3js-modal",t.content=".t3js-modal-content",t.title=".t3js-modal-title",t.close=".t3js-modal-close",t.body=".t3js-modal-body",t.footer=".t3js-modal-footer",t.iframe=".t3js-modal-iframe",t.iconPlaceholder=".t3js-modal-icon-placeholder"}(d||(d={})),function(t){t.small="small",t.default="default",t.medium="medium",t.large="large",t.full="full"}(r||(r={})),function(t){t.default="default",t.light="light",t.dark="dark"}(c||(c={})),function(t){t.default="default",t.ajax="ajax",t.iframe="iframe"}(u||(u={}));class m{constructor(t){this.sizes=r,this.styles=c,this.types=u,this.currentModal=null,this.instances=[],this.$template=a('<div class="t3js-modal modal fade"><div class="modal-dialog"><div class="t3js-modal-content modal-content"><div class="modal-header"><button class="t3js-modal-close close"><span aria-hidden="true"><span class="t3js-modal-icon-placeholder" data-icon="actions-close"></span></span><span class="sr-only"></span></button><h4 class="t3js-modal-title modal-title"></h4></div><div class="t3js-modal-body modal-body"></div><div class="t3js-modal-footer modal-footer"></div></div></div></div>'),this.defaultConfiguration={type:u.default,title:"Information",content:"No content provided, please check your <code>Modal</code> configuration.",severity:n.SeverityEnum.notice,buttons:[],style:c.default,size:r.default,additionalCssClasses:[],callback:a.noop(),ajaxCallback:a.noop(),ajaxTarget:null},this.securityUtility=t,a(document).on("modal-dismiss",this.dismiss),this.initializeMarkupTrigger(document)}static createModalResponseEventFromElement(t,e){return t.dataset.eventName?new CustomEvent(t.dataset.eventName,{detail:{result:e,payload:t.dataset.eventPayload||null}}):null}dismiss(){this.currentModal&&this.currentModal.modal("hide")}confirm(t,e,s=n.SeverityEnum.warning,i=[],l){return 0===i.length&&i.push({text:a(this).data("button-close-text")||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:a(this).data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(s),name:"ok"}),this.advanced({title:t,content:e,severity:s,buttons:i,additionalCssClasses:l,callback:t=>{t.on("button.clicked",t=>{"cancel"===t.target.getAttribute("name")?a(t.currentTarget).trigger("confirm.button.cancel"):"ok"===t.target.getAttribute("name")&&a(t.currentTarget).trigger("confirm.button.ok")})}})}loadUrl(t,e=n.SeverityEnum.info,a,s,i,l){return this.advanced({type:u.ajax,title:t,severity:e,buttons:a,ajaxCallback:i,ajaxTarget:l,content:s})}show(t,e,a=n.SeverityEnum.info,s,i){return this.advanced({type:u.default,title:t,content:e,severity:a,buttons:s,additionalCssClasses:i})}advanced(t){return t.type="string"==typeof t.type&&t.type in u?t.type:this.defaultConfiguration.type,t.title="string"==typeof t.title?t.title:this.defaultConfiguration.title,t.content="string"==typeof t.content||"object"==typeof t.content?t.content:this.defaultConfiguration.content,t.severity=void 0!==t.severity?t.severity:this.defaultConfiguration.severity,t.buttons=t.buttons||this.defaultConfiguration.buttons,t.size="string"==typeof t.size&&t.size in r?t.size:this.defaultConfiguration.size,t.style="string"==typeof t.style&&t.style in c?t.style:this.defaultConfiguration.style,t.additionalCssClasses=t.additionalCssClasses||this.defaultConfiguration.additionalCssClasses,t.callback="function"==typeof t.callback?t.callback:this.defaultConfiguration.callback,t.ajaxCallback="function"==typeof t.ajaxCallback?t.ajaxCallback:this.defaultConfiguration.ajaxCallback,t.ajaxTarget="string"==typeof t.ajaxTarget?t.ajaxTarget:this.defaultConfiguration.ajaxTarget,this.generate(t)}setButtons(t){const e=this.currentModal.find(d.footer);if(t.length>0){e.empty();for(let n=0;n<t.length;n++){const s=t[n],i=a("<button />",{class:"btn"});i.html("<span>"+this.securityUtility.encodeHtml(s.text,!1)+"</span>"),s.active&&i.addClass("t3js-active"),""!==s.btnClass&&i.addClass(s.btnClass),""!==s.name&&i.attr("name",s.name),s.action?i.on("click",()=>{e.find("button").not(i).addClass("disabled"),s.action.execute(i.get(0)).then(()=>{this.currentModal.modal("hide")})}):s.trigger&&i.on("click",s.trigger),s.dataAttributes&&Object.keys(s.dataAttributes).length>0&&Object.keys(s.dataAttributes).map(t=>{i.attr("data-"+t,s.dataAttributes[t])}),s.icon&&i.prepend('<span class="t3js-modal-icon-placeholder" data-icon="'+s.icon+'"></span>'),e.append(i)}e.show(),e.find("button").on("click",t=>{a(t.currentTarget).trigger("button.clicked")})}else e.hide();return this.currentModal}initializeMarkupTrigger(t){a(t).on("click",".t3js-modal-trigger",t=>{t.preventDefault();const e=a(t.currentTarget),s=e.data("content")||"Are you sure?",i=void 0!==n.SeverityEnum[e.data("severity")]?n.SeverityEnum[e.data("severity")]:n.SeverityEnum.info;let l=e.data("url")||null;if(null!==l){const t=l.includes("?")?"&":"?";l=l+t+a.param({data:e.data()})}this.advanced({type:null!==l?u.ajax:u.default,title:e.data("title")||"Alert",content:null!==l?l:s,severity:i,buttons:[{text:e.data("button-close-text")||TYPO3.lang["button.close"]||"Close",active:!0,btnClass:"btn-default",trigger:()=>{this.currentModal.trigger("modal-dismiss");const e=m.createModalResponseEventFromElement(t.currentTarget,!1);null!==e&&t.currentTarget.dispatchEvent(e)}},{text:e.data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(i),trigger:()=>{this.currentModal.trigger("modal-dismiss");const a=m.createModalResponseEventFromElement(t.currentTarget,!0);null!==a&&t.currentTarget.dispatchEvent(a);const n=e.data("href")||e.attr("href");n&&"#"!==n&&(t.target.ownerDocument.location.href=n)}}]})})}generate(t){const e=this.$template.clone();if(t.additionalCssClasses.length>0)for(let a of t.additionalCssClasses)e.addClass(a);if(e.addClass("modal-type-"+t.type),e.addClass("modal-severity-"+o.getCssClass(t.severity)),e.addClass("modal-style-"+t.style),e.addClass("modal-size-"+t.size),e.attr("tabindex","-1"),e.find(d.title).text(t.title),e.find(d.close).on("click",()=>{e.modal("hide")}),"ajax"===t.type){const a=t.ajaxTarget?t.ajaxTarget:d.body,n=e.find(a);l.getIcon("spinner-circle",l.sizes.default,null,null,l.markupIdentifiers.inline).then(e=>{n.html('<div class="modal-loading">'+e+"</div>"),new s(t.content).get().then(async e=>{this.currentModal.find(a).empty().append(await e.raw().text()),t.ajaxCallback&&t.ajaxCallback(),this.currentModal.trigger("modal-loaded")})})}else"iframe"===t.type?(e.find(d.body).append(a("<iframe />",{src:t.content,name:"modal_frame",class:"modal-iframe t3js-modal-iframe"})),e.find(d.iframe).on("load",()=>{e.find(d.title).text(e.find(d.iframe).get(0).contentDocument.title)})):("string"==typeof t.content&&(t.content=a("<p />").html(this.securityUtility.encodeHtml(t.content))),e.find(d.body).append(t.content));return e.on("shown.bs.modal",t=>{const e=a(t.currentTarget);e.find(d.footer).find(".t3js-active").first().focus(),e.find(d.iconPlaceholder).each((t,e)=>{l.getIcon(a(e).data("icon"),l.sizes.small,null,null,l.markupIdentifiers.inline).then(t=>{this.currentModal.find(d.iconPlaceholder+"[data-icon="+a(t).data("identifier")+"]").replaceWith(t)})})}),e.on("hidden.bs.modal",t=>{if(this.instances.length>0){const t=this.instances.length-1;this.instances.splice(t,1),this.currentModal=this.instances[t-1]}e.trigger("modal-destroyed"),a(t.currentTarget).remove(),this.instances.length>0&&a("body").addClass("modal-open")}),e.on("show.bs.modal",e=>{this.currentModal=a(e.currentTarget),this.setButtons(t.buttons),this.instances.push(this.currentModal)}),e.on("modal-dismiss",t=>{a(t.currentTarget).modal("hide")}),t.callback&&t.callback(e),e.modal()}}let f=null;try{parent&&parent.window.TYPO3&&parent.window.TYPO3.Modal?(parent.window.TYPO3.Modal.initializeMarkupTrigger(document),f=parent.window.TYPO3.Modal):top&&top.TYPO3.Modal&&(top.TYPO3.Modal.initializeMarkupTrigger(document),f=top.TYPO3.Modal)}catch(t){}return f||(f=new m(new i),TYPO3.Modal=f),f}));
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ModalInterface.js b/typo3/sysext/backend/Resources/Public/JavaScript/ModalInterface.js
new file mode 100644
index 000000000000..d3632d54e8aa
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/ModalInterface.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+define(["require","exports"],(function(e,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0})}));
\ No newline at end of file
diff --git a/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php b/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php
index f6e55da2f8d7..b2697aeea2bf 100644
--- a/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php
+++ b/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php
@@ -84,6 +84,16 @@ class CreateFolderController
      */
     protected $moduleTemplate;
 
+    /**
+     * @var UriBuilder
+     */
+    protected $uriBuilder;
+
+    public function __construct()
+    {
+        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+    }
+
     /**
      * Processes the request, currently everything is handled and put together via "main()"
      *
@@ -133,44 +143,10 @@ class CreateFolderController
         $pathInfo = [
             'combined_identifier' => $this->folderObject->getCombinedIdentifier(),
         ];
-        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
 
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
-        $this->moduleTemplate->addJavaScriptCode(
-            'CreateFolderInlineJavaScript',
-            'var path = "' . $this->target . '";
-            var confirmTitle = '
-            . GeneralUtility::quoteJSvalue(
-                $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:pleaseConfirm')
-            )
-            . ';
-            var confirmText = '
-            . GeneralUtility::quoteJSvalue(
-                $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.redraw')
-            )
-            . ';
-            function reload(a) {
-                var params = "&target="+encodeURIComponent(path)+"&number="+a+"&returnUrl=' . rawurlencode($this->returnUrl) . '";
-                var url = \'' . (string)$uriBuilder->buildUriFromRoute('file_newfolder') . '\';
-                if (!changed) {
-                    window.location.href = url + params;
-                } else {
-                    var modal = top.TYPO3.Modal.confirm(confirmTitle, confirmText);
-                    modal.on(\'confirm.button.cancel\', function(e) {
-                        top.TYPO3.Modal.currentModal.trigger(\'modal-dismiss\');
-                    });
-                    modal.on(\'confirm.button.ok\', function(e) {
-                        top.TYPO3.Modal.currentModal.trigger(\'modal-dismiss\');
-                        window.location.href = url + params;
-                    });
-                }
-            }
-            function backToList() {
-                top.goToModule("file_FilelistList");
-            }
-            var changed = 0;'
-        );
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Filelist/CreateFolder');
     }
 
     /**
@@ -179,11 +155,18 @@ class CreateFolderController
     protected function main()
     {
         $lang = $this->getLanguageService();
-        $assigns = [];
-        $assigns['target'] = $this->target;
+        $assigns = [
+            'target' => $this->target,
+            'confirmTitle' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:pleaseConfirm'),
+            'confirmText' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.redraw'),
+            'selfUrl' => (string)$this->uriBuilder->buildUriFromRoute('file_newfolder', [
+                'target' => $this->target,
+                'returnUrl' => $this->returnUrl,
+                'number' => 'AMOUNT',
+            ]),
+        ];
         if ($this->folderObject->checkActionPermission('add')) {
-            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
+            $assigns['moduleUrlTceFile'] = (string)$this->uriBuilder->buildUriFromRoute('tce_file');
             $assigns['cshFileNewFolder'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfolder');
             // Making the selector box for the number of concurrent folder-creations
             $this->number = MathUtility::forceIntegerInRange($this->number, 1, 10);
@@ -205,8 +188,7 @@ class CreateFolderController
         }
 
         if ($this->folderObject->getStorage()->checkUserActionPermission('add', 'File')) {
-            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $assigns['moduleUrlOnlineMedia'] = (string)$uriBuilder->buildUriFromRoute('online_media');
+            $assigns['moduleUrlOnlineMedia'] = (string)$this->uriBuilder->buildUriFromRoute('online_media');
             $assigns['cshFileNewMedia'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newMedia');
             // Create a list of allowed file extensions with the readable format "youtube, vimeo" etc.
             $fileExtList = [];
@@ -219,7 +201,7 @@ class CreateFolderController
             }
             $assigns['fileExtList'] = $fileExtList;
 
-            $assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
+            $assigns['moduleUrlTceFile'] = (string)$this->uriBuilder->buildUriFromRoute('tce_file');
             $assigns['cshFileNewFile'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfile');
             // Create a list of allowed file extensions with a text format "*.txt, *.css" etc.
             $fileExtList = [];
diff --git a/typo3/sysext/filelist/Classes/Controller/File/EditFileController.php b/typo3/sysext/filelist/Classes/Controller/File/EditFileController.php
index 52d040b16b2c..62427c82a4d7 100644
--- a/typo3/sysext/filelist/Classes/Controller/File/EditFileController.php
+++ b/typo3/sysext/filelist/Classes/Controller/File/EditFileController.php
@@ -32,6 +32,7 @@ use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
+use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
@@ -73,7 +74,7 @@ class EditFileController
     /**
      * the file that is being edited on
      *
-     * @var \TYPO3\CMS\Core\Resource\AbstractFile
+     * @var File
      */
     protected $fileObject;
 
@@ -84,12 +85,18 @@ class EditFileController
      */
     protected $moduleTemplate;
 
+    /**
+     * @var UriBuilder
+     */
+    protected $uriBuilder;
+
     /**
      * Constructor
      */
     public function __construct()
     {
         $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
+        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
     }
 
     /**
@@ -122,7 +129,6 @@ class EditFileController
 
         // Setting target, which must be a file reference to a file within the mounts.
         $this->target = $this->origTarget = $parsedBody['target'] ?? $queryParams['target'] ?? '';
-        $this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
         // create the file object
         if ($this->target) {
             $this->fileObject = GeneralUtility::makeInstance(ResourceFactory::class)
@@ -140,13 +146,12 @@ class EditFileController
                 1375889832
             );
         }
-
-        // Setting template object
-        $this->moduleTemplate->addJavaScriptCode(
-            'FileEditBackToList',
-            'function backToList() {
-				top.goToModule("file_FilelistList");
-			}'
+        $this->returnUrl = GeneralUtility::sanitizeLocalUrl(
+            $parsedBody['returnUrl']
+                ?? $queryParams['returnUrl']
+                ?? (string)$this->uriBuilder->buildUriFromRoute('file_FilelistList', [
+                    'id' => $this->fileObject->getParentFolder()->getCombinedIdentifier()
+                ])
         );
     }
 
@@ -180,8 +185,7 @@ class EditFileController
         }
 
         $assigns = [];
-        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        $assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
+        $assigns['moduleUrlTceFile'] = (string)$this->uriBuilder->buildUriFromRoute('tce_file');
         $assigns['fileName'] = $this->fileObject->getName();
 
         try {
@@ -192,17 +196,12 @@ class EditFileController
             }
 
             // Making the formfields
-            $hValue = (string)$uriBuilder->buildUriFromRoute('file_edit', [
-                'target' => $this->origTarget,
-                'returnUrl' => $this->returnUrl
-            ]);
-
             $formData = [
                 'databaseRow' => [
                     'uid' => 0,
                     'data' => $this->fileObject->getContents(),
                     'target' => $this->fileObject->getUid(),
-                    'redirect' => $hValue,
+                    'redirect' => $this->returnUrl,
                 ],
                 'tableName' => 'editfile',
                 'processedTca' => [
@@ -303,11 +302,6 @@ class EditFileController
             ->setName('_saveandclosedok')
             ->setValue('1')
             ->setForm('EditFileController')
-            ->setOnClick(
-                'document.editform.elements.namedItem("data[editfile][0][redirect]").value='
-                . GeneralUtility::quoteJSvalue($this->returnUrl)
-                . ';'
-            )
             ->setTitle($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_edit.php.saveAndClose'))
             ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
                 'actions-document-save-close',
@@ -321,8 +315,8 @@ class EditFileController
 
         // Cancel button
         $closeButton = $buttonBar->makeLinkButton()
-            ->setHref('#')
-            ->setOnClick('backToList(); return false;')
+            ->setShowLabelText(true)
+            ->setHref($this->returnUrl)
             ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'))
             ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
         $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
diff --git a/typo3/sysext/filelist/Classes/Controller/File/RenameFileController.php b/typo3/sysext/filelist/Classes/Controller/File/RenameFileController.php
index 125b46c1bcef..8eda2232144f 100644
--- a/typo3/sysext/filelist/Classes/Controller/File/RenameFileController.php
+++ b/typo3/sysext/filelist/Classes/Controller/File/RenameFileController.php
@@ -133,12 +133,6 @@ class RenameFileController
         // Setting up the context sensitive menu
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Filelist/RenameFile');
-
-        // Add javaScript
-        $this->moduleTemplate->addJavaScriptCode(
-            'RenameFileInlineJavaScript',
-            'function backToList() {top.goToModule("file_FilelistList");}'
-        );
     }
 
     /**
diff --git a/typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php b/typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php
index e3d2ba7fdf22..847ff3b55941 100644
--- a/typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php
+++ b/typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php
@@ -133,10 +133,7 @@ class ReplaceFileController
         ];
         $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
         $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
-        $this->moduleTemplate->addJavaScriptCode(
-            'ReplaceFileOnlineJavaScript',
-            'function backToList() {top.goToModule("file_FilelistList");}'
-        );
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Filelist/FileReplace');
     }
 
     /**
diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php
index e4b84381b13f..00aa380c2d61 100644
--- a/typo3/sysext/filelist/Classes/Controller/FileListController.php
+++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php
@@ -292,9 +292,17 @@ class FileListController extends ActionController implements LoggerAwareInterfac
         parent::initializeView($view);
         $pageRenderer = $this->view->getModuleTemplate()->getPageRenderer();
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileListLocalisation');
+        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileList');
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileSearch');
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
         $this->registerDocHeaderButtons();
+        if ($this->folderObject instanceof Folder) {
+            $view->assign(
+                'currentFolderHash',
+                'folder' . GeneralUtility::md5int($this->folderObject->getCombinedIdentifier())
+            );
+        }
+        $view->assign('currentIdentifier', $this->id);
     }
 
     protected function initializeIndexAction()
@@ -399,8 +407,6 @@ class FileListController extends ActionController implements LoggerAwareInterfac
                 );
             }
 
-            // Set top JavaScript:
-            $this->addJumpToUrl();
             $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClipboardComponent');
             $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileDelete');
             $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf', 'buttons');
@@ -539,10 +545,6 @@ class FileListController extends ActionController implements LoggerAwareInterfac
 
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
 
-        $pageRenderer = $this->view->getModuleTemplate()->getPageRenderer();
-        $pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('show_item'));
-        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileList');
-
         $thumbnailConfiguration = GeneralUtility::makeInstance(ThumbnailConfiguration::class);
         $this->view->assign('thumbnail', [
             'width' => $thumbnailConfiguration->getWidth(),
@@ -557,12 +559,12 @@ class FileListController extends ActionController implements LoggerAwareInterfac
         ]);
         $this->view->assign('moduleSettings', $this->MOD_SETTINGS);
 
+        $pageRenderer = $this->view->getModuleTemplate()->getPageRenderer();
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileDelete');
         $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf', 'buttons');
 
         $this->initClipboard();
         $this->buildListOptionCheckboxes($searchWord);
-        $this->addJumpToUrl();
     }
 
     /**
@@ -641,11 +643,11 @@ class FileListController extends ActionController implements LoggerAwareInterfac
             if ($parentFolder->getIdentifier() !== $this->folderObject->getIdentifier()
                 && $currentStorage->isWithinFileMountBoundaries($parentFolder)
             ) {
-                $levelUpClick = 'top.document.getElementsByName("nav_frame")[0].contentWindow.Tree.highlightActiveItem("file","folder'
-                    . GeneralUtility::md5int($parentFolder->getCombinedIdentifier()) . '_"+top.fsMod.currentBank)';
                 $levelUpButton = $buttonBar->makeLinkButton()
+                    ->setDataAttributes([
+                        'tree-update-request' => htmlspecialchars('folder' . GeneralUtility::md5int($parentFolder->getCombinedIdentifier())),
+                    ])
                     ->setHref((string)$uriBuilder->buildUriFromRoute('file_FilelistList', ['id' => $parentFolder->getCombinedIdentifier()]))
-                    ->setOnClick($levelUpClick)
                     ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.upOneLevel'))
                     ->setIcon($iconFactory->getIcon('actions-view-go-up', Icon::SIZE_SMALL));
                 $buttonBar->addButton($levelUpButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
@@ -840,16 +842,4 @@ class FileListController extends ActionController implements LoggerAwareInterfac
         $this->view->assign('showClipBoard', (bool)$this->MOD_SETTINGS['clipBoard']);
         $this->view->assign('clipBoardHtml', $this->filelist->clipObj->printClipboard());
     }
-
-    /**
-     * add javascript jumpToUrl function
-     */
-    protected function addJumpToUrl(): void
-    {
-        $this->view->getModuleTemplate()->addJavaScriptCode(
-            'FileListIndex',
-            'if (top.fsMod) top.fsMod.recentIds["file"] = "' . rawurlencode($this->id) . '";
-                '
-        );
-    }
 }
diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php
index ef8a62c36fe0..0e4627969398 100644
--- a/typo3/sysext/filelist/Classes/FileList.php
+++ b/typo3/sysext/filelist/Classes/FileList.php
@@ -38,7 +38,8 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Filelist\Configuration\ThumbnailConfiguration;
 
 /**
- * Class for rendering of File>Filelist
+ * Class for rendering of File>Filelist (basically used in FileListController)
+ * @see \TYPO3\CMS\Filelist\Controller\FileListController
  * @internal this is a concrete TYPO3 controller implementation and solely used for EXT:filelist and not part of TYPO3's Core API.
  */
 class FileList
@@ -240,18 +241,15 @@ class FileList
      */
     public function linkClipboardHeaderIcon($string, $cmd, $warning = '')
     {
-        $jsCode = 'document.dblistForm.cmd.value=' . GeneralUtility::quoteJSvalue($cmd)
-            . ';document.dblistForm.submit();';
-
-        $attributes = [];
         if ($warning) {
             $attributes['class'] = 'btn btn-default t3js-modal-trigger';
-            $attributes['data-href'] = 'javascript:' . $jsCode;
             $attributes['data-severity'] = 'warning';
             $attributes['data-content'] = $warning;
+            $attributes['data-event-name'] = 'filelist:clipboard:cmd';
+            $attributes['data-event-payload'] = $cmd;
         } else {
             $attributes['class'] = 'btn btn-default';
-            $attributes['onclick'] = $jsCode . 'return false;';
+            $attributes['data-filelist-clipboard-cmd'] = $cmd;
         }
 
         return '<a href="#" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $string . '</a>';
@@ -630,12 +628,15 @@ class FileList
     public function linkWrapDir($title, Folder $folderObject)
     {
         $href = (string)$this->uriBuilder->buildUriFromRoute('file_FilelistList', ['id' => $folderObject->getCombinedIdentifier()]);
-        $onclick = ' onclick="' . htmlspecialchars('top.document.getElementsByName("nav_frame")[0].contentWindow.Tree.highlightActiveItem("file","folder' . GeneralUtility::md5int($folderObject->getCombinedIdentifier()) . '_"+top.fsMod.currentBank)') . '"';
+        $triggerTreeUpdateAttribute = sprintf(
+            ' data-tree-update-request="%s"',
+            htmlspecialchars('folder' . GeneralUtility::md5int($folderObject->getCombinedIdentifier()))
+        );
         // Sometimes $code contains plain HTML tags. In such a case the string should not be modified!
         if ((string)$title === strip_tags($title)) {
-            return '<a href="' . htmlspecialchars($href) . '"' . $onclick . ' title="' . htmlspecialchars($title) . '">' . $title . '</a>';
+            return '<a href="' . htmlspecialchars($href) . '"' . $triggerTreeUpdateAttribute . ' title="' . htmlspecialchars($title) . '">' . $title . '</a>';
         }
-        return '<a href="' . htmlspecialchars($href) . '"' . $onclick . '>' . $title . '</a>';
+        return '<a href="' . htmlspecialchars($href) . '"' . $triggerTreeUpdateAttribute . '>' . $title . '</a>';
     }
 
     /**
@@ -997,9 +998,11 @@ class FileList
 
         // Edit file content (if editable)
         if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && $fileOrFolderObject->isTextFile()) {
-            $url = (string)$this->uriBuilder->buildUriFromRoute('file_edit', ['target' => $fullIdentifier]);
-            $editOnClick = 'top.list_frame.location.href=' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
-            $cells['edit'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($editOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editcontent') . '">'
+            $attributes = [
+                'href' => (string)$this->uriBuilder->buildUriFromRoute('file_edit', ['target' => $fullIdentifier, 'returnUrl' => $this->listURL()]),
+                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editcontent'),
+            ];
+            $cells['edit'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>'
                 . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
                 . '</a>';
         } else {
@@ -1036,16 +1039,20 @@ class FileList
 
         // replace file
         if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
-            $url = (string)$this->uriBuilder->buildUriFromRoute('file_replace', ['target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid()]);
-            $replaceOnClick = 'top.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
-            $cells['replace'] = '<a href="#" class="btn btn-default" onclick="' . $replaceOnClick . '"  title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.replace') . '">' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
+            $attributes = [
+                'href' => $url = (string)$this->uriBuilder->buildUriFromRoute('file_replace', ['target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid(), 'returnUrl' => $this->listURL()]),
+                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.replace'),
+            ];
+            $cells['replace'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
         }
 
         // rename the file
         if ($fileOrFolderObject->checkActionPermission('rename')) {
-            $url = (string)$this->uriBuilder->buildUriFromRoute('file_rename', ['target' => $fullIdentifier]);
-            $renameOnClick = 'top.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
-            $cells['rename'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($renameOnClick) . '"  title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename') . '">' . $this->iconFactory->getIcon('actions-edit-rename', Icon::SIZE_SMALL)->render() . '</a>';
+            $attributes = [
+                'href' => (string)$this->uriBuilder->buildUriFromRoute('file_rename', ['target' => $fullIdentifier]),
+                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename'),
+            ];
+            $cells['rename'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-rename', Icon::SIZE_SMALL)->render() . '</a>';
         } else {
             $cells['rename'] = $this->spaceIcon;
         }
@@ -1053,20 +1060,24 @@ class FileList
         // upload files
         if ($fileOrFolderObject->getStorage()->checkUserActionPermission('add', 'File') && $fileOrFolderObject->checkActionPermission('write')) {
             if ($fileOrFolderObject instanceof Folder) {
-                $url = (string)$this->uriBuilder->buildUriFromRoute('file_upload', ['target' => $fullIdentifier]);
-                $uploadOnClick = 'top.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
-                $cells['upload'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($uploadOnClick) . '"  title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload')) . '">' . $this->iconFactory->getIcon('actions-edit-upload', Icon::SIZE_SMALL)->render() . '</a>';
+                $attributes = [
+                    'href' => (string)$this->uriBuilder->buildUriFromRoute('file_upload', ['target' => $fullIdentifier]),
+                    'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload'),
+                ];
+                $cells['upload'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-upload', Icon::SIZE_SMALL)->render() . '</a>';
             }
         }
 
         if ($fileOrFolderObject->checkActionPermission('read')) {
-            $infoOnClick = '';
-            if ($fileOrFolderObject instanceof Folder) {
-                $infoOnClick = 'top.TYPO3.InfoWindow.showItem(\'_FOLDER\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
-            } elseif ($fileOrFolderObject instanceof File) {
-                $infoOnClick = 'top.TYPO3.InfoWindow.showItem(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
+            $attributes = [
+                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info'),
+            ];
+            if ($fileOrFolderObject instanceof Folder || $fileOrFolderObject instanceof File) {
+                $attributes['data-filelist-show-item-type'] = $fileOrFolderObject instanceof File ? '_FILE' : '_FOLDER';
+                $attributes['data-filelist-show-item-identifier'] = $fullIdentifier;
             }
-            $cells['info'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($infoOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info') . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
+            $cells['info'] = '<a href="#" class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>'
+                . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
         } else {
             $cells['info'] = $this->spaceIcon;
         }
@@ -1190,13 +1201,14 @@ class FileList
         if (!$references) {
             return '-';
         }
-        $htmlCode = '<a href="#" onclick="' . htmlspecialchars(
-            'top.TYPO3.InfoWindow.showItem(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileObject->getCombinedIdentifier()) . '); return false;'
-        ) . '" title="' . htmlspecialchars(
-            $this->getLanguageService()->sL(
-                'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
-            ) . ' (' . $references . ')'
-        ) . '">';
+        $attributes = [
+            'data-filelist-show-item-type' => '_FILE',
+            'data-filelist-show-item-identifier' => $fileObject->getCombinedIdentifier(),
+            'title' => $this->getLanguageService()
+                ->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references')
+                . ' (' . $references . ')'
+        ];
+        $htmlCode = '<a href="#" ' . GeneralUtility::implodeAttributes($attributes, true) . '">';
         $htmlCode .= $references;
         $htmlCode .= '</a>';
         return $htmlCode;
diff --git a/typo3/sysext/filelist/Resources/Private/Layouts/Default.html b/typo3/sysext/filelist/Resources/Private/Layouts/Default.html
index 56866620e711..94c3e27950ad 100644
--- a/typo3/sysext/filelist/Resources/Private/Layouts/Default.html
+++ b/typo3/sysext/filelist/Resources/Private/Layouts/Default.html
@@ -1,5 +1,8 @@
-<f:render section="headline" />
-<f:render section="content" />
+<f:comment><!-- identifier initializes tree state --></f:comment>
+<div class="filelist-main" data-filelist-current-folder-hash="{currentFolderHash}" data-filelist-current-identifier="{currentIdentifier}">
+    <f:render section="headline" />
+    <f:render section="content" />
+</div>
 
 <div class="t3js-drag-uploader" data-target-folder="{folderIdentifier}" data-progress-container="#typo3-filelist"
     data-dropzone-trigger=".t3js-drag-uploader-trigger" data-dropzone-target=".t3js-module-body h1:first"
diff --git a/typo3/sysext/filelist/Resources/Private/Templates/File/CreateFolder.html b/typo3/sysext/filelist/Resources/Private/Templates/File/CreateFolder.html
index b6aaf95fab5c..389d97818320 100644
--- a/typo3/sysext/filelist/Resources/Private/Templates/File/CreateFolder.html
+++ b/typo3/sysext/filelist/Resources/Private/Templates/File/CreateFolder.html
@@ -1,4 +1,4 @@
-<div>
+<div class="filelist-create-folder-main" data-self-url="{selfUrl}" data-confirm-title="{confirmTitle}" data-confirm-text="{confirmText}">
     <h1><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_newfolder.php.pagetitle" /></h1>
     <f:if condition="{moduleUrlTceFile}">
         <h3><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_newfolder.php.newfolders" /></h3>
@@ -10,7 +10,7 @@
                             <label for="number-of-new-folders"><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_newfolder.php.number_of_folders" /></label> {cshFileNewFolder -> f:format.raw()}
                             <div class="form-control-wrap">
                                 <div class="input-group">
-                                    <select class="form-control form-control-adapt" name="number" id="number-of-new-folders" onchange="reload(this.options[this.selectedIndex].value);">';
+                                    <select class="form-control form-control-adapt" name="number" id="number-of-new-folders">';
                                         <f:for each="{options}" as="option">
                                             <option value="{option.value}"{option.selected}>{option.value}</option>
                                         </f:for>
@@ -24,7 +24,7 @@
                             <div class="form-group">
                                 <label for="folder_new_{folder.this}"><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_newfolder.php.label_newfolder" /> {folder.next}:</label>
                                 <div class="form-control-wrap">
-                                    <input type="text" class="form-control" id="folder_new_{folder.this}" name="data[newfolder][{folder.this}][data]" onchange="changed=true;" />
+                                    <input type="text" class="form-control" id="folder_new_{folder.this}" name="data[newfolder][{folder.this}][data]" />
                                     <input type="hidden" name="data[newfolder][{folder.this}][target]" value="{target}" />
                                 </div>
                             </div>
@@ -77,7 +77,7 @@
                             <div class="form-group">
                                 <label for="newfile"><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_newfolder.php.label_newfile" /></label> {cshFileNewFile -> f:format.raw()}
                                 <div class="form-control-wrap">
-                                    <input class="form-control" type="text" id="newfile" name="data[newfile][0][data]" onchange="changed=true;" />
+                                    <input class="form-control" type="text" id="newfile" name="data[newfile][0][data]" />
                                     <input type="hidden" name="data[newfile][0][target]" value="{target}" />
                                 </div>
                                 <div class="help-block">
diff --git a/typo3/sysext/filelist/Resources/Private/Templates/File/ReplaceFile.html b/typo3/sysext/filelist/Resources/Private/Templates/File/ReplaceFile.html
index 13b7f96b9e19..b33349285709 100644
--- a/typo3/sysext/filelist/Resources/Private/Templates/File/ReplaceFile.html
+++ b/typo3/sysext/filelist/Resources/Private/Templates/File/ReplaceFile.html
@@ -10,30 +10,23 @@
         <label for="file_replace"><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_replace.php.selectfile" /></label>
         <div class="input-group col-xs-6">
             <input type="text" name="fakefile" id="fakefile" class="form-control input-xlarge" readonly>
-            <a class="input-group-addon btn btn-primary" onclick="$('#file_replace').click();">
+            <a class="input-group-addon btn btn-primary" data-filelist-click-target="#file_replace">
                 <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_replace.php.browse" />
             </a>
         </div>
-        <input class="form-control" type="file" id="file_replace" name="replace_1" style="visibility: hidden;" />
+        <input class="form-control" type="file" id="file_replace" data-filelist-change-target="#fakefile" name="replace_1" style="visibility: hidden;" />
     </div>
 
-    <script>
-        require(['jquery'], function($) {
-            $('#file_replace').change(function() {
-                $('#fakefile').val($(this).val());
-            });
-        });
-    </script>
-
     <input type="hidden" name="overwriteExistingFiles" value="replace" />
     <input type="hidden" name="data[replace][1][data]" value="1" />
     <input type="hidden" name="data[replace][1][uid]" value="{uid}" />
 
     <div class="form-group">
-        <input class="btn btn-primary" type="submit" value="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_replace.php.submit')}" />
-        <input class="btn btn-danger" type="submit" value="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel')}"
-            onclick="backToList(); return false;" />
         <input type="hidden" name="data[replace][1][redirect]" value="{returnUrl}" />
+        <input class="btn btn-primary" type="submit" value="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_replace.php.submit')}" />
+        <a href="{returnUrl}" class="btn btn-danger">
+            <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel" />
+        </a>
     </div>
     </form>
 </div>
diff --git a/typo3/sysext/filelist/Resources/Public/JavaScript/CreateFolder.js b/typo3/sysext/filelist/Resources/Public/JavaScript/CreateFolder.js
new file mode 100644
index 000000000000..34d9f24b3754
--- /dev/null
+++ b/typo3/sysext/filelist/Resources/Public/JavaScript/CreateFolder.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+define(["require","exports","jquery","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,n,i,o){"use strict";return new class{constructor(){this.changed=!1,n(()=>{const e=document.querySelector(".filelist-create-folder-main");if(!(e instanceof HTMLElement))throw new Error("Main element not found");this.selfUrl=e.dataset.selfUrl,this.confirmTitle=e.dataset.confirmTitle,this.confirmText=e.dataset.confirmText,this.registerEvents()})}reload(e){const t=this.selfUrl.replace(/AMOUNT/,e.toString());if(this.changed){const e=i.confirm(this.confirmTitle,this.confirmText);e.on("confirm.button.cancel",()=>{e.trigger("modal-dismiss")}),e.on("confirm.button.ok",()=>{e.trigger("modal-dismiss"),window.location.href=t})}else window.location.href=t}registerEvents(){new o("change",()=>{this.changed=!0}).delegateTo(document,['input[type="text"][name^="data[newfolder]"]','input[type="text"][name^="data[newfile]"]','input[type="text"][name^="data[newMedia]"]'].join(",")),new o("change",e=>{const t=parseInt(e.target.value,10);this.reload(t)}).bindTo(document.getElementById("number-of-new-folders"))}}}));
\ No newline at end of file
diff --git a/typo3/sysext/filelist/Resources/Public/JavaScript/FileList.js b/typo3/sysext/filelist/Resources/Public/JavaScript/FileList.js
index f9c4f4667a55..e7ff4500cb04 100644
--- a/typo3/sysext/filelist/Resources/Public/JavaScript/FileList.js
+++ b/typo3/sysext/filelist/Resources/Public/JavaScript/FileList.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","jquery","TYPO3/CMS/Backend/InfoWindow"],(function(e,t,n,o){"use strict";class r{static openFileInfoPopup(e){o.showItem("_FILE",e)}constructor(){n(()=>{n("a.btn.filelist-file-info").click(e=>{e.preventDefault(),r.openFileInfoPopup(n(e.currentTarget).attr("data-identifier"))}),n("a.filelist-file-references").click(e=>{e.preventDefault(),r.openFileInfoPopup(n(e.currentTarget).attr("data-identifier"))}),n("a.btn.filelist-file-copy").click(e=>{e.preventDefault();const t=n(e.currentTarget).attr("href");let o=t?encodeURIComponent(t):encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);top.list_frame.location.href=t+"&redirect="+o})})}}return new r}));
\ No newline at end of file
+define(["require","exports","jquery","TYPO3/CMS/Backend/InfoWindow","TYPO3/CMS/Backend/BroadcastMessage","TYPO3/CMS/Backend/BroadcastService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,i,a,r,o,n){"use strict";class s{static openInfoPopup(e,t){a.showItem(e,t)}static processTriggers(){const e=document.querySelector(".filelist-main");null!==e&&(s.emitTreeUpdateRequest(e.dataset.filelistCurrentFolderHash),top.fsMod&&top.fsMod.recentIds instanceof Object&&(top.fsMod.recentIds.file=encodeURIComponent(e.dataset.filelistCurrentIdentifier)))}static registerTreeUpdateEvents(){new n("click",(function(){s.emitTreeUpdateRequest(this.dataset.treeUpdateRequest)})).delegateTo(document.body,"[data-tree-update-request]")}static emitTreeUpdateRequest(e){const t=new r.BroadcastMessage("filelist","treeUpdateRequested",{type:"folder",identifier:e});o.post(t)}static submitClipboardFormWithCommand(e){const t=i('form[name="dblistForm"]');t.find('input[name="cmd"]').val(e),t.submit()}constructor(){s.processTriggers(),i(()=>{s.registerTreeUpdateEvents(),i("[data-filelist-show-item-identifier][data-filelist-show-item-type]").click(e=>{const t=i(e.currentTarget);e.preventDefault(),s.openInfoPopup(t.data("filelistShowItemType"),t.data("filelistShowItemIdentifier"))}),i("a.btn.filelist-file-info").click(e=>{e.preventDefault(),s.openInfoPopup("_FILE",i(e.currentTarget).attr("data-identifier"))}),i("a.filelist-file-references").click(e=>{e.preventDefault(),s.openInfoPopup("_FILE",i(e.currentTarget).attr("data-identifier"))}),i("a.btn.filelist-file-copy").click(e=>{e.preventDefault();const t=i(e.currentTarget).attr("href");let a=t?encodeURIComponent(t):encodeURIComponent(top.list_frame.document.location.pathname+top.list_frame.document.location.search);top.list_frame.location.href=t+"&redirect="+a}),i('[data-event-name="filelist:clipboard:cmd"]').on("filelist:clipboard:cmd",e=>{const t=e.originalEvent;t.detail.result&&s.submitClipboardFormWithCommand(t.detail.payload)}),i('[data-filelist-clipboard-cmd]:not([data-filelist-clipboard-cmd=""])').click(e=>{const t=i(e.currentTarget).data("filelistClipboardCmd");s.submitClipboardFormWithCommand(t)})})}}return new s}));
\ No newline at end of file
diff --git a/typo3/sysext/filelist/Resources/Public/JavaScript/FileReplace.js b/typo3/sysext/filelist/Resources/Public/JavaScript/FileReplace.js
new file mode 100644
index 000000000000..d61e2366e8ec
--- /dev/null
+++ b/typo3/sysext/filelist/Resources/Public/JavaScript/FileReplace.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+define(["require","exports","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,i){"use strict";return new class{constructor(){this.registerEvents()}registerEvents(){new i("click",(function(){const e=this.dataset.filelistClickTarget;document.querySelector(e).click()})).delegateTo(document.body,'[data-filelist-click-target]:not([data-filelist-click-target=""]'),new i("change",(function(){const e=this.dataset.filelistChangeTarget;document.querySelector(e).value=this.value})).delegateTo(document.body,'[data-filelist-change-target]:not([data-filelist-change-target=""])')}}}));
\ No newline at end of file
-- 
GitLab