[TASK] Migrate EXT:workspaces to TypeScript 30/61530/9
authorAndreas Fernandez <a.fernandez@scripting-base.de>
Fri, 23 Aug 2019 10:01:57 +0000 (12:01 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Thu, 29 Aug 2019 07:56:00 +0000 (09:56 +0200)
Resolves: #89008
Releases: master
Change-Id: I6d967dd14986fc70631718da88427bff181bf4a9
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61530
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Richard Haeser <richard@maxserv.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
16 files changed:
Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Utility.ts
Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Backend.ts [new file with mode: 0644]
Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Preview.ts [new file with mode: 0644]
Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Toolbar/WorkspacesMenu.ts [new file with mode: 0644]
Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Workspaces.ts [new file with mode: 0644]
Build/types/TYPO3/index.d.ts
typo3/sysext/backend/Classes/Controller/BackendController.php
typo3/sysext/backend/Resources/Public/JavaScript/Utility.js
typo3/sysext/workspaces/Classes/Controller/PreviewController.php
typo3/sysext/workspaces/Classes/Controller/Remote/RemoteServer.php
typo3/sysext/workspaces/Classes/Service/GridDataService.php
typo3/sysext/workspaces/Resources/Private/Partials/WorkingTable.html
typo3/sysext/workspaces/Resources/Public/JavaScript/Backend.js
typo3/sysext/workspaces/Resources/Public/JavaScript/Preview.js
typo3/sysext/workspaces/Resources/Public/JavaScript/Toolbar/WorkspacesMenu.js
typo3/sysext/workspaces/Resources/Public/JavaScript/Workspaces.js

index 3a499cb..367a396 100644 (file)
@@ -98,6 +98,22 @@ class Utility {
     }
     return url + separator + key + '=' + value;
   }
+
+  public static convertFormToObject(form: HTMLFormElement): { [key: string]: any } {
+    const obj: { [key: string]: any } = {};
+    const elements = form.querySelectorAll('input, select, textarea');
+    for (let i = 0; i < elements.length; ++i ) {
+      const element = <HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>(elements[i]);
+      const name = element.name;
+      const value = element.value;
+
+      if (name) {
+        obj[name] = value;
+      }
+    }
+
+    return obj;
+  }
 }
 
 export = Utility;
diff --git a/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Backend.ts b/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Backend.ts
new file mode 100644 (file)
index 0000000..d9aabf3
--- /dev/null
@@ -0,0 +1,1250 @@
+/*
+ * 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 {SeverityEnum} from 'TYPO3/CMS/Backend/Enum/Severity';
+import 'twbs/bootstrap-slider';
+import 'nprogress';
+import 'TYPO3/CMS/Backend/jquery.clearable';
+import * as $ from 'jquery';
+import Modal = require('TYPO3/CMS/Backend/Modal');
+import Persistent = require('TYPO3/CMS/Backend/Storage/Persistent');
+import Tooltip = require('TYPO3/CMS/Backend/Tooltip');
+import Utility = require('TYPO3/CMS/Backend/Utility');
+import Viewport = require('TYPO3/CMS/Backend/Viewport');
+import Wizard = require('TYPO3/CMS/Backend/Wizard');
+import Workspaces from './Workspaces';
+
+enum Identifiers {
+  searchForm = '#workspace-settings-form',
+  searchTextField = '#workspace-settings-form input[name="search-text"]',
+  searchSubmitBtn = '#workspace-settings-form button[type="submit"]',
+  depthSelector = '#workspace-settings-form [name="depth"]',
+  languageSelector = '#workspace-settings-form select[name="languages"]',
+  chooseStageAction = '#workspace-actions-form [name="stage-action"]',
+  chooseSelectionAction = '#workspace-actions-form [name="selection-action"]',
+  chooseMassAction = '#workspace-actions-form [name="mass-action"]',
+  container = '#workspace-panel',
+  actionIcons = '#workspace-action-icons',
+  toggleAll = '.t3js-toggle-all',
+  previewLinksButton = '.t3js-preview-link',
+  pagination = '#workspace-pagination',
+}
+
+class Backend extends Workspaces {
+  private elements: { [key: string]: JQuery } = {};
+  private settings: { [key: string]: string|number } = {
+    dir: 'ASC',
+    id: TYPO3.settings.Workspaces.id,
+    language: TYPO3.settings.Workspaces.language,
+    limit: 30,
+    query: '',
+    sort: 'label_Live',
+    start: 0,
+    filterTxt: '',
+  };
+  private paging: { [key: string]: number } = {
+    currentPage: 1,
+    totalPages: 1,
+    totalItems: 0,
+  };
+  private allToggled: boolean = false;
+  private latestPath: string = '';
+  private markedRecordsForMassAction: Array<any> = [];
+  private indentationPadding: number = 26;
+
+  /**
+   * Reloads the page tree
+   */
+  private static refreshPageTree(): void {
+    if (Viewport.NavigationContainer && Viewport.NavigationContainer.PageTree) {
+      Viewport.NavigationContainer.PageTree.refreshTree();
+    }
+  }
+
+  /**
+   * Generates the diff view of a record
+   *
+   * @param {Object} diff
+   * @return {$}
+   */
+  private static generateDiffView(diff: Array<any>): JQuery {
+    const $diff = $('<div />', {class: 'diff'});
+
+    for (let i = 0; i < diff.length; ++i) {
+      $diff.append(
+        $('<div />', {class: 'diff-item'}).append(
+          $('<div />', {class: 'diff-item-title'}).text(diff[i].label),
+          $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(diff[i].content),
+        ),
+      );
+    }
+    return $diff;
+  }
+
+  /**
+   * Generates the comments view of a record
+   *
+   * @param {Object} comments
+   * @return {$}
+   */
+  private static generateCommentView(comments: Array<any>): JQuery {
+    const $comments = $('<div />');
+
+    for (let i = 0; i < comments.length; ++i) {
+      const $panel = $('<div />', {class: 'panel panel-default'});
+
+      if (comments[i].user_comment.length > 0) {
+        $panel.append(
+          $('<div />', {class: 'panel-body'}).html(comments[i].user_comment),
+        );
+      }
+
+      $panel.append(
+        $('<div />', {class: 'panel-footer'}).append(
+          $('<span />', {class: 'label label-success'}).text(comments[i].stage_title),
+          $('<span />', {class: 'label label-info'}).text(comments[i].tstamp),
+        ),
+      );
+
+      $comments.append(
+        $('<div />', {class: 'media'}).append(
+          $('<div />', {class: 'media-left text-center'}).text(comments[i].user_username).prepend(
+            $('<div />').html(comments[i].user_avatar),
+          ),
+          $('<div />', {class: 'media-body'}).append($panel),
+        ),
+      );
+    }
+
+    return $comments;
+  }
+
+  /**
+   * Renders the record's history
+   *
+   * @param {Object} data
+   * @return {JQuery}
+   */
+  private static generateHistoryView(data: Array<any>): JQuery {
+    const $history = $('<div />');
+
+    for (let i = 0; i < data.length; ++i) {
+      const $panel = $('<div />', {class: 'panel panel-default'});
+      let $diff;
+
+      if (typeof data[i].differences === 'object') {
+        if (data[i].differences.length === 0) {
+          // Somehow here are no differences. What a pity, skip that record
+          continue;
+        }
+        $diff = $('<div />', {class: 'diff'});
+
+        for (let j = 0; j < data[i].differences.length; ++j) {
+          $diff.append(
+            $('<div />', {class: 'diff-item'}).append(
+              $('<div />', {class: 'diff-item-title'}).text(data[i].differences[j].label),
+              $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(data[i].differences[j].html),
+            ),
+          );
+        }
+
+        $panel.append(
+          $('<div />').append($diff),
+        );
+      } else {
+        $panel.append(
+          $('<div />', {class: 'panel-body'}).text(data[i].differences),
+        );
+      }
+      $panel.append(
+        $('<div />', {class: 'panel-footer'}).append(
+          $('<span />', {class: 'label label-info'}).text(data[i].datetime),
+        ),
+      );
+
+      $history.append(
+        $('<div />', {class: 'media'}).append(
+          $('<div />', {class: 'media-left text-center'}).text(data[i].user).prepend(
+            $('<div />').html(data[i].user_avatar),
+          ),
+          $('<div />', {class: 'media-body'}).append($panel),
+        ),
+      );
+    }
+
+    return $history;
+  }
+
+  constructor() {
+    super();
+
+    $((): void => {
+      let persistedDepth;
+      this.getElements();
+      this.registerEvents();
+
+      if (Persistent.isset('this.Module.depth')) {
+        persistedDepth = Persistent.get('this.Module.depth');
+        this.elements.$depthSelector.val(persistedDepth);
+        this.settings.depth = persistedDepth;
+      } else {
+        this.settings.depth = TYPO3.settings.Workspaces.depth;
+      }
+
+      this.loadWorkspaceComponents();
+    });
+  }
+
+  private getElements(): void {
+    this.elements.$searchForm = $(Identifiers.searchForm);
+    this.elements.$searchTextField = $(Identifiers.searchTextField);
+    this.elements.$searchSubmitBtn = $(Identifiers.searchSubmitBtn);
+    this.elements.$depthSelector = $(Identifiers.depthSelector);
+    this.elements.$languageSelector = $(Identifiers.languageSelector);
+    this.elements.$container = $(Identifiers.container);
+    this.elements.$tableBody = this.elements.$container.find('tbody');
+    this.elements.$actionIcons = $(Identifiers.actionIcons);
+    this.elements.$toggleAll = $(Identifiers.toggleAll);
+    this.elements.$chooseStageAction = $(Identifiers.chooseStageAction);
+    this.elements.$chooseSelectionAction = $(Identifiers.chooseSelectionAction);
+    this.elements.$chooseMassAction = $(Identifiers.chooseMassAction);
+    this.elements.$previewLinksButton = $(Identifiers.previewLinksButton);
+    this.elements.$pagination = $(Identifiers.pagination);
+  }
+
+  private registerEvents(): void {
+    $(document).on('click', '[data-action="swap"]', (e: JQueryEventObject): void => {
+      const row = <HTMLTableRowElement>e.target.closest('tr');
+      this.checkIntegrity(
+        {
+          selection: [
+            {
+              liveId: row.dataset.uid,
+              versionId: row.dataset.t3ver_oid,
+              table: row.dataset.table,
+            },
+          ],
+          type: 'selection',
+        },
+      ).done((response: any): void => {
+        if (response[0].result.result === 'warning') {
+          this.addIntegrityCheckWarningToWizard();
+        }
+
+        Wizard.set('forceSelection', false);
+        Wizard.addSlide(
+          'swap-confirm',
+          'Swap',
+          TYPO3.lang['window.swap.message'],
+          SeverityEnum.info,
+        );
+        Wizard.addFinalProcessingSlide((): void => {
+          // We passed this slide, swap the record now
+          this.sendRemoteRequest(
+            this.generateRemoteActionsPayload('swapSingleRecord', [
+              row.dataset.table,
+              row.dataset.t3ver_oid,
+              row.dataset.uid,
+            ]),
+          ).done((): void => {
+            Wizard.dismiss();
+            this.getWorkspaceInfos();
+            Backend.refreshPageTree();
+          });
+        }).done((): void => {
+          Wizard.show();
+        });
+      });
+    }).on('click', '[data-action="prevstage"]', (e: JQueryEventObject): void => {
+      this.sendToStage($(e.currentTarget).closest('tr'), 'prev');
+    }).on('click', '[data-action="nextstage"]', (e: JQueryEventObject): void => {
+      this.sendToStage($(e.currentTarget).closest('tr'), 'next');
+    }).on('click', '[data-action="changes"]', this.viewChanges)
+      .on('click', '[data-action="preview"]', this.openPreview)
+      .on('click', '[data-action="open"]', (e: JQueryEventObject): void => {
+        const row = <HTMLTableRowElement>e.currentTarget.closest('tr');
+        let newUrl = TYPO3.settings.FormEngine.moduleUrl
+          + '&returnUrl=' + encodeURIComponent(document.location.href)
+          + '&id=' + TYPO3.settings.Workspaces.id + '&edit[' + row.dataset.table + '][' + row.dataset.uid + ']=edit';
+
+        // Append workspace of record in all-workspaces view
+        if (TYPO3.settings.Workspaces.allView) {
+          newUrl += '&workspace=' + row.dataset.t3ver_wsid;
+        }
+
+        window.location.href = newUrl;
+    }).on('click', '[data-action="version"]', (e: JQueryEventObject): void => {
+      const row = <HTMLTableRowElement>e.currentTarget.closest('tr');
+      const recordUid = row.dataset.table === 'pages' ? row.dataset.t3ver_oid : row.dataset.pid;
+      window.location.href = top.TYPO3.configuration.pageModuleUrl
+        + '&id=' + recordUid
+        + '&returnUrl=' + encodeURIComponent(window.location.href);
+    }).on('click', '[data-action="remove"]', this.confirmDeleteRecordFromWorkspace)
+      .on('click', '[data-action="expand"]', (e: JQueryEventObject): void => {
+        const $me = $(e.currentTarget);
+        const $target = this.elements.$tableBody.find($me.data('target'));
+        let iconIdentifier;
+
+        if ($target.first().attr('aria-expanded') === 'true') {
+          iconIdentifier = 'apps-pagetree-expand';
+        } else {
+          iconIdentifier = 'apps-pagetree-collapse';
+        }
+
+        $me.empty().append(this.getPreRenderedIcon(iconIdentifier));
+    });
+    $(window.top.document).on('click', '.t3js-workspace-recipients-selectall', (e: JQueryEventObject): void => {
+      e.preventDefault();
+      $('.t3js-workspace-recipient', window.top.document).not(':disabled').prop('checked', true);
+    }).on('click', '.t3js-workspace-recipients-deselectall', (e: JQueryEventObject): void => {
+      e.preventDefault();
+      $('.t3js-workspace-recipient', window.top.document).not(':disabled').prop('checked', false);
+    });
+
+    this.elements.$searchForm.on('submit', (e: JQueryEventObject): void => {
+      e.preventDefault();
+      this.settings.filterTxt = this.elements.$searchTextField.val();
+      this.getWorkspaceInfos();
+    });
+
+    this.elements.$searchTextField.on('keyup', (e: JQueryEventObject): void => {
+      const me = <HTMLInputElement>e.target;
+
+      if (me.value !== '') {
+        this.elements.$searchSubmitBtn.removeClass('disabled');
+      } else {
+        this.elements.$searchSubmitBtn.addClass('disabled');
+        this.getWorkspaceInfos();
+      }
+    }).clearable(
+      {
+        onClear: (): void => {
+          this.elements.$searchSubmitBtn.addClass('disabled');
+          this.settings.filterTxt = '';
+          this.getWorkspaceInfos();
+        },
+      },
+    );
+
+    // checkboxes in the table
+    this.elements.$toggleAll.on('click', (): void => {
+      this.allToggled = !this.allToggled;
+      this.elements.$tableBody.find('input[type="checkbox"]').prop('checked', this.allToggled).trigger('change');
+    });
+    this.elements.$tableBody.on('change', 'tr input[type=checkbox]', this.handleCheckboxChange);
+
+    // Listen for depth changes
+    this.elements.$depthSelector.on('change', (e: JQueryEventObject): void => {
+      const depth = (<HTMLSelectElement>e.target).value;
+      Persistent.set('this.Module.depth', depth);
+      this.settings.depth = depth;
+      this.getWorkspaceInfos();
+    });
+
+    // Generate preview links
+    this.elements.$previewLinksButton.on('click', this.generatePreviewLinks);
+
+    // Listen for language changes
+    this.elements.$languageSelector.on('change', (e: JQueryEventObject): void => {
+      const $me = $(e.target);
+      this.settings.language = $me.val();
+
+      this.sendRemoteRequest([
+        this.generateRemoteActionsPayload('saveLanguageSelection', [$me.val()]),
+        this.generateRemotePayload('getWorkspaceInfos', this.settings),
+      ]).done((response: any): void => {
+        this.elements.$languageSelector.prev().html($me.find(':selected').data('icon'));
+        this.renderWorkspaceInfos(response[1].result);
+      });
+    });
+
+    // Listen for actions
+    this.elements.$chooseStageAction.on('change', this.sendToSpecificStageAction);
+    this.elements.$chooseSelectionAction.on('change', this.runSelectionAction);
+    this.elements.$chooseMassAction.on('change', this.runMassAction);
+
+    // clicking an action in the paginator
+    this.elements.$pagination.on('click', 'a[data-action]', (e: JQueryEventObject): void => {
+      e.preventDefault();
+
+      const $el = $(e.currentTarget);
+      let reload = false;
+
+      switch ($el.data('action')) {
+        case 'previous':
+          if (this.paging.currentPage > 1) {
+            this.paging.currentPage--;
+            reload = true;
+          }
+          break;
+        case 'next':
+          if (this.paging.currentPage < this.paging.totalPages) {
+            this.paging.currentPage++;
+            reload = true;
+          }
+          break;
+        case 'page':
+          this.paging.currentPage = parseInt($el.data('page'), 10);
+          reload = true;
+          break;
+        default:
+          throw 'Unknown action "' + $el.data('action') + '"';
+      }
+
+      if (reload) {
+        // Adjust settings
+        this.settings.start = parseInt(this.settings.limit.toString(), 10) * (this.paging.currentPage - 1);
+        this.getWorkspaceInfos();
+      }
+    });
+  }
+
+  private handleCheckboxChange = (e: JQueryEventObject): void => {
+    const $checkbox = $(e.currentTarget);
+    const $tr = $checkbox.parents('tr');
+    const table = $tr.data('table');
+    const uid = $tr.data('uid');
+    const t3ver_oid = $tr.data('t3ver_oid');
+    const record = table + ':' + uid + ':' + t3ver_oid;
+
+    if ($checkbox.prop('checked')) {
+      this.markedRecordsForMassAction.push(record);
+      $tr.addClass('warning');
+    } else {
+      const index = this.markedRecordsForMassAction.indexOf(record);
+      if (index > -1) {
+        this.markedRecordsForMassAction.splice(index, 1);
+      }
+      $tr.removeClass('warning');
+    }
+
+    this.elements.$chooseStageAction.prop('disabled', this.markedRecordsForMassAction.length === 0);
+    this.elements.$chooseSelectionAction.prop('disabled', this.markedRecordsForMassAction.length === 0);
+    this.elements.$chooseMassAction.prop('disabled', this.markedRecordsForMassAction.length > 0);
+  }
+
+  /**
+   * Sends a record to a stage
+   *
+   * @param {Object} $row
+   * @param {String} direction
+   */
+  private sendToStage($row: JQuery, direction: string): void {
+    let nextStage: string;
+    let stageWindowAction: string;
+    let stageExecuteAction: string;
+
+    if (direction === 'next') {
+      nextStage = $row.data('nextStage');
+      stageWindowAction = 'sendToNextStageWindow';
+      stageExecuteAction = 'sendToNextStageExecute';
+    } else if (direction === 'prev') {
+      nextStage = $row.data('prevStage');
+      stageWindowAction = 'sendToPrevStageWindow';
+      stageExecuteAction = 'sendToPrevStageExecute';
+    } else {
+      throw 'Invalid direction given.';
+    }
+
+    this.sendRemoteRequest(
+      this.generateRemoteActionsPayload(stageWindowAction, [
+        $row.data('uid'), $row.data('table'), $row.data('t3ver_oid'),
+      ]),
+    ).done((response: any): void => {
+      const $modal = this.renderSendToStageWindow(response);
+      $modal.on('button.clicked', (modalEvent: JQueryEventObject): void => {
+        if ((<HTMLAnchorElement>modalEvent.target).name === 'ok') {
+          const serializedForm = Utility.convertFormToObject(modalEvent.currentTarget.querySelector('form'));
+          serializedForm.affects = {
+            table: $row.data('table'),
+            nextStage: nextStage,
+            t3ver_oid: $row.data('t3ver_oid'),
+            uid: $row.data('uid'),
+            elements: [],
+          };
+
+          this.sendRemoteRequest([
+            this.generateRemoteActionsPayload(stageExecuteAction, [serializedForm]),
+            this.generateRemotePayload('getWorkspaceInfos', this.settings),
+          ]).done((requestResponse: any): void => {
+            $modal.modal('hide');
+            this.renderWorkspaceInfos(requestResponse[1].result);
+            Backend.refreshPageTree();
+          });
+        }
+      });
+    });
+  }
+
+  /**
+   * Loads the workspace components, like available stage actions and items of the workspace
+   */
+  private loadWorkspaceComponents(): void {
+    this.sendRemoteRequest([
+      this.generateRemotePayload('getWorkspaceInfos', this.settings),
+      this.generateRemotePayload('getStageActions', {}),
+      this.generateRemoteMassActionsPayload('getMassStageActions', {}),
+      this.generateRemotePayload('getSystemLanguages', {
+        pageUid: this.elements.$container.data('pageUid'),
+      }),
+    ]).done((response: any): void => {
+      this.elements.$depthSelector.prop('disabled', false);
+
+      // Records
+      this.renderWorkspaceInfos(response[0].result);
+
+      // Stage actions
+      const stageActions = response[1].result.data;
+      let i;
+      for (i = 0; i < stageActions.length; ++i) {
+        this.elements.$chooseStageAction.append(
+          $('<option />').val(stageActions[i].uid).text(stageActions[i].title),
+        );
+      }
+
+      // Mass actions
+      const massActions = response[2].result.data;
+      for (i = 0; i < massActions.length; ++i) {
+        this.elements.$chooseSelectionAction.append(
+          $('<option />').val(massActions[i].action).text(massActions[i].title),
+        );
+
+        this.elements.$chooseMassAction.append(
+          $('<option />').val(massActions[i].action).text(massActions[i].title),
+        );
+      }
+
+      // Languages
+      const languages = response[3].result.data;
+      for (i = 0; i < languages.length; ++i) {
+        const $option = $('<option />').val(languages[i].uid).text(languages[i].title).data('icon', languages[i].icon);
+        if (String(languages[i].uid) === String(TYPO3.settings.Workspaces.language)) {
+          $option.prop('selected', true);
+          this.elements.$languageSelector.prev().html(languages[i].icon);
+        }
+        this.elements.$languageSelector.append($option);
+      }
+      this.elements.$languageSelector.prop('disabled', false);
+    });
+  }
+
+  /**
+   * Gets the workspace infos
+   *
+   * @return {Promise}
+   * @protected
+   */
+  private getWorkspaceInfos(): void {
+    this.sendRemoteRequest(
+      this.generateRemotePayload('getWorkspaceInfos', this.settings),
+    ).done((response: any): void => {
+      this.renderWorkspaceInfos(response[0].result);
+    });
+  }
+
+  /**
+   * Renders fetched workspace informations
+   *
+   * @param {Object} result
+   */
+  private renderWorkspaceInfos(result: any): void {
+    this.elements.$tableBody.children().remove();
+    this.allToggled = false;
+    this.elements.$chooseStageAction.prop('disabled', true);
+    this.elements.$chooseSelectionAction.prop('disabled', true);
+    this.elements.$chooseMassAction.prop('disabled', result.data.length === 0);
+
+    this.buildPagination(result.total);
+
+    for (let i = 0; i < result.data.length; ++i) {
+      const item = result.data[i];
+      const $actions = $('<div />', {class: 'btn-group'});
+      let $integrityIcon: JQuery;
+      $actions.append(
+        this.getAction(
+          item.Workspaces_CollectionChildren > 0 && item.Workspaces_CollectionCurrent !== '',
+          'expand',
+          'apps-pagetree-collapse',
+        ).attr('title', TYPO3.lang['tooltip.expand'])
+          .attr('data-target', '[data-collection="' + item.Workspaces_CollectionCurrent + '"]')
+          .attr('data-toggle', 'collapse'),
+        $('<button />', {
+          class: 'btn btn-default',
+          'data-action': 'changes',
+          'data-toggle': 'tooltip',
+          title: TYPO3.lang['tooltip.showChanges'],
+        }).append(this.getPreRenderedIcon('actions-document-info')),
+        this.getAction(
+          item.allowedAction_swap && item.Workspaces_CollectionParent === '',
+          'swap',
+          'actions-version-swap-version')
+          .attr('title', TYPO3.lang['tooltip.swap']),
+        this.getAction(
+          item.allowedAction_view,
+          'preview',
+          'actions-version-workspace-preview',
+          ).attr('title', TYPO3.lang['tooltip.viewElementAction']),
+        this.getAction(
+          item.allowedAction_edit,
+          'open',
+          'actions-open',
+        ).attr('title', TYPO3.lang['tooltip.editElementAction']),
+        this.getAction(
+          true,
+          'version',
+          'actions-version-page-open',
+          ).attr('title', TYPO3.lang['tooltip.openPage']),
+        this.getAction(
+          item.allowedAction_delete,
+          'remove',
+          'actions-version-document-remove').attr('title', TYPO3.lang['tooltip.discardVersion'],
+        ),
+      );
+
+      if (item.integrity.messages !== '') {
+        $integrityIcon = $(TYPO3.settings.Workspaces.icons[item.integrity.status]);
+        $integrityIcon
+          .attr('data-toggle', 'tooltip')
+          .attr('data-placement', 'top')
+          .attr('data-html', 'true')
+          .attr('title', item.integrity.messages);
+      }
+
+      if (this.latestPath !== item.path_Workspace) {
+        this.latestPath = item.path_Workspace;
+        this.elements.$tableBody.append(
+          $('<tr />').append(
+            $('<th />'),
+            $('<th />', {colspan: 6}).text(this.latestPath),
+          ),
+        );
+      }
+      const $checkbox = $('<label />', {class: 'btn btn-default btn-checkbox'}).append(
+        $('<input />', {type: 'checkbox'}),
+        $('<span />', {class: 't3-icon fa'}),
+      );
+
+      const rowConfiguration: { [key: string]: any } = {
+        'data-uid': item.uid,
+        'data-pid': item.livepid,
+        'data-t3ver_oid': item.t3ver_oid,
+        'data-t3ver_wsid': item.t3ver_wsid,
+        'data-table': item.table,
+        'data-next-stage': item.value_nextStage,
+        'data-prev-stage': item.value_prevStage,
+        'data-stage': item.stage,
+      };
+
+      if (item.Workspaces_CollectionParent !== '') {
+        rowConfiguration['data-collection'] = item.Workspaces_CollectionParent;
+        rowConfiguration.class = 'collapse';
+      }
+
+      this.elements.$tableBody.append(
+        $('<tr />', rowConfiguration).append(
+          $('<td />').empty().append($checkbox),
+          $('<td />', {
+            class: 't3js-title-workspace',
+            style: item.Workspaces_CollectionLevel > 0
+              ? 'padding-left: ' + this.indentationPadding * item.Workspaces_CollectionLevel + 'px'
+              : '',
+          }).html(
+            item.icon_Workspace + '&nbsp;'
+            + '<a href="#" data-action="changes">'
+            + '<span class="item-state-' + item.state_Workspace + '">' + item.label_Workspace + '</span>'
+            + '</a>',
+          ),
+          $('<td />', {class: 't3js-title-live'}).html(item.icon_Live + '&nbsp;' + item.label_Live),
+          $('<td />').text(item.label_Stage),
+          $('<td />').empty().append($integrityIcon),
+          $('<td />').html(item.language.icon),
+          $('<td />', {class: 'text-right nowrap'}).append($actions),
+        ),
+      );
+
+      Tooltip.initialize('[data-toggle="tooltip"]', {
+        delay: {
+          show: 500,
+          hide: 100,
+        },
+        trigger: 'hover',
+        container: 'body',
+      });
+    }
+  }
+
+  /**
+   * Renders the pagination
+   *
+   * @param {Number} totalItems
+   */
+  private buildPagination(totalItems: number): void {
+    if (totalItems === 0) {
+      this.elements.$pagination.contents().remove();
+      return;
+    }
+
+    this.paging.totalItems = totalItems;
+    this.paging.totalPages = Math.ceil(totalItems / parseInt(this.settings.limit.toString(), 10));
+
+    if (this.paging.totalPages === 1) {
+      // early abort if only one page is available
+      this.elements.$pagination.contents().remove();
+      return;
+    }
+
+    const $ul = $('<ul />', {class: 'pagination pagination-block'});
+    const liElements: Array<JQuery> = [];
+    const $controlFirstPage = $('<li />').append(
+        $('<a />', {'data-action': 'previous'}).append(
+          $('<span />', {class: 't3-icon fa fa-arrow-left'}),
+        ),
+      ),
+      $controlLastPage = $('<li />').append(
+        $('<a />', {'data-action': 'next'}).append(
+          $('<span />', {class: 't3-icon fa fa-arrow-right'}),
+        ),
+      );
+
+    if (this.paging.currentPage === 1) {
+      $controlFirstPage.disablePagingAction();
+    }
+
+    if (this.paging.currentPage === this.paging.totalPages) {
+      $controlLastPage.disablePagingAction();
+    }
+
+    for (let i = 1; i <= this.paging.totalPages; i++) {
+      const $li = $('<li />', {class: this.paging.currentPage === i ? 'active' : ''});
+      $li.append(
+        $('<a />', {'data-action': 'page', 'data-page': i}).append(
+          $('<span />').text(i),
+        ),
+      );
+      liElements.push($li);
+    }
+
+    $ul.append($controlFirstPage, liElements, $controlLastPage);
+    this.elements.$pagination.empty().append($ul);
+  }
+
+  /**
+   * View changes of a record
+   *
+   * @param {Event} e
+   */
+  private viewChanges = (e: JQueryEventObject): void => {
+    e.preventDefault();
+
+    const $tr = $(e.currentTarget).closest('tr');
+    this.sendRemoteRequest(
+      this.generateRemotePayload('getRowDetails', {
+        stage: $tr.data('stage'),
+        t3ver_oid: $tr.data('t3ver_oid'),
+        table: $tr.data('table'),
+        uid: $tr.data('uid'),
+      }),
+    ).done((response: any): void => {
+      const item = response[0].result.data[0];
+      const $content = $('<div />');
+      const $tabsNav = $('<ul />', {class: 'nav nav-tabs', role: 'tablist'});
+      const $tabsContent = $('<div />', {class: 'tab-content'});
+      const modalButtons = [];
+
+      $content.append(
+        $('<p />').html(TYPO3.lang.path.replace('{0}', item.path_Live)),
+        $('<p />').html(
+          TYPO3.lang.current_step.replace('{0}', item.label_Stage)
+            .replace('{1}', item.stage_position)
+            .replace('{2}', item.stage_count),
+        ),
+      );
+
+      if (item.diff.length > 0) {
+        $tabsNav.append(
+          $('<li />', {role: 'presentation'}).append(
+            $('<a />', {
+              href: '#workspace-changes',
+              'aria-controls': 'workspace-changes',
+              role: 'tab',
+              'data-toggle': 'tab',
+            }).text(TYPO3.lang['window.recordChanges.tabs.changeSummary']),
+          ),
+        );
+        $tabsContent.append(
+          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-changes'}).append(
+            $('<div />', {class: 'form-section'}).append(
+              Backend.generateDiffView(item.diff),
+            ),
+          ),
+        );
+      }
+
+      if (item.comments.length > 0) {
+        $tabsNav.append(
+          $('<li />', {role: 'presentation'}).append(
+            $('<a />', {
+              href: '#workspace-comments',
+              'aria-controls': 'workspace-comments',
+              role: 'tab',
+              'data-toggle': 'tab',
+            }).html(TYPO3.lang['window.recordChanges.tabs.comments'] + '&nbsp;').append(
+              $('<span />', {class: 'badge'}).text(item.comments.length),
+            ),
+          ),
+        );
+        $tabsContent.append(
+          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-comments'}).append(
+            $('<div />', {class: 'form-section'}).append(
+              Backend.generateCommentView(item.comments),
+            ),
+          ),
+        );
+      }
+
+      if (item.history.total > 0) {
+        $tabsNav.append(
+          $('<li />', {role: 'presentation'}).append(
+            $('<a />', {
+              href: '#workspace-history',
+              'aria-controls': 'workspace-history',
+              role: 'tab',
+              'data-toggle': 'tab',
+            }).text(TYPO3.lang['window.recordChanges.tabs.history']),
+          ),
+        );
+
+        $tabsContent.append(
+          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-history'}).append(
+            $('<div />', {class: 'form-section'}).append(
+              Backend.generateHistoryView(item.history.data),
+            ),
+          ),
+        );
+      }
+
+      // Mark the first tab and pane as active
+      $tabsNav.find('li').first().addClass('active');
+      $tabsContent.find('.tab-pane').first().addClass('active');
+
+      // Attach tabs
+      $content.append(
+        $('<div />').append(
+          $tabsNav,
+          $tabsContent,
+        ),
+      );
+
+      if ($tr.data('stage') !== $tr.data('prevStage')) {
+        modalButtons.push({
+          text: item.label_PrevStage.title,
+          active: true,
+          btnClass: 'btn-default',
+          name: 'prevstage',
+          trigger: (): void => {
+            Modal.currentModal.trigger('modal-dismiss');
+            this.sendToStage($tr, 'prev');
+          },
+        });
+      }
+
+      modalButtons.push({
+        text: item.label_NextStage.title,
+        active: true,
+        btnClass: 'btn-default',
+        name: 'nextstage',
+        trigger: (): void => {
+          Modal.currentModal.trigger('modal-dismiss');
+          this.sendToStage($tr, 'next');
+        },
+      });
+      modalButtons.push({
+        text: TYPO3.lang.close,
+        active: true,
+        btnClass: 'btn-info',
+        name: 'cancel',
+        trigger: (): void => {
+          Modal.currentModal.trigger('modal-dismiss');
+        },
+      });
+
+      Modal.advanced({
+        type: Modal.types.default,
+        title: TYPO3.lang['window.recordInformation'].replace('{0}', $.trim($tr.find('.t3js-title-live').text())),
+        content: $content,
+        severity: SeverityEnum.info,
+        buttons: modalButtons,
+        size: Modal.sizes.medium,
+      });
+    });
+  }
+
+  /**
+   * Opens a record in a preview window
+   *
+   * @param {Event} e
+   */
+  private openPreview = (e: JQueryEventObject): void => {
+    const $tr = $(e.currentTarget).closest('tr');
+
+    this.sendRemoteRequest(
+      this.generateRemoteActionsPayload('viewSingleRecord', [
+        $tr.data('table'), $tr.data('uid'),
+      ]),
+    ).done((response: any): void => {
+      // tslint:disable-next-line:no-eval
+      eval(response[0].result);
+    });
+  }
+
+  /**
+   * Shows a confirmation modal and deletes the selected record from workspace.
+   *
+   * @param {Event} e
+   */
+  private confirmDeleteRecordFromWorkspace = (e: JQueryEventObject): void => {
+    const $tr = $(e.target).closest('tr');
+    const $modal = Modal.confirm(
+      TYPO3.lang['window.discard.title'],
+      TYPO3.lang['window.discard.message'],
+      SeverityEnum.warning,
+      [
+        {
+          text: TYPO3.lang.cancel,
+          active: true,
+          btnClass: 'btn-default',
+          name: 'cancel',
+          trigger: (): void => {
+            $modal.modal('hide');
+          },
+        },
+        {
+          text: TYPO3.lang.ok,
+          btnClass: 'btn-warning',
+          name: 'ok',
+        },
+      ],
+    );
+    $modal.on('button.clicked', (modalEvent: JQueryEventObject): void => {
+      if ((<HTMLAnchorElement>modalEvent.target).name === 'ok') {
+        this.sendRemoteRequest([
+          this.generateRemoteActionsPayload('deleteSingleRecord', [
+            $tr.data('table'),
+            $tr.data('uid'),
+          ]),
+        ]).done((): void => {
+          $modal.modal('hide');
+          this.getWorkspaceInfos();
+          Backend.refreshPageTree();
+        });
+      }
+    });
+  }
+
+  /**
+   * Runs a mass action
+   */
+  private runSelectionAction = (): void => {
+    const selectedAction = this.elements.$chooseSelectionAction.val();
+    const integrityCheckRequired = selectedAction !== 'discard';
+
+    if (selectedAction.length === 0) {
+      // Don't do anything if that value is empty
+      return;
+    }
+
+    const affectedRecords: Array<object> = [];
+    for (let i = 0; i < this.markedRecordsForMassAction.length; ++i) {
+      const affected = this.markedRecordsForMassAction[i].split(':');
+      affectedRecords.push({
+        table: affected[0],
+        liveId: affected[2],
+        versionId: affected[1],
+      });
+    }
+
+    if (!integrityCheckRequired) {
+      Wizard.setup.forceSelection = false;
+      this.renderSelectionActionWizard(selectedAction, affectedRecords);
+    } else {
+      this.checkIntegrity(
+        {
+          selection: affectedRecords,
+          type: 'selection',
+        },
+      ).done((response: any): void => {
+        Wizard.setup.forceSelection = false;
+        if (response[0].result.result === 'warning') {
+          this.addIntegrityCheckWarningToWizard();
+        }
+        this.renderSelectionActionWizard(selectedAction, affectedRecords);
+      });
+    }
+  }
+
+  /**
+   * Adds a slide to the wizard concerning an integrity check warning.
+   */
+  private addIntegrityCheckWarningToWizard = (): void => {
+    Wizard.addSlide(
+      'integrity-warning',
+      'Warning',
+      TYPO3.lang['integrity.hasIssuesDescription'] + '<br>' + TYPO3.lang['integrity.hasIssuesQuestion'],
+      SeverityEnum.warning,
+    );
+  }
+
+  /**
+   * Renders the wizard for selection actions
+   *
+   * @param {String} selectedAction
+   * @param {Array<object>} affectedRecords
+   */
+  private renderSelectionActionWizard(selectedAction: string, affectedRecords: Array<object>): void {
+    Wizard.addSlide(
+      'mass-action-confirmation',
+      TYPO3.lang['window.selectionAction.title'],
+      $('<p />').text(TYPO3.lang['tooltip.' + selectedAction + 'Selected']),
+      SeverityEnum.warning,
+    );
+    Wizard.addFinalProcessingSlide((): void => {
+      this.sendRemoteRequest(
+        this.generateRemoteActionsPayload('executeSelectionAction', {
+          action: selectedAction,
+          selection: affectedRecords,
+        }),
+      ).done((): void => {
+        this.getWorkspaceInfos();
+        Wizard.dismiss();
+        Backend.refreshPageTree();
+      });
+    }).done((): void => {
+      Wizard.show();
+
+      Wizard.getComponent().on('wizard-dismissed', (): void => {
+        this.elements.$chooseSelectionAction.val('');
+      });
+    });
+  }
+
+  /**
+   * Runs a mass action
+   */
+  private runMassAction = (): void => {
+    const selectedAction = this.elements.$chooseMassAction.val();
+    const integrityCheckRequired = selectedAction !== 'discard';
+
+    if (selectedAction.length === 0) {
+      // Don't do anything if that value is empty
+      return;
+    }
+
+    if (!integrityCheckRequired) {
+      Wizard.setup.forceSelection = false;
+      this.renderMassActionWizard(selectedAction);
+    } else {
+      this.checkIntegrity(
+        {
+          language: this.settings.language,
+          type: selectedAction,
+        },
+      ).done((response: any): void => {
+        Wizard.setup.forceSelection = false;
+        if (response[0].result.result === 'warning') {
+          this.addIntegrityCheckWarningToWizard();
+        }
+        this.renderMassActionWizard(selectedAction);
+      });
+    }
+  }
+
+  /**
+   * Renders the wizard for mass actions
+   *
+   * @param {String} selectedAction
+   */
+  private renderMassActionWizard(selectedAction: string): void {
+    let massAction: string;
+    let doSwap = false;
+
+    switch (selectedAction) {
+      case 'publish':
+        massAction = 'publishWorkspace';
+        break;
+      case 'swap':
+        massAction = 'publishWorkspace';
+        doSwap = true;
+        break;
+      case 'discard':
+        massAction = 'flushWorkspace';
+        break;
+      default:
+        throw 'Invalid mass action ' + selectedAction + ' called.';
+    }
+
+    Wizard.set('forceSelection', false);
+    Wizard.addSlide(
+      'mass-action-confirmation',
+      TYPO3.lang['window.massAction.title'],
+      $('<p />').html(TYPO3.lang['tooltip.' + selectedAction + 'All'] + '<br><br>' + TYPO3.lang['tooltip.affectWholeWorkspace']),
+      SeverityEnum.warning,
+    );
+
+    const sendRequestsUntilAllProcessed = (response: any): void => {
+      const result = response[0].result;
+      // Make sure to process all items
+      if (result.processed < result.total) {
+        this.sendRemoteRequest(
+          this.generateRemoteMassActionsPayload(massAction, result),
+        ).done(sendRequestsUntilAllProcessed);
+      } else {
+        this.getWorkspaceInfos();
+        Wizard.dismiss();
+      }
+    };
+
+    Wizard.addFinalProcessingSlide((): void => {
+      this.sendRemoteRequest(
+        this.generateRemoteMassActionsPayload(massAction, {
+          init: true,
+          total: 0,
+          processed: 0,
+          language: this.settings.language,
+          swap: doSwap,
+        }),
+      ).done(sendRequestsUntilAllProcessed);
+    }).done((): void => {
+      Wizard.show();
+
+      Wizard.getComponent().on('wizard-dismissed', (): void => {
+        this.elements.$chooseMassAction.val('');
+      });
+    });
+  }
+
+  /**
+   * Sends marked records to a stage
+   *
+   * @param {Event} e
+   */
+  private sendToSpecificStageAction = (e: JQueryEventObject): void => {
+    const affectedRecords: Array<{ [key: string]: number|string }> = [];
+    const stage = $(e.currentTarget).val();
+    for (let i = 0; i < this.markedRecordsForMassAction.length; ++i) {
+      const affected = this.markedRecordsForMassAction[i].split(':');
+      affectedRecords.push({
+        table: affected[0],
+        uid: affected[1],
+        t3ver_oid: affected[2],
+      });
+    }
+    this.sendRemoteRequest(
+      this.generateRemoteActionsPayload('sendToSpecificStageWindow', [
+        stage, affectedRecords,
+      ]),
+    ).done((response: any): void => {
+      const $modal = this.renderSendToStageWindow(response);
+      $modal.on('button.clicked', (modalEvent: JQueryEventObject): void => {
+        if ((<HTMLAnchorElement>modalEvent.target).name === 'ok') {
+          const serializedForm = Utility.convertFormToObject(modalEvent.currentTarget.querySelector('form'));
+          serializedForm.affects = {
+            elements: affectedRecords,
+            nextStage: stage,
+          };
+
+          this.sendRemoteRequest([
+            this.generateRemoteActionsPayload('sendToSpecificStageExecute', [serializedForm]),
+            this.generateRemotePayload('getWorkspaceInfos', this.settings),
+          ]).done((actionResponse: any): void => {
+            $modal.modal('hide');
+            this.renderWorkspaceInfos(actionResponse[1].result);
+            Backend.refreshPageTree();
+          });
+        }
+      }).on('modal-destroyed', (): void => {
+        this.elements.$chooseStageAction.val('');
+      });
+    });
+  }
+
+  /**
+   * Renders the action button based on the user's permission.
+   *
+   * @param {string} condition
+   * @param {string} action
+   * @param {string} iconIdentifier
+   * @return {JQuery}
+   */
+  private getAction(condition: boolean, action: string, iconIdentifier: string): JQuery {
+    if (condition) {
+      return $('<button />', {
+        class: 'btn btn-default',
+        'data-action': action,
+        'data-toggle': 'tooltip',
+      }).append(this.getPreRenderedIcon(iconIdentifier));
+    }
+    return $('<span />', {class: 'btn btn-default disabled'}).append(this.getPreRenderedIcon('empty-empty'));
+  }
+
+  /**
+   * Fetches and renders available preview links
+   */
+  private generatePreviewLinks = (): void => {
+    this.sendRemoteRequest(
+      this.generateRemoteActionsPayload('generateWorkspacePreviewLinksForAllLanguages', [
+        this.settings.id,
+      ]),
+    ).done((response: any): void => {
+      const result = response[0].result;
+      const $list = $('<dl />');
+
+      $.each(result, (language: string, url: string): void => {
+        $list.append(
+          $('<dt />').text(language),
+          $('<dd />').append(
+            $('<a />', {href: url, target: '_blank'}).text(url),
+          ),
+        );
+      });
+
+      Modal.show(
+        TYPO3.lang.previewLink,
+        $list,
+        SeverityEnum.info,
+        [{
+          text: TYPO3.lang.ok,
+          active: true,
+          btnClass: 'btn-info',
+          name: 'ok',
+          trigger: (): void => {
+            Modal.currentModal.trigger('modal-dismiss');
+          },
+        }],
+        ['modal-inner-scroll'],
+      );
+    });
+  }
+
+  /**
+   * Gets the pre-rendered icon
+   * This method is intended to be dropped once we use Fluid's StandaloneView.
+   *
+   * @param {String} identifier
+   * @returns {$}
+   */
+  private getPreRenderedIcon(identifier: string): JQuery {
+    return this.elements.$actionIcons.find('[data-identifier="' + identifier + '"]').clone();
+  }
+}
+
+export = new Backend();
diff --git a/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Preview.ts b/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Preview.ts
new file mode 100644 (file)
index 0000000..b9867ea
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * 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 {SeverityEnum} from 'TYPO3/CMS/Backend/Enum/Severity';
+import 'twbs/bootstrap-slider';
+import * as $ from 'jquery';
+import Modal = require('TYPO3/CMS/Backend/Modal');
+import Utility = require('TYPO3/CMS/Backend/Utility');
+import Workspaces from './Workspaces';
+
+enum Identifiers {
+  topbar = '#typo3-topbar',
+  workspacePanel = '.workspace-panel',
+  liveView = '#live-view',
+  workspaceTabs = '.t3js-workspace-tabs [data-toggle="tab"]',
+  workspaceActions = '.t3js-workspace-actions',
+  stageSlider = '#workspace-stage-slider',
+  workspaceView = '#workspace-view',
+  workspaceList = '#workspace-list',
+  sendToStageAction = '[data-action="send-to-stage"]',
+  discardAction = '[data-action="discard"]',
+  stageButtonsContainer = '.t3js-stage-buttons',
+  previewModeContainer = '.t3js-preview-mode',
+  activePreviewMode = '.t3js-active-preview-mode',
+  workspacePreview = '.t3js-workspace-preview',
+}
+
+class Preview extends Workspaces {
+  private currentSlidePosition: number = 100;
+  private elements: { [key: string]: JQuery } = {};
+
+  /**
+   * Calculate the available space based on the viewport height
+   *
+   * @returns {Number}
+   */
+  private static getAvailableSpace(): number {
+    const $viewportHeight = $(window).height();
+    const $topbarHeight = $(Identifiers.topbar).outerHeight();
+
+    return $viewportHeight - $topbarHeight;
+  }
+
+  constructor() {
+    super();
+
+    $((): void => {
+      this.getElements();
+      this.resizeViews();
+      this.adjustPreviewModeSelectorWidth();
+      this.elements.$stageSlider.slider();
+      this.registerEvents();
+    });
+  }
+
+  /**
+   * Fetches and stores often required elements
+   */
+  private getElements(): void {
+    this.elements.$liveView = $(Identifiers.liveView);
+    this.elements.$workspacePanel = $(Identifiers.workspacePanel);
+    this.elements.$workspaceTabs = $(Identifiers.workspaceTabs);
+    this.elements.$workspaceActions = $(Identifiers.workspaceActions);
+    this.elements.$stageSlider = $(Identifiers.stageSlider);
+    this.elements.$workspaceView = $(Identifiers.workspaceView);
+    this.elements.$workspaceList = $(Identifiers.workspaceList);
+    this.elements.$stageButtonsContainer = $(Identifiers.stageButtonsContainer);
+    this.elements.$previewModeContainer = $(Identifiers.previewModeContainer);
+    this.elements.$activePreviewMode = $(Identifiers.activePreviewMode);
+    this.elements.$workspacePreview = $(Identifiers.workspacePreview);
+  }
+
+  /**
+   * Registers the events
+   */
+  private registerEvents(): void {
+    $(window).on('resize', (): void => {
+      this.resizeViews();
+    });
+    $(document)
+      .on('click', Identifiers.discardAction, this.renderDiscardWindow)
+      .on('click', Identifiers.sendToStageAction, this.renderSendPageToStageWindow)
+    ;
+
+    this.elements.$workspaceTabs.on('show.bs.tab', (e: JQueryEventObject): void => {
+      this.elements.$workspaceActions.toggle((<HTMLElement>e.currentTarget).dataset.actions);
+    });
+    this.elements.$stageSlider.on('change', this.updateSlidePosition);
+    this.elements.$previewModeContainer.find('[data-preview-mode]').on('click', this.changePreviewMode);
+  }
+
+  /**
+   * Renders the staging buttons
+   *
+   * @param {String} buttons
+   */
+  private renderStageButtons(buttons: string): void {
+    this.elements.$stageButtonsContainer.html(buttons);
+  }
+
+  /**
+   * Updates the position of the comparison slider
+   *
+   * @param {Event} e
+   */
+  private updateSlidePosition = (e: any): void => {
+    this.currentSlidePosition = e.value.newValue;
+    this.resizeViews();
+  }
+
+  /**
+   * Resize the views based on the current viewport height and slider position
+   */
+  private resizeViews(): void {
+    const availableSpace = Preview.getAvailableSpace();
+    const relativeHeightOfLiveView = (this.currentSlidePosition - 100) * -1;
+    const absoluteHeightOfLiveView = Math.round(Math.abs(availableSpace * relativeHeightOfLiveView / 100));
+    const outerHeightDifference = this.elements.$liveView.outerHeight() - this.elements.$liveView.height();
+
+    this.elements.$workspacePreview.height(availableSpace);
+
+    if (this.elements.$activePreviewMode.data('activePreviewMode') === 'slider') {
+      this.elements.$liveView.height(absoluteHeightOfLiveView - outerHeightDifference);
+    }
+    this.elements.$workspaceList.height(availableSpace);
+  }
+
+  /**
+   * Renders the discard window
+   */
+  private renderDiscardWindow = (): void => {
+    const $modal = Modal.confirm(
+      TYPO3.lang['window.discardAll.title'],
+      TYPO3.lang['window.discardAll.message'],
+      SeverityEnum.warning,
+      [
+        {
+          text: TYPO3.lang.cancel,
+          active: true,
+          btnClass: 'btn-default',
+          name: 'cancel',
+          trigger: (): void => {
+            $modal.modal('hide');
+          },
+        },
+        {
+          text: TYPO3.lang.ok,
+          btnClass: 'btn-warning',
+          name: 'ok',
+        },
+      ],
+    );
+    $modal.on('button.clicked', (e: JQueryEventObject): void => {
+      if ((<HTMLAnchorElement>e.target).name === 'ok') {
+        this.sendRemoteRequest([
+          this.generateRemoteActionsPayload('discardStagesFromPage', [TYPO3.settings.Workspaces.id]),
+          this.generateRemoteActionsPayload('updateStageChangeButtons', [TYPO3.settings.Workspaces.id]),
+        ]).done((response: Array<any>): void => {
+          $modal.modal('hide');
+          this.renderStageButtons(response[1].result);
+          // Reloading live view and and workspace list view IFRAME
+          this.elements.$workspaceView.attr('src', this.elements.$workspaceView.attr('src'));
+          this.elements.$workspaceList.attr('src', this.elements.$workspaceList.attr('src'));
+        });
+      }
+    });
+  }
+
+  /**
+   * Adjusts the width of the preview mode selector to avoid jumping around due to different widths of the labels
+   */
+  private adjustPreviewModeSelectorWidth(): void {
+    const $btnGroup = this.elements.$previewModeContainer.find('.btn-group');
+    let maximumWidth = 0;
+
+    $btnGroup.addClass('open');
+    this.elements.$previewModeContainer.find('li > a > span').each((_: number, el: Element): void => {
+      const width = $(el).width();
+      if (maximumWidth < width) {
+        maximumWidth = width;
+      }
+    });
+    $btnGroup.removeClass('open');
+    this.elements.$activePreviewMode.width(maximumWidth);
+  }
+
+  /**
+   * Renders the "send page to stage" window
+   */
+  private renderSendPageToStageWindow = (e: JQueryEventObject): void => {
+    const me = (<HTMLElement>e.currentTarget);
+    const direction = me.dataset.direction;
+    let actionName;
+
+    if (direction === 'prev') {
+      actionName = 'sendPageToPreviousStage';
+    } else if (direction === 'next') {
+      actionName = 'sendPageToNextStage';
+    } else {
+      throw 'Invalid direction ' + direction + ' requested.';
+    }
+
+    this.sendRemoteRequest(
+      this.generateRemoteActionsPayload(actionName, [TYPO3.settings.Workspaces.id]),
+    ).done((response: any): void => {
+      const $modal = this.renderSendToStageWindow(response);
+      $modal.on('button.clicked', (modalEvent: JQueryEventObject): void => {
+        if ((<HTMLAnchorElement>modalEvent.target).name === 'ok') {
+          const serializedForm = Utility.convertFormToObject(modalEvent.currentTarget.querySelector('form'));
+          serializedForm.affects = response[0].result.affects;
+          serializedForm.stageId = me.dataset.stageId;
+
+          this.sendRemoteRequest([
+            this.generateRemoteActionsPayload('sentCollectionToStage', [serializedForm]),
+            this.generateRemoteActionsPayload('updateStageChangeButtons', [TYPO3.settings.Workspaces.id]),
+          ]).done((updateResponse: any): void => {
+            $modal.modal('hide');
+
+            this.renderStageButtons(updateResponse[1].result);
+          });
+        }
+      });
+    });
+  }
+
+  /**
+   * Changes the preview mode
+   *
+   * @param {Event} e
+   */
+  private changePreviewMode = (e: JQueryEventObject): void => {
+    e.preventDefault();
+
+    const $trigger = $(e.currentTarget);
+    const currentPreviewMode = this.elements.$activePreviewMode.data('activePreviewMode');
+    const newPreviewMode = $trigger.data('previewMode');
+
+    this.elements.$activePreviewMode.text($trigger.text()).data('activePreviewMode', newPreviewMode);
+    this.elements.$workspacePreview.parent()
+      .removeClass('preview-mode-' + currentPreviewMode)
+      .addClass('preview-mode-' + newPreviewMode);
+
+    if (newPreviewMode === 'slider') {
+      this.elements.$stageSlider.parent().toggle(true);
+      this.resizeViews();
+    } else {
+      this.elements.$stageSlider.parent().toggle(false);
+
+      if (newPreviewMode === 'vbox') {
+        this.elements.$liveView.height('100%');
+      } else {
+        this.elements.$liveView.height('50%');
+      }
+    }
+  }
+}
+
+export = new Preview();
diff --git a/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Toolbar/WorkspacesMenu.ts b/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Toolbar/WorkspacesMenu.ts
new file mode 100644 (file)
index 0000000..8fdad54
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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 ModuleMenu = require('TYPO3/CMS/Backend/ModuleMenu');
+import Viewport = require('TYPO3/CMS/Backend/Viewport');
+
+enum Identifiers {
+  containerSelector = '#typo3-cms-workspaces-backend-toolbaritems-workspaceselectortoolbaritem',
+  activeMenuItemLinkSelector = '.dropdown-menu .selected',
+  menuItemSelector = '.t3js-workspace-item',
+  menuItemLinkSelector = '.t3js-workspaces-switchlink',
+  toolbarItemSelector = '.dropdown-toggle',
+  workspaceModuleLinkSelector = '.t3js-workspaces-modulelink',
+}
+
+enum Classes {
+  workspaceBodyClass = 'typo3-in-workspace',
+  workspacesTitleInToolbarClass = 'toolbar-item-name',
+}
+
+/**
+ * Module: TYPO3/CMS/Workspaces/Toolbar/WorkspacesMenu
+ * toolbar menu for the workspaces functionality to switch between the workspaces
+ * and jump to the workspaces module
+ */
+class WorkspacesMenu {
+  /**
+   * Refresh the page tree
+   */
+  private static refreshPageTree(): void {
+    if (Viewport.NavigationContainer && Viewport.NavigationContainer.PageTree) {
+      Viewport.NavigationContainer.PageTree.refreshTree();
+    }
+  }
+
+  /**
+   * adds the workspace title to the toolbar next to the username
+   *
+   * @param {String} workspaceTitle
+   */
+  private static updateTopBar(workspaceTitle: string): void {
+    $('.' + Classes.workspacesTitleInToolbarClass, Identifiers.containerSelector).remove();
+
+    if (workspaceTitle && workspaceTitle.length) {
+      let title = $('<span>', {
+        'class': Classes.workspacesTitleInToolbarClass,
+      }).text(workspaceTitle);
+      $(Identifiers.toolbarItemSelector, Identifiers.containerSelector).append(title);
+    }
+  }
+
+  private static updateBackendContext(title: string = ''): void {
+    let topBarTitle = '';
+    if (TYPO3.configuration.inWorkspace) {
+      $('body').addClass(Classes.workspaceBodyClass);
+      topBarTitle = title || TYPO3.lang['Workspaces.workspaceTitle'];
+    } else {
+      $('body').removeClass(Classes.workspaceBodyClass);
+    }
+
+    WorkspacesMenu.updateTopBar(topBarTitle);
+  }
+
+  constructor() {
+    Viewport.Topbar.Toolbar.registerEvent((): void => {
+      this.initializeEvents();
+      WorkspacesMenu.updateBackendContext();
+    });
+  }
+
+  /**
+   * Changes the data in the module menu and the updates the backend context
+   * This method is also used in the workspaces backend module.
+   *
+   * @param {Number} id the workspace ID
+   * @param {String} title the workspace title
+   */
+  public performWorkspaceSwitch(id: number, title: string): void {
+    top.TYPO3.Backend.workspaceTitle = title;
+    top.TYPO3.configuration.inWorkspace = id !== 0;
+
+    WorkspacesMenu.updateBackendContext(title);
+
+    // first remove all checks, then set the check in front of the selected workspace
+    const stateActiveClass = 'fa fa-check';
+    const stateInactiveClass = 'fa fa-empty-empty';
+
+    // remove "selected" class and checkmark
+    $(Identifiers.activeMenuItemLinkSelector + ' i', Identifiers.containerSelector)
+      .removeClass(stateActiveClass)
+      .addClass(stateInactiveClass);
+    $(Identifiers.activeMenuItemLinkSelector, Identifiers.containerSelector).removeClass('selected');
+
+    // add "selected" class and checkmark
+    const $activeElement = $(Identifiers.menuItemLinkSelector + '[data-workspaceid=' + id + ']', Identifiers.containerSelector);
+    const $menuItem = $activeElement.closest(Identifiers.menuItemSelector);
+    $menuItem.find('i')
+      .removeClass(stateInactiveClass)
+      .addClass(stateActiveClass);
+    $menuItem.addClass('selected');
+  }
+
+  private initializeEvents(): void {
+    $(Identifiers.containerSelector).on('click', Identifiers.workspaceModuleLinkSelector, (evt: JQueryEventObject): void => {
+      evt.preventDefault();
+      ModuleMenu.App.showModule((<HTMLAnchorElement>evt.currentTarget).dataset.module);
+    });
+
+    $(Identifiers.containerSelector).on('click', Identifiers.menuItemLinkSelector, (evt: JQueryEventObject): void => {
+      evt.preventDefault();
+      this.switchWorkspace(parseInt((<HTMLAnchorElement>evt.currentTarget).dataset.workspaceid, 10));
+    });
+  }
+
+  private switchWorkspace(workspaceId: number): void {
+    $.ajax({
+      url: TYPO3.settings.ajaxUrls.workspace_switch,
+      type: 'post',
+      data: {
+        workspaceId: workspaceId,
+        pageId: top.fsMod.recentIds.web,
+      },
+      success: (response: any): void => {
+        if (!response.workspaceId) {
+          response.workspaceId = 0;
+        }
+
+        this.performWorkspaceSwitch(parseInt(response.workspaceId, 10), response.title);
+
+        // append the returned page ID to the current module URL
+        if (response.pageId) {
+          top.fsMod.recentIds.web = response.pageId;
+          let url = TYPO3.Backend.ContentContainer.getUrl();
+          url += (url.indexOf('?') === -1 ? '?' : '&') + '&id=' + response.pageId;
+          WorkspacesMenu.refreshPageTree();
+          Viewport.ContentContainer.setUrl(url);
+
+          // when in web module reload, otherwise send the user to the web module
+        } else if (top.currentModuleLoaded.indexOf('web_') === 0) {
+          WorkspacesMenu.refreshPageTree();
+          ModuleMenu.App.reloadFrames();
+        } else if (TYPO3.configuration.pageModule) {
+          ModuleMenu.App.showModule(TYPO3.configuration.pageModule);
+        }
+
+        // reload the module menu
+        ModuleMenu.App.refreshMenu();
+      },
+    });
+  }
+}
+
+const workspacesMenu = new WorkspacesMenu();
+// expose the module in a global object
+TYPO3.WorkspacesMenu = workspacesMenu;
+
+export = workspacesMenu;
diff --git a/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Workspaces.ts b/Build/Sources/TypeScript/workspaces/Resources/Public/TypeScript/Workspaces.ts
new file mode 100644 (file)
index 0000000..578977a
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * 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 {SeverityEnum} from 'TYPO3/CMS/Backend/Enum/Severity';
+import * as $ from 'jquery';
+import Modal = require('TYPO3/CMS/Backend/Modal');
+
+export default class Workspaces {
+  private tid: number = 0;
+
+  /**
+   * Renders the send to stage window
+   * @param {Object} response
+   * @return {$}
+   */
+  protected renderSendToStageWindow(response: Array<any>): JQuery {
+    const result = response[0].result;
+    const $form = $('<form />');
+
+    if (typeof result.sendMailTo !== 'undefined' && result.sendMailTo.length > 0) {
+      $form.append(
+        $('<label />', {class: 'control-label'}).text(TYPO3.lang['window.sendToNextStageWindow.itemsWillBeSentTo']),
+      );
+      $form.append(
+        $('<div />', {class: 'form-group'}).append(
+          $('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-selectall" />')
+            .text(TYPO3.lang['window.sendToNextStageWindow.selectAll']),
+          '&nbsp;',
+          $('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-deselectall" />')
+            .text(TYPO3.lang['window.sendToNextStageWindow.deselectAll']),
+        ),
+      );
+
+      for (let i = 0; i < result.sendMailTo.length; ++i) {
+        const recipient = result.sendMailTo[i];
+
+        $form.append(
+          $('<div />', {class: 'checkbox'}).append(
+            $('<label />').text(recipient.label).prepend(
+              $('<input />', {
+                type: 'checkbox',
+                name: 'recipients',
+                class: 't3js-workspace-recipient',
+                id: recipient.name,
+                value: recipient.value,
+              }).prop('checked', recipient.checked).prop('disabled', recipient.disabled),
+            ),
+          ),
+        );
+      }
+    }
+
+    if (typeof result.additional !== 'undefined') {
+      $form.append(
+        $('<div />', {class: 'form-group'}).append(
+          $('<label />', {
+            class: 'control-label',
+            'for': 'additional',
+          }).text(TYPO3.lang['window.sendToNextStageWindow.additionalRecipients']),
+          $('<textarea />', {
+            class: 'form-control',
+            name: 'additional',
+            id: 'additional',
+          }).text(result.additional.value),
+          $('<span />', {class: 'help-block'}).text(TYPO3.lang['window.sendToNextStageWindow.additionalRecipients.hint']),
+        ),
+      );
+    }
+
+    $form.append(
+      $('<div />', {class: 'form-group'}).append(
+        $('<label />', {
+          class: 'control-label',
+          'for': 'comments',
+        }).text(TYPO3.lang['window.sendToNextStageWindow.comments']),
+        $('<textarea />', {
+          class: 'form-control',
+          name: 'comments',
+          id: 'comments',
+        }).text(result.comments.value),
+      ),
+    );
+
+    const $modal = Modal.show(
+      TYPO3.lang.actionSendToStage,
+      $form,
+      SeverityEnum.info,
+      [
+        {
+          text: TYPO3.lang.cancel,
+          active: true,
+          btnClass: 'btn-default',
+          name: 'cancel',
+          trigger: (): void => {
+            $modal.modal('hide');
+          },
+        },
+        {
+          text: TYPO3.lang.ok,
+          btnClass: 'btn-info',
+          name: 'ok',
+        },
+      ],
+    );
+
+    return $modal;
+  }
+
+  /**
+   * Checks the integrity of a record
+   *
+   * @param {Array} payload
+   * @return {$}
+   */
+  protected checkIntegrity(payload: object): JQueryXHR {
+    return this.sendRemoteRequest(
+      this.generateRemotePayload('checkIntegrity', payload),
+    );
+  }
+
+  /**
+   * Sends an AJAX request
+   *
+   * @param {Object} payload
+   * @return {$}
+   */
+  protected sendRemoteRequest(payload: object): JQueryXHR {
+    return $.ajax({
+      url: TYPO3.settings.ajaxUrls.workspace_dispatch,
+      method: 'POST',
+      contentType: 'application/json; charset=utf-8',
+      dataType: 'json',
+      data: JSON.stringify(payload),
+    });
+  }
+
+  /**
+   * Generates the payload for a remote call
+   *
+   * @param {String} method
+   * @param {Object} data
+   * @return {{action, data, method, type}}
+   */
+  protected generateRemotePayload(method: string, data: object = {}): object {
+    return this.generateRemotePayloadBody('RemoteServer', method, data);
+  }
+
+  /**
+   * Generates the payload for MassActions
+   *
+   * @param {String} method
+   * @param {Object} data
+   * @return {{action, data, method, type}}
+   */
+  protected generateRemoteMassActionsPayload(method: string, data: object = {}): object {
+    return this.generateRemotePayloadBody('MassActions', method, data);
+  }
+
+  /**
+   * Generates the payload for Actions
+   *
+   * @param {String} method
+   * @param {Object} data
+   * @return {{action, data, method, type}}
+   */
+  protected generateRemoteActionsPayload(method: string, data: object = {}): object {
+    return this.generateRemotePayloadBody('Actions', method, data);
+  }
+
+  /**
+   * Generates the payload body
+   *
+   * @param {String} action
+   * @param {String} method
+   * @param {Object} data
+   * @return {{action: String, data: Object, method: String, type: string}}
+   */
+  private generateRemotePayloadBody(action: string, method: string, data: object): object {
+    if (data instanceof Array) {
+      data.push(TYPO3.settings.Workspaces.token);
+    } else {
+      data = [
+        data,
+        TYPO3.settings.Workspaces.token,
+      ];
+    }
+    return {
+      action: action,
+      data: data,
+      method: method,
+      type: 'rpc',
+      tid: this.tid++,
+    };
+  }
+}
index 27243e4..0727b0c 100644 (file)
@@ -22,6 +22,7 @@ declare namespace TYPO3 {
   export let Tooltip: any;
   export let Utility: any;
   export let Wizard: any;
+  export let WorkspacesMenu: any;
   export let settings: any;
   export const lang: { [key: string]: string };
   export const configuration: any;
@@ -63,16 +64,6 @@ declare namespace TYPO3 {
         public initializeNullNoPlaceholderCheckboxes(): void;
         public initializeNullWithPlaceholderCheckboxes(): void;
       }
-
-      export class Wizard {
-        public addSlide(identifier: string, title: string, content: string, severity: number, callback?: Function): Wizard;
-        public lockNextStep(): JQuery;
-        public unlockNextStep(): JQuery;
-        public getComponent(): JQuery;
-        public addFinalProcessingSlide(callback?: Function): JQueryXHR;
-        public show(): Wizard;
-        public dismiss(): Wizard;
-      }
     }
   }
 }
@@ -97,10 +88,6 @@ declare module 'TYPO3/CMS/Backend/FormEngine/Element/SelectTree' {
   export = TYPO3.CMS.Backend.FormEngine.Element.SelectTree;
 }
 
-declare module 'TYPO3/CMS/Backend/Wizard' {
-  export = new TYPO3.CMS.Backend.Wizard();
-}
-
 // type definition for global namespace object
 interface Window {
   TYPO3: any;
@@ -109,6 +96,7 @@ interface Window {
   inline: {
     delayedImportElement: (objectId: number, table: string, uid: number, type: string) => void,
   };
+  loadEditId: (id: number, addGetVars?: string) => void;
   require: Function;
   list_frame: Window;
   jump: Function;
index 2798189..915c11c 100644 (file)
@@ -345,12 +345,16 @@ class BackendController
         // If another page module was specified, replace the default Page module with the new one
         $newPageModule = trim($beUser->getTSConfig()['options.']['overridePageModule'] ?? '');
         $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
+        $pageModuleUrl = '';
         if (!$beUser->check('modules', $pageModule)) {
             $pageModule = '';
+        } else {
+            $pageModuleUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($pageModule);
         }
         $t3Configuration = [
             'username' => htmlspecialchars($beUser->user['username']),
             'pageModule' => $pageModule,
+            'pageModuleUrl' => $pageModuleUrl,
             'inWorkspace' => $beUser->workspace !== 0,
             'showRefreshLoginPopup' => (bool)($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] ?? false)
         ];
index 3fd3af2..f94c479 100644 (file)
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports"],function(t,n){"use strict";return function(){function t(){}return t.trimExplode=function(t,n){return n.split(t).map(function(t){return t.trim()}).filter(function(t){return""!==t})},t.intExplode=function(t,n,r){return void 0===r&&(r=!1),n.split(t).map(function(t){return parseInt(t,10)}).filter(function(t){return!isNaN(t)||r&&0===t})},t.isNumber=function(t){return!isNaN(parseFloat(t.toString()))&&isFinite(t)},t.getParameterFromUrl=function(t,n){if("function"!=typeof t.split)return"";var r=t.split("?"),e="";if(r.length>=2)for(var i=r.join("?"),u=encodeURIComponent(n)+"=",o=i.split(/[&;]/g),f=o.length;f-- >0;)if(-1!==o[f].lastIndexOf(u,0)){e=o[f].split("=")[1];break}return e},t.updateQueryStringParameter=function(t,n,r){var e=new RegExp("([?&])"+n+"=.*?(&|$)","i"),i=-1!==t.indexOf("?")?"&":"?";return t.match(e)?t.replace(e,"$1"+n+"="+r+"$2"):t+i+n+"="+r},t}()});
\ No newline at end of file
+define(["require","exports"],function(t,n){"use strict";return function(){function t(){}return t.trimExplode=function(t,n){return n.split(t).map(function(t){return t.trim()}).filter(function(t){return""!==t})},t.intExplode=function(t,n,r){return void 0===r&&(r=!1),n.split(t).map(function(t){return parseInt(t,10)}).filter(function(t){return!isNaN(t)||r&&0===t})},t.isNumber=function(t){return!isNaN(parseFloat(t.toString()))&&isFinite(t)},t.getParameterFromUrl=function(t,n){if("function"!=typeof t.split)return"";var r=t.split("?"),e="";if(r.length>=2)for(var i=r.join("?"),u=encodeURIComponent(n)+"=",o=i.split(/[&;]/g),a=o.length;a-- >0;)if(-1!==o[a].lastIndexOf(u,0)){e=o[a].split("=")[1];break}return e},t.updateQueryStringParameter=function(t,n,r){var e=new RegExp("([?&])"+n+"=.*?(&|$)","i"),i=-1!==t.indexOf("?")?"&":"?";return t.match(e)?t.replace(e,"$1"+n+"="+r+"$2"):t+i+n+"="+r},t.convertFormToObject=function(t){for(var n={},r=t.querySelectorAll("input, select, textarea"),e=0;e<r.length;++e){var i=r[e],u=i.name,o=i.value;u&&(n[u]=o)}return n},t}()});
\ No newline at end of file
index 8d5236f..b67b104 100644 (file)
@@ -82,6 +82,7 @@ class PreviewController
         $this->moduleTemplate->getPageRenderer()->addInlineSetting('RecordHistory', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_history'));
         $this->moduleTemplate->getPageRenderer()->addJsInlineCode('workspace-inline-code', $this->generateJavascript());
         $this->moduleTemplate->getPageRenderer()->addCssFile('EXT:workspaces/Resources/Public/Css/preview.css');
+        $this->moduleTemplate->getPageRenderer()->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/wizard.xlf');
         $this->moduleTemplate->getPageRenderer()->addInlineLanguageLabelFile('EXT:workspaces/Resources/Private/Language/locallang.xlf');
     }
 
@@ -180,6 +181,7 @@ class PreviewController
         if (!array_intersect($splitPreviewModes, $allPreviewModes)) {
             $splitPreviewModes = $allPreviewModes;
         }
+        $this->moduleTemplate->getPageRenderer()->addJsFile('EXT:backend/Resources/Public/JavaScript/backend.js');
         $this->moduleTemplate->getPageRenderer()->addInlineSetting('Workspaces', 'SplitPreviewModes', $splitPreviewModes);
         $this->moduleTemplate->getPageRenderer()->addInlineSetting('Workspaces', 'id', $this->pageId);
 
@@ -224,15 +226,23 @@ class PreviewController
      */
     protected function generateJavascript(): string
     {
+        // Needed for FormEngine manipulation (date picker)
+        $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
+        $this->moduleTemplate->getPageRenderer()->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
+
         // If another page module was specified, replace the default Page module with the new one
         $pageModule = \trim($this->getBackendUser()->getTSConfig()['options.']['overridePageModule'] ?? '');
         $pageModule = BackendUtility::isModuleSetInTBE_MODULES($pageModule) ? $pageModule : 'web_layout';
+        $pageModuleUrl = '';
         if (!$this->getBackendUser()->check('modules', $pageModule)) {
             $pageModule = '';
+        } else {
+            $pageModuleUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($pageModule);
         }
         $t3Configuration = [
             'username' => htmlspecialchars($this->getBackendUser()->user['username']),
             'pageModule' => $pageModule,
+            'pageModuleUrl' => $pageModuleUrl,
             'inWorkspace' => $this->getBackendUser()->workspace !== 0,
             'showRefreshLoginPopup' => (bool)($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] ?? false)
         ];
index db4aaeb..eb3f785 100644 (file)
@@ -413,9 +413,10 @@ class RemoteServer
     /**
      * Gets all available system languages.
      *
+     * @param \stdClass $parameters
      * @return array
      */
-    public function getSystemLanguages()
+    public function getSystemLanguages(\stdClass $parameters)
     {
         $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
         $systemLanguages = [
@@ -425,7 +426,7 @@ class RemoteServer
                 'icon' => $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render()
             ]
         ];
-        foreach ($this->gridDataService->getSystemLanguages() as $id => $systemLanguage) {
+        foreach ($this->gridDataService->getSystemLanguages($parameters->pageUid ?? 0) as $id => $systemLanguage) {
             if ($id < 0) {
                 continue;
             }
index e45535e..4fafab0 100644 (file)
@@ -159,6 +159,7 @@ class GridDataService implements LoggerAwareInterface
                     }
 
                     $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
+                    $pageId = $table === 'pages' ? $record['uid'] : $record['pid'];
                     $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, $record['uid'], $origRecord, $versionRecord);
                     $versionArray = [];
                     $versionArray['table'] = $table;
@@ -188,7 +189,7 @@ class GridDataService implements LoggerAwareInterface
                     $languageValue = $this->getLanguageValue($table, $versionRecord);
                     $versionArray['languageValue'] = $languageValue;
                     $versionArray['language'] = [
-                        'icon' => $iconFactory->getIcon($this->getSystemLanguageValue($languageValue, 'flagIcon'), Icon::SIZE_SMALL)->render()
+                        'icon' => $iconFactory->getIcon($this->getSystemLanguageValue($languageValue, $pageId, 'flagIcon'), Icon::SIZE_SMALL)->render()
                     ];
                     $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
                     $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
@@ -587,14 +588,15 @@ class GridDataService implements LoggerAwareInterface
      * Gets a named value of the available sys_language elements.
      *
      * @param int $id sys_language uid
+     * @param int $pageId page id of a site
      * @param string $key Name of the value to be fetched (e.g. title)
      * @return string|null
      * @see getSystemLanguages
      */
-    protected function getSystemLanguageValue($id, $key)
+    protected function getSystemLanguageValue($id, $pageId, $key)
     {
         $value = null;
-        $systemLanguages = $this->getSystemLanguages();
+        $systemLanguages = $this->getSystemLanguages($pageId);
         if (!empty($systemLanguages[$id][$key])) {
             $value = $systemLanguages[$id][$key];
         }
@@ -604,13 +606,14 @@ class GridDataService implements LoggerAwareInterface
     /**
      * Gets all available system languages.
      *
+     * @param int $pageId
      * @return array
      */
-    public function getSystemLanguages()
+    public function getSystemLanguages(int $pageId)
     {
         if (!isset($this->systemLanguages)) {
             $translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
-            $this->systemLanguages = $translateTools->getSystemLanguages();
+            $this->systemLanguages = $translateTools->getSystemLanguages($pageId);
         }
         return $this->systemLanguages;
     }
index 861b895..3ec3aba 100644 (file)
@@ -14,7 +14,7 @@
     <!-- Tab panes -->
     <div class="tab-content">
         <div role="tabpanel" class="tab-pane active">
-            <div class="form-section" id="workspace-panel">
+            <div class="form-section" id="workspace-panel" data-page-uid="{pageUid}">
                 <form id="workspace-settings-form" class="form-inline form-inline-spaced">
                     <div class="form-group">
                         <select name="depth" class="form-control" disabled>
index dfc1aef..f7b1595 100644 (file)
@@ -1,5 +1,4 @@
 /*
-/*
  * This file is part of the TYPO3 CMS project.
  *
  * It is free software; you can redistribute it and/or modify it under
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * RequireJS module for the workspace backend module
- */
-define([
-  'jquery',
-  'TYPO3/CMS/Workspaces/Workspaces',
-  'TYPO3/CMS/Backend/Tooltip',
-  'TYPO3/CMS/Backend/Severity',
-  'TYPO3/CMS/Backend/Modal',
-  'TYPO3/CMS/Backend/Wizard',
-  'TYPO3/CMS/Backend/Storage/Persistent',
-  'nprogress',
-  'TYPO3/CMS/Backend/jquery.clearable'
-], function($, Workspaces, Tooltip, Severity, Modal, Wizard, Persistent, NProgress) {
-  'use strict';
-
-  var Backend = {
-    workspaceTitle: '',
-    identifiers: {
-      searchForm: '#workspace-settings-form',
-      searchTextField: '#workspace-settings-form input[name="search-text"]',
-      searchSubmitBtn: '#workspace-settings-form button[type="submit"]',
-      depthSelector: '#workspace-settings-form [name="depth"]',
-      languageSelector: '#workspace-settings-form select[name="languages"]',
-      actionForm: '#workspace-actions-form',
-      chooseStageAction: '#workspace-actions-form [name="stage-action"]',
-      chooseSelectionAction: '#workspace-actions-form [name="selection-action"]',
-      chooseMassAction: '#workspace-actions-form [name="mass-action"]',
-      container: '#workspace-panel',
-      actionIcons: '#workspace-action-icons',
-      toggleAll: '.t3js-toggle-all',
-      previewLinksButton: '.t3js-preview-link',
-      pagination: '#workspace-pagination'
-    },
-    settings: {
-      dir: 'ASC',
-      id: TYPO3.settings.Workspaces.id,
-      language: TYPO3.settings.Workspaces.language,
-      limit: 30,
-      query: '',
-      sort: 'label_Live',
-      start: 0,
-      filterTxt: ''
-    },
-    paging: {
-      currentPage: 1,
-      totalPages: 1,
-      totalItems: 0
-    },
-    allToggled: false,
-    elements: {}, // filled in Backend.getElements()
-    latestPath: '',
-    markedRecordsForMassAction: [],
-    indentationPadding: 26
-  };
-
-  Backend.initialize = function() {
-    var persistedDepth;
-    Backend.getElements();
-    Backend.registerEvents();
-
-    if (Persistent.isset('Workspaces.Module.depth')) {
-      persistedDepth = Persistent.get('Workspaces.Module.depth');
-      Backend.elements.$depthSelector.val(persistedDepth);
-      Backend.settings.depth = persistedDepth;
-    } else {
-      Backend.settings.depth = TYPO3.settings.Workspaces.depth;
-    }
-
-    Backend.loadWorkspaceComponents();
-  };
-
-  Backend.getElements = function() {
-    Backend.elements.$searchForm = $(Backend.identifiers.searchForm);
-    Backend.elements.$searchTextField = $(Backend.identifiers.searchTextField);
-    Backend.elements.$searchSubmitBtn = $(Backend.identifiers.searchSubmitBtn);
-    Backend.elements.$depthSelector = $(Backend.identifiers.depthSelector);
-    Backend.elements.$languageSelector = $(Backend.identifiers.languageSelector);
-    Backend.elements.$container = $(Backend.identifiers.container);
-    Backend.elements.$tableBody = Backend.elements.$container.find('tbody');
-    Backend.elements.$actionIcons = $(Backend.identifiers.actionIcons);
-    Backend.elements.$toggleAll = $(Backend.identifiers.toggleAll);
-    Backend.elements.$chooseStageAction = $(Backend.identifiers.chooseStageAction);
-    Backend.elements.$chooseSelectionAction = $(Backend.identifiers.chooseSelectionAction);
-    Backend.elements.$chooseMassAction = $(Backend.identifiers.chooseMassAction);
-    Backend.elements.$previewLinksButton = $(Backend.identifiers.previewLinksButton);
-    Backend.elements.$pagination = $(Backend.identifiers.pagination);
-  };
-
-  Backend.registerEvents = function() {
-    $(document).on('click', '[data-action="swap"]', function(e) {
-      var $tr = $(e.target).closest('tr');
-      Workspaces.checkIntegrity(
-        {
-          selection: [
-            {
-              liveId: $tr.data('uid'),
-              versionId: $tr.data('t3ver_oid'),
-              table: $tr.data('table')
-            }
-          ],
-          type: 'selection'
-        }
-      ).done(function(response) {
-        if (response[0].result.result === 'warning') {
-          Backend.addIntegrityCheckWarningToWizard();
-        }
-
-        Wizard.setup.forceSelection = false;
-        Wizard.addSlide(
-          'swap-confirm',
-          'Swap',
-          TYPO3.lang['window.swap.message'],
-          Severity.info
-        );
-        Wizard.addFinalProcessingSlide(function() {
-          // We passed this slide, swap the record now
-          Workspaces.sendRemoteRequest(
-            Workspaces.generateRemoteActionsPayload('swapSingleRecord', [
-              $tr.data('table'),
-              $tr.data('t3ver_oid'),
-              $tr.data('uid')
-            ])
-          ).done(function() {
-            Wizard.dismiss();
-            Backend.getWorkspaceInfos();
-            Backend.refreshPageTree();
-          });
-        }).done(function() {
-          Wizard.show();
-        });
-      });
-    }).on('click', '[data-action="prevstage"]', function(e) {
-      Backend.sendToStage($(e.target).closest('tr'), 'prev');
-    }).on('click', '[data-action="nextstage"]', function(e) {
-      Backend.sendToStage($(e.target).closest('tr'), 'next');
-    }).on('click', '[data-action="changes"]', Backend.viewChanges
-    ).on('click', '[data-action="preview"]', Backend.openPreview
-    ).on('click', '[data-action="open"]', function(e) {
-      var $tr = $(e.target).closest('tr'),
-        newUrl = TYPO3.settings.FormEngine.moduleUrl + '&returnUrl=' + encodeURIComponent(document.location.href) + '&id=' + TYPO3.settings.Workspaces.id + '&edit[' + $tr.data('table') + '][' + $tr.data('uid') + ']=edit';
-
-      // Append workspace of record in all-workspaces view
-      if (TYPO3.settings.Workspaces.allView) {
-        newUrl += '&workspace=' + $tr.data('t3ver_wsid');
-      }
-      window.location.href = newUrl;
-    }).on('click', '[data-action="version"]', function(e) {
-      var $tr = $(e.target).closest('tr');
-      if ($tr.data('table') === 'pages') {
-        top.loadEditId($tr.data('t3ver_oid'));
-      } else {
-        top.loadEditId($tr.data('pid'));
-      }
-    }).on('click', '[data-action="remove"]', Backend.confirmDeleteRecordFromWorkspace
-    ).on('click', '[data-action="expand"]', function(e) {
-      var $me = $(this),
-        $target = Backend.elements.$tableBody.find($me.data('target')),
-        iconIdentifier;
-
-      if ($target.first().attr('aria-expanded') === 'true') {
-        iconIdentifier = 'apps-pagetree-expand';
-      } else {
-        iconIdentifier = 'apps-pagetree-collapse';
-      }
-
-      $me.html(Backend.getPreRenderedIcon(iconIdentifier));
-    });
-    $(window.top.document).on('click', '.t3js-workspace-recipients-selectall', function(e) {
-      e.preventDefault();
-      $('.t3js-workspace-recipient', window.top.document).not(':disabled').prop('checked', true);
-    }).on('click', '.t3js-workspace-recipients-deselectall', function(e) {
-      e.preventDefault();
-      $('.t3js-workspace-recipient', window.top.document).not(':disabled').prop('checked', false);
-    });
-
-    Backend.elements.$searchForm.on('submit', function(e) {
-      e.preventDefault();
-      Backend.settings.filterTxt = Backend.elements.$searchTextField.val();
-      Backend.getWorkspaceInfos();
-    });
-
-    Backend.elements.$searchTextField.on('keyup', function() {
-      var $me = $(this);
-
-      if ($me.val() !== '') {
-        Backend.elements.$searchSubmitBtn.removeClass('disabled');
-      } else {
-        Backend.elements.$searchSubmitBtn.addClass('disabled');
-        Backend.getWorkspaceInfos();
-      }
-    }).clearable(
-      {
-        onClear: function() {
-          Backend.elements.$searchSubmitBtn.addClass('disabled');
-          Backend.settings.filterTxt = '';
-          Backend.getWorkspaceInfos();
-        }
-      }
-    );
-
-    // checkboxes in the table
-    Backend.elements.$toggleAll.on('click', function() {
-      Backend.allToggled = !Backend.allToggled;
-      Backend.elements.$tableBody.find('input[type="checkbox"]').prop('checked', Backend.allToggled).trigger('change');
-    });
-    Backend.elements.$tableBody.on('change', 'tr input[type=checkbox]', Backend.handleCheckboxChange);
-
-    // Listen for depth changes
-    Backend.elements.$depthSelector.on('change', function(e) {
-      var depth = $(this).val();
-      Persistent.set('Workspaces.Module.depth', depth);
-      Backend.settings.depth = depth;
-      Backend.getWorkspaceInfos();
-    });
-
-    // Generate preview links
-    Backend.elements.$previewLinksButton.on('click', Backend.generatePreviewLinks);
-
-    // Listen for language changes
-    Backend.elements.$languageSelector.on('change', function(e) {
-      var $me = $(this);
-      Backend.settings.language = $me.val();
-
-      Workspaces.sendRemoteRequest([
-        Workspaces.generateRemoteActionsPayload('saveLanguageSelection', [$me.val()]),
-        Workspaces.generateRemotePayload('getWorkspaceInfos', Backend.settings)
-      ]).done(function(response) {
-        Backend.elements.$languageSelector.prev().html($me.find(':selected').data('icon'));
-        Backend.renderWorkspaceInfos(response[1].result);
-      });
-    });
-
-    // Listen for actions
-    Backend.elements.$chooseStageAction.on('change', Backend.sendToSpecificStageAction);
-    Backend.elements.$chooseSelectionAction.on('change', Backend.runSelectionAction);
-    Backend.elements.$chooseMassAction.on('change', Backend.runMassAction);
-
-    // clicking an action in the paginator
-    Backend.elements.$pagination.on('click', 'a[data-action]', function(e) {
-      e.preventDefault();
-
-      var $el = $(this),
-        reload = false;
-
-      switch ($el.data('action')) {
-        case 'previous':
-          if (Backend.paging.currentPage > 1) {
-            Backend.paging.currentPage--;
-            reload = true;
-          }
-          break;
-        case 'next':
-          if (Backend.paging.currentPage < Backend.paging.totalPages) {
-            Backend.paging.currentPage++;
-            reload = true;
-          }
-          break;
-        case 'page':
-          Backend.paging.currentPage = parseInt($el.data('page'));
-          reload = true;
-          break;
-      }
-
-      if (reload) {
-        // Adjust settings
-        Backend.settings.start = Backend.settings.limit * (Backend.paging.currentPage - 1);
-        Backend.getWorkspaceInfos();
-      }
-    });
-  };
-
-  Backend.handleCheckboxChange = function(e) {
-    var $checkbox = $(this),
-      $tr = $checkbox.parents('tr'),
-      table = $tr.data('table'),
-      uid = $tr.data('uid'),
-      t3ver_oid = $tr.data('t3ver_oid'),
-      record = table + ':' + uid + ':' + t3ver_oid;
-
-    if ($checkbox.prop('checked')) {
-      Backend.markedRecordsForMassAction.push(record);
-      $tr.addClass('warning');
-    } else {
-      var index = Backend.markedRecordsForMassAction.indexOf(record);
-      if (index > -1) {
-        Backend.markedRecordsForMassAction.splice(index, 1);
-      }
-      $tr.removeClass('warning');
-    }
-
-    Backend.elements.$chooseStageAction.prop('disabled', Backend.markedRecordsForMassAction.length === 0);
-    Backend.elements.$chooseSelectionAction.prop('disabled', Backend.markedRecordsForMassAction.length === 0);
-    Backend.elements.$chooseMassAction.prop('disabled', Backend.markedRecordsForMassAction.length > 0);
-  };
-
-  /**
-   * Generates the diff view of a record
-   *
-   * @param {Object} diff
-   * @return {$}
-   */
-  Backend.generateDiffView = function(diff) {
-    var $diff = $('<div />', {class: 'diff'});
-
-    for (var i = 0; i < diff.length; ++i) {
-      $diff.append(
-        $('<div />', {class: 'diff-item'}).append(
-          $('<div />', {class: 'diff-item-title'}).text(diff[i].label),
-          $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(diff[i].content)
-        )
-      );
-    }
-    return $diff;
-  };
-
-  /**
-   * Generates the comments view of a record
-   *
-   * @param {Object} comments
-   * @return {$}
-   */
-  Backend.generateCommentView = function(comments) {
-    var $comments = $('<div />');
-
-    for (var i = 0; i < comments.length; ++i) {
-      var $panel = $('<div />', {class: 'panel panel-default'});
-
-      if (comments[i].user_comment.length > 0) {
-        $panel.append(
-          $('<div />', {class: 'panel-body'}).html(comments[i].user_comment)
-        );
-      }
-
-      $panel.append(
-        $('<div />', {class: 'panel-footer'}).append(
-          $('<span />', {class: 'label label-success'}).text(comments[i].stage_title),
-          $('<span />', {class: 'label label-info'}).text(comments[i].tstamp)
-        )
-      );
-
-      $comments.append(
-        $('<div />', {class: 'media'}).append(
-          $('<div />', {class: 'media-left text-center'}).text(comments[i].user_username).prepend(
-            $('<div />').html(comments[i].user_avatar)
-          ),
-          $('<div />', {class: 'media-body'}).append($panel)
-        )
-      );
-    }
-
-    return $comments;
-  };
-
-  /**
-   * Sends a record to a stage
-   *
-   * @param {Object} $row
-   * @param {String} direction
-   */
-  Backend.sendToStage = function($row, direction) {
-    var nextStage,
-      stageWindowAction,
-      stageExecuteAction;
-
-    if (direction === 'next') {
-      nextStage = $row.data('nextStage');
-      stageWindowAction = 'sendToNextStageWindow';
-      stageExecuteAction = 'sendToNextStageExecute';
-    } else if (direction === 'prev') {
-      nextStage = $row.data('prevStage');
-      stageWindowAction = 'sendToPrevStageWindow';
-      stageExecuteAction = 'sendToPrevStageExecute';
-    } else {
-      throw 'Invalid direction given.';
-    }
-
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemoteActionsPayload(stageWindowAction, [
-        $row.data('uid'), $row.data('table'), $row.data('t3ver_oid')
-      ])
-    ).done(function(response) {
-      var $modal = Workspaces.renderSendToStageWindow(response);
-      $modal.on('button.clicked', function(e) {
-        if (e.target.name === 'ok') {
-          var $form = $(e.currentTarget).find('form'),
-            serializedForm = $form.serializeObject();
-
-          serializedForm.affects = {
-            table: $row.data('table'),
-            nextStage: nextStage,
-            t3ver_oid: $row.data('t3ver_oid'),
-            uid: $row.data('uid'),
-            elements: []
-          };
-
-          Workspaces.sendRemoteRequest([
-            Workspaces.generateRemoteActionsPayload(stageExecuteAction, [serializedForm]),
-            Workspaces.generateRemotePayload('getWorkspaceInfos', Backend.settings)
-          ]).done(function(response) {
-            $modal.modal('hide');
-            Backend.renderWorkspaceInfos(response[1].result);
-            Backend.refreshPageTree();
-          });
-        }
-      });
-    });
-  };
-
-  /**
-   * Loads the workspace components, like available stage actions and items of the workspace
-   */
-  Backend.loadWorkspaceComponents = function() {
-    Workspaces.sendRemoteRequest([
-      Workspaces.generateRemotePayload('getWorkspaceInfos', Backend.settings),
-      Workspaces.generateRemotePayload('getStageActions', {}),
-      Workspaces.generateRemoteMassActionsPayload('getMassStageActions', {}),
-      Workspaces.generateRemotePayload('getSystemLanguages', {})
-    ]).done(function(response) {
-      Backend.elements.$depthSelector.prop('disabled', false);
-
-      // Records
-      Backend.renderWorkspaceInfos(response[0].result);
-
-      // Stage actions
-      var stageActions = response[1].result.data,
-        i;
-      for (i = 0; i < stageActions.length; ++i) {
-        Backend.elements.$chooseStageAction.append(
-          $('<option />').val(stageActions[i].uid).text(stageActions[i].title)
-        );
-      }
-
-      // Mass actions
-      var massActions = response[2].result.data;
-      for (i = 0; i < massActions.length; ++i) {
-        Backend.elements.$chooseSelectionAction.append(
-          $('<option />').val(massActions[i].action).text(massActions[i].title)
-        );
-
-        Backend.elements.$chooseMassAction.append(
-          $('<option />').val(massActions[i].action).text(massActions[i].title)
-        );
-      }
-
-      // Languages
-      var languages = response[3].result.data;
-      for (i = 0; i < languages.length; ++i) {
-        var $option = $('<option />').val(languages[i].uid).text(languages[i].title).data('icon', languages[i].icon);
-        if (String(languages[i].uid) === String(TYPO3.settings.Workspaces.language)) {
-          $option.prop('selected', true);
-          Backend.elements.$languageSelector.prev().html(languages[i].icon);
-        }
-        Backend.elements.$languageSelector.append($option);
-      }
-      Backend.elements.$languageSelector.prop('disabled', false);
-    });
-  };
-
-  /**
-   * Gets the workspace infos
-   *
-   * @return {Promise}
-   * @protected
-   */
-  Backend.getWorkspaceInfos = function() {
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemotePayload('getWorkspaceInfos', Backend.settings)
-    ).done(function(response) {
-      Backend.renderWorkspaceInfos(response[0].result);
-    });
-  };
-
-  /**
-   * Renders fetched workspace informations
-   *
-   * @param {Object} result
-   */
-  Backend.renderWorkspaceInfos = function(result) {
-    Backend.elements.$tableBody.children().remove();
-    Backend.allToggled = false;
-    Backend.elements.$chooseStageAction.prop('disabled', true);
-    Backend.elements.$chooseSelectionAction.prop('disabled', true);
-    Backend.elements.$chooseMassAction.prop('disabled', result.data.length === 0);
-
-    Backend.buildPagination(result.total);
-
-    for (var i = 0; i < result.data.length; ++i) {
-      var item = result.data[i],
-        $actions = $('<div />', {class: 'btn-group'}),
-        $integrityIcon = '';
-      $actions.append(
-        Backend.getAction(item.Workspaces_CollectionChildren > 0 && item.Workspaces_CollectionCurrent !== '', 'expand', 'apps-pagetree-collapse').attr('title', TYPO3.lang['tooltip.expand']).attr('data-target', '[data-collection="' + item.Workspaces_CollectionCurrent + '"]').attr('data-toggle', 'collapse'),
-        $('<button />', {
-          class: 'btn btn-default',
-          'data-action': 'changes',
-          'data-toggle': 'tooltip',
-          title: TYPO3.lang['tooltip.showChanges']
-        }).append(Backend.getPreRenderedIcon('actions-document-info')),
-        Backend.getAction(item.allowedAction_swap && item.Workspaces_CollectionParent === '', 'swap', 'actions-version-swap-version').attr('title', TYPO3.lang['tooltip.swap']),
-        Backend.getAction(item.allowedAction_view, 'preview', 'actions-version-workspace-preview').attr('title', TYPO3.lang['tooltip.viewElementAction']),
-        $('<button />', {
-          class: 'btn btn-default',
-          'data-action': 'open',
-          'data-toggle': 'tooltip',
-          title: TYPO3.lang['tooltip.editElementAction']
-        }).append(Backend.getPreRenderedIcon('actions-open')),
-        $('<button />', {
-          class: 'btn btn-default',
-          'data-action': 'version',
-          'data-toggle': 'tooltip',
-          title: TYPO3.lang['tooltip.openPage']
-        }).append(Backend.getPreRenderedIcon('actions-version-page-open')),
-        Backend.getAction(item.allowedAction_delete, 'remove', 'actions-version-document-remove').attr('title', TYPO3.lang['tooltip.discardVersion'])
-      );
-
-      if (item.integrity.messages !== '') {
-        $integrityIcon = $(TYPO3.settings.Workspaces.icons[item.integrity.status]);
-        $integrityIcon
-          .attr('data-toggle', 'tooltip')
-          .attr('data-placement', 'top')
-          .attr('data-html', true)
-          .attr('title', item.integrity.messages);
-      }
-
-      if (Backend.latestPath !== item.path_Workspace) {
-        Backend.latestPath = item.path_Workspace;
-        Backend.elements.$tableBody.append(
-          $('<tr />').append(
-            $('<th />'),
-            $('<th />', {colspan: 6}).text(Backend.latestPath)
-          )
-        );
-      }
-      var $checkbox = $('<label />', {class: 'btn btn-default btn-checkbox'}).append(
-        $('<input />', {type: 'checkbox'}),
-        $('<span />', {class: 't3-icon fa'})
-      );
-
-      var rowConfiguration = {
-        'data-uid': item.uid,
-        'data-pid': item.livepid,
-        'data-t3ver_oid': item.t3ver_oid,
-        'data-t3ver_wsid': item.t3ver_wsid,
-        'data-table': item.table,
-        'data-next-stage': item.value_nextStage,
-        'data-prev-stage': item.value_prevStage,
-        'data-stage': item.stage
-      };
-
-      if (item.Workspaces_CollectionParent !== '') {
-        rowConfiguration['data-collection'] = item.Workspaces_CollectionParent;
-        rowConfiguration['class'] = 'collapse';
-      }
-
-      Backend.elements.$tableBody.append(
-        $('<tr />', rowConfiguration).append(
-          $('<td />').html($checkbox),
-          $('<td />', {
-            class: 't3js-title-workspace',
-            style: item.Workspaces_CollectionLevel > 0 ? 'padding-left: ' + Backend.indentationPadding * item.Workspaces_CollectionLevel + 'px' : ''
-          }).html(item.icon_Workspace + '&nbsp;' + '<a href="#" data-action="changes"><span class="item-state-' + item.state_Workspace + '">' + item.label_Workspace + '</span></a>'),
-          $('<td />', {class: 't3js-title-live'}).html(item.icon_Live + '&nbsp;' + item.label_Live),
-          $('<td />').text(item.label_Stage),
-          $('<td />').html($integrityIcon),
-          $('<td />').html(item.language.icon),
-          $('<td />', {class: 'text-right nowrap'}).append($actions)
-        )
-      );
-
-      Tooltip.initialize('[data-toggle="tooltip"]', {
-        delay: {
-          show: 500,
-          hide: 100
-        },
-        trigger: 'hover',
-        container: 'body'
-      });
-    }
-  };
-
-  /**
-   * Renders the pagination
-   *
-   * @param {Number} totalItems
-   */
-  Backend.buildPagination = function(totalItems) {
-    if (totalItems === 0) {
-      Backend.elements.$pagination.contents().remove();
-      return;
-    }
-
-    Backend.paging.totalItems = totalItems;
-    Backend.paging.totalPages = Math.ceil(totalItems / Backend.settings.limit);
-
-    if (Backend.paging.totalPages === 1) {
-      // early abort if only one page is available
-      Backend.elements.$pagination.contents().remove();
-      return;
-    }
-
-    var $ul = $('<ul />', {class: 'pagination pagination-block'}),
-      liElements = [],
-      $controlFirstPage = $('<li />').append(
-        $('<a />', {'data-action': 'previous'}).append(
-          $('<span />', {class: 't3-icon fa fa-arrow-left'})
-        )
-      ),
-      $controlLastPage = $('<li />').append(
-        $('<a />', {'data-action': 'next'}).append(
-          $('<span />', {class: 't3-icon fa fa-arrow-right'})
-        )
-      );
-
-    if (Backend.paging.currentPage === 1) {
-      $controlFirstPage.disablePagingAction();
-    }
-
-    if (Backend.paging.currentPage === Backend.paging.totalPages) {
-      $controlLastPage.disablePagingAction();
-    }
-
-    for (var i = 1; i <= Backend.paging.totalPages; i++) {
-      var $li = $('<li />', {class: Backend.paging.currentPage === i ? 'active' : ''});
-      $li.append(
-        $('<a />', {'data-action': 'page', 'data-page': i}).append(
-          $('<span />').text(i)
-        )
-      );
-      liElements.push($li);
-    }
-
-    $ul.append($controlFirstPage, liElements, $controlLastPage);
-    Backend.elements.$pagination.html($ul);
-  };
-
-  /**
-   * View changes of a record
-   *
-   * @param {Event} e
-   */
-  Backend.viewChanges = function(e) {
-    e.preventDefault();
-
-    var $tr = $(e.target).closest('tr');
-
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemotePayload('getRowDetails', {
-        stage: $tr.data('stage'),
-        t3ver_oid: $tr.data('t3ver_oid'),
-        table: $tr.data('table'),
-        uid: $tr.data('uid')
-      })
-    ).done(function(response) {
-      var item = response[0].result.data[0],
-        $content = $('<div />'),
-        $tabsNav = $('<ul />', {class: 'nav nav-tabs', role: 'tablist'}),
-        $tabsContent = $('<div />', {class: 'tab-content'}),
-        modalButtons = [];
-
-      $content.append(
-        $('<p />').html(TYPO3.lang['path'].replace('{0}', item.path_Live)),
-        $('<p />').html(TYPO3.lang['current_step'].replace('{0}', item.label_Stage).replace('{1}', item.stage_position).replace('{2}', item.stage_count))
-      );
-
-      if (item.diff.length > 0) {
-        $tabsNav.append(
-          $('<li />', {role: 'presentation'}).append(
-            $('<a />', {
-              href: '#workspace-changes',
-              'aria-controls': 'workspace-changes',
-              role: 'tab',
-              'data-toggle': 'tab'
-            }).text(TYPO3.lang['window.recordChanges.tabs.changeSummary'])
-          )
-        );
-        $tabsContent.append(
-          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-changes'}).append(
-            $('<div />', {class: 'form-section'}).append(
-              Backend.generateDiffView(item.diff)
-            )
-          )
-        );
-      }
-
-      if (item.comments.length > 0) {
-        $tabsNav.append(
-          $('<li />', {role: 'presentation'}).append(
-            $('<a />', {
-              href: '#workspace-comments',
-              'aria-controls': 'workspace-comments',
-              role: 'tab',
-              'data-toggle': 'tab'
-            }).html(TYPO3.lang['window.recordChanges.tabs.comments'] + '&nbsp;').append(
-              $('<span />', {class: 'badge'}).text(item.comments.length)
-            )
-          )
-        );
-        $tabsContent.append(
-          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-comments'}).append(
-            $('<div />', {class: 'form-section'}).append(
-              Backend.generateCommentView(item.comments)
-            )
-          )
-        );
-      }
-
-      if (item.history.total > 0) {
-        $tabsNav.append(
-          $('<li />', {role: 'presentation'}).append(
-            $('<a />', {
-              href: '#workspace-history',
-              'aria-controls': 'workspace-history',
-              role: 'tab',
-              'data-toggle': 'tab'
-            }).text(TYPO3.lang['window.recordChanges.tabs.history'])
-          )
-        );
-
-        $tabsContent.append(
-          $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-history'}).append(
-            $('<div />', {class: 'form-section'}).append(
-              Backend.generateHistoryView(item.history.data)
-            )
-          )
-        );
-      }
-
-      // Mark the first tab and pane as active
-      $tabsNav.find('li').first().addClass('active');
-      $tabsContent.find('.tab-pane').first().addClass('active');
-
-      // Attach tabs
-      $content.append(
-        $('<div />').append(
-          $tabsNav,
-          $tabsContent
-        )
-      );
-
-      if ($tr.data('stage') !== $tr.data('prevStage')) {
-        modalButtons.push({
-          text: item.label_PrevStage.title,
-          active: true,
-          btnClass: 'btn-default',
-          name: 'prevstage',
-          trigger: function() {
-            Modal.currentModal.trigger('modal-dismiss');
-            Backend.sendToStage($(e.target).closest('tr'), 'prev');
-          }
-        });
-      }
-
-      modalButtons.push({
-        text: item.label_NextStage.title,
-        active: true,
-        btnClass: 'btn-default',
-        name: 'nextstage',
-        trigger: function() {
-          Modal.currentModal.trigger('modal-dismiss');
-          Backend.sendToStage($(e.target).closest('tr'), 'next');
-        }
-      });
-      modalButtons.push({
-        text: TYPO3.lang['close'],
-        active: true,
-        btnClass: 'btn-info',
-        name: 'cancel',
-        trigger: function() {
-          Modal.currentModal.trigger('modal-dismiss');
-        }
-      });
-
-      Modal.advanced({
-        type: Modal.default,
-        title: TYPO3.lang['window.recordInformation'].replace('{0}', $.trim($tr.find('.t3js-title-live').text())),
-        content: $content,
-        severity: Severity.info,
-        buttons: modalButtons,
-        size: 'medium'
-      });
-    });
-  };
-
-  /**
-   * Opens a record in a preview window
-   *
-   * @param {Event} e
-   */
-  Backend.openPreview = function(e) {
-    var $tr = $(e.target).closest('tr');
-
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemoteActionsPayload('viewSingleRecord', [
-        $tr.data('table'), $tr.data('uid')
-      ])
-    ).done(function(response) {
-      eval(response[0].result);
-    });
-  };
-
-  /**
-   * Renders the record's history
-   *
-   * @param {Object} data
-   */
-  Backend.generateHistoryView = function(data) {
-    var $history = $('<div />');
-
-    for (var i = 0; i < data.length; ++i) {
-      var $panel = $('<div />', {class: 'panel panel-default'}),
-        $diff;
-
-      if (typeof data[i].differences === 'object') {
-        if (data[i].differences.length === 0) {
-          // Somehow here are no differences. What a pity, skip that record
-          continue;
-        }
-        $diff = $('<div />', {class: 'diff'});
-
-        for (var j = 0; j < data[i].differences.length; ++j) {
-          $diff.append(
-            $('<div />', {class: 'diff-item'}).append(
-              $('<div />', {class: 'diff-item-title'}).text(data[i].differences[j].label),
-              $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(data[i].differences[j].html)
-            )
-          );
-        }
-
-        $panel.append(
-          $('<div />').append($diff)
-        );
-      } else {
-        $panel.append(
-          $('<div />', {class: 'panel-body'}).text(data[i].differences)
-        );
-      }
-      $panel.append(
-        $('<div />', {class: 'panel-footer'}).append(
-          $('<span />', {class: 'label label-info'}).text(data[i].datetime)
-        )
-      );
-
-      $history.append(
-        $('<div />', {class: 'media'}).append(
-          $('<div />', {class: 'media-left text-center'}).text(data[i].user).prepend(
-            $('<div />').html(data[i].user_avatar)
-          ),
-          $('<div />', {class: 'media-body'}).append($panel)
-        )
-      );
-    }
-
-    return $history;
-  };
-
-  /**
-   * Shows a confirmation modal and deletes the selected record from workspace.
-   *
-   * @param {Event} e
-   */
-  Backend.confirmDeleteRecordFromWorkspace = function(e) {
-    var $tr = $(e.target).closest('tr');
-    var $modal = Modal.confirm(
-      TYPO3.lang['window.discard.title'],
-      TYPO3.lang['window.discard.message'],
-      Severity.warning,
-      [
-        {
-          text: TYPO3.lang['cancel'],
-          active: true,
-          btnClass: 'btn-default',
-          name: 'cancel',
-          trigger: function() {
-            $modal.modal('hide');
-          }
-        }, {
-        text: TYPO3.lang['ok'],
-        btnClass: 'btn-warning',
-        name: 'ok'
-      }
-      ]
-    );
-    $modal.on('button.clicked', function(e) {
-      if (e.target.name === 'ok') {
-        Workspaces.sendRemoteRequest([
-          Workspaces.generateRemoteActionsPayload('deleteSingleRecord', [
-            $tr.data('table'),
-            $tr.data('uid')
-          ])
-        ]).done(function() {
-          $modal.modal('hide');
-          Backend.getWorkspaceInfos();
-          Backend.refreshPageTree();
-        });
-      }
-    });
-  };
-
-  /**
-   * Runs a mass action
-   */
-  Backend.runSelectionAction = function() {
-    var selectedAction = Backend.elements.$chooseSelectionAction.val(),
-      integrityCheckRequired = selectedAction !== 'discard';
-
-    if (selectedAction.length === 0) {
-      // Don't do anything if that value is empty
-      return;
-    }
-
-    var affectedRecords = [];
-    for (var i = 0; i < Backend.markedRecordsForMassAction.length; ++i) {
-      var affected = Backend.markedRecordsForMassAction[i].split(':');
-      affectedRecords.push({
-        table: affected[0],
-        liveId: affected[2],
-        versionId: affected[1]
-      });
-    }
-
-    if (!integrityCheckRequired) {
-      Wizard.setup.forceSelection = false;
-      Backend.renderSelectionActionWizard(selectedAction, affectedRecords);
-    } else {
-      Workspaces.checkIntegrity(
-        {
-          selection: affectedRecords,
-          type: 'selection'
-        }
-      ).done(function(response) {
-        Wizard.setup.forceSelection = false;
-        if (response[0].result.result === 'warning') {
-          Backend.addIntegrityCheckWarningToWizard();
-        }
-        Backend.renderSelectionActionWizard(selectedAction, affectedRecords);
-      });
-    }
-  };
-
-  /**
-   * Adds a slide to the wizard concerning an integrity check warning.
-   */
-  Backend.addIntegrityCheckWarningToWizard = function() {
-    Wizard.addSlide(
-      'integrity-warning',
-      'Warning',
-      TYPO3.lang['integrity.hasIssuesDescription'] + '<br>' + TYPO3.lang['integrity.hasIssuesQuestion'],
-      Severity.warning
-    );
-  };
-
-  /**
-   * Renders the wizard for selection actions
-   *
-   * @param {String} selectedAction
-   * @param {Object} affectedRecords
-   */
-  Backend.renderSelectionActionWizard = function(selectedAction, affectedRecords) {
-    Wizard.addSlide(
-      'mass-action-confirmation',
-      TYPO3.lang['window.selectionAction.title'],
-      $('<p />').text(TYPO3.lang['tooltip.' + selectedAction + 'Selected']),
-      Severity.warning
-    );
-    Wizard.addFinalProcessingSlide(function() {
-      Workspaces.sendRemoteRequest(
-        Workspaces.generateRemoteActionsPayload('executeSelectionAction', {
-          action: selectedAction,
-          selection: affectedRecords
-        })
-      ).done(function() {
-        Backend.getWorkspaceInfos();
-        Wizard.dismiss();
-        Backend.refreshPageTree();
-      });
-    }).done(function() {
-      Wizard.show();
-
-      Wizard.getComponent().on('wizard-dismissed', function() {
-        Backend.elements.$chooseSelectionAction.val('');
-      });
-    });
-  };
-
-  /**
-   * Runs a mass action
-   */
-  Backend.runMassAction = function() {
-    var selectedAction = Backend.elements.$chooseMassAction.val(),
-      integrityCheckRequired = selectedAction !== 'discard';
-
-    if (selectedAction.length === 0) {
-      // Don't do anything if that value is empty
-      return;
-    }
-
-    if (!integrityCheckRequired) {
-      Wizard.setup.forceSelection = false;
-      Backend.renderMassActionWizard(selectedAction);
-    } else {
-      Workspaces.checkIntegrity(
-        {
-          language: Backend.settings.language,
-          type: selectedAction
-        }
-      ).done(function(response) {
-        Wizard.setup.forceSelection = false;
-        if (response[0].result.result === 'warning') {
-          Backend.addIntegrityCheckWarningToWizard();
-        }
-        Backend.renderMassActionWizard(selectedAction);
-      });
-    }
-  };
-
-  /**
-   * Renders the wizard for mass actions
-   *
-   * @param {String} selectedAction
-   */
-  Backend.renderMassActionWizard = function(selectedAction) {
-    var massAction,
-      doSwap = false;
-
-    switch (selectedAction) {
-      case 'publish':
-        massAction = 'publishWorkspace';
-        break;
-      case 'swap':
-        massAction = 'publishWorkspace';
-        doSwap = true;
-        break;
-      case 'discard':
-        massAction = 'flushWorkspace';
-        break;
-    }
-
-    if (massAction === null) {
-      throw 'Invalid mass action ' + selectedAction + ' called.';
-    }
-
-    Wizard.setup.forceSelection = false;
-    Wizard.addSlide(
-      'mass-action-confirmation',
-      TYPO3.lang['window.massAction.title'],
-      $('<p />').html(TYPO3.lang['tooltip.' + selectedAction + 'All'] + '<br><br>' + TYPO3.lang['tooltip.affectWholeWorkspace']),
-      Severity.warning
-    );
-
-    var sendRequestsUntilAllProcessed = function(response) {
-      var result = response[0].result;
-      // Make sure to process all items
-      if (result.processed < result.total) {
-        Workspaces.sendRemoteRequest(
-          Workspaces.generateRemoteMassActionsPayload(massAction, result)
-        ).done(sendRequestsUntilAllProcessed);
-      } else {
-        Backend.getWorkspaceInfos();
-        Wizard.dismiss();
-      }
-    }
-
-    Wizard.addFinalProcessingSlide(function() {
-      Workspaces.sendRemoteRequest(
-        Workspaces.generateRemoteMassActionsPayload(massAction, {
-          init: true,
-          total: 0,
-          processed: 0,
-          language: Backend.settings.language,
-          swap: doSwap
-        })
-      ).done(sendRequestsUntilAllProcessed);
-    }).done(function() {
-      Wizard.show();
-
-      Wizard.getComponent().on('wizard-dismissed', function() {
-        Backend.elements.$chooseMassAction.val('');
-      });
-    });
-  };
-
-  /**
-   * Sends marked records to a stage
-   *
-   * @param {Event} e
-   */
-  Backend.sendToSpecificStageAction = function(e) {
-    var affectedRecords = [],
-      stage = $(e.currentTarget).val();
-    for (var i = 0; i < Backend.markedRecordsForMassAction.length; ++i) {
-      var affected = Backend.markedRecordsForMassAction[i].split(':');
-      affectedRecords.push({
-        table: affected[0],
-        uid: affected[1],
-        t3ver_oid: affected[2]
-      });
-    }
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemoteActionsPayload('sendToSpecificStageWindow', [
-        stage, affectedRecords
-      ])
-    ).done(function(response) {
-      var $modal = Workspaces.renderSendToStageWindow(response);
-      $modal.on('button.clicked', function(e) {
-        if (e.target.name === 'ok') {
-          var $form = $(e.currentTarget).find('form'),
-            serializedForm = $form.serializeObject();
-
-          serializedForm.affects = {
-            elements: affectedRecords,
-            nextStage: stage
-          };
-
-          Workspaces.sendRemoteRequest([
-            Workspaces.generateRemoteActionsPayload('sendToSpecificStageExecute', [serializedForm]),
-            Workspaces.generateRemotePayload('getWorkspaceInfos', Backend.settings)
-          ]).done(function(response) {
-            $modal.modal('hide');
-            Backend.renderWorkspaceInfos(response[1].result);
-            Backend.refreshPageTree();
-          });
-        }
-      }).on('modal-destroyed', function() {
-        Backend.elements.$chooseStageAction.val('');
-      });
-    });
-  };
-
-  /**
-   * Reloads the page tree
-   */
-  Backend.refreshPageTree = function() {
-    if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) {
-      top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
-    }
-  };
-
-  /**
-   * Renders the action button based on the user's permission.
-   *
-   * @returns {$}
-   * @private
-   */
-  Backend.getAction = function(condition, action, iconIdentifier) {
-    if (condition) {
-      return $('<button />', {
-        class: 'btn btn-default',
-        'data-action': action,
-        'data-toggle': 'tooltip'
-      }).append(Backend.getPreRenderedIcon(iconIdentifier))
-    }
-    return $('<span />', {class: 'btn btn-default disabled'}).append(Backend.getPreRenderedIcon('empty-empty'));
-  };
-
-  /**
-   * Fetches and renders available preview links
-   */
-  Backend.generatePreviewLinks = function() {
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemoteActionsPayload('generateWorkspacePreviewLinksForAllLanguages', [
-        Backend.settings.id
-      ])
-    ).done(function(response) {
-      var result = response[0].result,
-        $list = $('<dl />');
-
-      $.each(result, function(language, url) {
-        $list.append(
-          $('<dt />').text(language),
-          $('<dd />').append(
-            $('<a />', {href: url, target: '_blank'}).text(url)
-          )
-        );
-      });
-
-      Modal.show(
-        TYPO3.lang['previewLink'],
-        $list,
-        Severity.info,
-        [{
-          text: TYPO3.lang['ok'],
-          active: true,
-          btnClass: 'btn-info',
-          name: 'ok',
-          trigger: function() {
-            Modal.currentModal.trigger('modal-dismiss');
-          }
-        }],
-        ['modal-inner-scroll']
-      );
-    });
-  };
-
-  /**
-   * Gets the pre-rendered icon
-   * This method is intended to be dropped once we use Fluid's StandaloneView.
-   *
-   * @param {String} identifier
-   * @returns {$}
-   */
-  Backend.getPreRenderedIcon = function(identifier) {
-    return Backend.elements.$actionIcons.find('[data-identifier="' + identifier + '"]').clone();
-  };
-
-  /**
-   * Serialize a form to a JavaScript object
-   *
-   * @see http://stackoverflow.com/a/1186309/4828813
-   * @return {Object}
-   */
-  $.fn.serializeObject = function() {
-    var o = {};
-    var a = this.serializeArray();
-    $.each(a, function() {
-      if (typeof o[this.name] !== 'undefined') {
-        if (!o[this.name].push) {
-          o[this.name] = [o[this.name]];
-        }
-        o[this.name].push(this.value || '');
-      } else {
-        o[this.name] = this.value || '';
-      }
-    });
-    return o;
-  };
-
-  /**
-   * Changes the markup of a pagination action being disabled
-   */
-  $.fn.disablePagingAction = function() {
-    $(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />'));
-  };
-
-  $(Backend.initialize);
-});
+var __extends=this&&this.__extends||function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var a in t)t.hasOwnProperty(a)&&(e[a]=t[a])};return function(t,a){function n(){this.constructor=t}e(t,a),t.prototype=null===a?Object.create(a):(n.prototype=a.prototype,new n)}}();define(["require","exports","TYPO3/CMS/Backend/Enum/Severity","jquery","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Storage/Persistent","TYPO3/CMS/Backend/Tooltip","TYPO3/CMS/Backend/Utility","TYPO3/CMS/Backend/Viewport","TYPO3/CMS/Backend/Wizard","./Workspaces","twbs/bootstrap-slider","nprogress","TYPO3/CMS/Backend/jquery.clearable"],function(require,exports,Severity_1,$,Modal,Persistent,Tooltip,Utility,Viewport,Wizard,Workspaces_1){"use strict";var Identifiers,k;k=Identifiers||(Identifiers={}),k.searchForm="#workspace-settings-form",k.searchTextField='#workspace-settings-form input[name="search-text"]',k.searchSubmitBtn='#workspace-settings-form button[type="submit"]',k.depthSelector='#workspace-settings-form [name="depth"]',k.languageSelector='#workspace-settings-form select[name="languages"]',k.chooseStageAction='#workspace-actions-form [name="stage-action"]',k.chooseSelectionAction='#workspace-actions-form [name="selection-action"]',k.chooseMassAction='#workspace-actions-form [name="mass-action"]',k.container="#workspace-panel",k.actionIcons="#workspace-action-icons",k.toggleAll=".t3js-toggle-all",k.previewLinksButton=".t3js-preview-link",k.pagination="#workspace-pagination";var Backend=function(_super){function Backend(){var _this=_super.call(this)||this;return _this.elements={},_this.settings={dir:"ASC",id:TYPO3.settings.Workspaces.id,language:TYPO3.settings.Workspaces.language,limit:30,query:"",sort:"label_Live",start:0,filterTxt:""},_this.paging={currentPage:1,totalPages:1,totalItems:0},_this.allToggled=!1,_this.latestPath="",_this.markedRecordsForMassAction=[],_this.indentationPadding=26,_this.handleCheckboxChange=function(e){var t=$(e.currentTarget),a=t.parents("tr"),n=a.data("table")+":"+a.data("uid")+":"+a.data("t3ver_oid");if(t.prop("checked"))_this.markedRecordsForMassAction.push(n),a.addClass("warning");else{var i=_this.markedRecordsForMassAction.indexOf(n);i>-1&&_this.markedRecordsForMassAction.splice(i,1),a.removeClass("warning")}_this.elements.$chooseStageAction.prop("disabled",0===_this.markedRecordsForMassAction.length),_this.elements.$chooseSelectionAction.prop("disabled",0===_this.markedRecordsForMassAction.length),_this.elements.$chooseMassAction.prop("disabled",_this.markedRecordsForMassAction.length>0)},_this.viewChanges=function(e){e.preventDefault();var t=$(e.currentTarget).closest("tr");_this.sendRemoteRequest(_this.generateRemotePayload("getRowDetails",{stage:t.data("stage"),t3ver_oid:t.data("t3ver_oid"),table:t.data("table"),uid:t.data("uid")})).done(function(e){var a=e[0].result.data[0],n=$("<div />"),i=$("<ul />",{class:"nav nav-tabs",role:"tablist"}),s=$("<div />",{class:"tab-content"}),o=[];n.append($("<p />").html(TYPO3.lang.path.replace("{0}",a.path_Live)),$("<p />").html(TYPO3.lang.current_step.replace("{0}",a.label_Stage).replace("{1}",a.stage_position).replace("{2}",a.stage_count))),a.diff.length>0&&(i.append($("<li />",{role:"presentation"}).append($("<a />",{href:"#workspace-changes","aria-controls":"workspace-changes",role:"tab","data-toggle":"tab"}).text(TYPO3.lang["window.recordChanges.tabs.changeSummary"]))),s.append($("<div />",{role:"tabpanel",class:"tab-pane",id:"workspace-changes"}).append($("<div />",{class:"form-section"}).append(Backend.generateDiffView(a.diff))))),a.comments.length>0&&(i.append($("<li />",{role:"presentation"}).append($("<a />",{href:"#workspace-comments","aria-controls":"workspace-comments",role:"tab","data-toggle":"tab"}).html(TYPO3.lang["window.recordChanges.tabs.comments"]+"&nbsp;").append($("<span />",{class:"badge"}).text(a.comments.length)))),s.append($("<div />",{role:"tabpanel",class:"tab-pane",id:"workspace-comments"}).append($("<div />",{class:"form-section"}).append(Backend.generateCommentView(a.comments))))),a.history.total>0&&(i.append($("<li />",{role:"presentation"}).append($("<a />",{href:"#workspace-history","aria-controls":"workspace-history",role:"tab","data-toggle":"tab"}).text(TYPO3.lang["window.recordChanges.tabs.history"]))),s.append($("<div />",{role:"tabpanel",class:"tab-pane",id:"workspace-history"}).append($("<div />",{class:"form-section"}).append(Backend.generateHistoryView(a.history.data))))),i.find("li").first().addClass("active"),s.find(".tab-pane").first().addClass("active"),n.append($("<div />").append(i,s)),t.data("stage")!==t.data("prevStage")&&o.push({text:a.label_PrevStage.title,active:!0,btnClass:"btn-default",name:"prevstage",trigger:function(){Modal.currentModal.trigger("modal-dismiss"),_this.sendToStage(t,"prev")}}),o.push({text:a.label_NextStage.title,active:!0,btnClass:"btn-default",name:"nextstage",trigger:function(){Modal.currentModal.trigger("modal-dismiss"),_this.sendToStage(t,"next")}}),o.push({text:TYPO3.lang.close,active:!0,btnClass:"btn-info",name:"cancel",trigger:function(){Modal.currentModal.trigger("modal-dismiss")}}),Modal.advanced({type:Modal.types.default,title:TYPO3.lang["window.recordInformation"].replace("{0}",$.trim(t.find(".t3js-title-live").text())),content:n,severity:Severity_1.SeverityEnum.info,buttons:o,size:Modal.sizes.medium})})},_this.openPreview=function(e){var $tr=$(e.currentTarget).closest("tr");_this.sendRemoteRequest(_this.generateRemoteActionsPayload("viewSingleRecord",[$tr.data("table"),$tr.data("uid")])).done(function(response){eval(response[0].result)})},_this.confirmDeleteRecordFromWorkspace=function(e){var t=$(e.target).closest("tr"),a=Modal.confirm(TYPO3.lang["window.discard.title"],TYPO3.lang["window.discard.message"],Severity_1.SeverityEnum.warning,[{text:TYPO3.lang.cancel,active:!0,btnClass:"btn-default",name:"cancel",trigger:function(){a.modal("hide")}},{text:TYPO3.lang.ok,btnClass:"btn-warning",name:"ok"}]);a.on("button.clicked",function(e){"ok"===e.target.name&&_this.sendRemoteRequest([_this.generateRemoteActionsPayload("deleteSingleRecord",[t.data("table"),t.data("uid")])]).done(function(){a.modal("hide"),_this.getWorkspaceInfos(),Backend.refreshPageTree()})})},_this.runSelectionAction=function(){var e=_this.elements.$chooseSelectionAction.val(),t="discard"!==e;if(0!==e.length){for(var a=[],n=0;n<_this.markedRecordsForMassAction.length;++n){var i=_this.markedRecordsForMassAction[n].split(":");a.push({table:i[0],liveId:i[2],versionId:i[1]})}t?_this.checkIntegrity({selection:a,type:"selection"}).done(function(t){Wizard.setup.forceSelection=!1,"warning"===t[0].result.result&&_this.addIntegrityCheckWarningToWizard(),_this.renderSelectionActionWizard(e,a)}):(Wizard.setup.forceSelection=!1,_this.renderSelectionActionWizard(e,a))}},_this.addIntegrityCheckWarningToWizard=function(){Wizard.addSlide("integrity-warning","Warning",TYPO3.lang["integrity.hasIssuesDescription"]+"<br>"+TYPO3.lang["integrity.hasIssuesQuestion"],Severity_1.SeverityEnum.warning)},_this.runMassAction=function(){var e=_this.elements.$chooseMassAction.val(),t="discard"!==e;0!==e.length&&(t?_this.checkIntegrity({language:_this.settings.language,type:e}).done(function(t){Wizard.setup.forceSelection=!1,"warning"===t[0].result.result&&_this.addIntegrityCheckWarningToWizard(),_this.renderMassActionWizard(e)}):(Wizard.setup.forceSelection=!1,_this.renderMassActionWizard(e)))},_this.sendToSpecificStageAction=function(e){for(var t=[],a=$(e.currentTarget).val(),n=0;n<_this.markedRecordsForMassAction.length;++n){var i=_this.markedRecordsForMassAction[n].split(":");t.push({table:i[0],uid:i[1],t3ver_oid:i[2]})}_this.sendRemoteRequest(_this.generateRemoteActionsPayload("sendToSpecificStageWindow",[a,t])).done(function(e){var n=_this.renderSendToStageWindow(e);n.on("button.clicked",function(e){if("ok"===e.target.name){var i=Utility.convertFormToObject(e.currentTarget.querySelector("form"));i.affects={elements:t,nextStage:a},_this.sendRemoteRequest([_this.generateRemoteActionsPayload("sendToSpecificStageExecute",[i]),_this.generateRemotePayload("getWorkspaceInfos",_this.settings)]).done(function(e){n.modal("hide"),_this.renderWorkspaceInfos(e[1].result),Backend.refreshPageTree()})}}).on("modal-destroyed",function(){_this.elements.$chooseStageAction.val("")})})},_this.generatePreviewLinks=function(){_this.sendRemoteRequest(_this.generateRemoteActionsPayload("generateWorkspacePreviewLinksForAllLanguages",[_this.settings.id])).done(function(e){var t=e[0].result,a=$("<dl />");$.each(t,function(e,t){a.append($("<dt />").text(e),$("<dd />").append($("<a />",{href:t,target:"_blank"}).text(t)))}),Modal.show(TYPO3.lang.previewLink,a,Severity_1.SeverityEnum.info,[{text:TYPO3.lang.ok,active:!0,btnClass:"btn-info",name:"ok",trigger:function(){Modal.currentModal.trigger("modal-dismiss")}}],["modal-inner-scroll"])})},$(function(){var e;_this.getElements(),_this.registerEvents(),Persistent.isset("this.Module.depth")?(e=Persistent.get("this.Module.depth"),_this.elements.$depthSelector.val(e),_this.settings.depth=e):_this.settings.depth=TYPO3.settings.Workspaces.depth,_this.loadWorkspaceComponents()}),_this}return __extends(Backend,_super),Backend.refreshPageTree=function(){Viewport.NavigationContainer&&Viewport.NavigationContainer.PageTree&&Viewport.NavigationContainer.PageTree.refreshTree()},Backend.generateDiffView=function(e){for(var t=$("<div />",{class:"diff"}),a=0;a<e.length;++a)t.append($("<div />",{class:"diff-item"}).append($("<div />",{class:"diff-item-title"}).text(e[a].label),$("<div />",{class:"diff-item-result diff-item-result-inline"}).html(e[a].content)));return t},Backend.generateCommentView=function(e){for(var t=$("<div />"),a=0;a<e.length;++a){var n=$("<div />",{class:"panel panel-default"});e[a].user_comment.length>0&&n.append($("<div />",{class:"panel-body"}).html(e[a].user_comment)),n.append($("<div />",{class:"panel-footer"}).append($("<span />",{class:"label label-success"}).text(e[a].stage_title),$("<span />",{class:"label label-info"}).text(e[a].tstamp))),t.append($("<div />",{class:"media"}).append($("<div />",{class:"media-left text-center"}).text(e[a].user_username).prepend($("<div />").html(e[a].user_avatar)),$("<div />",{class:"media-body"}).append(n)))}return t},Backend.generateHistoryView=function(e){for(var t=$("<div />"),a=0;a<e.length;++a){var n=$("<div />",{class:"panel panel-default"}),i=void 0;if("object"==typeof e[a].differences){if(0===e[a].differences.length)continue;i=$("<div />",{class:"diff"});for(var s=0;s<e[a].differences.length;++s)i.append($("<div />",{class:"diff-item"}).append($("<div />",{class:"diff-item-title"}).text(e[a].differences[s].label),$("<div />",{class:"diff-item-result diff-item-result-inline"}).html(e[a].differences[s].html)));n.append($("<div />").append(i))}else n.append($("<div />",{class:"panel-body"}).text(e[a].differences));n.append($("<div />",{class:"panel-footer"}).append($("<span />",{class:"label label-info"}).text(e[a].datetime))),t.append($("<div />",{class:"media"}).append($("<div />",{class:"media-left text-center"}).text(e[a].user).prepend($("<div />").html(e[a].user_avatar)),$("<div />",{class:"media-body"}).append(n)))}return t},Backend.prototype.getElements=function(){this.elements.$searchForm=$(Identifiers.searchForm),this.elements.$searchTextField=$(Identifiers.searchTextField),this.elements.$searchSubmitBtn=$(Identifiers.searchSubmitBtn),this.elements.$depthSelector=$(Identifiers.depthSelector),this.elements.$languageSelector=$(Identifiers.languageSelector),this.elements.$container=$(Identifiers.container),this.elements.$tableBody=this.elements.$container.find("tbody"),this.elements.$actionIcons=$(Identifiers.actionIcons),this.elements.$toggleAll=$(Identifiers.toggleAll),this.elements.$chooseStageAction=$(Identifiers.chooseStageAction),this.elements.$chooseSelectionAction=$(Identifiers.chooseSelectionAction),this.elements.$chooseMassAction=$(Identifiers.chooseMassAction),this.elements.$previewLinksButton=$(Identifiers.previewLinksButton),this.elements.$pagination=$(Identifiers.pagination)},Backend.prototype.registerEvents=function(){var e=this;$(document).on("click",'[data-action="swap"]',function(t){var a=t.target.closest("tr");e.checkIntegrity({selection:[{liveId:a.dataset.uid,versionId:a.dataset.t3ver_oid,table:a.dataset.table}],type:"selection"}).done(function(t){"warning"===t[0].result.result&&e.addIntegrityCheckWarningToWizard(),Wizard.set("forceSelection",!1),Wizard.addSlide("swap-confirm","Swap",TYPO3.lang["window.swap.message"],Severity_1.SeverityEnum.info),Wizard.addFinalProcessingSlide(function(){e.sendRemoteRequest(e.generateRemoteActionsPayload("swapSingleRecord",[a.dataset.table,a.dataset.t3ver_oid,a.dataset.uid])).done(function(){Wizard.dismiss(),e.getWorkspaceInfos(),Backend.refreshPageTree()})}).done(function(){Wizard.show()})})}).on("click",'[data-action="prevstage"]',function(t){e.sendToStage($(t.currentTarget).closest("tr"),"prev")}).on("click",'[data-action="nextstage"]',function(t){e.sendToStage($(t.currentTarget).closest("tr"),"next")}).on("click",'[data-action="changes"]',this.viewChanges).on("click",'[data-action="preview"]',this.openPreview).on("click",'[data-action="open"]',function(e){var t=e.currentTarget.closest("tr"),a=TYPO3.settings.FormEngine.moduleUrl+"&returnUrl="+encodeURIComponent(document.location.href)+"&id="+TYPO3.settings.Workspaces.id+"&edit["+t.dataset.table+"]["+t.dataset.uid+"]=edit";TYPO3.settings.Workspaces.allView&&(a+="&workspace="+t.dataset.t3ver_wsid),window.location.href=a}).on("click",'[data-action="version"]',function(e){var t=e.currentTarget.closest("tr"),a="pages"===t.dataset.table?t.dataset.t3ver_oid:t.dataset.pid;window.location.href=top.TYPO3.configuration.pageModuleUrl+"&id="+a+"&returnUrl="+encodeURIComponent(window.location.href)}).on("click",'[data-action="remove"]',this.confirmDeleteRecordFromWorkspace).on("click",'[data-action="expand"]',function(t){var a,n=$(t.currentTarget);a="true"===e.elements.$tableBody.find(n.data("target")).first().attr("aria-expanded")?"apps-pagetree-expand":"apps-pagetree-collapse",n.empty().append(e.getPreRenderedIcon(a))}),$(window.top.document).on("click",".t3js-workspace-recipients-selectall",function(e){e.preventDefault(),$(".t3js-workspace-recipient",window.top.document).not(":disabled").prop("checked",!0)}).on("click",".t3js-workspace-recipients-deselectall",function(e){e.preventDefault(),$(".t3js-workspace-recipient",window.top.document).not(":disabled").prop("checked",!1)}),this.elements.$searchForm.on("submit",function(t){t.preventDefault(),e.settings.filterTxt=e.elements.$searchTextField.val(),e.getWorkspaceInfos()}),this.elements.$searchTextField.on("keyup",function(t){""!==t.target.value?e.elements.$searchSubmitBtn.removeClass("disabled"):(e.elements.$searchSubmitBtn.addClass("disabled"),e.getWorkspaceInfos())}).clearable({onClear:function(){e.elements.$searchSubmitBtn.addClass("disabled"),e.settings.filterTxt="",e.getWorkspaceInfos()}}),this.elements.$toggleAll.on("click",function(){e.allToggled=!e.allToggled,e.elements.$tableBody.find('input[type="checkbox"]').prop("checked",e.allToggled).trigger("change")}),this.elements.$tableBody.on("change","tr input[type=checkbox]",this.handleCheckboxChange),this.elements.$depthSelector.on("change",function(t){var a=t.target.value;Persistent.set("this.Module.depth",a),e.settings.depth=a,e.getWorkspaceInfos()}),this.elements.$previewLinksButton.on("click",this.generatePreviewLinks),this.elements.$languageSelector.on("change",function(t){var a=$(t.target);e.settings.language=a.val(),e.sendRemoteRequest([e.generateRemoteActionsPayload("saveLanguageSelection",[a.val()]),e.generateRemotePayload("getWorkspaceInfos",e.settings)]).done(function(t){e.elements.$languageSelector.prev().html(a.find(":selected").data("icon")),e.renderWorkspaceInfos(t[1].result)})}),this.elements.$chooseStageAction.on("change",this.sendToSpecificStageAction),this.elements.$chooseSelectionAction.on("change",this.runSelectionAction),this.elements.$chooseMassAction.on("change",this.runMassAction),this.elements.$pagination.on("click","a[data-action]",function(t){t.preventDefault();var a=$(t.currentTarget),n=!1;switch(a.data("action")){case"previous":e.paging.currentPage>1&&(e.paging.currentPage--,n=!0);break;case"next":e.paging.currentPage<e.paging.totalPages&&(e.paging.currentPage++,n=!0);break;case"page":e.paging.currentPage=parseInt(a.data("page"),10),n=!0;break;default:throw'Unknown action "'+a.data("action")+'"'}n&&(e.settings.start=parseInt(e.settings.limit.toString(),10)*(e.paging.currentPage-1),e.getWorkspaceInfos())})},Backend.prototype.sendToStage=function(e,t){var a,n,i,s=this;if("next"===t)a=e.data("nextStage"),n="sendToNextStageWindow",i="sendToNextStageExecute";else{if("prev"!==t)throw"Invalid direction given.";a=e.data("prevStage"),n="sendToPrevStageWindow",i="sendToPrevStageExecute"}this.sendRemoteRequest(this.generateRemoteActionsPayload(n,[e.data("uid"),e.data("table"),e.data("t3ver_oid")])).done(function(t){var n=s.renderSendToStageWindow(t);n.on("button.clicked",function(t){if("ok"===t.target.name){var o=Utility.convertFormToObject(t.currentTarget.querySelector("form"));o.affects={table:e.data("table"),nextStage:a,t3ver_oid:e.data("t3ver_oid"),uid:e.data("uid"),elements:[]},s.sendRemoteRequest([s.generateRemoteActionsPayload(i,[o]),s.generateRemotePayload("getWorkspaceInfos",s.settings)]).done(function(e){n.modal("hide"),s.renderWorkspaceInfos(e[1].result),Backend.refreshPageTree()})}})})},Backend.prototype.loadWorkspaceComponents=function(){var e=this;this.sendRemoteRequest([this.generateRemotePayload("getWorkspaceInfos",this.settings),this.generateRemotePayload("getStageActions",{}),this.generateRemoteMassActionsPayload("getMassStageActions",{}),this.generateRemotePayload("getSystemLanguages",{pageUid:this.elements.$container.data("pageUid")})]).done(function(t){e.elements.$depthSelector.prop("disabled",!1),e.renderWorkspaceInfos(t[0].result);var a,n=t[1].result.data;for(a=0;a<n.length;++a)e.elements.$chooseStageAction.append($("<option />").val(n[a].uid).text(n[a].title));var i=t[2].result.data;for(a=0;a<i.length;++a)e.elements.$chooseSelectionAction.append($("<option />").val(i[a].action).text(i[a].title)),e.elements.$chooseMassAction.append($("<option />").val(i[a].action).text(i[a].title));var s=t[3].result.data;for(a=0;a<s.length;++a){var o=$("<option />").val(s[a].uid).text(s[a].title).data("icon",s[a].icon);String(s[a].uid)===String(TYPO3.settings.Workspaces.language)&&(o.prop("selected",!0),e.elements.$languageSelector.prev().html(s[a].icon)),e.elements.$languageSelector.append(o)}e.elements.$languageSelector.prop("disabled",!1)})},Backend.prototype.getWorkspaceInfos=function(){var e=this;this.sendRemoteRequest(this.generateRemotePayload("getWorkspaceInfos",this.settings)).done(function(t){e.renderWorkspaceInfos(t[0].result)})},Backend.prototype.renderWorkspaceInfos=function(e){this.elements.$tableBody.children().remove(),this.allToggled=!1,this.elements.$chooseStageAction.prop("disabled",!0),this.elements.$chooseSelectionAction.prop("disabled",!0),this.elements.$chooseMassAction.prop("disabled",0===e.data.length),this.buildPagination(e.total);for(var t=0;t<e.data.length;++t){var a=e.data[t],n=$("<div />",{class:"btn-group"}),i=void 0;n.append(this.getAction(a.Workspaces_CollectionChildren>0&&""!==a.Workspaces_CollectionCurrent,"expand","apps-pagetree-collapse").attr("title",TYPO3.lang["tooltip.expand"]).attr("data-target",'[data-collection="'+a.Workspaces_CollectionCurrent+'"]').attr("data-toggle","collapse"),$("<button />",{class:"btn btn-default","data-action":"changes","data-toggle":"tooltip",title:TYPO3.lang["tooltip.showChanges"]}).append(this.getPreRenderedIcon("actions-document-info")),this.getAction(a.allowedAction_swap&&""===a.Workspaces_CollectionParent,"swap","actions-version-swap-version").attr("title",TYPO3.lang["tooltip.swap"]),this.getAction(a.allowedAction_view,"preview","actions-version-workspace-preview").attr("title",TYPO3.lang["tooltip.viewElementAction"]),this.getAction(a.allowedAction_edit,"open","actions-open").attr("title",TYPO3.lang["tooltip.editElementAction"]),this.getAction(!0,"version","actions-version-page-open").attr("title",TYPO3.lang["tooltip.openPage"]),this.getAction(a.allowedAction_delete,"remove","actions-version-document-remove").attr("title",TYPO3.lang["tooltip.discardVersion"])),""!==a.integrity.messages&&(i=$(TYPO3.settings.Workspaces.icons[a.integrity.status])).attr("data-toggle","tooltip").attr("data-placement","top").attr("data-html","true").attr("title",a.integrity.messages),this.latestPath!==a.path_Workspace&&(this.latestPath=a.path_Workspace,this.elements.$tableBody.append($("<tr />").append($("<th />"),$("<th />",{colspan:6}).text(this.latestPath))));var s=$("<label />",{class:"btn btn-default btn-checkbox"}).append($("<input />",{type:"checkbox"}),$("<span />",{class:"t3-icon fa"})),o={"data-uid":a.uid,"data-pid":a.livepid,"data-t3ver_oid":a.t3ver_oid,"data-t3ver_wsid":a.t3ver_wsid,"data-table":a.table,"data-next-stage":a.value_nextStage,"data-prev-stage":a.value_prevStage,"data-stage":a.stage};""!==a.Workspaces_CollectionParent&&(o["data-collection"]=a.Workspaces_CollectionParent,o.class="collapse"),this.elements.$tableBody.append($("<tr />",o).append($("<td />").empty().append(s),$("<td />",{class:"t3js-title-workspace",style:a.Workspaces_CollectionLevel>0?"padding-left: "+this.indentationPadding*a.Workspaces_CollectionLevel+"px":""}).html(a.icon_Workspace+'&nbsp;<a href="#" data-action="changes"><span class="item-state-'+a.state_Workspace+'">'+a.label_Workspace+"</span></a>"),$("<td />",{class:"t3js-title-live"}).html(a.icon_Live+"&nbsp;"+a.label_Live),$("<td />").text(a.label_Stage),$("<td />").empty().append(i),$("<td />").html(a.language.icon),$("<td />",{class:"text-right nowrap"}).append(n))),Tooltip.initialize('[data-toggle="tooltip"]',{delay:{show:500,hide:100},trigger:"hover",container:"body"})}},Backend.prototype.buildPagination=function(e){if(0!==e)if(this.paging.totalItems=e,this.paging.totalPages=Math.ceil(e/parseInt(this.settings.limit.toString(),10)),1!==this.paging.totalPages){var t=$("<ul />",{class:"pagination pagination-block"}),a=[],n=$("<li />").append($("<a />",{"data-action":"previous"}).append($("<span />",{class:"t3-icon fa fa-arrow-left"}))),i=$("<li />").append($("<a />",{"data-action":"next"}).append($("<span />",{class:"t3-icon fa fa-arrow-right"})));1===this.paging.currentPage&&n.disablePagingAction(),this.paging.currentPage===this.paging.totalPages&&i.disablePagingAction();for(var s=1;s<=this.paging.totalPages;s++){var o=$("<li />",{class:this.paging.currentPage===s?"active":""});o.append($("<a />",{"data-action":"page","data-page":s}).append($("<span />").text(s))),a.push(o)}t.append(n,a,i),this.elements.$pagination.empty().append(t)}else this.elements.$pagination.contents().remove();else this.elements.$pagination.contents().remove()},Backend.prototype.renderSelectionActionWizard=function(e,t){var a=this;Wizard.addSlide("mass-action-confirmation",TYPO3.lang["window.selectionAction.title"],$("<p />").text(TYPO3.lang["tooltip."+e+"Selected"]),Severity_1.SeverityEnum.warning),Wizard.addFinalProcessingSlide(function(){a.sendRemoteRequest(a.generateRemoteActionsPayload("executeSelectionAction",{action:e,selection:t})).done(function(){a.getWorkspaceInfos(),Wizard.dismiss(),Backend.refreshPageTree()})}).done(function(){Wizard.show(),Wizard.getComponent().on("wizard-dismissed",function(){a.elements.$chooseSelectionAction.val("")})})},Backend.prototype.renderMassActionWizard=function(e){var t,a=this,n=!1;switch(e){case"publish":t="publishWorkspace";break;case"swap":t="publishWorkspace",n=!0;break;case"discard":t="flushWorkspace";break;default:throw"Invalid mass action "+e+" called."}Wizard.set("forceSelection",!1),Wizard.addSlide("mass-action-confirmation",TYPO3.lang["window.massAction.title"],$("<p />").html(TYPO3.lang["tooltip."+e+"All"]+"<br><br>"+TYPO3.lang["tooltip.affectWholeWorkspace"]),Severity_1.SeverityEnum.warning);var i=function(e){var n=e[0].result;n.processed<n.total?a.sendRemoteRequest(a.generateRemoteMassActionsPayload(t,n)).done(i):(a.getWorkspaceInfos(),Wizard.dismiss())};Wizard.addFinalProcessingSlide(function(){a.sendRemoteRequest(a.generateRemoteMassActionsPayload(t,{init:!0,total:0,processed:0,language:a.settings.language,swap:n})).done(i)}).done(function(){Wizard.show(),Wizard.getComponent().on("wizard-dismissed",function(){a.elements.$chooseMassAction.val("")})})},Backend.prototype.getAction=function(e,t,a){return e?$("<button />",{class:"btn btn-default","data-action":t,"data-toggle":"tooltip"}).append(this.getPreRenderedIcon(a)):$("<span />",{class:"btn btn-default disabled"}).append(this.getPreRenderedIcon("empty-empty"))},Backend.prototype.getPreRenderedIcon=function(e){return this.elements.$actionIcons.find('[data-identifier="'+e+'"]').clone()},Backend}(Workspaces_1.default);return new Backend});
\ No newline at end of file
index 8ceaa66..41de814 100644 (file)
@@ -1,5 +1,4 @@
 /*
-/*
  * This file is part of the TYPO3 CMS project.
  *
  * It is free software; you can redistribute it and/or modify it under
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * RequireJS module for workspace preview
- */
-define([
-  'jquery',
-  'TYPO3/CMS/Workspaces/Workspaces',
-  'TYPO3/CMS/Backend/Severity',
-  'TYPO3/CMS/Backend/Modal',
-  'twbs/bootstrap-slider'
-], function($, Workspaces, Severity, Modal) {
-  'use strict';
-
-  var Preview = {
-    identifiers: {
-      topbar: '#typo3-topbar',
-      workspacePanel: '.workspace-panel',
-      liveView: '#live-view',
-      workspaceTabs: '.t3js-workspace-tabs [data-toggle="tab"]',
-      workspaceActions: '.t3js-workspace-actions',
-      stageSlider: '#workspace-stage-slider',
-      workspaceView: '#workspace-view',
-      workspaceList: '#workspace-list',
-      sendToStageAction: '[data-action="send-to-stage"]',
-      discardAction: '[data-action="discard"]',
-      stageButtonsContainer: '.t3js-stage-buttons',
-      previewModeContainer: '.t3js-preview-mode',
-      activePreviewMode: '.t3js-active-preview-mode',
-      workspacePreview: '.t3js-workspace-preview'
-    },
-    currentSlidePosition: 100,
-    elements: {} // filled in Preview.getElements()
-  };
-
-  /**
-   * Initializes the preview module
-   */
-  Preview.initialize = function() {
-    Preview.getElements();
-    Preview.resizeViews();
-
-    Preview.adjustPreviewModeSelectorWidth();
-    Preview.elements.$stageSlider.slider();
-
-    Preview.registerEvents();
-  };
-
-  /**
-   * Fetches and stores often required elements
-   */
-  Preview.getElements = function() {
-    Preview.elements.$liveView = $(Preview.identifiers.liveView);
-    Preview.elements.$workspacePanel = $(Preview.identifiers.workspacePanel);
-    Preview.elements.$workspaceTabs = $(Preview.identifiers.workspaceTabs);
-    Preview.elements.$workspaceActions = $(Preview.identifiers.workspaceActions);
-    Preview.elements.$stageSlider = $(Preview.identifiers.stageSlider);
-    Preview.elements.$workspaceView = $(Preview.identifiers.workspaceView);
-    Preview.elements.$workspaceList = $(Preview.identifiers.workspaceList);
-    Preview.elements.$stageButtonsContainer = $(Preview.identifiers.stageButtonsContainer);
-    Preview.elements.$previewModeContainer = $(Preview.identifiers.previewModeContainer);
-    Preview.elements.$activePreviewMode = $(Preview.identifiers.activePreviewMode);
-    Preview.elements.$workspacePreview = $(Preview.identifiers.workspacePreview);
-  };
-
-  /**
-   * Registers the events
-   */
-  Preview.registerEvents = function() {
-    $(window).on('resize', function() {
-      Preview.resizeViews();
-    });
-    $(document)
-      .on('click', Preview.identifiers.discardAction, Preview.renderDiscardWindow)
-      .on('click', Preview.identifiers.sendToStageAction, Preview.renderSendPageToStageWindow)
-    ;
-
-    Preview.elements.$workspaceTabs.on('show.bs.tab', function() {
-      Preview.elements.$workspaceActions.toggle($(this).data('actions'));
-    });
-    Preview.elements.$stageSlider.on('change', Preview.updateSlidePosition);
-    Preview.elements.$previewModeContainer.find('[data-preview-mode]').on('click', Preview.changePreviewMode);
-  };
-
-  /**
-   * Renders the staging buttons
-   *
-   * @param {String} buttons
-   */
-  Preview.renderStageButtons = function(buttons) {
-    Preview.elements.$stageButtonsContainer.html(buttons);
-  };
-
-  /**
-   * Calculate the available space based on the viewport height
-   *
-   * @returns {Number}
-   */
-  Preview.getAvailableSpace = function() {
-    var $viewportHeight = $(window).height(),
-      $topbarHeight = $(Preview.identifiers.topbar).outerHeight();
-
-    return $viewportHeight - $topbarHeight;
-  };
-
-  /**
-   * Updates the position of the comparison slider
-   *
-   * @param {Event} e
-   */
-  Preview.updateSlidePosition = function(e) {
-    Preview.currentSlidePosition = e.value.newValue;
-    Preview.resizeViews();
-  };
-
-  /**
-   * Resize the views based on the current viewport height and slider position
-   */
-  Preview.resizeViews = function() {
-    var availableSpace = Preview.getAvailableSpace(),
-      relativeHeightOfLiveView = (Preview.currentSlidePosition - 100) * -1,
-      absoluteHeightOfLiveView = Math.round(Math.abs(availableSpace * relativeHeightOfLiveView / 100)),
-      outerHeightDifference = Preview.elements.$liveView.outerHeight() - Preview.elements.$liveView.height();
-
-    Preview.elements.$workspacePreview.height(availableSpace);
-
-    if (Preview.elements.$activePreviewMode.data('activePreviewMode') === 'slider') {
-      Preview.elements.$liveView.height(absoluteHeightOfLiveView - outerHeightDifference);
-    }
-    Preview.elements.$workspaceList.height(availableSpace);
-  };
-
-  /**
-   * Renders the discard window
-   *
-   * @private
-   */
-  Preview.renderDiscardWindow = function() {
-    var $modal = Modal.confirm(
-      TYPO3.lang['window.discardAll.title'],
-      TYPO3.lang['window.discardAll.message'],
-      Severity.warning,
-      [
-        {
-          text: TYPO3.lang['cancel'],
-          active: true,
-          btnClass: 'btn-default',
-          name: 'cancel',
-          trigger: function() {
-            $modal.modal('hide');
-          }
-        }, {
-        text: TYPO3.lang['ok'],
-        btnClass: 'btn-warning',
-        name: 'ok'
-      }
-      ]
-    );
-    $modal.on('button.clicked', function(e) {
-      if (e.target.name === 'ok') {
-        Workspaces.sendRemoteRequest([
-          Workspaces.generateRemoteActionsPayload('discardStagesFromPage', [TYPO3.settings.Workspaces.id]),
-          Workspaces.generateRemoteActionsPayload('updateStageChangeButtons', [TYPO3.settings.Workspaces.id])
-        ]).done(function(response) {
-          $modal.modal('hide');
-          Preview.renderStageButtons(response[1].result);
-          // Reloading live view and and workspace list view IFRAME
-          Preview.elements.$workspaceView.attr('src', Preview.elements.$workspaceView.attr('src'));
-          Preview.elements.$workspaceList.attr('src', Preview.elements.$workspaceList.attr('src'));
-        });
-      }
-    });
-  };
-
-  /**
-   * Adjusts the width of the preview mode selector to avoid jumping around due to different widths of the labels
-   */
-  Preview.adjustPreviewModeSelectorWidth = function() {
-    var $btnGroup = Preview.elements.$previewModeContainer.find('.btn-group'),
-      maximumWidth = 0;
-
-    $btnGroup.addClass('open');
-    Preview.elements.$previewModeContainer.find('li > a > span').each(function(_, el) {
-      var width = $(el).width();
-      if (maximumWidth < width) {
-        maximumWidth = width;
-      }
-    });
-    $btnGroup.removeClass('open');
-    Preview.elements.$activePreviewMode.width(maximumWidth);
-  };
-
-  /**
-   * Renders the "send page to stage" window
-   *
-   * @private
-   */
-  Preview.renderSendPageToStageWindow = function() {
-    var $me = $(this),
-      direction = $me.data('direction'),
-      actionName;
-
-    if (direction === 'prev') {
-      actionName = 'sendPageToPreviousStage';
-    } else if (direction === 'next') {
-      actionName = 'sendPageToNextStage';
-    } else {
-      throw 'Invalid direction ' + direction + ' requested.';
-    }
-
-    Workspaces.sendRemoteRequest(
-      Workspaces.generateRemoteActionsPayload(actionName, [TYPO3.settings.Workspaces.id])
-    ).done(function(response) {
-      var $modal = Workspaces.renderSendToStageWindow(response);
-      $modal.on('button.clicked', function(e) {
-        if (e.target.name === 'ok') {
-          var $form = $(e.currentTarget).find('form'),
-            serializedForm = $form.serializeObject();
-
-          serializedForm.affects = response[0].result.affects;
-          serializedForm.stageId = $me.data('stageId');
-
-          Workspaces.sendRemoteRequest([
-            Workspaces.generateRemoteActionsPayload('sentCollectionToStage', [serializedForm]),
-            Workspaces.generateRemoteActionsPayload('updateStageChangeButtons', [TYPO3.settings.Workspaces.id])
-          ]).done(function(response) {
-            $modal.modal('hide');
-
-            Preview.renderStageButtons(response[1].result);
-          });
-        }
-      });
-    });
-  };
-
-  /**
-   * Changes the preview mode
-   *
-   * @param {Event} e
-   */
-  Preview.changePreviewMode = function(e) {
-    e.preventDefault();
-
-    var $trigger = $(this),
-      currentPreviewMode = Preview.elements.$activePreviewMode.data('activePreviewMode'),
-      newPreviewMode = $trigger.data('previewMode');
-
-    Preview.elements.$activePreviewMode.text($trigger.text()).data('activePreviewMode', newPreviewMode);
-    Preview.elements.$workspacePreview.parent()
-      .removeClass('preview-mode-' + currentPreviewMode)
-      .addClass('preview-mode-' + newPreviewMode);
-
-    if (newPreviewMode === 'slider') {
-      Preview.elements.$stageSlider.parent().toggle(true);
-      Preview.resizeViews();
-    } else {
-      Preview.elements.$stageSlider.parent().toggle(false);
-
-      if (newPreviewMode === 'vbox') {
-        Preview.elements.$liveView.height('100%');
-      } else {
-        Preview.elements.$liveView.height('50%');
-      }
-    }
-
-  };
-
-  /**
-   * Serialize a form to a JavaScript object
-   *
-   * @see http://stackoverflow.com/a/1186309/4828813
-   * @return {Object}
-   */
-  $.fn.serializeObject = function() {
-    var o = {};
-    var a = this.serializeArray();
-    $.each(a, function() {
-      if (typeof o[this.name] !== 'undefined') {
-        if (!o[this.name].push) {
-          o[this.name] = [o[this.name]];
-        }
-        o[this.name].push(this.value || '');
-      } else {
-        o[this.name] = this.value || '';
-      }
-    });
-    return o;
-  };
-
-  $(document).ready(function() {
-    Preview.initialize();
-  });
-});
+var __extends=this&&this.__extends||function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])};return function(t,i){function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}}();define(["require","exports","TYPO3/CMS/Backend/Enum/Severity","jquery","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Utility","./Workspaces","twbs/bootstrap-slider"],function(e,t,i,n,s,o,a){"use strict";var r,c;return(c=r||(r={})).topbar="#typo3-topbar",c.workspacePanel=".workspace-panel",c.liveView="#live-view",c.workspaceTabs='.t3js-workspace-tabs [data-toggle="tab"]',c.workspaceActions=".t3js-workspace-actions",c.stageSlider="#workspace-stage-slider",c.workspaceView="#workspace-view",c.workspaceList="#workspace-list",c.sendToStageAction='[data-action="send-to-stage"]',c.discardAction='[data-action="discard"]',c.stageButtonsContainer=".t3js-stage-buttons",c.previewModeContainer=".t3js-preview-mode",c.activePreviewMode=".t3js-active-preview-mode",c.workspacePreview=".t3js-workspace-preview",new(function(e){function t(){var t=e.call(this)||this;return t.currentSlidePosition=100,t.elements={},t.updateSlidePosition=function(e){t.currentSlidePosition=e.value.newValue,t.resizeViews()},t.renderDiscardWindow=function(){var e=s.confirm(TYPO3.lang["window.discardAll.title"],TYPO3.lang["window.discardAll.message"],i.SeverityEnum.warning,[{text:TYPO3.lang.cancel,active:!0,btnClass:"btn-default",name:"cancel",trigger:function(){e.modal("hide")}},{text:TYPO3.lang.ok,btnClass:"btn-warning",name:"ok"}]);e.on("button.clicked",function(i){"ok"===i.target.name&&t.sendRemoteRequest([t.generateRemoteActionsPayload("discardStagesFromPage",[TYPO3.settings.Workspaces.id]),t.generateRemoteActionsPayload("updateStageChangeButtons",[TYPO3.settings.Workspaces.id])]).done(function(i){e.modal("hide"),t.renderStageButtons(i[1].result),t.elements.$workspaceView.attr("src",t.elements.$workspaceView.attr("src")),t.elements.$workspaceList.attr("src",t.elements.$workspaceList.attr("src"))})})},t.renderSendPageToStageWindow=function(e){var i,n=e.currentTarget,s=n.dataset.direction;if("prev"===s)i="sendPageToPreviousStage";else{if("next"!==s)throw"Invalid direction "+s+" requested.";i="sendPageToNextStage"}t.sendRemoteRequest(t.generateRemoteActionsPayload(i,[TYPO3.settings.Workspaces.id])).done(function(e){var i=t.renderSendToStageWindow(e);i.on("button.clicked",function(s){if("ok"===s.target.name){var a=o.convertFormToObject(s.currentTarget.querySelector("form"));a.affects=e[0].result.affects,a.stageId=n.dataset.stageId,t.sendRemoteRequest([t.generateRemoteActionsPayload("sentCollectionToStage",[a]),t.generateRemoteActionsPayload("updateStageChangeButtons",[TYPO3.settings.Workspaces.id])]).done(function(e){i.modal("hide"),t.renderStageButtons(e[1].result)})}})})},t.changePreviewMode=function(e){e.preventDefault();var i=n(e.currentTarget),s=t.elements.$activePreviewMode.data("activePreviewMode"),o=i.data("previewMode");t.elements.$activePreviewMode.text(i.text()).data("activePreviewMode",o),t.elements.$workspacePreview.parent().removeClass("preview-mode-"+s).addClass("preview-mode-"+o),"slider"===o?(t.elements.$stageSlider.parent().toggle(!0),t.resizeViews()):(t.elements.$stageSlider.parent().toggle(!1),"vbox"===o?t.elements.$liveView.height("100%"):t.elements.$liveView.height("50%"))},n(function(){t.getElements(),t.resizeViews(),t.adjustPreviewModeSelectorWidth(),t.elements.$stageSlider.slider(),t.registerEvents()}),t}return __extends(t,e),t.getAvailableSpace=function(){return n(window).height()-n(r.topbar).outerHeight()},t.prototype.getElements=function(){this.elements.$liveView=n(r.liveView),this.elements.$workspacePanel=n(r.workspacePanel),this.elements.$workspaceTabs=n(r.workspaceTabs),this.elements.$workspaceActions=n(r.workspaceActions),this.elements.$stageSlider=n(r.stageSlider),this.elements.$workspaceView=n(r.workspaceView),this.elements.$workspaceList=n(r.workspaceList),this.elements.$stageButtonsContainer=n(r.stageButtonsContainer),this.elements.$previewModeContainer=n(r.previewModeContainer),this.elements.$activePreviewMode=n(r.activePreviewMode),this.elements.$workspacePreview=n(r.workspacePreview)},t.prototype.registerEvents=function(){var e=this;n(window).on("resize",function(){e.resizeViews()}),n(document).on("click",r.discardAction,this.renderDiscardWindow).on("click",r.sendToStageAction,this.renderSendPageToStageWindow),this.elements.$workspaceTabs.on("show.bs.tab",function(t){e.elements.$workspaceActions.toggle(t.currentTarget.dataset.actions)}),this.elements.$stageSlider.on("change",this.updateSlidePosition),this.elements.$previewModeContainer.find("[data-preview-mode]").on("click",this.changePreviewMode)},t.prototype.renderStageButtons=function(e){this.elements.$stageButtonsContainer.html(e)},t.prototype.resizeViews=function(){var e=t.getAvailableSpace(),i=-1*(this.currentSlidePosition-100),n=Math.round(Math.abs(e*i/100)),s=this.elements.$liveView.outerHeight()-this.elements.$liveView.height();this.elements.$workspacePreview.height(e),"slider"===this.elements.$activePreviewMode.data("activePreviewMode")&&this.elements.$liveView.height(n-s),this.elements.$workspaceList.height(e)},t.prototype.adjustPreviewModeSelectorWidth=function(){var e=this.elements.$previewModeContainer.find(".btn-group"),t=0;e.addClass("open"),this.elements.$previewModeContainer.find("li > a > span").each(function(e,i){var s=n(i).width();t<s&&(t=s)}),e.removeClass("open"),this.elements.$activePreviewMode.width(t)},t}(a.default))});
\ No newline at end of file
index 11d7380..c598f04 100644 (file)
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * Module: TYPO3/CMS/Workspaces/Toolbar/WorkspacesMenu
- * toolbar menu for the workspaces functionality to switch between the workspaces
- * and jump to the workspaces module
- */
-define(['jquery', 'TYPO3/CMS/Backend/Viewport'], function($, Viewport) {
-  'use strict';
-
-  /**
-   *
-   * @type {{options: {containerSelector: string, menuItemSelector: string, activeMenuItemSelector: string, toolbarItemSelector: string, workspaceBodyClass: string, workspacesTitleInToolbarClass: string, workspaceModuleLinkSelector: string}}}
-   * @exports TYPO3/CMS/Workspaces/Toolbar/WorkspacesMenu
-   */
-  var WorkspacesMenu = {
-    options: {
-      containerSelector: '#typo3-cms-workspaces-backend-toolbaritems-workspaceselectortoolbaritem',
-      menuItemSelector: '.t3js-workspaces-switchlink',
-      activeMenuItemSelector: '.dropdown-menu .selected',
-      toolbarItemSelector: '.dropdown-toggle',
-      workspaceBodyClass: 'typo3-in-workspace',        // attached to <body> when in a workspace
-      workspacesTitleInToolbarClass: 'toolbar-item-name',
-      workspaceModuleLinkSelector: '.t3js-workspaces-modulelink'
-    }
-  };
-
-  /**
-   * registers event listeners
-   */
-  WorkspacesMenu.initializeEvents = function() {
-
-    // link to the module
-    $(WorkspacesMenu.options.containerSelector).on('click', WorkspacesMenu.options.workspaceModuleLinkSelector, function(evt) {
-      evt.preventDefault();
-      top.goToModule($(this).data('module'));
-    });
-
-    // observe all clicks on workspace links in the menu
-    $(WorkspacesMenu.options.containerSelector).on('click', WorkspacesMenu.options.menuItemSelector, function(evt) {
-      evt.preventDefault();
-      WorkspacesMenu.switchWorkspace($(this).data('workspaceid'));
-    });
-  };
-
-  /**
-   * switches the workspace via AJAX (which returns the new data, as JSON),
-   * then reloads the module menu, and the content frame
-   *
-   * @param {String} workspaceId
-   */
-  WorkspacesMenu.switchWorkspace = function(workspaceId) {
-    $.ajax({
-      url: TYPO3.settings.ajaxUrls['workspace_switch'],
-      type: 'post',
-      data: {
-        workspaceId: workspaceId,
-        pageId: fsMod.recentIds['web']
-      },
-      success: function(response) {
-        if (!response.workspaceId) {
-          response.workspaceId = 0;
-        }
-
-        WorkspacesMenu.performWorkspaceSwitch(response.workspaceId, response.title);
-
-        // append the returned page ID to the current module URL
-        if (response.pageId) {
-          fsMod.recentIds['web'] = response.pageId;
-          var url = TYPO3.Backend.ContentContainer.getUrl();
-          url += (url.indexOf('?') == -1 ? '?' : '&') + '&id=' + response.pageId;
-          if (TYPO3.Backend.NavigationContainer.PageTree) {
-            TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
-          }
-          TYPO3.Backend.ContentContainer.setUrl(url);
-
-          // when in web module reload, otherwise send the user to the web module
-        } else if (currentModuleLoaded.indexOf('web_') === 0) {
-          if (TYPO3.Backend.NavigationContainer.PageTree) {
-            TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
-          }
-          TYPO3.ModuleMenu.App.reloadFrames();
-        } else if (TYPO3.configuration.pageModule) {
-          TYPO3.ModuleMenu.App.showModule(TYPO3.configuration.pageModule);
-        }
-
-        // reload the module menu
-        TYPO3.ModuleMenu.App.refreshMenu();
-      }
-    });
-  };
-
-  /**
-   * changes the data in the module menu and the updates the backend context
-   *
-   * @param {String} id the workspace ID
-   * @param {String} title the workspace title
-   */
-  WorkspacesMenu.performWorkspaceSwitch = function(id, title) {
-    top.TYPO3.Backend.workspaceTitle = title;
-    top.TYPO3.configuration.inWorkspace = id !== 0;
-
-    WorkspacesMenu.updateBackendContext(title);
-
-    // first remove all checks, then set the check in front of the selected workspace
-    var stateActiveClass = 'fa fa-check';
-    var stateInactiveClass = 'fa fa-empty-empty';
-
-    // remove "selected" class and checkmark
-    $(WorkspacesMenu.options.activeMenuItemSelector + ' i', WorkspacesMenu.options.containerSelector).removeClass(stateActiveClass).addClass(stateInactiveClass);
-    $(WorkspacesMenu.options.activeMenuItemSelector, WorkspacesMenu.options.containerSelector).removeClass('selected');
-
-    // add "selected" class and checkmark
-    var $activeElement = $(WorkspacesMenu.options.menuItemSelector + '[data-workspaceid=' + id + ']', WorkspacesMenu.options.containerSelector);
-    $activeElement.parent().parent().find('i').removeClass(stateInactiveClass).addClass(stateActiveClass);
-    $activeElement.parent().parent().addClass('selected');
-  };
-
-  /**
-   * checks if the TYPO3 backend is within a backend context and adds a class
-   * also updates the workspaces title
-   *
-   * @param {String} title
-   */
-  WorkspacesMenu.updateBackendContext = function(title) {
-
-    if (TYPO3.configuration.inWorkspace) {
-      $('body').addClass(WorkspacesMenu.options.workspaceBodyClass);
-      WorkspacesMenu.updateTopBar(title || TYPO3.lang['Workspaces.workspaceTitle']);
-    } else {
-      $('body').removeClass(WorkspacesMenu.options.workspaceBodyClass);
-      WorkspacesMenu.updateTopBar();
-    }
-  };
-
-  /**
-   * adds the workspace title to the toolbar next to the username
-   *
-   * @param {String} workspaceTitle
-   */
-  WorkspacesMenu.updateTopBar = function(workspaceTitle) {
-    $('.' + WorkspacesMenu.options.workspacesTitleInToolbarClass, WorkspacesMenu.options.containerSelector).remove();
-
-    if (workspaceTitle && workspaceTitle.length) {
-      var title = $('<span>', {
-        'class': WorkspacesMenu.options.workspacesTitleInToolbarClass
-      }).text(workspaceTitle);
-      $(WorkspacesMenu.options.toolbarItemSelector, WorkspacesMenu.options.containerSelector).append(title);
-    }
-  };
-
-  Viewport.Topbar.Toolbar.registerEvent(function() {
-    WorkspacesMenu.initializeEvents();
-    WorkspacesMenu.updateBackendContext();
-  });
-
-  // expose the module in a global object
-  TYPO3.WorkspacesMenu = WorkspacesMenu;
-
-  return WorkspacesMenu;
-});
+define(["require","exports","jquery","TYPO3/CMS/Backend/ModuleMenu","TYPO3/CMS/Backend/Viewport"],function(e,t,o,a,r){"use strict";var n,s,c,i;(s=n||(n={})).containerSelector="#typo3-cms-workspaces-backend-toolbaritems-workspaceselectortoolbaritem",s.activeMenuItemLinkSelector=".dropdown-menu .selected",s.menuItemSelector=".t3js-workspace-item",s.menuItemLinkSelector=".t3js-workspaces-switchlink",s.toolbarItemSelector=".dropdown-toggle",s.workspaceModuleLinkSelector=".t3js-workspaces-modulelink",(i=c||(c={})).workspaceBodyClass="typo3-in-workspace",i.workspacesTitleInToolbarClass="toolbar-item-name";var p=new(function(){function e(){var t=this;r.Topbar.Toolbar.registerEvent(function(){t.initializeEvents(),e.updateBackendContext()})}return e.refreshPageTree=function(){r.NavigationContainer&&r.NavigationContainer.PageTree&&r.NavigationContainer.PageTree.refreshTree()},e.updateTopBar=function(e){if(o("."+c.workspacesTitleInToolbarClass,n.containerSelector).remove(),e&&e.length){var t=o("<span>",{class:c.workspacesTitleInToolbarClass}).text(e);o(n.toolbarItemSelector,n.containerSelector).append(t)}},e.updateBackendContext=function(t){void 0===t&&(t="");var a="";TYPO3.configuration.inWorkspace?(o("body").addClass(c.workspaceBodyClass),a=t||TYPO3.lang["Workspaces.workspaceTitle"]):o("body").removeClass(c.workspaceBodyClass),e.updateTopBar(a)},e.prototype.performWorkspaceSwitch=function(t,a){top.TYPO3.Backend.workspaceTitle=a,top.TYPO3.configuration.inWorkspace=0!==t,e.updateBackendContext(a);var r="fa fa-check",s="fa fa-empty-empty";o(n.activeMenuItemLinkSelector+" i",n.containerSelector).removeClass(r).addClass(s),o(n.activeMenuItemLinkSelector,n.containerSelector).removeClass("selected");var c=o(n.menuItemLinkSelector+"[data-workspaceid="+t+"]",n.containerSelector).closest(n.menuItemSelector);c.find("i").removeClass(s).addClass(r),c.addClass("selected")},e.prototype.initializeEvents=function(){var e=this;o(n.containerSelector).on("click",n.workspaceModuleLinkSelector,function(e){e.preventDefault(),a.App.showModule(e.currentTarget.dataset.module)}),o(n.containerSelector).on("click",n.menuItemLinkSelector,function(t){t.preventDefault(),e.switchWorkspace(parseInt(t.currentTarget.dataset.workspaceid,10))})},e.prototype.switchWorkspace=function(t){var n=this;o.ajax({url:TYPO3.settings.ajaxUrls.workspace_switch,type:"post",data:{workspaceId:t,pageId:top.fsMod.recentIds.web},success:function(t){if(t.workspaceId||(t.workspaceId=0),n.performWorkspaceSwitch(parseInt(t.workspaceId,10),t.title),t.pageId){top.fsMod.recentIds.web=t.pageId;var o=TYPO3.Backend.ContentContainer.getUrl();o+=(-1===o.indexOf("?")?"?":"&")+"&id="+t.pageId,e.refreshPageTree(),r.ContentContainer.setUrl(o)}else 0===top.currentModuleLoaded.indexOf("web_")?(e.refreshPageTree(),a.App.reloadFrames()):TYPO3.configuration.pageModule&&a.App.showModule(TYPO3.configuration.pageModule);a.App.refreshMenu()}})},e}());return TYPO3.WorkspacesMenu=p,p});
\ No newline at end of file
index e9c351a..a647bbc 100644 (file)
@@ -1,5 +1,4 @@
 /*
-/*
  * This file is part of the TYPO3 CMS project.
  *
  * It is free software; you can redistribute it and/or modify it under
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * RequireJS module for Workspaces
- */
-define([
-  'jquery',
-  'TYPO3/CMS/Backend/Severity',
-  'TYPO3/CMS/Backend/Modal'
-], function($, Severity, Modal) {
-  'use strict';
-
-  var Workspaces = {
-    tid: 0
-  };
-
-  /**
-   * Renders the send to stage window
-   * @param {Object} response
-   * @return {$}
-   */
-  Workspaces.renderSendToStageWindow = function(response) {
-    var result = response[0].result,
-      $form = $('<form />');
-
-    if (typeof result.sendMailTo !== 'undefined' && result.sendMailTo.length > 0) {
-      $form.append(
-        $('<label />', {class: 'control-label'}).text(TYPO3.lang['window.sendToNextStageWindow.itemsWillBeSentTo'])
-      );
-      $form.append(
-        $('<div />', {class: 'form-group'}).append(
-          $('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-selectall" />').text(TYPO3.lang['window.sendToNextStageWindow.selectAll']),
-          '&nbsp;',
-          $('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-deselectall" />').text(TYPO3.lang['window.sendToNextStageWindow.deselectAll'])
-        )
-      );
-
-      for (var i = 0; i < result.sendMailTo.length; ++i) {
-        var recipient = result.sendMailTo[i];
-
-        $form.append(
-          $('<div />', {class: 'checkbox'}).append(
-            $('<label />').text(recipient.label).prepend(
-              $('<input />', {
-                type: 'checkbox',
-                name: 'recipients',
-                class: 't3js-workspace-recipient',
-                id: recipient.name,
-                value: recipient.value
-              }).prop('checked', recipient.checked).prop('disabled', recipient.disabled)
-            )
-          )
-        );
-      }
-    }
-
-    if (typeof result.additional !== 'undefined') {
-      $form.append(
-        $('<div />', {class: 'form-group'}).append(
-          $('<label />', {
-            class: 'control-label',
-            'for': 'additional'
-          }).text(TYPO3.lang['window.sendToNextStageWindow.additionalRecipients']),
-          $('<textarea />', {
-            class: 'form-control',
-            name: 'additional',
-            id: 'additional'
-          }).text(result.additional.value),
-          $('<span />', {class: 'help-block'}).text(TYPO3.lang['window.sendToNextStageWindow.additionalRecipients.hint'])
-        )
-      );
-    }
-
-    $form.append(
-      $('<div />', {class: 'form-group'}).append(
-        $('<label />', {
-          class: 'control-label',
-          'for': 'comments'
-        }).text(TYPO3.lang['window.sendToNextStageWindow.comments']),
-        $('<textarea />', {
-          class: 'form-control',
-          name: 'comments',
-          id: 'comments'
-        }).text(result.comments.value)
-      )
-    );
-
-    var $modal = Modal.show(
-      TYPO3.lang['actionSendToStage'],
-      $form,
-      Severity.info,
-      [
-        {
-          text: TYPO3.lang['cancel'],
-          active: true,
-          btnClass: 'btn-default',
-          name: 'cancel',
-          trigger: function() {
-            $modal.modal('hide');
-          }
-        }, {
-        text: TYPO3.lang['ok'],
-        btnClass: 'btn-info',
-        name: 'ok'
-      }
-      ]
-    );
-
-    return $modal;
-  };
-
-  /**
-   * Checks the integrity of a record
-   *
-   * @param {Array} payload
-   * @return {$}
-   */
-  Workspaces.checkIntegrity = function(payload) {
-    return Workspaces.sendRemoteRequest(
-      Workspaces.generateRemotePayload('checkIntegrity', payload)
-    );
-  };
-
-  /**
-   * Sends an AJAX request
-   *
-   * @param {Object} payload
-   * @return {$}
-   */
-  Workspaces.sendRemoteRequest = function(payload) {
-    return $.ajax({
-      url: TYPO3.settings.ajaxUrls['workspace_dispatch'],
-      method: 'POST',
-      contentType: 'application/json; charset=utf-8',
-      dataType: 'json',
-      data: JSON.stringify(payload)
-    });
-  };
-
-  /**
-   * Generates the payload for a remote call
-   *
-   * @param {String} method
-   * @param {Object} data
-   * @return {{action, data, method, type}}
-   */
-  Workspaces.generateRemotePayload = function(method, data) {
-    if (typeof data === 'undefined') {
-      data = {};
-    }
-    return Workspaces.generateRemotePayloadBody('RemoteServer', method, data);
-  };
-
-  /**
-   * Generates the payload for MassActions
-   *
-   * @param {String} method
-   * @param {Object} data
-   * @return {{action, data, method, type}}
-   */
-  Workspaces.generateRemoteMassActionsPayload = function(method, data) {
-    if (typeof data === 'undefined') {
-      data = {};
-    }
-    return Workspaces.generateRemotePayloadBody('MassActions', method, data);
-  };
-
-  /**
-   * Generates the payload for Actions
-   *
-   * @param {String} method
-   * @param {Object} data
-   * @return {{action, data, method, type}}
-   */
-  Workspaces.generateRemoteActionsPayload = function(method, data) {
-    if (typeof data === 'undefined') {
-      data = [];
-    }
-    return Workspaces.generateRemotePayloadBody('Actions', method, data);
-  };
-
-  /**
-   * Generates the payload body
-   *
-   * @param {String} action
-   * @param {String} method
-   * @param {Object} data
-   * @return {{action: String, data: Object, method: String, type: string}}
-   */
-  Workspaces.generateRemotePayloadBody = function(action, method, data) {
-    if (data instanceof Array) {
-      data.push(TYPO3.settings.Workspaces.token);
-    } else {
-      data = [
-        data,
-        TYPO3.settings.Workspaces.token
-      ];
-    }
-    return {
-      action: action,
-      data: data,
-      method: method,
-      type: 'rpc',
-      tid: Workspaces.tid++
-    };
-  };
-
-  /**
-   * Serialize a form to a JavaScript object
-   *
-   * @see http://stackoverflow.com/a/1186309/4828813
-   * @return {Object}
-   */
-  $.fn.serializeObject = function() {
-    var o = {};
-    var a = this.serializeArray();
-    $.each(a, function() {
-      if (typeof o[this.name] !== 'undefined') {
-        if (!o[this.name].push) {
-          o[this.name] = [o[this.name]];
-        }
-        o[this.name].push(this.value || '');
-      } else {
-        o[this.name] = this.value || '';
-      }
-    });
-    return o;
-  };
-
-  return Workspaces;
-});
+define(["require","exports","TYPO3/CMS/Backend/Enum/Severity","jquery","TYPO3/CMS/Backend/Modal"],function(e,t,n,a,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(){this.tid=0}return e.prototype.renderSendToStageWindow=function(e){var t=e[0].result,i=a("<form />");if(void 0!==t.sendMailTo&&t.sendMailTo.length>0){i.append(a("<label />",{class:"control-label"}).text(TYPO3.lang["window.sendToNextStageWindow.itemsWillBeSentTo"])),i.append(a("<div />",{class:"form-group"}).append(a('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-selectall" />').text(TYPO3.lang["window.sendToNextStageWindow.selectAll"]),"&nbsp;",a('<a href="#" class="btn btn-default btn-xs t3js-workspace-recipients-deselectall" />').text(TYPO3.lang["window.sendToNextStageWindow.deselectAll"])));for(var s=0;s<t.sendMailTo.length;++s){var d=t.sendMailTo[s];i.append(a("<div />",{class:"checkbox"}).append(a("<label />").text(d.label).prepend(a("<input />",{type:"checkbox",name:"recipients",class:"t3js-workspace-recipient",id:d.name,value:d.value}).prop("checked",d.checked).prop("disabled",d.disabled))))}}void 0!==t.additional&&i.append(a("<div />",{class:"form-group"}).append(a("<label />",{class:"control-label",for:"additional"}).text(TYPO3.lang["window.sendToNextStageWindow.additionalRecipients"]),a("<textarea />",{class:"form-control",name:"additional",id:"additional"}).text(t.additional.value),a("<span />",{class:"help-block"}).text(TYPO3.lang["window.sendToNextStageWindow.additionalRecipients.hint"]))),i.append(a("<div />",{class:"form-group"}).append(a("<label />",{class:"control-label",for:"comments"}).text(TYPO3.lang["window.sendToNextStageWindow.comments"]),a("<textarea />",{class:"form-control",name:"comments",id:"comments"}).text(t.comments.value)));var l=o.show(TYPO3.lang.actionSendToStage,i,n.SeverityEnum.info,[{text:TYPO3.lang.cancel,active:!0,btnClass:"btn-default",name:"cancel",trigger:function(){l.modal("hide")}},{text:TYPO3.lang.ok,btnClass:"btn-info",name:"ok"}]);return l},e.prototype.checkIntegrity=function(e){return this.sendRemoteRequest(this.generateRemotePayload("checkIntegrity",e))},e.prototype.sendRemoteRequest=function(e){return a.ajax({url:TYPO3.settings.ajaxUrls.workspace_dispatch,method:"POST",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify(e)})},e.prototype.generateRemotePayload=function(e,t){return void 0===t&&(t={}),this.generateRemotePayloadBody("RemoteServer",e,t)},e.prototype.generateRemoteMassActionsPayload=function(e,t){return void 0===t&&(t={}),this.generateRemotePayloadBody("MassActions",e,t)},e.prototype.generateRemoteActionsPayload=function(e,t){return void 0===t&&(t={}),this.generateRemotePayloadBody("Actions",e,t)},e.prototype.generateRemotePayloadBody=function(e,t,n){return n instanceof Array?n.push(TYPO3.settings.Workspaces.token):n=[n,TYPO3.settings.Workspaces.token],{action:e,data:n,method:t,type:"rpc",tid:this.tid++}},e}();t.default=i});
\ No newline at end of file