[TASK] The Number of The Workspace 73/46573/44
authorAndreas Fernandez <a.fernandez@scripting-base.de>
Sun, 6 Mar 2016 12:18:46 +0000 (13:18 +0100)
committerSusanne Moog <typo3@susannemoog.de>
Sun, 17 Apr 2016 09:08:45 +0000 (11:08 +0200)
This change ports EXT:workspace to Bootstrap and jQuery.

The workspace interface is simplified now, similar actions are tied together:
- The record history is now part of ``getRowDetails()`` to get rid of the extra
  button and popup.
- The "Send to stage" buttons are now also in the record information modal
  as separate buttons.

The JavaScript has some wrapper methods to simplify the remaining
ExtDirect calls.

ExtDirectServer::getDifferenceHandler() now instantiates the DiffUtility and
does not use the internal diff library directly anymore.

Resolves: #74359
Releases: master
Change-Id: Id706ae8a886f05aafeb402cdc2352068f1021dbe
Reviewed-on: https://review.typo3.org/46573
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Susanne Moog <typo3@susannemoog.de>
Tested-by: Susanne Moog <typo3@susannemoog.de>
65 files changed:
Build/Gruntfile.js
Build/Resources/Public/Less/TYPO3/_module_workspaces.less
typo3/sysext/t3skin/Resources/Public/Css/backend.css
typo3/sysext/workspaces/Classes/Controller/AbstractController.php
typo3/sysext/workspaces/Classes/Controller/PreviewController.php
typo3/sysext/workspaces/Classes/Controller/ReviewController.php
typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php
typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php
typo3/sysext/workspaces/Classes/Service/GridDataService.php
typo3/sysext/workspaces/Classes/Service/HistoryService.php
typo3/sysext/workspaces/Classes/Service/StagesService.php
typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf
typo3/sysext/workspaces/Resources/Private/Layouts/Empty.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/Module.html
typo3/sysext/workspaces/Resources/Private/Layouts/Nodoc.html
typo3/sysext/workspaces/Resources/Private/Layouts/Popup.html
typo3/sysext/workspaces/Resources/Private/Less/preview.less [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/Legend.html
typo3/sysext/workspaces/Resources/Private/Partials/Navigation.html
typo3/sysext/workspaces/Resources/Private/Partials/Preview/StageButtons.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/WorkingTable.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Ajax/StageButtons.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html
typo3/sysext/workspaces/Resources/Private/Templates/Preview/NewPage.html
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Preview.html
typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html
typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html
typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html
typo3/sysext/workspaces/Resources/Public/Css/module.css [deleted file]
typo3/sysext/workspaces/Resources/Public/Css/preview.css
typo3/sysext/workspaces/Resources/Public/JavaScript/Backend.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowDetailTemplate.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowExpander.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/Component/TabPanel.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/Preview.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/Workspaces.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/component.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js [deleted file]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js [deleted file]
typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php

index 7ee9654..1dcf43a 100644 (file)
@@ -39,6 +39,7 @@ module.exports = function(grunt) {
                        install   : '<%= paths.sysext %>install/Resources/',
                        linkvalidator : '<%= paths.sysext %>linkvalidator/Resources/',
                        backend   : '<%= paths.sysext %>backend/Resources/',
+                       workspaces: '<%= paths.sysext %>workspaces/Resources/',
                        core      : '<%= paths.sysext %>core/Resources/',
                        flags     : 'bower_components/region-flags/svg/',
                        t3icons   : 'bower_components/wmdbsystems-typo3-icons/dist/'
@@ -77,6 +78,11 @@ module.exports = function(grunt) {
                                files: {
                                        "<%= paths.linkvalidator %>Public/Css/linkvalidator.css": "<%= paths.less %>linkvalidator.less"
                                }
+                       },
+                       workspaces: {
+                               files: {
+                                       "<%= paths.workspaces %>Public/Css/preview.css": "<%= paths.workspaces %>Private/Less/preview.less"
+                               }
                        }
                },
                postcss: {
@@ -109,6 +115,9 @@ module.exports = function(grunt) {
                        },
                        linkvalidator: {
                                src: '<%= paths.linkvalidator %>Public/Css/*.css'
+                       },
+                       workspaces: {
+                               src: '<%= paths.workspaces %>Public/Css/*.css'
                        }
                },
                watch: {
index b79e023..162ae0a 100644 (file)
-ul.x-tab-strip.x-tab-strip-top {
-       margin-bottom: 0;
-       margin-top: 0;
-       padding-left: 0;
-}
-ul.x-tab-strip.x-tab-strip-top li.last {
-       float: right;
-}
-span.item-state-modified {
-       color: #f78f25;
-}
-span.item-state-moved {
-       color: #457fb8;
-}
-span.item-state-new {
-       color: #3c9934;
-}
-span.item-state-hidden {
-       color: #abaaaa;
-}
-span.item-state-deleted {
-       color: #000000;
-       text-decoration: line-through;
-}
-.legend {
-       margin: 5px;
-       height: 18px;
-       color: #888888;
-}
-.legend dd, .legend dt {
-       display: inline;
-       overflow: hidden;
-}
-.legend dd span {
-       display: inline-block;
-       padding: 4px 4px;
-}
-.x-toolbar {
-       background:none !important;
-}
-.x-grid3 {
-       background: none !important;
-}
-.x-grid3-row-expanded {
-       background-color: #ececec;
-}
-.x-grid3-row-selected {
-       color: #4D4D4D;
-}
-#typo3-mod-php div.typo3-noDoc {
-       margin: 0px 0px;
-}
-#typo3-mod-php div.typo3-noDoc #typo3-docbody {
-       padding: 0px 0px;
-       top: 0px;
-}
-.icon-hidden {
-       display: none;
-       visibility: hidden;
-}
-div.x-grid3-row img.x-action-col-icon {
-       display:none;
-}
+#workspace-panel {
+       tr.collapsing {
+               transition: none;
+       }
+       tr.collapse {
+               display: none;
 
-div.x-grid3-row-over img.x-action-col-icon,
-div.x-grid3-row img.x-action-col-icon.t3-visible {
-       display:inline-block;
+               &.in {
+                       display: table-row;
+               }
+       }
 }
 
-div.t3-workspaces-foldoutWrapper {
-       padding: 10px;
-       margin-left: 40px;
-       background-color: #FFFFFF;
-       background-image: linear-gradient(center top, #ececec 0px, #f7f7f7 200px);
-       background-repeat: repeat-x;
-       color: #606060;
-}
-.x-grid3-row-selected div.t3-workspaces-foldoutWrapper {
-       background-image: linear-gradient(center top, #dedede 0px, #f7f7f7 200px);
-       background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper table {
-       border-collapse: collapse;
-       width: 100%;
-}
-div.t3-workspaces-foldoutWrapper tr.header {
-       background-color: #B5B5B5;
-       background-image: linear-gradient(center top, #7f7f7f 10%, #5b5b5b 100%);
-       background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper tr.header th {
-       padding: 4px 8px;
-       color: #FFFFFF;
-       line-height: 15px;
-}
+span.item-state- {
+       &modified {
+               color: #f78f25;
+       }
 
-.t3-workspaces-foldout-subheaderLeft,
-.t3-workspaces-foldout-subheaderRight {
-       padding: 15px 0 10px 0;
-       margin: 10px;
-}
-.t3-workspaces-foldout-td-contentDiffLeft .t3-workspaces-foldout-contentDiff-container,
-.t3-workspaces-foldout-td-contentDiffRight .t3-workspaces-foldout-contentDiff-container {
-       padding-top: 10px;
-       border-top: 1px solid #cdcdcd;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffLeft,
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft {
-       padding-right: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffRight,
-.x-grid3-row .t3-workspaces-foldout-subheaderRight {
-       padding-left: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft .t3-workspaces-foldout-subheader-container {
-       padding-bottom: 10px;
-       border-bottom: 1px solid #cdcdcd;
-}
-table.t3-workspaces-foldout-contentDiff  {
-       padding: 8px 8px;
-       table-layout: auto;
-       background-color: #ffffff;
-}
-table.t3-workspaces-foldout-contentDiff th {
-       padding: 8px 0 8px 8px;
-       width: 80px;
-}
-table.t3-workspaces-foldout-contentDiff td {
-       padding: 8px 8px 8px 0;
-       line-height: 1.3em;
-}
-table.t3-workspaces-foldout-contentDiff .diff-r {
-       text-decoration: line-through;
-}
-.t3-workspaces-foldout-contentDiff .content ins > img {
-       padding: 1px;
-       margin-right: 2px;
-       border: 2px solid green;
-}
-.t3-workspaces-foldout-contentDiff .content del > img {
-       padding: 1px;
-       margin-right: 2px;
-       border: 2px solid red;
-}
-div.t3-workspaces-foldoutWrapper td.char_select_profile_stats {
-       padding-right: 10px;
-}
-div.t3-workspaces-comments {
-       background-color: #dedede;
-       padding: 10px 10px 10px 10px;
-}
-div.t3-workspaces-comments-singleComment {
-       overflow: hidden;
-       margin-bottom: 10px;
-       position: relative;
-}
-div.t3-workspaces-comments-singleComment:last-child {
-       margin: 0;
-}
-div .t3-workspaces-comments-singleComment-author {
-       width: 60px;
-       margin: 10px 0;
-       position: absolute;
-       left: 0px;
-       top: 0px;
-       font-weight: bold;
-       overflow: hidden;
-}
-div .t3-workspaces-comments-singleComment-content-wrapper {
-       background: url(../Images/workspaces-comments-arrow.gif) left 10px no-repeat;
-       margin-left: 70px;
-}
-div .t3-workspaces-comments-singleComment-content-date {
-       font-size: 10px;
-}
-div .t3-workspaces-comments-singleComment-content {
-       background-color: #ffffff;
-       padding: 10px 10px;
-       margin-left: 10px;
-}
-div .t3-workspaces-comments-singleComment-content-title {
-       padding: 8px 0 8px 0;
-       font-weight: bold;
-}
-
-.typo3-workspaces-row-disabled .x-grid3-td-checker {
-       visibility: hidden;
-}
-
-div.x-grid3-row img.t3-icon-extensions-workspaces {
-       display: inline-block !important;
-       width: 17px;
-       visibility: hidden;
-}
-
-div.x-grid3-row-over img.t3-icon-extensions-workspaces {
-       visibility: visible;
-}
-
-img.t3-icon-workspaces-sendtonextstage {
-       background: url(../Images/version-workspace-sendtonextstage.png) no-repeat;
-}
+       &moved {
+               color: #457fb8;
+       }
 
-img.t3-icon-workspaces-sendtoprevstage {
-       background: url(../Images/version-workspace-sendtoprevstage.png) no-repeat;
-}
+       &new {
+               color: #3c9934;
+       }
 
-table.t3-workspaces-foldout-contentDiff td.content {
-       word-break: break-all;
-}
+       &hidden {
+               color: #abaaaa;
+       }
 
-.typo3-workspaces-collection-level-node,
-.typo3-workspaces-collection-level-leaf,
-.typo3-workspaces-collection-level-none {
-       float: left;
-       width: 18px;
+       &deleted {
+               color: #000000;
+               text-decoration: line-through;
+       }
 }
 
-.x-grid3-row-expander {
-       background-position: left top;
-       float: left;
-}
-.x-grid3-row-collapsed .x-grid3-row-expander {
-       background-position: left top;
-       background-image: url('../Images/zoom_in.png');
-}
-.x-grid3-row-expanded .x-grid3-row-expander {
-       background-position: left top;
-       background-image: url('../Images/zoom_out.png');
-}
-
-.typo3-workspaces-collection-child-collapsed {
-       display: none;
-}
-
-.typo3-workspaces-collection-child-expanded {
-       display: block;
-}
-
-.typo3-workspaces-collection-level-node {
-       width: 18px;
+.legend {
+       margin: 5px;
        height: 18px;
-       background-position: 4px 2px;
-       background-color: transparent;
-       background-repeat: no-repeat;
-       background-image: url('../../../../t3skin/extjs/images/grid/row-expand-sprite.png');
-}
-.typo3-workspaces-collection-parent-collapsed .typo3-workspaces-collection-level-node {
-       background-position: 4px 2px;
-}
-.typo3-workspaces-collection-parent-expanded .typo3-workspaces-collection-level-node {
-       background-position: -21px 2px;
-}
-
-.x-menu.typo3-workspaces-menu {
-       background-image: none;
-}
-.x-menu.typo3-workspaces-menu a.x-menu-item {
-       padding: 3px 14px;
-}
-
-.x-tab-menu-right {
-       background: #dadada;
-       border-color: #adadad;
-       border-style: solid;
-       border-width: 1px;
-       border-top-left-radius: 3px;
-       border-top-right-radius: 3px;
-       color: #666666;
-       background-position: center 6px;
-       padding: 4px;
-
-       background-image: url('../Images/menu.png');
-       background-repeat: no-repeat;
-       width: 16px;
-       position: absolute;
-       right: 0;
-       top: 0;
-       z-index: 10;
-       cursor:pointer;
-}
-
-.t3-workspaces-foldoutWrapper .char_select_profile_titleLeft .icon,
-.t3-workspaces-foldoutWrapper .char_select_profile_titleRight .icon{
-       float: left;
-}
+       color: #888888;
 
-.t3-workspaces-foldoutWrapper .t3-workspaces-foldout-contentDiff {
-       text-align: left;
+       dd,
+       dt {
+               display: inline;
+               overflow: hidden;
+       }
+
+       dd {
+               span {
+                       display: inline-block;
+                       padding: 4px 4px;
+               }
+       }
 }
index 539d833..bd5af74 100644 (file)
@@ -12621,13 +12621,14 @@ iframe {
   background-position: -80px -224px;
   border-width: 0;
 }
-ul.x-tab-strip.x-tab-strip-top {
-  margin-bottom: 0;
-  margin-top: 0;
-  padding-left: 0;
+#workspace-panel tr.collapsing {
+  transition: none;
 }
-ul.x-tab-strip.x-tab-strip-top li.last {
-  float: right;
+#workspace-panel tr.collapse {
+  display: none;
+}
+#workspace-panel tr.collapse.in {
+  display: table-row;
 }
 span.item-state-modified {
   color: #f78f25;
@@ -12659,240 +12660,6 @@ span.item-state-deleted {
   display: inline-block;
   padding: 4px 4px;
 }
-.x-toolbar {
-  background: none !important;
-}
-.x-grid3 {
-  background: none !important;
-}
-.x-grid3-row-expanded {
-  background-color: #ececec;
-}
-.x-grid3-row-selected {
-  color: #4D4D4D;
-}
-#typo3-mod-php div.typo3-noDoc {
-  margin: 0px 0px;
-}
-#typo3-mod-php div.typo3-noDoc #typo3-docbody {
-  padding: 0px 0px;
-  top: 0px;
-}
-.icon-hidden {
-  display: none;
-  visibility: hidden;
-}
-div.x-grid3-row img.x-action-col-icon {
-  display: none;
-}
-div.x-grid3-row-over img.x-action-col-icon,
-div.x-grid3-row img.x-action-col-icon.t3-visible {
-  display: inline-block;
-}
-div.t3-workspaces-foldoutWrapper {
-  padding: 10px;
-  margin-left: 40px;
-  background-color: #FFFFFF;
-  background-image: linear-gradient(center top, #ececec 0px, #f7f7f7 200px);
-  background-repeat: repeat-x;
-  color: #606060;
-}
-.x-grid3-row-selected div.t3-workspaces-foldoutWrapper {
-  background-image: linear-gradient(center top, #dedede 0px, #f7f7f7 200px);
-  background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper table {
-  border-collapse: collapse;
-  width: 100%;
-}
-div.t3-workspaces-foldoutWrapper tr.header {
-  background-color: #B5B5B5;
-  background-image: linear-gradient(center top, #7f7f7f 10%, #5b5b5b 100%);
-  background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper tr.header th {
-  padding: 4px 8px;
-  color: #FFFFFF;
-  line-height: 15px;
-}
-.t3-workspaces-foldout-subheaderLeft,
-.t3-workspaces-foldout-subheaderRight {
-  padding: 15px 0 10px 0;
-  margin: 10px;
-}
-.t3-workspaces-foldout-td-contentDiffLeft .t3-workspaces-foldout-contentDiff-container,
-.t3-workspaces-foldout-td-contentDiffRight .t3-workspaces-foldout-contentDiff-container {
-  padding-top: 10px;
-  border-top: 1px solid #cdcdcd;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffLeft,
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft {
-  padding-right: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffRight,
-.x-grid3-row .t3-workspaces-foldout-subheaderRight {
-  padding-left: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft .t3-workspaces-foldout-subheader-container {
-  padding-bottom: 10px;
-  border-bottom: 1px solid #cdcdcd;
-}
-table.t3-workspaces-foldout-contentDiff {
-  padding: 8px 8px;
-  table-layout: auto;
-  background-color: #ffffff;
-}
-table.t3-workspaces-foldout-contentDiff th {
-  padding: 8px 0 8px 8px;
-  width: 80px;
-}
-table.t3-workspaces-foldout-contentDiff td {
-  padding: 8px 8px 8px 0;
-  line-height: 1.3em;
-}
-table.t3-workspaces-foldout-contentDiff .diff-r {
-  text-decoration: line-through;
-}
-.t3-workspaces-foldout-contentDiff .content ins > img {
-  padding: 1px;
-  margin-right: 2px;
-  border: 2px solid green;
-}
-.t3-workspaces-foldout-contentDiff .content del > img {
-  padding: 1px;
-  margin-right: 2px;
-  border: 2px solid red;
-}
-div.t3-workspaces-foldoutWrapper td.char_select_profile_stats {
-  padding-right: 10px;
-}
-div.t3-workspaces-comments {
-  background-color: #dedede;
-  padding: 10px 10px 10px 10px;
-}
-div.t3-workspaces-comments-singleComment {
-  overflow: hidden;
-  margin-bottom: 10px;
-  position: relative;
-}
-div.t3-workspaces-comments-singleComment:last-child {
-  margin: 0;
-}
-div .t3-workspaces-comments-singleComment-author {
-  width: 60px;
-  margin: 10px 0;
-  position: absolute;
-  left: 0px;
-  top: 0px;
-  font-weight: bold;
-  overflow: hidden;
-}
-div .t3-workspaces-comments-singleComment-content-wrapper {
-  background: url(../Images/workspaces-comments-arrow.gif) left 10px no-repeat;
-  margin-left: 70px;
-}
-div .t3-workspaces-comments-singleComment-content-date {
-  font-size: 10px;
-}
-div .t3-workspaces-comments-singleComment-content {
-  background-color: #ffffff;
-  padding: 10px 10px;
-  margin-left: 10px;
-}
-div .t3-workspaces-comments-singleComment-content-title {
-  padding: 8px 0 8px 0;
-  font-weight: bold;
-}
-.typo3-workspaces-row-disabled .x-grid3-td-checker {
-  visibility: hidden;
-}
-div.x-grid3-row img.t3-icon-extensions-workspaces {
-  display: inline-block !important;
-  width: 17px;
-  visibility: hidden;
-}
-div.x-grid3-row-over img.t3-icon-extensions-workspaces {
-  visibility: visible;
-}
-img.t3-icon-workspaces-sendtonextstage {
-  background: url(../Images/version-workspace-sendtonextstage.png) no-repeat;
-}
-img.t3-icon-workspaces-sendtoprevstage {
-  background: url(../Images/version-workspace-sendtoprevstage.png) no-repeat;
-}
-table.t3-workspaces-foldout-contentDiff td.content {
-  word-break: break-all;
-}
-.typo3-workspaces-collection-level-node,
-.typo3-workspaces-collection-level-leaf,
-.typo3-workspaces-collection-level-none {
-  float: left;
-  width: 18px;
-}
-.x-grid3-row-expander {
-  background-position: left top;
-  float: left;
-}
-.x-grid3-row-collapsed .x-grid3-row-expander {
-  background-position: left top;
-  background-image: url('../Images/zoom_in.png');
-}
-.x-grid3-row-expanded .x-grid3-row-expander {
-  background-position: left top;
-  background-image: url('../Images/zoom_out.png');
-}
-.typo3-workspaces-collection-child-collapsed {
-  display: none;
-}
-.typo3-workspaces-collection-child-expanded {
-  display: block;
-}
-.typo3-workspaces-collection-level-node {
-  width: 18px;
-  height: 18px;
-  background-position: 4px 2px;
-  background-color: transparent;
-  background-repeat: no-repeat;
-  background-image: url('../../../../t3skin/extjs/images/grid/row-expand-sprite.png');
-}
-.typo3-workspaces-collection-parent-collapsed .typo3-workspaces-collection-level-node {
-  background-position: 4px 2px;
-}
-.typo3-workspaces-collection-parent-expanded .typo3-workspaces-collection-level-node {
-  background-position: -21px 2px;
-}
-.x-menu.typo3-workspaces-menu {
-  background-image: none;
-}
-.x-menu.typo3-workspaces-menu a.x-menu-item {
-  padding: 3px 14px;
-}
-.x-tab-menu-right {
-  background: #dadada;
-  border-color: #adadad;
-  border-style: solid;
-  border-width: 1px;
-  border-top-left-radius: 3px;
-  border-top-right-radius: 3px;
-  color: #666666;
-  background-position: center 6px;
-  padding: 4px;
-  background-image: url('../Images/menu.png');
-  background-repeat: no-repeat;
-  width: 16px;
-  position: absolute;
-  right: 0;
-  top: 0;
-  z-index: 10;
-  cursor: pointer;
-}
-.t3-workspaces-foldoutWrapper .char_select_profile_titleLeft .icon,
-.t3-workspaces-foldoutWrapper .char_select_profile_titleRight .icon {
-  float: left;
-}
-.t3-workspaces-foldoutWrapper .t3-workspaces-foldout-contentDiff {
-  text-align: left;
-}
 [id="typo3-topbar"],
 [id="typo3-topbar"] .x-panel-body {
   min-width: 1000px;
index 6235cee..ad17322 100644 (file)
@@ -19,12 +19,14 @@ use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+use TYPO3\CMS\Workspaces\Service\AdditionalColumnService;
+use TYPO3\CMS\Workspaces\Service\AdditionalResourceService;
 
 /**
  * Abstract action controller.
  */
-class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
+class AbstractController extends ActionController
 {
     /**
      * @var string
@@ -62,6 +64,7 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
         // @todo Evaluate how the intval() call can be used with Extbase validators/filters
         $this->pageId = (int)GeneralUtility::_GP('id');
         $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $lang = $this->getLanguageService();
         $icons = array(
             'language' => $iconFactory->getIcon('flags-multiple', Icon::SIZE_SMALL)->render(),
             'integrity' => $iconFactory->getIcon('status-dialog-information', Icon::SIZE_SMALL)->render(),
@@ -74,18 +77,18 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
         $this->pageRenderer->addInlineSetting('Workspaces', 'id', $this->pageId);
         $this->pageRenderer->addInlineSetting('Workspaces', 'depth', $this->pageId === 0 ? 999 : 1);
         $this->pageRenderer->addInlineSetting('Workspaces', 'language', $this->getLanguageSelection());
-        $this->pageRenderer->addInlineLanguageLabelArray(array(
-            'title' => $GLOBALS['LANG']->getLL('title'),
-            'path' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.path'),
-            'table' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.table'),
-            'depth' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_perm.xlf:Depth'),
-            'depth_0' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_0'),
-            'depth_1' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_1'),
-            'depth_2' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_2'),
-            'depth_3' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_3'),
-            'depth_4' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_4'),
-            'depth_infi' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_infi')
-        ));
+        $this->pageRenderer->addInlineLanguageLabelArray([
+            'title' => $lang->getLL('title'),
+            'path' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.path'),
+            'table' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.table'),
+            'depth' => $lang->sL('LLL:EXT:lang/locallang_mod_web_perm.xlf:Depth'),
+            'depth_0' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_0'),
+            'depth_1' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_1'),
+            'depth_2' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_2'),
+            'depth_3' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_3'),
+            'depth_4' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_4'),
+            'depth_infi' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_infi')
+        ]);
         $this->pageRenderer->addInlineLanguageLabelFile('EXT:workspaces/Resources/Private/Language/locallang.xlf');
         $this->assignExtensionSettings();
     }
@@ -125,19 +128,19 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
     }
 
     /**
-     * @return \TYPO3\CMS\Workspaces\Service\AdditionalColumnService
+     * @return AdditionalColumnService
      */
     protected function getAdditionalColumnService()
     {
-        return $this->objectManager->get(\TYPO3\CMS\Workspaces\Service\AdditionalColumnService::class);
+        return $this->objectManager->get(AdditionalColumnService::class);
     }
 
     /**
-     * @return \TYPO3\CMS\Workspaces\Service\AdditionalResourceService
+     * @return AdditionalResourceService
      */
     protected function getAdditionalResourceService()
     {
-        return $this->objectManager->get(\TYPO3\CMS\Workspaces\Service\AdditionalResourceService::class);
+        return $this->objectManager->get(AdditionalResourceService::class);
     }
 
     /**
@@ -149,6 +152,14 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
     }
 
     /**
+     * @return \TYPO3\CMS\Lang\LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
      * @return PageRenderer
      */
     protected function getPageRenderer()
index 41e6685..5885fa5 100644 (file)
@@ -16,9 +16,16 @@ namespace TYPO3\CMS\Workspaces\Controller;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
+use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
+use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
+use TYPO3\CMS\Workspaces\Service\StagesService;
+use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
 /**
  * Implements the preview controller of the workspace module.
@@ -26,12 +33,12 @@ use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
 class PreviewController extends AbstractController
 {
     /**
-     * @var \TYPO3\CMS\Workspaces\Service\StagesService
+     * @var StagesService
      */
     protected $stageService;
 
     /**
-     * @var \TYPO3\CMS\Workspaces\Service\WorkspaceService
+     * @var WorkspaceService
      */
     protected $workspaceService;
 
@@ -59,32 +66,17 @@ class PreviewController extends AbstractController
     {
         parent::initializeAction();
         $backendRelPath = ExtensionManagementUtility::extRelPath('backend');
-        $workspacesRelPath = ExtensionManagementUtility::extRelPath('workspaces');
-        $this->stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
-        $this->workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        $this->stageService = GeneralUtility::makeInstance(StagesService::class);
+        $this->workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/ExtDirect.StateProvider.js');
-        $resourcePath = $workspacesRelPath . 'Resources/Public/Css/preview.css';
-        $GLOBALS['TBE_STYLES']['extJS']['theme'] = $resourcePath;
-        $this->pageRenderer->loadExtJS();
+        $this->pageRenderer->loadExtJS(false, false);
         // Load  JavaScript:
         $this->pageRenderer->addExtDirectCode(array(
             'TYPO3.Workspaces',
             'TYPO3.ExtDirectStateProvider'
         ));
-        $states = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['States'];
+        $states = $this->getBackendUser()->uc['moduleData']['Workspaces']['States'];
         $this->pageRenderer->addInlineSetting('Workspaces', 'States', $states);
-        $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/notifications.js');
-        $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/iframepanel.js');
-        $resourcePathJavaScript = $workspacesRelPath . 'Resources/Public/JavaScript/';
-        $jsFiles = array(
-            'Ext.ux.plugins.TabStripContainer.js',
-            'Store/mainstore.js',
-            'helpers.js',
-            'actions.js'
-        );
-        foreach ($jsFiles as $jsFile) {
-            $this->pageRenderer->addJsFile($resourcePathJavaScript . $jsFile);
-        }
         $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
         $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
         // @todo this part should be done with inlineLocallanglabels
@@ -101,81 +93,103 @@ class PreviewController extends AbstractController
      */
     public function indexAction($previewWS = null)
     {
+        $backendUser = $this->getBackendUser();
+
         // Get all the GET parameters to pass them on to the frames
         $queryParameters = GeneralUtility::_GET();
-            // Remove the GET parameters related to the workspaces module and the page id
+
+        // Remove the GET parameters related to the workspaces module and the page id
         unset($queryParameters['tx_workspaces_web_workspacesworkspaces']);
         unset($queryParameters['M']);
         unset($queryParameters['id']);
-            // Assemble a query string from the retrieved parameters
+
+        // Assemble a query string from the retrieved parameters
         $queryString = GeneralUtility::implodeArrayForUrl('', $queryParameters);
 
         // fetch the next and previous stage
         $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $this->pageId, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
         list(, $nextStage) = $this->stageService->getNextStageForElementCollection($workspaceItemsArray);
         list(, $previousStage) = $this->stageService->getPreviousStageForElementCollection($workspaceItemsArray);
-        /** @var $wsService \TYPO3\CMS\Workspaces\Service\WorkspaceService */
-        $wsService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        /** @var $wsService WorkspaceService */
+        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
         $wsList = $wsService->getAvailableWorkspaces();
-        $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+        $activeWorkspace = $backendUser->workspace;
         if (!is_null($previewWS)) {
             if (in_array($previewWS, array_keys($wsList)) && $activeWorkspace != $previewWS) {
                 $activeWorkspace = $previewWS;
-                $GLOBALS['BE_USER']->setWorkspace($activeWorkspace);
+                $backendUser->setWorkspace($activeWorkspace);
                 BackendUtility::setUpdateSignal('updatePageTree');
             }
         }
-        /** @var $uriBuilder \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder */
-        $uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
+        /** @var $uriBuilder UriBuilder */
+        $uriBuilder = $this->objectManager->get(UriBuilder::class);
         $wsSettingsPath = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
-        $wsSettingsUri = $uriBuilder->uriFor('singleIndex', array(), \TYPO3\CMS\Workspaces\Controller\ReviewController::class, 'workspaces', 'web_workspacesworkspaces');
+        $wsSettingsUri = $uriBuilder->uriFor('singleIndex', array(), ReviewController::class, 'workspaces', 'web_workspacesworkspaces');
         $wsSettingsParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Review';
         $wsSettingsUrl = $wsSettingsPath . $wsSettingsUri . $wsSettingsParams;
         $viewDomain = BackendUtility::getViewDomain($this->pageId);
         $wsBaseUrl = $viewDomain . '/index.php?id=' . $this->pageId . $queryString;
         // @todo - handle new pages here
         // branchpoints are not handled anymore because this feature is not supposed anymore
-        if (\TYPO3\CMS\Workspaces\Service\WorkspaceService::isNewPage($this->pageId)) {
-            $wsNewPageUri = $uriBuilder->uriFor('newPage', array(), \TYPO3\CMS\Workspaces\Controller\PreviewController::class, 'workspaces', 'web_workspacesworkspaces');
+        if (WorkspaceService::isNewPage($this->pageId)) {
+            $wsNewPageUri = $uriBuilder->uriFor('newPage', array(), PreviewController::class, 'workspaces', 'web_workspacesworkspaces');
             $wsNewPageParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Preview';
-            $this->view->assign('liveUrl', $wsSettingsPath . $wsNewPageUri . $wsNewPageParams . '&ADMCMD_prev=IGNORE');
+            $liveUrl = $wsSettingsPath . $wsNewPageUri . $wsNewPageParams . '&ADMCMD_prev=IGNORE';
         } else {
-            $this->view->assign('liveUrl', $wsBaseUrl . '&ADMCMD_noBeUser=1&ADMCMD_prev=IGNORE');
+            $liveUrl = $wsBaseUrl . '&ADMCMD_noBeUser=1&ADMCMD_prev=IGNORE';
         }
-        $this->view->assign('wsUrl', $wsBaseUrl . '&ADMCMD_prev=IGNORE&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=' . $GLOBALS['BE_USER']->workspace);
-        $this->view->assign('wsSettingsUrl', $wsSettingsUrl);
-        $this->view->assign('backendDomain', GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
+        $wsUrl = $wsBaseUrl . '&ADMCMD_prev=IGNORE&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=' . $backendUser->workspace;
+        $backendDomain = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
         $splitPreviewTsConfig = BackendUtility::getModTSconfig($this->pageId, 'workspaces.splitPreviewModes');
         $splitPreviewModes = GeneralUtility::trimExplode(',', $splitPreviewTsConfig['value']);
         $allPreviewModes = array('slider', 'vbox', 'hbox');
         if (!array_intersect($splitPreviewModes, $allPreviewModes)) {
             $splitPreviewModes = $allPreviewModes;
         }
+
+        $wsList = $wsService->getAvailableWorkspaces();
+        $activeWorkspace = $backendUser->workspace;
+
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Workspaces/Preview');
         $this->pageRenderer->addInlineSetting('Workspaces', 'SplitPreviewModes', $splitPreviewModes);
-        $GLOBALS['BE_USER']->setAndSaveSessionData('workspaces.backend_domain', GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
-        $this->pageRenderer->addInlineSetting('Workspaces', 'disableNextStageButton', $this->isInvalidStage($nextStage));
-        $this->pageRenderer->addInlineSetting('Workspaces', 'disablePreviousStageButton', $this->isInvalidStage($previousStage));
-        $this->pageRenderer->addInlineSetting('Workspaces', 'disableDiscardStageButton', $this->isInvalidStage($nextStage) && $this->isInvalidStage($previousStage));
-        $resourcePath = ExtensionManagementUtility::extRelPath('lang') . 'Resources/Public/JavaScript/';
-        $this->pageRenderer->addJsFile($resourcePath . 'Typo3Lang.js');
-        $this->pageRenderer->addJsInlineCode('workspaces.preview.lll', '
-               TYPO3.lang = {
-                       visualPreview: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.visualPreview', true)) . ',
-                       listView: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.listView', true)) . ',
-                       livePreview: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.livePreview', true)) . ',
-                       livePreviewDetail: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.livePreviewDetail', true)) . ',
-                       workspacePreview: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.workspacePreview', true)) . ',
-                       workspacePreviewDetail: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.workspacePreviewDetail', true)) . ',
-                       modeSlider: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.modeSlider', true)) . ',
-                       modeVbox: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.modeVbox', true)) . ',
-                       modeHbox: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:preview.modeHbox', true)) . ',
-                       discard: ' . GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:label_doaction_discard', true)) . ',
-                       nextStage: ' . GeneralUtility::quoteJSvalue($nextStage['title']) . ',
-                       previousStage: ' . GeneralUtility::quoteJSvalue($previousStage['title']) . '
-               };TYPO3.l10n.initialize();
-');
-        $resourcePath = ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/';
-        $this->pageRenderer->addJsFile($resourcePath . 'JavaScript/preview.js');
+        $this->pageRenderer->addInlineSetting('Workspaces', 'token', FormProtectionFactory::get('backend')->generateToken('extDirect'));
+
+        $cssFile = 'EXT:workspaces/Resources/Public/Css/preview.css';
+        $cssFile = GeneralUtility::getFileAbsFileName($cssFile);
+        $this->pageRenderer->addCssFile(PathUtility::getAbsoluteWebPath($cssFile));
+
+        $backendUser->setAndSaveSessionData('workspaces.backend_domain', GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
+
+        $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3-topbar@2x.png');
+        list($logoWidth, $logoHeight) = @getimagesize($logoPath);
+
+        // High-resolution?
+        $logoWidth = $logoWidth/2;
+        $logoHeight = $logoHeight/2;
+
+        $this->view->assignMultiple([
+            'logoUrl' => PathUtility::getAbsoluteWebPath($logoPath),
+            'logoLink' => TYPO3_URL_GENERAL,
+            'logoWidth' => $logoWidth,
+            'logoHeight' => $logoHeight,
+            'liveUrl' => $liveUrl,
+            'wsUrl' => $wsUrl,
+            'wsSettingsUrl' => $wsSettingsUrl,
+            'backendDomain' => $backendDomain,
+            'activeWorkspace' => $wsList[$activeWorkspace],
+            'splitPreviewModes' => $splitPreviewModes,
+            'firstPreviewMode' => current($splitPreviewModes),
+            'enablePreviousStageButton' => !$this->isInvalidStage($previousStage),
+            'enableNextStageButton' => !$this->isInvalidStage($nextStage),
+            'enableDiscardStageButton' => !$this->isInvalidStage($nextStage) || !$this->isInvalidStage($previousStage),
+            'nextStage' => $nextStage['title'],
+            'nextStageId' => $nextStage['uid'],
+            'prevStage' => $previousStage['title'],
+            'prevStageId' => $previousStage['uid'],
+        ]);
+        foreach ($this->getAdditionalResourceService()->getLocalizationResources() as $localizationResource) {
+            $this->pageRenderer->addInlineLanguageLabelFile($localizationResource);
+        }
     }
 
     /**
@@ -194,9 +208,10 @@ class PreviewController extends AbstractController
      */
     public function newPageAction()
     {
-        $flashMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessage::class, $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:info.newpage.detail'), $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:info.newpage'), \TYPO3\CMS\Core\Messaging\FlashMessage::INFO);
-        /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-        $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
+        /** @var FlashMessage $flashMessage */
+        $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:info.newpage.detail'), $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:info.newpage'), FlashMessage::INFO);
+        /** @var $flashMessageService FlashMessageService */
+        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
         /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
         $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
         $defaultFlashMessageQueue->enqueue($flashMessage);
@@ -211,58 +226,59 @@ class PreviewController extends AbstractController
      */
     protected function generateJavascript()
     {
+        $backendUser = $this->getBackendUser();
+        $lang = $this->getLanguageService();
         // If another page module was specified, replace the default Page module with the new one
-        $newPageModule = trim($GLOBALS['BE_USER']->getTSConfigVal('options.overridePageModule'));
+        $newPageModule = trim($backendUser->getTSConfigVal('options.overridePageModule'));
         $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
-        if (!$GLOBALS['BE_USER']->check('modules', $pageModule)) {
+        if (!$backendUser->check('modules', $pageModule)) {
             $pageModule = '';
         }
         $t3Configuration = array(
             'siteUrl' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL'),
-            'username' => htmlspecialchars($GLOBALS['BE_USER']->user['username']),
+            'username' => htmlspecialchars($backendUser->user['username']),
             'uniqueID' => GeneralUtility::shortMD5(uniqid('', true)),
             'pageModule' => $pageModule,
-            'inWorkspace' => $GLOBALS['BE_USER']->workspace !== 0,
-            'workspaceFrontendPreviewEnabled' => $GLOBALS['BE_USER']->user['workspace_preview'] ? 1 : 0,
-            'moduleMenuWidth' => $this->menuWidth - 1,
+            'inWorkspace' => $backendUser->workspace !== 0,
+            'workspaceFrontendPreviewEnabled' => $backendUser->user['workspace_preview'] ? 1 : 0,
             'topBarHeight' => isset($GLOBALS['TBE_STYLES']['dims']['topFrameH']) ? (int)$GLOBALS['TBE_STYLES']['dims']['topFrameH'] : 30,
             'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] : false,
-            'debugInWindow' => $GLOBALS['BE_USER']->uc['debugInWindow'] ? 1 : 0,
+            'debugInWindow' => $backendUser->uc['debugInWindow'] ? 1 : 0,
             'ContextHelpWindows' => array(
                 'width' => 600,
                 'height' => 400
             )
         );
         $t3LLLcore = array(
-            'waitTitle' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_logging_in'),
-            'refresh_login_failed' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_failed'),
-            'refresh_login_failed_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_failed_message'),
-            'refresh_login_title' => sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_title'), htmlspecialchars($GLOBALS['BE_USER']->user['username'])),
-            'login_expired' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_expired'),
-            'refresh_login_username' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_username'),
-            'refresh_login_password' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_password'),
-            'refresh_login_emptyPassword' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_emptyPassword'),
-            'refresh_login_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_button'),
-            'refresh_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_logout_button'),
-            'refresh_exit_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_exit_button'),
-            'please_wait' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.please_wait'),
-            'loadingIndicator' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:loadingIndicator'),
-            'be_locked' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.be_locked'),
-            'refresh_login_countdown_singular' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_countdown_singular'),
-            'refresh_login_countdown' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_countdown'),
-            'login_about_to_expire' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_about_to_expire'),
-            'login_about_to_expire_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_about_to_expire_title'),
-            'refresh_login_refresh_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_refresh_button'),
-            'refresh_direct_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_direct_logout_button'),
-            'tabs_closeAll' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:tabs.closeAll'),
-            'tabs_closeOther' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:tabs.closeOther'),
-            'tabs_close' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:tabs.close'),
-            'tabs_openInBrowserWindow' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:tabs.openInBrowserWindow'),
-            'donateWindow_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.title'),
-            'donateWindow_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.message'),
-            'donateWindow_button_donate' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_donate'),
-            'donateWindow_button_disable' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_disable'),
-            'donateWindow_button_postpone' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_postpone')
+            'waitTitle' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_logging_in'),
+            'refresh_login_failed' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_failed'),
+            'refresh_login_failed_message' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_failed_message'),
+            'refresh_login_title' => sprintf($lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_title'), htmlspecialchars($backendUser->user['username'])),
+            'login_expired' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_expired'),
+            'refresh_login_username' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_username'),
+            'refresh_login_password' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_password'),
+            'refresh_login_emptyPassword' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_emptyPassword'),
+            'refresh_login_button' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_button'),
+            'refresh_logout_button' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_logout_button'),
+            'refresh_exit_button' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_exit_button'),
+            'please_wait' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.please_wait'),
+            'loadingIndicator' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:loadingIndicator'),
+            'be_locked' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.be_locked'),
+            'refresh_login_countdown_singular' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_countdown_singular'),
+            'refresh_login_countdown' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_countdown'),
+            'login_about_to_expire' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_about_to_expire'),
+            'login_about_to_expire_title' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.login_about_to_expire_title'),
+            'refresh_login_refresh_button' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login_refresh_button'),
+            'refresh_direct_logout_button' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_direct_logout_button'),
+            'tabs_closeAll' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:tabs.closeAll'),
+            'tabs_closeOther' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:tabs.closeOther'),
+            'tabs_close' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:tabs.close'),
+            'tabs_openInBrowserWindow' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:tabs.openInBrowserWindow'),
+            'donateWindow_title' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.title'),
+            'donateWindow_message' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.message'),
+            'donateWindow_button_donate' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_donate'),
+            'donateWindow_button_disable' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_disable'),
+            'donateWindow_button_postpone' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:donateWindow.button_postpone')
         );
         return '
                TYPO3.configuration = ' . json_encode($t3Configuration) . ';
@@ -281,4 +297,20 @@ class PreviewController extends AbstractController
                        //backwards compatibility
                ';
     }
+
+    /**
+     * @return \TYPO3\CMS\Lang\LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
index 3b6c3cb..f93c2b3 100644 (file)
@@ -15,7 +15,11 @@ namespace TYPO3\CMS\Workspaces\Controller;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
@@ -66,26 +70,25 @@ class ReviewController extends AbstractController
      */
     public function indexAction()
     {
+        $backendUser = $this->getBackendUser();
+        $moduleTemplate = $this->view->getModuleTemplate();
+
         /** @var WorkspaceService $wsService */
         $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
-        $this->view->assign('showGrid', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
-        $this->view->assign('showAllWorkspaceTab', true);
-        $this->view->assign('pageUid', GeneralUtility::_GP('id'));
         if (GeneralUtility::_GP('id')) {
             $pageRecord = BackendUtility::getRecord('pages', GeneralUtility::_GP('id'));
             if ($pageRecord) {
-                $this->view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation($pageRecord);
+                $moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageRecord);
                 $this->view->assign('pageTitle', BackendUtility::getRecordTitle('pages', $pageRecord));
             }
         }
-        $this->view->assign('showLegend', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
         $wsList = $wsService->getAvailableWorkspaces();
-        $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+        $activeWorkspace = $backendUser->workspace;
         $performWorkspaceSwitch = false;
         // Only admins see multiple tabs, we decided to use it this
         // way for usability reasons. Regular users might be confused
         // by switching workspaces with the tabs in a module.
-        if (!$GLOBALS['BE_USER']->isAdmin()) {
+        if (!$backendUser->isAdmin()) {
             $wsCur = array($activeWorkspace => true);
             $wsList = array_intersect_key($wsList, $wsCur);
         } else {
@@ -93,7 +96,7 @@ class ReviewController extends AbstractController
                 $switchWs = (int)GeneralUtility::_GP('workspace');
                 if (in_array($switchWs, array_keys($wsList)) && $activeWorkspace != $switchWs) {
                     $activeWorkspace = $switchWs;
-                    $GLOBALS['BE_USER']->setWorkspace($activeWorkspace);
+                    $backendUser->setWorkspace($activeWorkspace);
                     $performWorkspaceSwitch = true;
                     BackendUtility::setUpdateSignal('updatePageTree');
                 } elseif ($switchWs == WorkspaceService::SELECT_ALL_WORKSPACES) {
@@ -101,26 +104,33 @@ class ReviewController extends AbstractController
                 }
             }
         }
-        $this->pageRenderer->addInlineSetting('Workspaces', 'isLiveWorkspace', (int)$GLOBALS['BE_USER']->workspace === 0);
+        $this->pageRenderer->addInlineSetting('Workspaces', 'isLiveWorkspace', (int)$backendUser->workspace === 0);
         $this->pageRenderer->addInlineSetting('Workspaces', 'workspaceTabs', $this->prepareWorkspaceTabs($wsList, $activeWorkspace));
         $this->pageRenderer->addInlineSetting('Workspaces', 'activeWorkspaceId', $activeWorkspace);
         $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
-        $this->view->assign('performWorkspaceSwitch', $performWorkspaceSwitch);
-        $this->view->assign('workspaceList', $wsList);
-        $this->view->assign('activeWorkspaceUid', $activeWorkspace);
-        $this->view->assign('activeWorkspaceTitle', WorkspaceService::getWorkspaceTitle($activeWorkspace));
+        $workspaceIsAccessible = !($backendUser->workspace === 0 && !$backendUser->isAdmin());
+        $this->view->assignMultiple([
+            'showGrid' => $workspaceIsAccessible,
+            'showLegend' => $workspaceIsAccessible,
+            'pageUid' => (int)GeneralUtility::_GP('id'),
+            'performWorkspaceSwitch' => $performWorkspaceSwitch,
+            'workspaceList' => $this->prepareWorkspaceTabs($wsList, $activeWorkspace),
+            'activeWorkspaceUid' => $activeWorkspace,
+            'activeWorkspaceTitle' => WorkspaceService::getWorkspaceTitle($activeWorkspace),
+            'showPreviewLink' => $wsService->canCreatePreviewLink(GeneralUtility::_GP('id'), $activeWorkspace)
+        ]);
+
         if ($wsService->canCreatePreviewLink(GeneralUtility::_GP('id'), $activeWorkspace)) {
-            $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
-            $iconFactory = $this->view->getModuleTemplate()->getIconFactory();
+            $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
+            $iconFactory = $moduleTemplate->getIconFactory();
             $showButton = $buttonBar->makeLinkButton()
                 ->setHref('#')
-                ->setOnClick('TYPO3.Workspaces.Actions.generateWorkspacePreviewLinksForAllLanguages();return false;')
+                ->setClasses('t3js-preview-link')
                 ->setTitle($this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:tooltip.generatePagePreview'))
                 ->setIcon($iconFactory->getIcon('module-workspaces-action-preview-link', Icon::SIZE_SMALL));
             $buttonBar->addButton($showButton);
         }
-        $this->view->assign('showPreviewLink', $wsService->canCreatePreviewLink(GeneralUtility::_GP('id'), $activeWorkspace));
-        $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', $activeWorkspace);
+        $backendUser->setAndSaveSessionData('tx_workspace_activeWorkspace', $activeWorkspace);
     }
 
     /**
@@ -131,25 +141,26 @@ class ReviewController extends AbstractController
      */
     public function fullIndexAction()
     {
-        $wsService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
         $wsList = $wsService->getAvailableWorkspaces();
 
-        if (!$GLOBALS['BE_USER']->isAdmin()) {
-            $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+        $activeWorkspace = $this->getBackendUser()->workspace;
+        if (!$this->getBackendUser()->isAdmin()) {
             $wsCur = array($activeWorkspace => true);
             $wsList = array_intersect_key($wsList, $wsCur);
         }
 
         $this->pageRenderer->addInlineSetting('Workspaces', 'workspaceTabs', $this->prepareWorkspaceTabs($wsList, WorkspaceService::SELECT_ALL_WORKSPACES));
         $this->pageRenderer->addInlineSetting('Workspaces', 'activeWorkspaceId', WorkspaceService::SELECT_ALL_WORKSPACES);
-        $this->view->assign('pageUid', GeneralUtility::_GP('id'));
-        $this->view->assign('showGrid', true);
-        $this->view->assign('showLegend', true);
-        $this->view->assign('showAllWorkspaceTab', true);
-        $this->view->assign('workspaceList', $wsList);
-        $this->view->assign('activeWorkspaceUid', WorkspaceService::SELECT_ALL_WORKSPACES);
-        $this->view->assign('showPreviewLink', false);
-        $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', WorkspaceService::SELECT_ALL_WORKSPACES);
+        $this->view->assignMultiple([
+            'pageUid' => (int)GeneralUtility::_GP('id'),
+            'showGrid' => true,
+            'showLegend' => true,
+            'workspaceList' => $this->prepareWorkspaceTabs($wsList, $activeWorkspace),
+            'activeWorkspaceUid' => WorkspaceService::SELECT_ALL_WORKSPACES,
+            'showPreviewLink', false
+        ]);
+        $this->getBackendUser()->setAndSaveSessionData('tx_workspace_activeWorkspace', WorkspaceService::SELECT_ALL_WORKSPACES);
         // set flag for javascript
         $this->pageRenderer->addInlineSetting('Workspaces', 'allView', '1');
     }
@@ -162,17 +173,19 @@ class ReviewController extends AbstractController
      */
     public function singleIndexAction()
     {
-        $wsService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
         $wsList = $wsService->getAvailableWorkspaces();
-        $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+        $activeWorkspace = $this->getBackendUser()->workspace;
         $wsCur = array($activeWorkspace => true);
         $wsList = array_intersect_key($wsList, $wsCur);
         $backendDomain = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-        $this->view->assign('pageUid', GeneralUtility::_GP('id'));
-        $this->view->assign('showGrid', true);
-        $this->view->assign('showAllWorkspaceTab', false);
-        $this->view->assign('workspaceList', $wsList);
-        $this->view->assign('backendDomain', $backendDomain);
+        $this->view->assignMultiple([
+            'pageUid' => (int)GeneralUtility::_GP('id'),
+            'showGrid' => true,
+            'workspaceList' => $this->prepareWorkspaceTabs($wsList, $activeWorkspace, false),
+            'activeWorkspaceUid' => $activeWorkspace,
+            'backendDomain' => $backendDomain
+        ]);
         // Setting the document.domain early before JavScript
         // libraries are loaded, try to access top frame reference
         // and possibly run into some CORS issue
@@ -194,68 +207,30 @@ class ReviewController extends AbstractController
         $backendRelPath = ExtensionManagementUtility::extRelPath('backend');
         $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/ExtDirect.StateProvider.js');
         if (WorkspaceService::isOldStyleWorkspaceUsed()) {
-            $flashMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessage::class, $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:warning.oldStyleWorkspaceInUser'), '', \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING);
-            /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-            $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
+            /** @var FlashMessage $flashMessage */
+            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:warning.oldStyleWorkspaceInUser'), '', FlashMessage::WARNING);
+            /** @var $flashMessageService FlashMessageService */
+            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
             /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
             $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
             $defaultFlashMessageQueue->enqueue($flashMessage);
         }
-        $this->pageRenderer->loadExtJS();
-        $states = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['States'];
+        $this->pageRenderer->loadExtJS(false, false);
+        $states = $this->getBackendUser()->uc['moduleData']['Workspaces']['States'];
         $this->pageRenderer->addInlineSetting('Workspaces', 'States', $states);
         // Load  JavaScript:
         $this->pageRenderer->addExtDirectCode(array(
             'TYPO3.Workspaces'
         ));
-        $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/extjs/ux/Ext.grid.RowExpander.js');
-        $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/extjs/ux/Ext.app.SearchField.js');
-        $this->pageRenderer->addJsFile($backendRelPath . 'Resources/Public/JavaScript/extjs/ux/Ext.ux.FitToParent.js');
-        $resourcePath = ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
-
-        // @todo Integrate additional stylesheet resources
-        $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/GridFilters.css');
-        $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/RangeMenu.css');
-
-        $filters = array(
-            $resourcePath . 'gridfilters/menu/RangeMenu.js',
-            $resourcePath . 'gridfilters/menu/ListMenu.js',
-            $resourcePath . 'gridfilters/GridFilters.js',
-            $resourcePath . 'gridfilters/filter/Filter.js',
-            $resourcePath . 'gridfilters/filter/StringFilter.js',
-            $resourcePath . 'gridfilters/filter/DateFilter.js',
-            $resourcePath . 'gridfilters/filter/ListFilter.js',
-            $resourcePath . 'gridfilters/filter/NumericFilter.js',
-            $resourcePath . 'gridfilters/filter/BooleanFilter.js',
-            $resourcePath . 'gridfilters/filter/BooleanFilter.js',
-        );
-
-        $custom = $this->getAdditionalResourceService()->getJavaScriptResources();
-
-        $resources = array(
-            $resourcePath . 'Component/RowDetailTemplate.js',
-            $resourcePath . 'Component/RowExpander.js',
-            $resourcePath . 'Component/TabPanel.js',
-            $resourcePath . 'Store/mainstore.js',
-            $resourcePath . 'configuration.js',
-            $resourcePath . 'helpers.js',
-            $resourcePath . 'actions.js',
-            $resourcePath . 'component.js',
-            $resourcePath . 'toolbar.js',
-            $resourcePath . 'grid.js',
-            $resourcePath . 'workspaces.js'
-        );
-
-        $javaScriptFiles = array_merge($filters, $custom, $resources);
 
-        foreach ($javaScriptFiles as $javaScriptFile) {
-            $this->pageRenderer->addJsFile($javaScriptFile);
-        }
         foreach ($this->getAdditionalResourceService()->getLocalizationResources() as $localizationResource) {
             $this->pageRenderer->addInlineLanguageLabelFile($localizationResource);
         }
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Workspaces/Backend');
         $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
         $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
+        $this->pageRenderer->addInlineSetting('Workspaces', 'token', FormProtectionFactory::get('backend')->generateToken('extDirect'));
+        $this->pageRenderer->addInlineSetting('Workspaces', 'id', (int)GeneralUtility::_GP('id'));
     }
 
     /**
@@ -263,9 +238,10 @@ class ReviewController extends AbstractController
      *
      * @param array $workspaceList
      * @param int $activeWorkspace
+     * @param bool $showAllWorkspaceTab
      * @return array
      */
-    protected function prepareWorkspaceTabs(array $workspaceList, $activeWorkspace)
+    protected function prepareWorkspaceTabs(array $workspaceList, $activeWorkspace, $showAllWorkspaceTab = true)
     {
         $tabs = array();
 
@@ -278,12 +254,14 @@ class ReviewController extends AbstractController
             );
         }
 
-        $tabs[] = array(
-            'title' => 'All workspaces',
-            'itemId' => 'workspace-' . WorkspaceService::SELECT_ALL_WORKSPACES,
-            'workspaceId' => WorkspaceService::SELECT_ALL_WORKSPACES,
-            'triggerUrl' => $this->getModuleUri(WorkspaceService::SELECT_ALL_WORKSPACES),
-        );
+        if ($showAllWorkspaceTab) {
+            $tabs[] = array(
+                'title' => 'All workspaces',
+                'itemId' => 'workspace-' . WorkspaceService::SELECT_ALL_WORKSPACES,
+                'workspaceId' => WorkspaceService::SELECT_ALL_WORKSPACES,
+                'triggerUrl' => $this->getModuleUri(WorkspaceService::SELECT_ALL_WORKSPACES),
+            );
+        }
 
         foreach ($workspaceList as $workspaceId => $workspaceTitle) {
             if ($workspaceId === $activeWorkspace) {
@@ -328,4 +306,12 @@ class ReviewController extends AbstractController
     {
         return $GLOBALS['LANG'];
     }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
index 4a88062..9d1f0a9 100644 (file)
@@ -15,10 +15,13 @@ namespace TYPO3\CMS\Workspaces\ExtDirect;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
 use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
 use TYPO3\CMS\Workspaces\Service\StagesService;
+use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
 /**
  * ExtDirect action handler
@@ -35,7 +38,7 @@ class ActionHandler extends AbstractHandler
      */
     public function __construct()
     {
-        $this->stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
+        $this->stageService = GeneralUtility::makeInstance(StagesService::class);
     }
 
     /**
@@ -114,7 +117,7 @@ class ActionHandler extends AbstractHandler
      */
     public function viewSingleRecord($table, $uid)
     {
-        return \TYPO3\CMS\Workspaces\Service\WorkspaceService::viewSingleRecord($table, $uid);
+        return WorkspaceService::viewSingleRecord($table, $uid);
     }
 
     /**
@@ -410,10 +413,10 @@ class ActionHandler extends AbstractHandler
     public function discardStagesFromPage($pageId)
     {
         $cmdMapArray = array();
-        /** @var $workspaceService \TYPO3\CMS\Workspaces\Service\WorkspaceService */
-        $workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        /** @var $workspaceService WorkspaceService */
+        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         /** @var $stageService StagesService */
-        $stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
+        $stageService = GeneralUtility::makeInstance(StagesService::class);
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $pageId, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
         foreach ($workspaceItemsArray as $tableName => $items) {
             foreach ($items as $item) {
@@ -437,7 +440,7 @@ class ActionHandler extends AbstractHandler
      * $parameters->stageId
      * </code>
      *
-     * @param stdClass $parameters
+     * @param \stdClass $parameters
      * @return array
      */
     public function sentCollectionToStage(\stdClass $parameters)
@@ -451,7 +454,7 @@ class ActionHandler extends AbstractHandler
         if (!is_object($parameters->affects) || empty($parameters->affects)) {
             throw new \InvalidArgumentException('Missing "affected items" in $parameters array.', 1319488195);
         }
-        $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $stageId);
+        $recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $stageId);
         foreach ($parameters->affects as $tableName => $items) {
             foreach ($items as $item) {
                 // Publishing uses live id in command map
@@ -516,7 +519,7 @@ class ActionHandler extends AbstractHandler
      * additional: string
      * comments: string
      *
-     * @param stdClass $parameters
+     * @param \stdClass $parameters
      * @return array
      */
     public function sendToNextStageExecute(\stdClass $parameters)
@@ -531,7 +534,7 @@ class ActionHandler extends AbstractHandler
         $elementRecord = BackendUtility::getRecord($table, $uid);
         $currentWorkspace = $this->setTemporaryWorkspace($elementRecord['t3ver_wsid']);
 
-        $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+        $recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
         if ($setStageId == StagesService::STAGE_PUBLISH_EXECUTE_ID) {
             $cmdArray[$table][$t3ver_oid]['version']['action'] = 'swap';
             $cmdArray[$table][$t3ver_oid]['version']['swapWith'] = $uid;
@@ -563,7 +566,7 @@ class ActionHandler extends AbstractHandler
      * additional: string
      * comments: string
      *
-     * @param stdClass $parameters
+     * @param \stdClass $parameters
      * @return array
      */
     public function sendToPrevStageExecute(\stdClass $parameters)
@@ -577,7 +580,7 @@ class ActionHandler extends AbstractHandler
         $elementRecord = BackendUtility::getRecord($table, $uid);
         $currentWorkspace = $this->setTemporaryWorkspace($elementRecord['t3ver_wsid']);
 
-        $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+        $recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
         $cmdArray[$table][$uid]['version']['action'] = 'setStage';
         $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
         $cmdArray[$table][$uid]['version']['comment'] = $comments;
@@ -618,7 +621,7 @@ class ActionHandler extends AbstractHandler
         $setStageId = $parameters->affects->nextStage;
         $comments = $parameters->comments;
         $elements = $parameters->affects->elements;
-        $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+        $recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
         foreach ($elements as $element) {
             // Avoid any action on records that have already been published to live
             $elementRecord = BackendUtility::getRecord($element->table, $element->uid);
@@ -648,7 +651,7 @@ class ActionHandler extends AbstractHandler
     /**
      * Gets the dialog window to be displayed before a record can be sent to a stage.
      *
-     * @param StageRecord|int $nextStageId
+     * @param StageRecord|int $nextStage
      * @return array
      */
     protected function getSentToStageWindow($nextStage)
@@ -657,43 +660,18 @@ class ActionHandler extends AbstractHandler
             $nextStage = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($nextStage);
         }
 
-        $result = array(
-            'title' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:actionSendToStage'),
-            'items' => array(
-                array(
-                    'xtype' => 'panel',
-                    'bodyStyle' => 'margin-bottom: 7px; border: none;',
-                    'html' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.itemsWillBeSentTo') . ' ' . $nextStage->getTitle()
-                )
-            )
-        );
-
+        $result = [];
         if ($nextStage->isDialogEnabled()) {
-            $result['items'][] = array(
-                'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.sendMailTo'),
-                'xtype' => 'checkboxgroup',
-                'itemCls' => 'x-check-group-alt',
-                'columns' => 1,
-                'style' => 'max-height: 200px',
-                'autoScroll' => true,
-                'items' => array(
-                    $this->getReceipientsOfStage($nextStage->getUid())
-                )
-            );
-            $result['items'][] = array(
-                'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.additionalRecipients'),
-                'name' => 'additional',
-                'xtype' => 'textarea',
-                'width' => 250
-            );
+            $result['sendMailTo'] = $this->getRecipientsOfStage($nextStage->getUid());
+            $result['additional'] = [
+                'type' => 'textarea',
+                'value' => ''
+            ];
         }
-        $result['items'][] = array(
-            'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.comments'),
-            'name' => 'comments',
-            'xtype' => 'textarea',
-            'width' => 250,
+        $result['comments'] = [
+            'type' => 'textarea',
             'value' => ($nextStage->isInternal() ? '' : $nextStage->getDefaultComment())
-        );
+        ];
 
         return $result;
     }
@@ -704,7 +682,7 @@ class ActionHandler extends AbstractHandler
      * @param StageRecord|int $stageRecord
      * @return array
      */
-    protected function getReceipientsOfStage($stageRecord)
+    protected function getRecipientsOfStage($stageRecord)
     {
         if (!$stageRecord instanceof StageRecord) {
             $stageRecord = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($stageRecord);
@@ -725,8 +703,9 @@ class ActionHandler extends AbstractHandler
             $disabled = ($checked && !$isPreselectionChangeable);
 
             $result[] = array(
-                'boxLabel' => sprintf('%s (%s)', $name, $backendUser['email']),
-                'name' => 'receipients-' . $backendUserId,
+                'label' => sprintf('%s (%s)', $name, $backendUser['email']),
+                'value' => $backendUserId,
+                'name' => 'recipients-' . $backendUserId,
                 'checked' => $checked,
                 'disabled' => $disabled
             );
@@ -755,7 +734,7 @@ class ActionHandler extends AbstractHandler
     protected function getStageService()
     {
         if (!isset($this->stageService)) {
-            $this->stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
+            $this->stageService = GeneralUtility::makeInstance(StagesService::class);
         }
         return $this->stageService;
     }
@@ -768,17 +747,19 @@ class ActionHandler extends AbstractHandler
      */
     public function sendPageToPreviousStage($id)
     {
-        $workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
         list($currentStage, $previousStage) = $this->getStageService()->getPreviousStageForElementCollection($workspaceItemsArray);
         // get only the relevant items for processing
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), $currentStage['uid'], $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
-        return array(
+        $stageFormFields = $this->getSentToStageWindow($previousStage['uid']);
+        $result = array_merge($stageFormFields, [
             'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $previousStage['title'],
             'items' => $this->getSentToStageWindow($previousStage['uid']),
             'affects' => $workspaceItemsArray,
             'stageId' => $previousStage['uid']
-        );
+        ]);
+        return $result;
     }
 
     /**
@@ -787,48 +768,55 @@ class ActionHandler extends AbstractHandler
      */
     public function sendPageToNextStage($id)
     {
-        $workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
         list($currentStage, $nextStage) = $this->getStageService()->getNextStageForElementCollection($workspaceItemsArray);
         // get only the relevant items for processing
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), $currentStage['uid'], $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
-        return array(
+        $stageFormFields = $this->getSentToStageWindow($nextStage['uid']);
+        $result = array_merge($stageFormFields, [
             'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $nextStage['title'],
-            'items' => $this->getSentToStageWindow($nextStage['uid']),
             'affects' => $workspaceItemsArray,
             'stageId' => $nextStage['uid']
-        );
+        ]);
+        return $result;
     }
 
     /**
      * Fetch the current label and visible state of the buttons.
      *
      * @param int $id
-     * @return array Contains the visibility state and label of the stage change buttons.
+     * @return string The pre-rendered HTML for the stage buttons
      */
     public function updateStageChangeButtons($id)
     {
-        $stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
-        $workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+        /** @var StagesService $stageService */
+        $stageService = GeneralUtility::makeInstance(StagesService::class);
+        /** @var WorkspaceService $workspaceService */
+        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         // fetch the next and previous stage
         $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
         list(, $nextStage) = $stageService->getNextStageForElementCollection($workspaceItemsArray);
         list(, $previousStage) = $stageService->getPreviousStageForElementCollection($workspaceItemsArray);
-        $toolbarButtons = array(
-            'feToolbarButtonNextStage' => array(
-                'visible' => is_array($nextStage) && !empty($nextStage),
-                'text' => $nextStage['title']
-            ),
-            'feToolbarButtonPreviousStage' => array(
-                'visible' => is_array($previousStage) && !empty($previousStage),
-                'text' => $previousStage['title']
-            ),
-            'feToolbarButtonDiscardStage' => array(
-                'visible' => is_array($nextStage) && !empty($nextStage) || is_array($previousStage) && !empty($previousStage),
-                'text' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:label_doaction_discard', true)
-            )
-        );
-        return $toolbarButtons;
+
+        /** @var StandaloneView $view */
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $extensionPath = ExtensionManagementUtility::extPath('workspaces');
+        $view->setPartialRootPaths(['default' => $extensionPath . 'Resources/Private/Partials']);
+        $view->setTemplatePathAndFilename($extensionPath . 'Resources/Private/Templates/Preview/Ajax/StageButtons.html');
+        $request = $view->getRequest();
+        $request->setControllerExtensionName('workspaces');
+        $view->assignMultiple([
+            'enablePreviousStageButton' => is_array($previousStage) && !empty($previousStage),
+            'enableNextStageButton' => is_array($nextStage) && !empty($nextStage),
+            'enableDiscardStageButton' => is_array($nextStage) && !empty($nextStage) || is_array($previousStage) && !empty($previousStage),
+            'nextStage' => $nextStage['title'],
+            'nextStageId' => $nextStage['uid'],
+            'prevStage' => $previousStage['title'],
+            'prevStageId' => $previousStage['uid'],
+        ]);
+        $renderedView = $view->render();
+        return $renderedView;
     }
 
     /**
index edcb127..dde139e 100644 (file)
@@ -14,12 +14,25 @@ namespace TYPO3\CMS\Workspaces\ExtDirect;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Html\RteHtmlParser;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Resource\FileReference;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Utility\DiffUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
+use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Workspaces\Service\GridDataService;
+use TYPO3\CMS\Workspaces\Service\HistoryService;
+use TYPO3\CMS\Workspaces\Service\StagesService;
+use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
 /**
  * ExtDirect server
@@ -27,17 +40,17 @@ use TYPO3\CMS\Extbase\Object\ObjectManager;
 class ExtDirectServer extends AbstractHandler
 {
     /**
-     * @var \TYPO3\CMS\Workspaces\Service\GridDataService
+     * @var GridDataService
      */
     protected $gridDataService;
 
     /**
-     * @var \TYPO3\CMS\Workspaces\Service\StagesService
+     * @var StagesService
      */
     protected $stagesService;
 
     /**
-     * @var \cogpowered\FineDiff\Diff
+     * @var DiffUtility
      */
     protected $differenceHandler;
 
@@ -67,7 +80,7 @@ class ExtDirectServer extends AbstractHandler
     {
         // To avoid too much work we use -1 to indicate that every page is relevant
         $pageId = $parameter->id > 0 ? $parameter->id : -1;
-        if (!isset($parameter->language) || !\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($parameter->language)) {
+        if (!isset($parameter->language) || !MathUtility::canBeInterpretedAsInteger($parameter->language)) {
             $parameter->language = null;
         }
         $versions = $this->getWorkspaceService()->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, $pageId, $parameter->depth, 'tables_select', $parameter->language);
@@ -76,23 +89,6 @@ class ExtDirectServer extends AbstractHandler
     }
 
     /**
-     * Gets the editing history of a record.
-     *
-     * @param stdClass $parameters
-     * @return array
-     */
-    public function getHistory($parameters)
-    {
-        /** @var $historyService \TYPO3\CMS\Workspaces\Service\HistoryService */
-        $historyService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\HistoryService::class);
-        $history = $historyService->getHistory($parameters->table, $parameters->liveId);
-        return array(
-            'data' => $history,
-            'total' => count($history)
-        );
-    }
-
-    /**
      * Get List of available workspace actions
      *
      * @param \stdClass $parameter
@@ -102,7 +98,7 @@ class ExtDirectServer extends AbstractHandler
     {
         $currentWorkspace = $this->getCurrentWorkspace();
         $stages = array();
-        if ($currentWorkspace != \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES) {
+        if ($currentWorkspace != WorkspaceService::SELECT_ALL_WORKSPACES) {
             $stages = $this->getStagesService()->getStagesForWSUser();
         }
         $data = array(
@@ -122,16 +118,16 @@ class ExtDirectServer extends AbstractHandler
     {
         $diffReturnArray = array();
         $liveReturnArray = array();
-        /** @var $diffUtility \TYPO3\CMS\Core\Utility\DiffUtility */
-        $diffUtility = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\DiffUtility::class);
-        /** @var $parseObj \TYPO3\CMS\Core\Html\RteHtmlParser */
-        $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\RteHtmlParser::class);
+        $diffUtility = $this->getDifferenceHandler();
+        /** @var $parseObj RteHtmlParser */
+        $parseObj = GeneralUtility::makeInstance(RteHtmlParser::class);
         $liveRecord = BackendUtility::getRecord($parameter->table, $parameter->t3ver_oid);
         $versionRecord = BackendUtility::getRecord($parameter->table, $parameter->uid);
         $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
         $icon_Live = $iconFactory->getIconForRecord($parameter->table, $liveRecord, Icon::SIZE_SMALL)->render();
         $icon_Workspace = $iconFactory->getIconForRecord($parameter->table, $versionRecord, Icon::SIZE_SMALL)->render();
-        $stagePosition = $this->getStagesService()->getPositionOfCurrentStage($parameter->stage);
+        $stagesService = $this->getStagesService();
+        $stagePosition = $stagesService->getPositionOfCurrentStage($parameter->stage);
         $fieldsOfRecords = array_keys($liveRecord);
         if ($GLOBALS['TCA'][$parameter->table]) {
             if ($GLOBALS['TCA'][$parameter->table]['interface']['showRecordFieldList']) {
@@ -144,14 +140,14 @@ class ExtDirectServer extends AbstractHandler
                 continue;
             }
             // Get the field's label. If not available, use the field name
-            $fieldTitle = $GLOBALS['LANG']->sL(BackendUtility::getItemLabel($parameter->table, $fieldName));
+            $fieldTitle = $this->getLanguageService()->sL(BackendUtility::getItemLabel($parameter->table, $fieldName));
             if (empty($fieldTitle)) {
                 $fieldTitle = $fieldName;
             }
             // Gets the TCA configuration for the current field
             $configuration = $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['config'];
             // check for exclude fields
-            if ($GLOBALS['BE_USER']->isAdmin() || $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['exclude'] == 0 || GeneralUtility::inList($GLOBALS['BE_USER']->groupData['non_exclude_fields'], $parameter->table . ':' . $fieldName)) {
+            if ($this->getBackendUser()->isAdmin() || $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['exclude'] == 0 || GeneralUtility::inList($this->getBackendUser()->groupData['non_exclude_fields'], $parameter->table . ':' . $fieldName)) {
                 // call diff class only if there is a difference
                 if ($configuration['type'] === 'inline' && $configuration['foreign_table'] === 'sys_file_reference') {
                     $useThumbnails = false;
@@ -216,8 +212,8 @@ class ExtDirectServer extends AbstractHandler
                     );
 
                     if ($configuration['type'] == 'group' && $configuration['internal_type'] == 'file') {
-                        $versionThumb = BackendUtility::thumbCode($versionRecord, $parameter->table, $fieldName);
-                        $liveThumb = BackendUtility::thumbCode($liveRecord, $parameter->table, $fieldName);
+                        $versionThumb = BackendUtility::thumbCode($versionRecord, $parameter->table, $fieldName, '');
+                        $liveThumb = BackendUtility::thumbCode($liveRecord, $parameter->table, $fieldName, '');
                         $diffReturnArray[] = array(
                             'field' => $fieldName,
                             'label' => $fieldTitle,
@@ -254,6 +250,22 @@ class ExtDirectServer extends AbstractHandler
             }
         }
         $commentsForRecord = $this->getCommentsForRecord($parameter->uid, $parameter->table);
+
+        /** @var $historyService HistoryService */
+        $historyService = GeneralUtility::makeInstance(HistoryService::class);
+        $history = $historyService->getHistory($parameter->table, $parameter->t3ver_oid);
+
+        $prevStage = $stagesService->getPrevStage($parameter->stage);
+        $nextStage = $stagesService->getNextStage($parameter->stage);
+
+        if (isset($prevStage[0])) {
+            $prevStage = current($prevStage);
+        }
+
+        if (isset($nextStage[0])) {
+            $nextStage = current($nextStage);
+        }
+
         return array(
             'total' => 1,
             'data' => array(
@@ -265,11 +277,21 @@ class ExtDirectServer extends AbstractHandler
                     'icon_Workspace' => $icon_Workspace,
                     // this part is already escaped in getCommentsForRecord()
                     'comments' => $commentsForRecord,
-                    // escape/santinize the others
-                    'path_Live' => htmlspecialchars($parameter->path_Live),
-                    'label_Stage' => htmlspecialchars($parameter->label_Stage),
+                    // escape/sanitize the others
+                    'path_Live' => htmlspecialchars(BackendUtility::getRecordPath($liveRecord['pid'], '', 999)),
+                    'label_Stage' => htmlspecialchars($stagesService->getStageTitle($parameter->stage)),
+                    'label_PrevStage' => $prevStage,
+                    'label_NextStage' => $nextStage,
                     'stage_position' => (int)$stagePosition['position'],
-                    'stage_count' => (int)$stagePosition['count']
+                    'stage_count' => (int)$stagePosition['count'],
+                    'parent' => [
+                        'table' => htmlspecialchars($parameter->table),
+                        'uid' => (int)$parameter->uid
+                    ],
+                    'history' => [
+                        'data' => $history,
+                        'total' => count($history)
+                    ]
                 )
             )
         );
@@ -323,7 +345,7 @@ class ExtDirectServer extends AbstractHandler
         foreach ($candidates as $identifierWithRandomValue => $fileReference) {
             if ($useThumbnails) {
                 $thumbnailFile = $fileReference->getOriginalFile()->process(
-                    \TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGEPREVIEW,
+                    ProcessedFile::CONTEXT_IMAGEPREVIEW,
                     array('width' => 40, 'height' => 40)
                 );
                 $thumbnailMarkup = '<img src="' . $thumbnailFile->getPublicUrl(true) . '" />';
@@ -333,7 +355,7 @@ class ExtDirectServer extends AbstractHandler
             }
         }
 
-        $differences = $this->getDifferenceHandler()->render($liveInformation, $versionInformation);
+        $differences = $this->getDifferenceHandler()->makeDiffDisplay($liveInformation, $versionInformation);
         $liveInformation = str_replace(array_keys($substitutes), array_values($substitutes), trim($liveInformation));
         $differences = str_replace(array_keys($substitutes), array_values($substitutes), trim($differences));
 
@@ -353,14 +375,18 @@ class ExtDirectServer extends AbstractHandler
     public function getCommentsForRecord($uid, $table)
     {
         $sysLogReturnArray = array();
-        $sysLogRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+        $sysLogRows = $this->getDatabaseConnection()->exec_SELECTgetRows(
             'log_data,tstamp,userid',
             'sys_log',
-            'action=6 and details_nr=30 AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_log')
+            'action=6 and details_nr=30 AND tablename=' . $this->getDatabaseConnection()->fullQuoteStr($table, 'sys_log')
                 . ' AND recuid=' . (int)$uid,
             '',
             'tstamp DESC'
         );
+
+        /** @var Avatar $avatar */
+        $avatar = GeneralUtility::makeInstance(Avatar::class);
+
         foreach ($sysLogRows as $sysLogRow) {
             $sysLogEntry = array();
             $data = unserialize($sysLogRow['log_data']);
@@ -370,6 +396,7 @@ class ExtDirectServer extends AbstractHandler
             $sysLogEntry['user_username'] = is_array($beUserRecord) ? htmlspecialchars($beUserRecord['username']) : '';
             $sysLogEntry['tstamp'] = htmlspecialchars(BackendUtility::datetime($sysLogRow['tstamp']));
             $sysLogEntry['user_comment'] = nl2br(htmlspecialchars($data['comment']));
+            $sysLogEntry['user_avatar'] = $avatar->render($beUserRecord);
             $sysLogReturnArray[] = $sysLogEntry;
         }
         return $sysLogReturnArray;
@@ -386,7 +413,7 @@ class ExtDirectServer extends AbstractHandler
         $systemLanguages = array(
             array(
                 'uid' => 'all',
-                'title' => \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('language.allLanguages', 'workspaces'),
+                'title' => LocalizationUtility::translate('language.allLanguages', 'workspaces'),
                 'icon' => $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render()
             )
         );
@@ -408,14 +435,38 @@ class ExtDirectServer extends AbstractHandler
     }
 
     /**
+     * @return BackendUserAuthentication;
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * @return LanguageService;
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * @return DatabaseConnection;
+     */
+    protected function getDatabaseConnection()
+    {
+        return $GLOBALS['TYPO3_DB'];
+    }
+
+    /**
      * Gets the Grid Data Service.
      *
-     * @return \TYPO3\CMS\Workspaces\Service\GridDataService
+     * @return GridDataService
      */
     protected function getGridDataService()
     {
         if (!isset($this->gridDataService)) {
-            $this->gridDataService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\GridDataService::class);
+            $this->gridDataService = GeneralUtility::makeInstance(GridDataService::class);
         }
         return $this->gridDataService;
     }
@@ -423,12 +474,12 @@ class ExtDirectServer extends AbstractHandler
     /**
      * Gets the Stages Service.
      *
-     * @return \TYPO3\CMS\Workspaces\Service\StagesService
+     * @return StagesService
      */
     protected function getStagesService()
     {
         if (!isset($this->stagesService)) {
-            $this->stagesService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
+            $this->stagesService = GeneralUtility::makeInstance(StagesService::class);
         }
         return $this->stagesService;
     }
@@ -436,13 +487,12 @@ class ExtDirectServer extends AbstractHandler
     /**
      * Gets the difference handler, parsing differences based on sentences.
      *
-     * @return \cogpowered\FineDiff\Diff
+     * @return DiffUtility
      */
     protected function getDifferenceHandler()
     {
         if (!isset($this->differenceHandler)) {
-            $granularity = new \cogpowered\FineDiff\Granularity\Word();
-            $this->differenceHandler = new \cogpowered\FineDiff\Diff($granularity);
+            $this->differenceHandler = GeneralUtility::makeInstance(DiffUtility::class);
         }
         return $this->differenceHandler;
     }
index 8edebad..bec3887 100644 (file)
@@ -163,8 +163,10 @@ class GridDataService
                     $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
                     $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
                     $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
+                    $versionArray['value_nextStage'] = (int)$tempStage['uid'];
                     $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
                     $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
+                    $versionArray['value_prevStage'] = (int)$tempStage['uid'];
                     $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath($record['livepid'], '', 999));
                     $versionArray['path_Workspace'] = htmlspecialchars(BackendUtility::getRecordPath($record['wspid'], '', 999));
                     $versionArray['workspace_Title'] = htmlspecialchars(\TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
index c872bb6..4483a6d 100644 (file)
@@ -14,7 +14,9 @@ namespace TYPO3\CMS\Workspaces\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Service for history
@@ -79,9 +81,15 @@ class HistoryService implements \TYPO3\CMS\Core\SingletonInterface
         } else {
             $differences = $this->getDifferences($entry);
         }
+
+        /** @var Avatar $avatar */
+        $avatar = GeneralUtility::makeInstance(Avatar::class);
+        $beUserRecord = BackendUtility::getRecord('be_users', $entry['user']);
+
         return array(
             'datetime' => htmlspecialchars(BackendUtility::datetime($entry['tstamp'])),
             'user' => htmlspecialchars($this->getUserName($entry['user'])),
+            'user_avatar' => $avatar->render($beUserRecord),
             'differences' => $differences
         );
     }
index f54579b..7c95c51 100644 (file)
@@ -318,7 +318,6 @@ class StagesService implements \TYPO3\CMS\Core\SingletonInterface
                 }
                 next($workspaceStageRecs);
             }
-        } else {
         }
         if ($nextStage === false) {
             $nextStage[] = array(
index 45037cc..c888fe6 100644 (file)
                                <source>Workspaces</source>
                        </trans-unit>
                        <trans-unit id="ok">
-                               <source>ok</source>
+                               <source>Ok</source>
                        </trans-unit>
                        <trans-unit id="cancel">
-                               <source>cancel</source>
+                               <source>Cancel</source>
                        </trans-unit>
                        <trans-unit id="chooseMassAction">
-                               <source>choose Mass Action</source>
+                               <source>Choose mass action</source>
+                       </trans-unit>
+                       <trans-unit id="chooseSelectionAction">
+                               <source>Choose selection action</source>
                        </trans-unit>
                        <trans-unit id="chooseAction">
-                               <source>choose Action</source>
+                               <source>Choose staging action</source>
                        </trans-unit>
                        <trans-unit id="item">
                                <source>item</source>
                        <trans-unit id="tooltip.generatePagePreview">
                                <source>Generate page preview links</source>
                        </trans-unit>
+                       <trans-unit id="tooltip.showChanges">
+                               <source>Show changes</source>
+                       </trans-unit>
                        <trans-unit id="previewLink">
                                <source>Preview Link</source>
                        </trans-unit>
                                <source>Changed</source>
                        </trans-unit>
                        <trans-unit id="column.uid">
-                               <source>WS-Id</source>
+                               <source>WS ID</source>
                        </trans-unit>
                        <trans-unit id="column.oid">
-                               <source>Live-Id</source>
+                               <source>Live ID</source>
                        </trans-unit>
                        <trans-unit id="column.workspaceName">
                                <source>Workspace</source>
                        </trans-unit>
                        <trans-unit id="column.livePath">
-                               <source>Live-Path</source>
+                               <source>Live path</source>
                        </trans-unit>
                        <trans-unit id="column.liveTitle">
-                               <source>Live-Title</source>
+                               <source>Live title</source>
                        </trans-unit>
                        <trans-unit id="column.wsSwapColumn">
                                <source>Swap workspace</source>
                        </trans-unit>
+                       <trans-unit id="column.integrity">
+                               <source>Integrity</source>
+                       </trans-unit>
                        <trans-unit id="tooltip.viewElementAction">
                                <source>Preview element</source>
                        </trans-unit>
                        <trans-unit id="window.sendToNextStageWindow.additionalRecipients">
                                <source>Additional recipients</source>
                        </trans-unit>
+                       <trans-unit id="window.sendToNextStageWindow.additionalRecipients.hint">
+                               <source>One recipient per line</source>
+                       </trans-unit>
                        <trans-unit id="window.sendToNextStageWindow.comments">
                                <source>Comments</source>
                        </trans-unit>
+                       <trans-unit id="window.recordChanges">
+                               <source>Record changes</source>
+                       </trans-unit>
+                       <trans-unit id="window.recordInformation">
+                               <source>Information for "{0}"</source>
+                       </trans-unit>
+                       <trans-unit id="window.recordChanges.tabs.changeSummary">
+                               <source>Summary of changes</source>
+                       </trans-unit>
+                       <trans-unit id="window.recordChanges.tabs.comments">
+                               <source>Comments</source>
+                       </trans-unit>
+                       <trans-unit id="window.recordChanges.tabs.history">
+                               <source>History</source>
+                       </trans-unit>
+                       <trans-unit id="window.recordHistory">
+                               <source>History of record "{0}"</source>
+                       </trans-unit>
                        <trans-unit id="error.getStageTitle.stageNotFound">
                                <source>Stage not found</source>
                        </trans-unit>
diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/Empty.html b/typo3/sysext/workspaces/Resources/Private/Layouts/Empty.html
new file mode 100644 (file)
index 0000000..54d9aa0
--- /dev/null
@@ -0,0 +1 @@
+<f:render section="main" />
\ No newline at end of file
index d480d8d..1cf117c 100644 (file)
@@ -1,4 +1,5 @@
 <f:if condition="{pageTitle}"><h1>{pageTitle}</h1></f:if>
-<div id="workspacetabs"></div>
-<div class="well well-sm"><f:render section="main" /></div>
+
+<f:render section="main" />
+
 <f:if condition="{showLegend}"><f:render partial="legend" /></f:if>
\ No newline at end of file
index 18efefc..71351b3 100644 (file)
@@ -1,3 +1,12 @@
+<style type="text/css">
+       .module-body {
+               padding: 0;
+       }
+
+       div.typo3-noDoc {
+               margin: 0;
+       }
+</style>
 <div class="typo3-noDoc">
        <!-- Content of module, for instance listing, info or editing -->
        <div id="typo3-docbody">
index f935b70..8b997bb 100644 (file)
@@ -1,21 +1,4 @@
-<!-- ###FULLDOC### begin -->
 <f:render section="main" />
 <script type="text/javascript">
-       var liveUrl = '{liveUrl}';
-       var wsUrl = '{wsUrl}';
-       var wsSettingsUrl = '{wsSettingsUrl}';
        document.domain = '{backendDomain}';
-
-       function resize(height) {
-                       // poor way to avoid that we require any scrollbars within the frames
-               if (Ext.getCmp('wsContainer')) {
-                       var currentHeight = isNaN(Ext.getCmp('wsContainer').getHeight()) ? 0 : Ext.getCmp('wsContainer').getHeight();
-                       var finalHeight = Math.max(currentHeight, height * 1.1);
-                       Ext.getCmp('visualPanel').setHeight(finalHeight);
-                       Ext.getCmp('wsContainer').setHeight(finalHeight);
-                       Ext.getCmp('wsPanel').setHeight(finalHeight);
-                       Ext.getCmp('liveContainer').setHeight(finalHeight * (100 - Ext.getCmp('sizeSlider').getValue()) / 100);
-                       Ext.getCmp('livePanel').setHeight(finalHeight);
-               }
-       }
 </script>
diff --git a/typo3/sysext/workspaces/Resources/Private/Less/preview.less b/typo3/sysext/workspaces/Resources/Private/Less/preview.less
new file mode 100644 (file)
index 0000000..a10257a
--- /dev/null
@@ -0,0 +1,69 @@
+.module-body {
+       padding: 0;
+}
+
+.typo3-topbar-workspace-actions {
+       float: right;
+       height: 100%;
+       display: table;
+}
+
+.workspace-action {
+       display: table-cell;
+       vertical-align: middle;
+       padding: 0 4px;
+
+       &:last-child {
+               padding-right: 16px;
+       }
+
+       .active-preview-mode {
+               display: inline-block;
+               text-align: left;
+       }
+
+       .slider-wrapper {
+               .slider {
+                       float: left;
+               }
+
+               b {
+                       margin: 7px 10px;
+                       display: block;
+                       float: left;
+               }
+       }
+}
+
+.workspaces {
+       position: relative;
+
+       iframe {
+               border: 0;
+       }
+}
+
+.preview-mode- {
+       &slider {
+               iframe {
+                       position: absolute;
+                       top: 0;
+                       z-index: 100;
+               }
+               #live-view {
+                       border-bottom: 2px solid #c83c3c;
+                       z-index: 200;
+               }
+       }
+
+       &vbox iframe {
+               width: 50%;
+               height: 100%;
+               float: left;
+       }
+
+       &hbox iframe {
+               width: 100%;
+               height: 50%;
+       }
+}
\ No newline at end of file
index 9accc01..fa91e9d 100644 (file)
@@ -1,3 +1,4 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <dl class="legend">
        <dt><f:translate key="legend.label" /></dt>
        <dd><span class="item-state-modified"><f:translate key="legend.edited" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
@@ -5,4 +6,5 @@
        <dd><span class="item-state-new"><f:translate key="legend.new" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
        <dd><span  class="item-state-hidden"><f:translate key="legend.hidden" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
        <dd><span class="item-state-deleted"><f:translate key="legend.deleted" /></span></dd>
-</dl>
\ No newline at end of file
+</dl>
+</html>
\ No newline at end of file
index d7af392..cbc66a9 100644 (file)
@@ -1,3 +1,4 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <ul class="x-tab-strip x-tab-strip-top">
        <f:for each="{workspaceList}" as="workspace" key="uid">
                <f:if condition="{uid}=={activeWorkspaceUid}">
@@ -32,3 +33,4 @@
        </f:if>
        <div class="x-clear"></div>
  </ul>
+</html>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/Preview/StageButtons.html b/typo3/sysext/workspaces/Resources/Private/Partials/Preview/StageButtons.html
new file mode 100644 (file)
index 0000000..5f74f59
--- /dev/null
@@ -0,0 +1,11 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
+<f:if condition="{enablePreviousStageButton}">
+       <input type="button" class="btn btn-sm btn-default" value="{prevStage}" data-stage-id="{prevStageId}" data-action="send-to-stage" data-direction="prev">
+</f:if>
+<f:if condition="{enableNextStageButton}">
+       <input type="button" class="btn btn-sm btn-success" value="{nextStage}" data-stage-id="{nextStageId}" data-action="send-to-stage" data-direction="next">
+</f:if>
+<f:if condition="{enableDiscardStageButton}">
+       <input type="button" class="btn btn-sm btn-danger" value="{f:translate(key: 'label_doaction_discard')}" data-action="discard">
+</f:if>
+</html>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/WorkingTable.html b/typo3/sysext/workspaces/Resources/Private/Partials/WorkingTable.html
new file mode 100644 (file)
index 0000000..d8e0f00
--- /dev/null
@@ -0,0 +1,94 @@
+<html
+       xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
+       xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
+       data-namespace-typo3-fluid="true"
+>
+<div role="tabpanel">
+       <!-- Nav tabs -->
+       <ul class="nav nav-tabs" role="tablist">
+               <f:for each="{workspaceList}" as="workspace">
+                       <li role="presentation" {f:if(condition: '{activeWorkspaceUid} == {workspace.workspaceId}', then: 'class="active"')}><a href="{workspace.triggerUrl}" aria-controls="{workspace.itemId}" role="tab">{workspace.title}</a></li>
+               </f:for>
+       </ul>
+
+       <!-- Tab panes -->
+       <div class="tab-content">
+               <div role="tabpanel" class="tab-pane active">
+                       <div class="form-section" id="workspace-panel">
+                               <form id="workspace-settings-form" class="form-inline form-inline-spaced">
+                                       <div class="form-group">
+                                               <select name="depth" class="form-control" disabled>
+                                                       <option value="0"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_0" /></option>
+                                                       <option value="1"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_1" /></option>
+                                                       <option value="2"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_2" /></option>
+                                                       <option value="3"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_3" /></option>
+                                                       <option value="4"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_4" /></option>
+                                                       <option value="999"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_infi" /></option>
+                                               </select>
+                                       </div>
+                                       <div class="form-group">
+                                               <div class="input-group">
+                                                       <span class="input-group-addon input-group-icon"></span>
+                                                       <select name="languages" class="form-control" disabled></select>
+                                               </div>
+                                       </div>
+                                       <div class="form-group">
+                                               <div class="input-group">
+                                                       <input class="form-control t3js-clearable" type="text" name="search-text">
+                                                       <span class="input-group-btn">
+                                                               <button type="submit" class="btn btn-default disabled"><core:icon identifier="actions-search" /></button>
+                                                       </span>
+                                               </div>
+                                       </div>
+                               </form>
+                               <div class="hidden" id="workspace-action-icons">
+                                       <f:comment>We pre-render the required icons that are used in the Workspaces module until we're able to use StandaloneView of Fluid</f:comment>
+                                       <core:icon identifier="empty-empty" size="small" />
+                                       <core:icon identifier="actions-version-workspace-preview" size="small" />
+                                       <core:icon identifier="actions-version-document-remove" size="small" />
+                                       <core:icon identifier="actions-version-page-open" size="small" />
+                                       <core:icon identifier="actions-version-swap-version" size="small" />
+                                       <core:icon identifier="actions-open" size="small" />
+                                       <core:icon identifier="actions-document-info" size="small" />
+                                       <core:icon identifier="apps-pagetree-expand" size="small" />
+                                       <core:icon identifier="apps-pagetree-collapse" size="small" />
+                               </div>
+                               <table class="table table-striped">
+                                       <thead>
+                                       <tr>
+                                               <th><f:translate key="column.wsTitle" /></th>
+                                               <th><f:translate key="column.liveTitle" /></th>
+                                               <th><f:translate key="column.stage" /></th>
+                                               <th><f:translate key="column.integrity" /></th>
+                                               <th><core:icon identifier="flags-multiple" size="small" /></th>
+                                               <th class="text-right">
+                                                       <button type="button" class="btn btn-default t3js-toggle-all"><span class="t3-icon fa fa-check-square-o"></span></button>
+                                               </th>
+                                       </tr>
+                                       </thead>
+                                       <tbody>
+                                       </tbody>
+                               </table>
+                               <form id="workspace-actions-form" class="form-inline form-inline-spaced">
+                                       <div class="form-group">
+                                               <select name="stage-action" class="form-control" disabled>
+                                                       <option value=""><f:translate key="chooseAction" /></option>
+                                               </select>
+                                       </div>
+                                       <div class="form-group">
+                                               <select name="selection-action" class="form-control" disabled>
+                                                       <option value=""><f:translate key="chooseSelectionAction" /></option>
+                                               </select>
+                                       </div>
+                                       <div class="form-group">
+                                               <select name="mass-action" class="form-control" disabled>
+                                                       <option value=""><f:translate key="chooseMassAction" /></option>
+                                               </select>
+                                       </div>
+                               </form>
+                               <nav id="workspace-pagination"></nav>
+                       </div>
+               </div>
+       </div>
+</div>
+</html>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Ajax/StageButtons.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Ajax/StageButtons.html
new file mode 100644 (file)
index 0000000..fac1cc3
--- /dev/null
@@ -0,0 +1,3 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
+<f:render partial="Preview/StageButtons" arguments="{_all}" />
+</html>
\ No newline at end of file
index 9b56cff..1dd38b7 100644 (file)
@@ -1,3 +1,5 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="nodoc" />
 
-<f:section name="main">Help contents - not yet defined</f:section>
\ No newline at end of file
+<f:section name="main">Help contents - not yet defined</f:section>
+</html>
\ No newline at end of file
index 29d3534..60a1162 100644 (file)
@@ -1,3 +1,66 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="popup" />
 
-<f:section name="main"></f:section>
\ No newline at end of file
+<f:section name="main">
+       <div id="typo3-topbar">
+               <div class="typo3-topbar-container" role="navigation" id="typo3-top-container">
+                       <div class="typo3-topbar-site">
+                               <a class="typo3-topbar-site-logo" href="{logoLink}" target="_blank">
+                                       <img src="{logoUrl}" width="{logoWidth}" height="{logoHeight}" title="TYPO3 Content Management System" alt="">
+                               </a>
+                               <span class="typo3-topbar-site-name">{activeWorkspace}</span>
+                       </div>
+                       <div class="typo3-topbar-tabs t3js-workspace-tabs" style="float: left; height: 100%;">
+                               <ul class="nav nav-tabs" role="tablist" style="position: absolute; bottom: 0">
+                                       <li role="presentation" class="active"><a href="#visual" aria-controls="visual" role="tab" data-toggle="tab" data-actions="true"><f:translate key="preview.visualPreview" /></a></li>
+                                       <li role="presentation"><a href="#list" aria-controls="list" role="tab" data-toggle="tab" data-actions="false"><f:translate key="preview.listView" /></a></li>
+                               </ul>
+                       </div>
+                       <div class="typo3-topbar-workspace-actions t3js-workspace-actions">
+                               <div class="workspace-action">
+                                       <div class="slider-wrapper">
+                                               <b>Live</b><div
+                                               id="workspace-stage-slider"
+                                               data-slider-min="0"
+                                               data-slider-max="100"
+                                               data-slider-value="100"
+                                               style="width: 150px;"
+                                       ></div>
+                                               <b>Workspace</b>
+                                       </div>
+                               </div>
+                               <div class="workspace-action t3js-stage-buttons">
+                                       <f:render partial="Preview/StageButtons" arguments="{_all}"/>
+                               </div>
+                               <div class="workspace-action t3js-preview-mode">
+                                       <div class="btn-group">
+                                               <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                       <span class="t3js-active-preview-mode active-preview-mode" data-active-preview-mode="{firstPreviewMode}"><f:translate key="preview.mode{firstPreviewMode -> f:format.case(mode: 'capital')}" /></span> <span class="caret"></span>
+                                               </button>
+                                               <ul class="dropdown-menu dropdown-menu-right">
+                                                       <f:for each="{splitPreviewModes}" as="mode">
+                                                               <li><a href="#" data-preview-mode="{mode}"><span><f:translate key="preview.mode{mode -> f:format.case(mode: 'capital')}" /></span></a></li>
+                                                       </f:for>
+                                               </ul>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+
+       <div role="tabpanel" class="workspace-panel">
+               <div class="tab-content">
+                       <div role="tabpanel" class="tab-pane active workspaces preview-mode-slider" id="visual">
+                               <div class="t3js-workspace-preview">
+                                       <iframe src="{liveUrl}" style="height: 0px;" id="live-view"></iframe>
+                                       <iframe src="{wsUrl}" id="workspace-view"></iframe>
+                               </div>
+                       </div>
+                       <div role="tabpanel" class="tab-pane workspaces" id="list">
+                               <iframe src="{wsSettingsUrl}" id="workspace-list"></iframe>
+                       </div>
+               </div>
+
+       </div>
+</f:section>
+</html>
\ No newline at end of file
index 41fac08..6207ef5 100644 (file)
@@ -1,3 +1,5 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="nodoc" />
 
 <f:section name="main"></f:section>
+</html>
\ No newline at end of file
index 2629a2a..0c16f8d 100644 (file)
@@ -1,47 +1,4 @@
 <script type="text/javascript">
-
-               // @todo redirect to split module if this is opened standalone
-
-               // having this is very important, otherwise the parent.resize call will fail
+       // having this is very important, otherwise the parent.resize call will fail
        document.domain = '{backendDomain}';
-
-       var asNumber = function(val) {
-               return isNaN(val) ? 0 : parseInt(val, 10);
-       };
-       var TYPO3 = TYPO3 || {};
-       TYPO3.ready = function () {
-               // make sure we're in the workspace preview module
-               if (typeof parent.resize == 'function') {
-                               // try to find the height of the document
-                       var docHeight = Math.max(
-                               asNumber(window.innerHeight),
-                               asNumber(document.height),
-                               asNumber(document.body.scrollHeight),
-                               asNumber(document.body.offsetHeight),
-                               asNumber(document.body.clientHeight),
-                               asNumber(document.documentElement.scrollHeight),
-                               asNumber(document.documentElement.offsetHeight),
-                               asNumber(document.documentElement.clientHeight)
-                       );
-                       parent.resize(docHeight);
-                               // remove the ugly red box if we're in the ws-repview frames
-                       var element = document.getElementById('typo3-previewInfo');
-                       if (element) {
-                               element.parentNode.removeChild(element);
-                       }
-               }
-       };
-               // trigger this after content is loaded, inspired by jQuery
-       if (document.addEventListener && !/opera/.test(navigator.userAgent.toLowerCase())) {
-               document.addEventListener("DOMContentLoaded", TYPO3.ready, false);
-       } else {
-               (function() {
-                       if (document.readyState != "loaded" && document.readyState != "complete") {
-                               setTimeout(arguments.callee, 10);
-                       } else {
-                               TYPO3.ready();
-                       }
-               })();
-       }
-
-</script>
+</script>
\ No newline at end of file
index e80d90f..8350fe2 100644 (file)
@@ -1,5 +1,9 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="module" />
 
 <f:section name="main">
-<div id="workspacegrid"></div>
-</f:section>
\ No newline at end of file
+
+       <f:render partial="WorkingTable" arguments="{_all}" />
+
+</f:section>
+</html>
\ No newline at end of file
index a151608..07863b1 100644 (file)
@@ -1,18 +1,23 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="module" />
 
 <f:section name="main">
 
-<f:if condition="{performWorkspaceSwitch}">
-<script type="text/javascript">
-       top.TYPO3.ModuleMenu.App.refreshMenu();
-       top.TYPO3.WorkspacesMenu.performWorkspaceSwitch({activeWorkspaceUid}, "{activeWorkspaceTitle}");
-</script>
-</f:if>
+       <f:if condition="{performWorkspaceSwitch}">
+               <script type="text/javascript">
+                       top.TYPO3.ModuleMenu.App.refreshMenu();
+                       top.TYPO3.WorkspacesMenu.performWorkspaceSwitch({activeWorkspaceUid}, "{activeWorkspaceTitle}");
+               </script>
+       </f:if>
 
-<f:if condition="{showGrid}">
-       <f:then><div id="workspacegrid"></div>
-       </f:then>
-       <f:else><f:translate key="editorInLive" /></f:else>
-</f:if>
+       <f:if condition="{showGrid}">
+               <f:then>
+                       <f:render partial="WorkingTable" arguments="{_all}" />
+               </f:then>
+               <f:else>
+                       <f:be.infobox message="{f:translate(key: 'editorInLive')}" state="-1" />
+               </f:else>
+       </f:if>
 
-</f:section>
\ No newline at end of file
+</f:section>
+</html>
\ No newline at end of file
index a76244a..8a521e2 100644 (file)
@@ -1,5 +1,9 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="nodoc" />
 
 <f:section name="main">
-       <div id="workspacegrid"></div>
-</f:section>
\ No newline at end of file
+
+       <f:render partial="WorkingTable" arguments="{_all}" />
+
+</f:section>
+</html>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/Css/module.css b/typo3/sysext/workspaces/Resources/Public/Css/module.css
deleted file mode 100644 (file)
index b79e023..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-ul.x-tab-strip.x-tab-strip-top {
-       margin-bottom: 0;
-       margin-top: 0;
-       padding-left: 0;
-}
-ul.x-tab-strip.x-tab-strip-top li.last {
-       float: right;
-}
-span.item-state-modified {
-       color: #f78f25;
-}
-span.item-state-moved {
-       color: #457fb8;
-}
-span.item-state-new {
-       color: #3c9934;
-}
-span.item-state-hidden {
-       color: #abaaaa;
-}
-span.item-state-deleted {
-       color: #000000;
-       text-decoration: line-through;
-}
-.legend {
-       margin: 5px;
-       height: 18px;
-       color: #888888;
-}
-.legend dd, .legend dt {
-       display: inline;
-       overflow: hidden;
-}
-.legend dd span {
-       display: inline-block;
-       padding: 4px 4px;
-}
-.x-toolbar {
-       background:none !important;
-}
-.x-grid3 {
-       background: none !important;
-}
-.x-grid3-row-expanded {
-       background-color: #ececec;
-}
-.x-grid3-row-selected {
-       color: #4D4D4D;
-}
-#typo3-mod-php div.typo3-noDoc {
-       margin: 0px 0px;
-}
-#typo3-mod-php div.typo3-noDoc #typo3-docbody {
-       padding: 0px 0px;
-       top: 0px;
-}
-.icon-hidden {
-       display: none;
-       visibility: hidden;
-}
-div.x-grid3-row img.x-action-col-icon {
-       display:none;
-}
-
-div.x-grid3-row-over img.x-action-col-icon,
-div.x-grid3-row img.x-action-col-icon.t3-visible {
-       display:inline-block;
-}
-
-div.t3-workspaces-foldoutWrapper {
-       padding: 10px;
-       margin-left: 40px;
-       background-color: #FFFFFF;
-       background-image: linear-gradient(center top, #ececec 0px, #f7f7f7 200px);
-       background-repeat: repeat-x;
-       color: #606060;
-}
-.x-grid3-row-selected div.t3-workspaces-foldoutWrapper {
-       background-image: linear-gradient(center top, #dedede 0px, #f7f7f7 200px);
-       background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper table {
-       border-collapse: collapse;
-       width: 100%;
-}
-div.t3-workspaces-foldoutWrapper tr.header {
-       background-color: #B5B5B5;
-       background-image: linear-gradient(center top, #7f7f7f 10%, #5b5b5b 100%);
-       background-repeat: repeat-x;
-}
-div.t3-workspaces-foldoutWrapper tr.header th {
-       padding: 4px 8px;
-       color: #FFFFFF;
-       line-height: 15px;
-}
-
-.t3-workspaces-foldout-subheaderLeft,
-.t3-workspaces-foldout-subheaderRight {
-       padding: 15px 0 10px 0;
-       margin: 10px;
-}
-.t3-workspaces-foldout-td-contentDiffLeft .t3-workspaces-foldout-contentDiff-container,
-.t3-workspaces-foldout-td-contentDiffRight .t3-workspaces-foldout-contentDiff-container {
-       padding-top: 10px;
-       border-top: 1px solid #cdcdcd;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffLeft,
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft {
-       padding-right: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-td-contentDiffRight,
-.x-grid3-row .t3-workspaces-foldout-subheaderRight {
-       padding-left: 10px;
-}
-.x-grid3-row .t3-workspaces-foldout-subheaderLeft .t3-workspaces-foldout-subheader-container {
-       padding-bottom: 10px;
-       border-bottom: 1px solid #cdcdcd;
-}
-table.t3-workspaces-foldout-contentDiff  {
-       padding: 8px 8px;
-       table-layout: auto;
-       background-color: #ffffff;
-}
-table.t3-workspaces-foldout-contentDiff th {
-       padding: 8px 0 8px 8px;
-       width: 80px;
-}
-table.t3-workspaces-foldout-contentDiff td {
-       padding: 8px 8px 8px 0;
-       line-height: 1.3em;
-}
-table.t3-workspaces-foldout-contentDiff .diff-r {
-       text-decoration: line-through;
-}
-.t3-workspaces-foldout-contentDiff .content ins > img {
-       padding: 1px;
-       margin-right: 2px;
-       border: 2px solid green;
-}
-.t3-workspaces-foldout-contentDiff .content del > img {
-       padding: 1px;
-       margin-right: 2px;
-       border: 2px solid red;
-}
-div.t3-workspaces-foldoutWrapper td.char_select_profile_stats {
-       padding-right: 10px;
-}
-div.t3-workspaces-comments {
-       background-color: #dedede;
-       padding: 10px 10px 10px 10px;
-}
-div.t3-workspaces-comments-singleComment {
-       overflow: hidden;
-       margin-bottom: 10px;
-       position: relative;
-}
-div.t3-workspaces-comments-singleComment:last-child {
-       margin: 0;
-}
-div .t3-workspaces-comments-singleComment-author {
-       width: 60px;
-       margin: 10px 0;
-       position: absolute;
-       left: 0px;
-       top: 0px;
-       font-weight: bold;
-       overflow: hidden;
-}
-div .t3-workspaces-comments-singleComment-content-wrapper {
-       background: url(../Images/workspaces-comments-arrow.gif) left 10px no-repeat;
-       margin-left: 70px;
-}
-div .t3-workspaces-comments-singleComment-content-date {
-       font-size: 10px;
-}
-div .t3-workspaces-comments-singleComment-content {
-       background-color: #ffffff;
-       padding: 10px 10px;
-       margin-left: 10px;
-}
-div .t3-workspaces-comments-singleComment-content-title {
-       padding: 8px 0 8px 0;
-       font-weight: bold;
-}
-
-.typo3-workspaces-row-disabled .x-grid3-td-checker {
-       visibility: hidden;
-}
-
-div.x-grid3-row img.t3-icon-extensions-workspaces {
-       display: inline-block !important;
-       width: 17px;
-       visibility: hidden;
-}
-
-div.x-grid3-row-over img.t3-icon-extensions-workspaces {
-       visibility: visible;
-}
-
-img.t3-icon-workspaces-sendtonextstage {
-       background: url(../Images/version-workspace-sendtonextstage.png) no-repeat;
-}
-
-img.t3-icon-workspaces-sendtoprevstage {
-       background: url(../Images/version-workspace-sendtoprevstage.png) no-repeat;
-}
-
-table.t3-workspaces-foldout-contentDiff td.content {
-       word-break: break-all;
-}
-
-.typo3-workspaces-collection-level-node,
-.typo3-workspaces-collection-level-leaf,
-.typo3-workspaces-collection-level-none {
-       float: left;
-       width: 18px;
-}
-
-.x-grid3-row-expander {
-       background-position: left top;
-       float: left;
-}
-.x-grid3-row-collapsed .x-grid3-row-expander {
-       background-position: left top;
-       background-image: url('../Images/zoom_in.png');
-}
-.x-grid3-row-expanded .x-grid3-row-expander {
-       background-position: left top;
-       background-image: url('../Images/zoom_out.png');
-}
-
-.typo3-workspaces-collection-child-collapsed {
-       display: none;
-}
-
-.typo3-workspaces-collection-child-expanded {
-       display: block;
-}
-
-.typo3-workspaces-collection-level-node {
-       width: 18px;
-       height: 18px;
-       background-position: 4px 2px;
-       background-color: transparent;
-       background-repeat: no-repeat;
-       background-image: url('../../../../t3skin/extjs/images/grid/row-expand-sprite.png');
-}
-.typo3-workspaces-collection-parent-collapsed .typo3-workspaces-collection-level-node {
-       background-position: 4px 2px;
-}
-.typo3-workspaces-collection-parent-expanded .typo3-workspaces-collection-level-node {
-       background-position: -21px 2px;
-}
-
-.x-menu.typo3-workspaces-menu {
-       background-image: none;
-}
-.x-menu.typo3-workspaces-menu a.x-menu-item {
-       padding: 3px 14px;
-}
-
-.x-tab-menu-right {
-       background: #dadada;
-       border-color: #adadad;
-       border-style: solid;
-       border-width: 1px;
-       border-top-left-radius: 3px;
-       border-top-right-radius: 3px;
-       color: #666666;
-       background-position: center 6px;
-       padding: 4px;
-
-       background-image: url('../Images/menu.png');
-       background-repeat: no-repeat;
-       width: 16px;
-       position: absolute;
-       right: 0;
-       top: 0;
-       z-index: 10;
-       cursor:pointer;
-}
-
-.t3-workspaces-foldoutWrapper .char_select_profile_titleLeft .icon,
-.t3-workspaces-foldoutWrapper .char_select_profile_titleRight .icon{
-       float: left;
-}
-
-.t3-workspaces-foldoutWrapper .t3-workspaces-foldout-contentDiff {
-       text-align: left;
-}
index 76fcadd..257bca2 100644 (file)
-/**
- * Top bar
+/*!
+ * 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!
  */
-
-/**
- * Tabs
- */
-
-.x-btn button {
-       font-size: 12px;
-}
-
-/* panel containing the tabs */
-.x-tab-panel-header {
-       background-color: #3f3f3f;
-       background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/topbar.png');
-       background-image: linear-gradient(center top, #494949 0%, #373737 91%, #343434 92%, #2A2A2A 100%);
-       background-repeat: repeat-x;
-       border: none;
-       padding-bottom: 0;
-       padding-top: 9px;
-}
-
-.x-tab-strip-wrap {
-       background: url('../Images/typo3-logo.png') no-repeat 16px 0;
-       padding-left: 140px;
-}
-
-/* normal tab */
-ul.x-tab-strip li {
-       margin-left: 4px;
-}
-
-ul.x-tab-strip-top {
-       border-bottom: 0;
-}
-
-/* reset ExtJS "no skin" nonsense */
-.x-tab-strip-top .x-tab-right,
-.x-tab-strip-top .x-tab-strip-over .x-tab-right {
-       background-position: 0 0;
-}
-
-/* we don't need the active tab to be 1px below the inactive */
-.x-tab-strip-top .x-tab-strip-active .x-tab-right {
-       margin-bottom: 0;
-}
-
-.x-tab-strip span.x-tab-strip-text,
-.x-tab-strip-top .x-tab-strip-active .x-tab-right span.x-tab-strip-text {
-       padding-bottom: 4px;
-}
-
-/* normal tab styling */
-.x-tab-strip .x-tab-right {
-       background-color: #707171;
-       background-image: linear-gradient(center top, #707171 0%, #474747 85%, #363636 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#707171', EndColorStr='#474747')"; /* IE8 */
-       border-top-left-radius: 3px;
-       border-top-right-radius: 3px;
-}
-
-/* container surrounding text */
-.x-tab-strip-inner {
-       padding: 4px;
-}
-
-/* hover tab */
-.x-tab-strip-over .x-tab-right {
-       background-color: #707171;
-       background-image: linear-gradient(center top, #888888 0%, #474747 85%, #363636 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#888888', EndColorStr='#474747')"; /* IE8 */
-}
-
-/* active tab */
-.x-tab-strip-active .x-tab-right {
-       background-color: #989898;
-       background-image: linear-gradient(center top, #989898 0%, #6c6c6c 85%, #474747 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#989898', EndColorStr='#6c6c6c')"; /* IE8 */
-}
-
-/* tab label */
-.x-tab-strip span.x-tab-strip-text {
-       color: #ffffff;
-}
-
-/* text in tabs and buttons should not be italic.. why should it be? */
-.x-tab-strip em,
-.x-btn-text {
-       font-style: normal;
-}
-
-/**
- * Slider
- */
-#controls {
-       padding-top: 5px;
-}
-#slider {
-       padding-top: 5px;
-}
-
-/* remove default ExtJS border */
-.x-panel-body {
-       border: none;
-}
-
-.x-slider-horz .x-slider-thumb {
-       background-image: url('../Images/slider-thumb.png');
-       height: 21px;
-       padding: 0 2px;
-       top: 0;
-       width: 7px;
-}
-
-.x-slider-horz .x-slider-thumb-over, .x-slider-horz .x-slider-thumb-drag {
-       background-position: -7px -21px;
-}
-
-.x-slider-horz .x-slider-inner {
-       background-image: url('../Images/slider-bg.png');
-}
-
-#visual-mode-selector {
-       list-style: none;
-       background-color: #f9f9f9;
-       border: 1px solid #abb2bc;
-       border-top: none;
-}
-
-#visual-mode-selector td {
-       text-align: left;
-}
-
-#visual-mode-selector td button {
-       text-decoration: none;
-}
-
-#visual-mode-options {
-       display: block;
-       height: 20px;
-       margin: 0px 0 0 10px;
-}
-#visual-mode-options.x-btn-menu-active {
-       background-color: #f9f9f9;
-       border: 1px solid #abb2bc;
-       border-bottom: none;
-}
-#visual-mode-options .x-btn-arrow {
-       padding-right: 2px;
-}
-
-#visual-mode-options.x-btn-menu-active .x-btn-text {
-       color: black;
-}
-
-#visual-mode-toolbar {
-       border:none;
-}
-
-/**
- * Preview panel
- */
-.x-panel-body-noheader {
-       border-top: 0;
-}
-
-.x-tip {
-       background-color: #ffffc7;
-       border: 1px solid #cccccc;
-       box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3);
-}
-
-/* the mask for dialogs */
-.ext-el-mask {
-       background-color: #000000;
-       opacity: 0.75;
-       filter:alpha(opacity=75);
-}
-
-.x-mask-loading {
-       border: none;
-}
-
-.x-mask-loading div {
-       background-image: url("../../../../t3skin/images/spinner/big-f0f0f0.gif");
-       background-position: top center;
-       border: none;
-       color: #828282;
-       padding-top: 40px;
-}
-
-.x-window-tc {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/top-bottom.png);
-}
-.x-window-tl {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-corners.png);
-}
-.x-window-tr {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/right-corners.png);
-}
-.x-window-bc {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/top-bottom.png);
-}
-.x-window-bl {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-corners.png);
-}
-.x-window-br {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/right-corners.png);
-}
-
-.x-window-ml {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-right.png);
-}
-.x-window-mr {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-right.png);
-}
-
-.x-window-mc {
-       border:1px solid #A2AAB8;
-       background:#e8e8e8;
-}
-
-.x-window-tl .x-window-header {
-    color: #FFFFFF;
-    font: bold 10px verdana,arial,tahoma,verdana,sans-serif;
-    padding: 4px 0;
-}
-.x-window-draggable, .x-window-draggable .x-window-header-text {
-    cursor: move;
-}
-
-.x-window-tl .x-window-header {
-       color:#fff;
-       font:bold 10px verdana, arial,tahoma,verdana,sans-serif;
-       padding:4px 0 4px 0;
-}
-
-.x-window-mc {
-       border-color:#A2AAB8;
-       font: normal 10px verdana, arial,tahoma,helvetica,sans-serif;
-       background-color:#e7e7e7;
-}
-
-.x-window-maximized .x-window-tc {
-       background-color:#fff;
-}
-
-.x-window-bbar .x-toolbar {
-       border-top-color:#A2AAB8;
-}
-
-.x-form-text, textarea.x-form-field {
-    background-color: #FFFFFF;
-    background-image: none;
-    border-color: #B5B8C8;
-}
-
-.x-btn {
-       color: #FFF;
-}
-.t3-icon-system-options-view {
-       float: right;
-}
-#feToolbarButtonNextStage.x-btn, #feToolbarButtonPreviousStage.x-btn, #feToolbarButtonDiscardStage.x-btn {
-       background: url('../Images/button_approve.png') #55A245 repeat-x;
-       border: 1px solid #7c7c7c;
-       border-radius: 1px;
-       margin-right: 10px;
-       margin-top:0px;
-}
-#feToolbarButtonNextStage.x-btn .x-btn-text, #feToolbarButtonPreviousStage.x-btn .x-btn-text, #feToolbarButtonDiscardStage.x-btn .x-btn-text {
-       color: #FFF;
-       line-height: 8px;
-       height: 13px;
-       padding: 0 3px 0 3px;
-}
-#feToolbarButtonPreviousStage.x-btn .x-btn-text {
-       color:#7c7c7c;
+.module-body {
+  padding: 0;
 }
-#feToolbarButtonPreviousStage.x-btn {
-       background-color: #D5D5D5;
-       background-image: url('../Images/button_reject.png');
+.typo3-topbar-workspace-actions {
+  float: right;
+  height: 100%;
+  display: table;
 }
-#feToolbarButtonDiscardStage.x-btn {
-       background-color: #CD0505;
-       background-image: url('../Images/button_discard.png');
+.workspace-action {
+  display: table-cell;
+  vertical-align: middle;
+  padding: 0 4px;
 }
-#sendToStageWindow .x-btn {
-       background-color: #d5d5d5;
-       background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/button.png');
-       background-repeat: repeat-x;
-       background-image: linear-gradient(center top, #f6f6f6 10%, #d5d5d5 90%);
-       border: 1px solid #7c7c7c;
-       border-radius: 1px;
-       color: #434343;
+.workspace-action:last-child {
+  padding-right: 16px;
 }
-#sendToStageWindow .x-btn-pressed,
-#sendToStageWindow .x-btn-over,
-#sendToStageWindow .x-btn-icon.x-btn-over {
-       background-color: #bdbcbc;
-       background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/button-hover.png');
-       background-image: linear-gradient(center top, #f6f6f6 10%, #bdbcbc 90%);
-       border-color: #737f91;
-       color: #1e1e1e;
+.workspace-action .active-preview-mode {
+  display: inline-block;
+  text-align: left;
 }
-
-#sendToStageWindow .x-btn-over .x-btn-mc em.x-btn-split,
-#sendToStageWindow .x-btn-click .x-btn-mc em.x-btn-split,
-#sendToStageWindow .x-btn-menu-active .x-btn-mc em.x-btn-split,
-#sendToStageWindow .x-btn-pressed .x-btn-mc em.x-btn-split {
-       background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/button/s-arrow-o.gif);
+.workspace-action .slider-wrapper .slider {
+  float: left;
 }
-
-.x-tool {
-    background-image: url("../../../../../../typo3/sysext/t3skin/extjs/images/panel/tool-sprites.gif");
+.workspace-action .slider-wrapper b {
+  margin: 7px 10px;
+  display: block;
+  float: left;
 }
-
-.x-tool-close {
-    background-position: 0 0;
+.workspaces {
+  position: relative;
 }
-.x-tool-close-over {
-    background-position: -15px 0;
+.workspaces iframe {
+  border: 0;
 }
-
-/* text */
-.x-btn.sliderButton .x-btn-text {
-    color: #A0A0A0;
-    font-style: normal;
+.preview-mode-slider iframe {
+  position: absolute;
+  top: 0;
+  z-index: 100;
 }
-
-/* alignment of text in Button "Live" */
-#sizeSliderButtonLive .x-btn-mc {
-       text-align: right;
+.preview-mode-slider #live-view {
+  border-bottom: 2px solid #c83c3c;
+  z-index: 200;
 }
-
-/* alignment of text in Button "Workspace" */
-#sizeSliderButtonWorkspace .x-btn-mc {
-       text-align: left;
+.preview-mode-vbox iframe {
+  width: 50%;
+  height: 100%;
+  float: left;
 }
-.x-panel-header {
-       border: none;
-       font-weight: bold;
-       padding-left:0px;
+.preview-mode-hbox iframe {
+  width: 100%;
+  height: 50%;
 }
-
-.x-window-dlg .x-btn {
-     background-color: #D5D5D5;
-     background-image: linear-gradient(center top , #F6F6F6 10%, #D5D5D5 90%);
-     border-radius: 1px 1px 1px 1px;
-     border: 1px solid #7C7C7C;
-     color: #434343;
-}
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Backend.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Backend.js
new file mode 100644 (file)
index 0000000..bbea867
--- /dev/null
@@ -0,0 +1,1191 @@
+/*
+/*
+ * 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!
+ */
+
+/**
+ * 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',
+       'nprogress',
+       'TYPO3/CMS/Backend/jquery.clearable'
+], function($, Workspaces, Tooltip, Severity, Modal, Wizard, 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: {
+                       depth: TYPO3.settings.Workspaces.depth,
+                       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: []
+       };
+
+       Backend.initialize = function() {
+               Backend.getElements();
+               Backend.registerEvents();
+
+               if (TYPO3.settings.Workspaces.depth > 0) {
+                       Backend.elements.$depthSelector.val(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.sendExtDirectRequest(
+                                               Workspaces.generateExtDirectActionsPayload('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));
+               });
+
+               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 $me = $(this);
+                       Backend.settings.depth = $me.val();
+
+                       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.sendExtDirectRequest([
+                               Workspaces.generateExtDirectActionsPayload('saveLanguageSelection', [$me.val()]),
+                               Workspaces.generateExtDirectPayload('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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectActionsPayload(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.sendExtDirectRequest([
+                                               Workspaces.generateExtDirectActionsPayload(stageExecuteAction, [serializedForm]),
+                                               Workspaces.generateExtDirectPayload('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.sendExtDirectRequest([
+                       Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings),
+                       Workspaces.generateExtDirectPayload('getStageActions', {}),
+                       Workspaces.generateExtDirectMassActionsPayload('getMassStageActions', {}),
+                       Workspaces.generateExtDirectPayload('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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectPayload('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.swap']).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']),
+                               $('<label />', {class: 'btn btn-default btn-checkbox'}).append(
+                                       $('<input />', {type: 'checkbox'}),
+                                       $('<span />', {class: 't3-icon fa'})
+                               )
+                       );
+
+                       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 />', {colspan: 6}).text(Backend.latestPath)
+                                       )
+                               );
+                       }
+
+                       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 />', {class: 't3js-title-workspace'}).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: '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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectPayload('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.show(
+                               TYPO3.lang['window.recordInformation'].replace('{0}', $.trim($tr.find('.t3js-title-live').text())),
+                               $content,
+                               Severity.info,
+                               modalButtons
+                       );
+               });
+       };
+
+       /**
+        * Opens a record in a preview window
+        *
+        * @param {Event} e
+        */
+       Backend.openPreview = function(e) {
+               var $tr = $(e.target).closest('tr');
+
+               Workspaces.sendExtDirectRequest(
+                       Workspaces.generateExtDirectActionsPayload('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.sendExtDirectRequest([
+                                       Workspaces.generateExtDirectActionsPayload('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.sendExtDirectRequest(
+                               Workspaces.generateExtDirectActionsPayload('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
+               );
+               Wizard.addFinalProcessingSlide(function() {
+                       Workspaces.sendExtDirectRequest(
+                               Workspaces.generateExtDirectMassActionsPayload(massAction, {
+                                       init: true,
+                                       total: 0,
+                                       processed: 0,
+                                       language: Backend.settings.language,
+                                       swap: doSwap
+                               })
+                       ).done(function(response) {
+                               var payload = response[0].result;
+                               Workspaces.sendExtDirectRequest(
+                                       Workspaces.generateExtDirectMassActionsPayload(massAction, payload)
+                               ).done(function() {
+                                       Backend.getWorkspaceInfos();
+                                       Wizard.dismiss();
+                               });
+                       });
+               }).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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectActionsPayload('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.sendExtDirectRequest([
+                                               Workspaces.generateExtDirectActionsPayload('sendToSpecificStageExecute', [serializedForm]),
+                                               Workspaces.generateExtDirectPayload('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.
+        * This method is intended to be dropped once we don't the ExtDirect stuff anymore.
+        *
+        * @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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectActionsPayload('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');
+                                       }
+                               }]
+                       );
+               });
+       };
+
+       /**
+        * 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);
+});
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowDetailTemplate.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowDetailTemplate.js
deleted file mode 100644 (file)
index 0a33cc6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-Ext.ns('TYPO3.Workspaces.Component');
-
-TYPO3.Workspaces.Component.RowDetailTemplate = Ext.extend(Ext.XTemplate, {
-       exists: function(o, name) {
-               return typeof o != 'undefined' && o != null && o!='';
-       },
-       hasComments: function(comments){
-               return comments.length>0;
-       }
-});
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowExpander.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/RowExpander.js
deleted file mode 100644 (file)
index 25d644a..0000000
+++ /dev/null
@@ -1,318 +0,0 @@
-Ext.ns('TYPO3.Workspaces.Component');
-
-TYPO3.Workspaces.Component.RowExpander = Ext.extend(Ext.grid.RowExpander, {
-       menuDisabled: true,
-       hideable: false,
-
-       rowDetailTemplate: [
-               '<div class="t3-workspaces-foldoutWrapper">',
-               '<tpl for=".">',
-                       '<tpl>',
-                               '<table class="char_select_template" width="100%">',
-                                       '<tr class="header">',
-                                               '<th class="char_select_profile_titleLeft">',
-                                                       '{icon_Workspace} {[TYPO3.l10n.localize(\'workspace_version\')]}',
-                                               '</th>',
-                                               '<th class="char_select_profile_titleRight">',
-                                                       '{icon_Live} {[TYPO3.l10n.localize(\'live_workspace\')]}',
-                                               '</th>',
-                                       '</tr>',
-                                       '<tr>',
-                                               '<td class="t3-workspaces-foldout-subheaderLeft">',
-                                                       '{[String.format(TYPO3.l10n.localize(\'current_step\'), values.label_Stage, values.stage_position, values.stage_count)]}',
-                                               '</td>',
-                                               '<td class="t3-workspaces-foldout-subheaderRight">',
-                                                       '{[String.format(TYPO3.l10n.localize(\'path\'), values.path_Live)]}',
-                                               '</td>',
-                                       '</tr>',
-                                       '<tr>',
-                                               '<td class="t3-workspaces-foldout-td-contentDiffLeft">',
-                                                       '<div class="t3-workspaces-foldout-contentDiff-container">',
-                                                               '<table class="t3-workspaces-foldout-contentDiff">',
-                                                                       '<tpl for="diff">',
-                                                                               '<tr><th>{label}</th><td class="content">',
-                                                                                       '<tpl if="this.exists(content)">',
-                                                                                               '{content}',
-                                                                                       '</tpl>',
-                                                                               '</td></tr>',
-                                                                       '</tpl>',
-                                                               '</table>',
-                                                       '</div>',
-                                               '</td>',
-                                               '<td class="t3-workspaces-foldout-td-contentDiffRight">',
-                                                       '<div class="t3-workspaces-foldout-contentDiff-container">',
-                                                               '<table class="t3-workspaces-foldout-contentDiff">',
-                                                                       '<tpl for="live_record">',
-                                                                               '<tr><th>{label}</th><td class="content">',
-                                                                                       '<tpl if="this.exists(content)">',
-                                                                                               '{content}',
-                                                                                       '</tpl>',
-                                                                               '</td></tr>',
-                                                                       '</tpl>',
-                                                               '</table>',
-                                                       '</div>',
-                                               '</td>',
-                                       '</tr>',
-                                       '<tpl if="this.hasComments(comments)">',
-                                       '<tr>',
-                                               '<td class="t3-workspaces-foldout-subheaderLeft">',
-                                                       '<div class="t3-workspaces-foldout-subheader-container">',
-                                                               '{[String.format(TYPO3.l10n.localize(\'comments\'), values.stage_position, values.label_Stage)]}',
-                                                       '</div>',
-                                               '</td>',
-                                               '<td class="t3-workspaces-foldout-subheaderRight">',
-                                                       '&nbsp;',
-                                               '</td>',
-                                       '</tr>',
-                                       '<tr>',
-                                               '<td class="char_select_profile_stats">',
-                                                       '<div class="t3-workspaces-comments">',
-                                                       '<tpl for="comments">',
-                                                               '<div class="t3-workspaces-comments-singleComment">',
-                                                                       '<div class="t3-workspaces-comments-singleComment-author">',
-                                                                               '{user_username}',
-                                                                       '</div>',
-                                                                       '<div class="t3-workspaces-comments-singleComment-content-wrapper"><div class="t3-workspaces-comments-singleComment-content">',
-                                                                               '<span class="t3-workspaces-comments-singleComment-content-date">{tstamp}</span>',
-                                                                               '<div class="t3-workspaces-comments-singleComment-content-title">@ {[String.format(TYPO3.l10n.localize(\'stage\'), values.stage_title)]}</div>',
-                                                                               '<div class="t3-workspaces-comments-singleComment-content-text">{user_comment}</div>',
-                                                                       '</div></div>',
-                                                               '</div>',
-                                                       '</tpl>',
-                                                       '</div>',
-                                               '</td>',
-                                               '<td class="char_select_profile_title">',
-                                                       '&nbsp;',
-                                               '</td>',
-                                               '</tpl>',
-                                       '</tr>',
-                               '</table>',
-                       '</tpl>',
-               '</tpl>',
-               '</div>',
-               '<div class="x-clear"></div>'
-       ],
-
-       detailStoreConfiguration: {
-               xtype : 'directstore',
-               storeId : 'rowDetailService',
-               root : 'data',
-               totalProperty : 'total',
-               idProperty : 'id',
-               fields : [
-                       {name : 'uid'},
-                       {name : 't3ver_oid'},
-                       {name : 'table'},
-                       {name : 'stage'},
-                       {name : 'diff'},
-                       {name : 'path_Live'},
-                       {name : 'label_Stage'},
-                       {name : 'stage_position'},
-                       {name : 'stage_count'},
-                       {name : 'live_record'},
-                       {name : 'comments'},
-                       {name : 'icon_Live'},
-                       {name : 'icon_Workspace'},
-                       {name : 'languageValue'},
-                       {name : 'integrity'}
-               ]
-       },
-
-       detailStore: null,
-
-       init : function(grid) {
-               TYPO3.Workspaces.Component.RowExpander.superclass.init.call(this, grid);
-               this.detailStore = Ext.create(this.detailStoreConfiguration);
-
-               this.addEvents({
-                       beforeExpandCollection: true,
-                       beforeExpandCollectionChild: true,
-                       beforeCollapseCollection: true,
-                       beforeCollapseCollectionChild: true
-               })
-       },
-
-       getRowClass : function(record, rowIndex, p, ds) {
-               var cls = [];
-
-               cls.push(Ext.grid.RowExpander.prototype.getRowClass.call(this, record, rowIndex, p, ds));
-
-               if (record.json.Workspaces_CollectionChildren > 0) {
-                       // @todo Extend by new nodeState check
-                       cls.push('typo3-workspaces-collection-parent-collapsed');
-               }
-               if (record.json.Workspaces_CollectionParent) {
-                       // @todo Extend by new nodeState check
-                       cls.push('typo3-workspaces-collection-child-collapsed');
-               }
-               if (!record.json.allowedAction_nextStage && !record.json.allowedAction_prevStage && !record.json.allowedAction_swap) {
-                       cls.push('typo3-workspaces-row-disabled');
-               }
-
-               return cls.join(' ');
-       },
-       renderer : function(v, p, record) {
-               var html;
-               html = Ext.grid.RowExpander.prototype.renderer.call(this, v, p, record);
-               return html;
-       },
-       remoteDataMethod : function (record, index) {
-               this.detailStore.baseParams = {
-                       uid: record.json.uid,
-                       table: record.json.table,
-                       stage: record.json.stage,
-                       t3ver_oid: record.json.t3ver_oid,
-                       path_Live: record.json.path_Live,
-                       label_Stage: record.json.label_Stage
-               };
-               this.detailStore.load({
-                       callback: function(r, options, success) {
-                               TYPO3.Workspaces.RowExpander.expandRow(index);
-                       }
-               });
-               new Ext.ux.TYPO3.Workspace.RowPanel({
-                       renderTo: 'remData' + index,
-                       items: [{
-                               xtype: 'dataview',
-                               store: this.detailStore,
-                               tpl: new TYPO3.Workspaces.Component.RowDetailTemplate(this.rowDetailTemplate)
-                       }]
-               });
-       },
-       onMouseDown : function(e, t) {
-               tObject = Ext.get(t);
-               if (tObject.hasClass('x-grid3-row-expander')) {
-                       e.stopEvent();
-                       row = e.getTarget('.x-grid3-row');
-                       this.toggleRow(row);
-               } else if (tObject.hasClass('typo3-workspaces-collection-level-node')) {
-                       e.stopEvent();
-                       row = e.getTarget('.x-grid3-row');
-                       this.toggleCollection(row);
-               }
-       },
-       toggleRow : function(row) {
-               this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'beforeExpand' : 'collapseRow'](row);
-       },
-       beforeExpand : function(row) {
-               if (typeof row == 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-               var record = this.grid.store.getAt(row.rowIndex);
-               var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
-
-               if (this.fireEvent('beforexpand', this, record, body, row.rowIndex) !== false) {
-                       this.tpl = new Ext.Template("<div id=\"remData" + row.rowIndex + "\" class=\"rem-data-expand\"><\div>");
-                       if (this.tpl && this.lazyRender) {
-                               body.innerHTML = this.getBodyContent(record, row.rowIndex);
-                       }
-               }
-                       // toggle remoteData loading
-               this.remoteDataMethod(record, row.rowIndex);
-               return true;
-       },
-       expandRow : function(row) {
-               if (typeof row == 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-               var record = this.grid.store.getAt(row.rowIndex);
-               var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
-               this.state[record.id] = true;
-               Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
-               this.fireEvent('expand', this, record, body, row.rowIndex);
-               var i;
-               for(i = 0; i < this.grid.store.getCount(); i++) {
-                       if(i != row.rowIndex) {
-                               this.collapseRow(i);
-                       }
-               }
-       },
-       collapseRow : function(row) {
-               if (typeof row == 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-               var record = this.grid.store.getAt(row.rowIndex);
-               var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
-               if (this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false) {
-                       this.state[record.id] = false;
-                       Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
-                       this.fireEvent('collapse', this, record, body, row.rowIndex);
-               }
-       },
-
-       toggleCollection : function(row) {
-               if (Ext.fly(row).hasClass('typo3-workspaces-collection-parent-collapsed')) {
-                       this.expandCollection(row);
-               } else {
-                       this.collapseCollection(row);
-               }
-       },
-       expandCollection : function(row) {
-               var record, body, child, i;
-
-               if (typeof row === 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-
-               record = this.grid.store.getAt(row.rowIndex);
-               body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
-               if (this.fireEvent('beforeExpandCollection', this, record, body, row.rowIndex) !== false) {
-                       for(i = 0; i < this.grid.store.getCount(); i++) {
-                               child = this.grid.store.getAt(i);
-                               if (child.json.Workspaces_CollectionParent === record.json.Workspaces_CollectionCurrent) {
-                                       this.expandCollectionChild(i);
-                               }
-                       }
-                       Ext.fly(row).replaceClass('typo3-workspaces-collection-parent-collapsed', 'typo3-workspaces-collection-parent-expanded');
-               }
-       },
-       expandCollectionChild : function(row) {
-               var record, body;
-
-               if (typeof row === 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-
-               record = this.grid.store.getAt(row.rowIndex);
-               body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
-               if (this.fireEvent('beforeCollapseCollectionChild', this, record, body, row.rowIndex) !== false) {
-                       Ext.fly(row).replaceClass('typo3-workspaces-collection-child-collapsed', 'typo3-workspaces-collection-child-expanded');
-               }
-       },
-       collapseCollection : function(row) {
-               var record, body, child, i;
-
-               if (typeof row === 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-
-               record = this.grid.store.getAt(row.rowIndex);
-               body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
-               if (this.fireEvent('beforeCollapseCollectionChild', this, record, body, row.rowIndex) !== false) {
-                       for(i = 0; i < this.grid.store.getCount(); i++) {
-                               child = this.grid.store.getAt(i);
-                               if (child.json.Workspaces_CollectionParent === record.json.Workspaces_CollectionCurrent) {
-                                       // Delegate collapsing to child if it has children as well
-                                       if (child.json.Workspaces_CollectionChildren > 0) {
-                                               this.collapseCollection(i);
-                                       }
-                                       this.collapseCollectionChild(i);
-                               }
-                       }
-                       Ext.fly(row).replaceClass('typo3-workspaces-collection-parent-expanded', 'typo3-workspaces-collection-parent-collapsed');
-               }
-       },
-       collapseCollectionChild : function(row) {
-               var record, body;
-
-               if (typeof row === 'number') {
-                       row = this.grid.view.getRow(row);
-               }
-
-               record = this.grid.store.getAt(row.rowIndex);
-               body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
-               if (this.fireEvent('beforeCollapseCollection', this, record, body, row.rowIndex) !== false) {
-                       Ext.fly(row).replaceClass('typo3-workspaces-collection-child-expanded', 'typo3-workspaces-collection-child-collapsed');
-               }
-       }
-});
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/TabPanel.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Component/TabPanel.js
deleted file mode 100644 (file)
index 635b47d..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-Ext.ns('TYPO3.Workspaces.Component');
-
-TYPO3.Workspaces.Component.TabPanel = Ext.extend(Ext.TabPanel, {
-       menuRight: null,
-       tabMenu: null,
-
-       menuItems: [],
-       menuItemTemplate: null,
-
-       listeners: {
-               beforetabchange: function(panel, newTab, currentTab) {
-                       if (typeof currentTab !== 'undefined' && newTab.triggerUrl) {
-                               this.handleTriggerUrl(newTab);
-                       }
-               },
-               afterrender: function() {
-                       this.createMenu();
-                       this.arrangeTabsAfterRender();
-                       this.updateMenu();
-               }
-       },
-
-       initComponent: function() {
-               TYPO3.Workspaces.Component.TabPanel.superclass.initComponent.call(this);
-               Ext.EventManager.onWindowResize(this.handleResize, this);
-
-               this.menuItemTemplate = new Ext.XTemplate(
-                       '<a id="{id}" class="{cls} x-unselectable" hidefocus="true" unselectable="on" href="{href}"',
-                               '<tpl if="hrefTarget">',
-                                       ' target="{hrefTarget}"',
-                               '</tpl>',
-                       '>',
-                               '<span class="x-menu-item-text">{text}</span>',
-                       '</a>'
-               );
-       },
-
-       getParentPanel: function() {
-               return this.findParentByType('panel');
-       },
-
-       createMenu : function() {
-               var position = this.tabPosition=='bottom' ? this.footer : this.header;
-               var h = this.stripWrap.dom.offsetHeight;
-               var menuRight = position.insertFirst({
-                       cls:'x-tab-menu-right'
-               });
-               menuRight.hide();
-               menuRight.setHeight(h);
-               menuRight.addClassOnOver('x-tab-menu-right-over');
-               menuRight.on('click', this.showMenu, this);
-               this.menuRight = menuRight;
-       },
-
-       updateMenu: function() {
-               if (this.menuItems.length) {
-                       this.menuRight.show();
-               } else {
-                       this.menuRight.hide();
-               }
-       },
-
-       showMenu: function(event) {
-               if (this.tabMenu) {
-                       this.tabMenu.destroy();
-                       this.un('destroy', this.tabMenu.destroy, this.tabMenu);
-                       this.tabMenu = null;
-               }
-
-               this.tabMenu =  new Ext.menu.Menu({
-                       cls: 'typo3-workspaces-menu'
-               });
-               this.on('destroy', this.tabMenu.destroy, this.tabMenu);
-
-               this.addMenuItems();
-
-               var target = Ext.get(event.getTarget());
-               var xy = target.getXY();
-               xy[1] += this.menuRight.getHeight() - 1;
-
-               this.tabMenu.showAt(xy);
-       },
-
-       addMenuItems: function() {
-               Ext.each(this.menuItems, function(cmp) {
-                       menuItem = new Ext.menu.Item({
-                               itemTpl: this.menuItemTemplate,
-                               text      : cmp.title,
-                               handler   : this.handleTriggerUrl,
-                               scope     : this,
-                               triggerUrl: cmp.triggerUrl
-                               //iconCls   : item.iconCls
-                       });
-                       this.tabMenu.add(menuItem);
-               }, this);
-       },
-
-       handleTriggerUrl: function(item) {
-               location.href = item.triggerUrl;
-       },
-
-       handleResize: function(width, height) {
-               this.setWidth(width);
-               this.arrangeTabsAfterResize();
-               this.updateMenu();
-       },
-
-       arrangeTabsAfterRender: function() {
-               var i, cmp, moveItems = [], width = 0;
-               var lastIndex = this.items.items.length;
-               var tabPanelWidth = this.getParentPanel().getWidth();
-
-               for (i = 0; i < lastIndex; i++) {
-                       cmp = this.getComponent(i);
-                       width += Ext.get(cmp.tabEl).getWidth() + this.tabMargin;
-                       if (width > tabPanelWidth - this.menuRight.getWidth()) {
-                               moveItems.push(cmp);
-                       }
-               }
-
-               Ext.each(moveItems, function(cmp) {
-                       this.remove(cmp);
-                       this.menuItems.push(cmp);
-               }, this);
-       },
-
-       arrangeTabsAfterResize: function() {
-               var i, cmp, moveItems = [], width = 0;
-               var lastIndex = this.items.items.length;
-               var tabPanelWidth = this.getParentPanel().getWidth();
-
-               for (i = 0; i < lastIndex; i++) {
-                       cmp = this.getComponent(i);
-                       width += Ext.get(cmp.tabEl).getWidth() + this.tabMargin;
-                       if (width > tabPanelWidth - this.menuRight.getWidth()) {
-                               moveItems.unshift(cmp);
-                       }
-               }
-
-               if (moveItems.length) {
-                       Ext.each(moveItems, function(cmp) {
-                               this.remove(cmp);
-                               this.menuItems.unshift(cmp);
-                       }, this);
-               } else {
-                       while (this.menuItems.length) {
-                               cmp = this.menuItems[0];
-                               this.add(cmp);
-                               width += Ext.get(cmp.tabEl).getWidth() + this.tabMargin;
-                               if (width > tabPanelWidth - this.menuRight.getWidth()) {
-                                       this.remove(cmp);
-                                       break;
-                               }
-                               this.menuItems.shift();
-                       }
-               }
-       }
-});
-Ext.reg('WorkspacesTabPanel', TYPO3.Workspaces.Component.TabPanel);
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js
deleted file mode 100644 (file)
index 67a3fe1..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Ext.ux.plugins.TabStripContainer
- * @date       December 19, 2010
- *
- * @class Ext.ux.plugins.TabStripContainer
- * @extends Object
- */
-
-Ext.ns('Ext.ux.plugins');
-
-Ext.ux.plugins.TabStripContainer = Ext.extend(Object, {
-
-       /**
-        * @hide        private
-        *
-        * Tab panel we are plugged in.
-        */
-       tabPanel : null,
-
-       /**
-        * @hide        private
-        *
-        * items for the panel
-        */
-       items: [],
-
-       /**
-        * @hide        private
-        *
-        * Cached tab panel's strip wrap element container, i.e. panel's header or footer element.
-        */
-       headerFooterEl : null,
-
-
-       /**
-        * @constructor
-        */
-       constructor : function(config) {
-               Ext.apply(this, config);
-       },
-
-       /**
-        * Initializes plugin
-        */
-       init : function(tabPanel) {
-               this.tabPanel = tabPanel;
-               tabPanel.on(
-                       'afterrender',
-                       this.onTabPanelAfterRender,
-                       this,
-                       {
-                               delay: 10
-                       }
-               );
-       },
-
-       /**
-        * Adds the panel to the tab header/footer
-        *
-        * @param tabPanel
-        */
-       onTabPanelAfterRender: function(tabPanel) {
-               var height, panelDiv, stripTarget, config;
-               // Getting and caching strip wrap element parent, i.e. tab panel footer or header.
-               this.headerFooterEl =
-                               this.tabPanel.tabPosition == 'bottom'
-                                       ? this.tabPanel.footer
-                                       : this.tabPanel.header;
-               height = this.headerFooterEl.getComputedHeight();
-               stripTarget = tabPanel[tabPanel.stripTarget];
-               stripTarget.applyStyles('position: relative;');
-
-               panelDiv = this.headerFooterEl.createChild({
-                       tag : 'div',
-                       id: this.id || Ext.id(),
-                       style : {
-                               position : 'absolute',
-                               right: 0,
-                               top: '1px'
-                       }
-               });
-               panelDiv.setSize(this.width, height, false);
-               config = Ext.applyIf({
-                       layout: 'hbox',
-                       height: height,
-                       width: this.width,
-                       renderTo: panelDiv
-               }, this.panelConfig);
-               this.panelContainer = new Ext.Panel(config);
-               this.panelContainer.add(this.items);
-               this.panelContainer.doLayout();
-       },
-
-       doLayout: function () {
-               this.panelContainer.doLayout();
-       }
-
-});
-Ext.preg('Ext.ux.plugins.TabStripContainer', Ext.ux.plugins.TabStripContainer);
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Preview.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Preview.js
new file mode 100644 (file)
index 0000000..1c11134
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+/*
+ * 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!
+ */
+
+/**
+ * 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.sendExtDirectRequest([
+                                       Workspaces.generateExtDirectActionsPayload('discardStagesFromPage', [TYPO3.settings.Workspaces.id]),
+                                       Workspaces.generateExtDirectActionsPayload('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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectActionsPayload(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.sendExtDirectRequest([
+                                               Workspaces.generateExtDirectActionsPayload('sentCollectionToStage', [serializedForm]),
+                                               Workspaces.generateExtDirectActionsPayload('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();
+       });
+});
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js
deleted file mode 100644 (file)
index b66d120..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-Ext.ns('TYPO3.Workspaces.Configuration');
-
-TYPO3.Workspaces.Configuration.StoreFieldArray = [
-       {name : 'Workspaces_Collection', type : 'int'},
-       {name : 'Workspaces_CollectionLevel', type : 'int'},
-       {name : 'Workspaces_CollectionParent'},
-       {name : 'Workspaces_CollectionCurrent'},
-       {name : 'Workspaces_CollectionChildren', type : 'int'},
-       {name : 'table'},
-       {name : 'uid', type : 'int'},
-       {name : 't3ver_oid', type : 'int'},
-       {name : 't3ver_wsid', type : 'int'},
-       {name : 'livepid', type : 'int'},
-       {name : 'stage', type: 'int'},
-       {name : 'change',type : 'int'},
-       {name : 'languageValue'},
-       {name : 'language'},
-       {name : 'integrity'},
-       {name : 'label_Live'},
-       {name : 'label_Workspace'},
-       {name : 'label_Stage'},
-       {name : 'label_nextStage'},
-       {name : 'label_prevStage'},
-       {name : 'workspace_Title'},
-       {name : 'actions'},
-       {name : 'icon_Workspace'},
-       {name : 'icon_Live'},
-       {name : 'path_Live'},
-       {name : 'path_Workspace'},
-       {name : 'state_Workspace'},
-       {name : 'workspace_Tstamp'},
-       {name : 'workspace_Formated_Tstamp'},
-       {name : 'allowedAction_nextStage'},
-       {name : 'allowedAction_prevStage'},
-       {name : 'allowedAction_swap'},
-       {name : 'allowedAction_delete'},
-       {name : 'allowedAction_edit'},
-       {name : 'allowedAction_editVersionedPage'},
-       {name : 'allowedAction_view'}
-].concat(TYPO3.settings.Workspaces.extension.AdditionalColumn.Definition);
-
-TYPO3.Workspaces.MainStore = new Ext.data.GroupingStore({
-       storeId : 'workspacesMainStore',
-       reader : new Ext.data.JsonReader({
-               idProperty : 'id',
-               root : 'data',
-               totalProperty : 'total'
-       }, TYPO3.Workspaces.Configuration.StoreFieldArray),
-       groupField: 'path_Workspace',
-       paramsAsHash : true,
-       sortInfo : {
-               field : 'label_Live',
-               direction : "ASC"
-       },
-       remoteSort : true,
-       baseParams: {
-               depth : 990,
-               id: TYPO3.settings.Workspaces.id,
-               language: TYPO3.settings.Workspaces.language,
-               query: '',
-               start: 0,
-               limit: 30
-       },
-
-       showAction : false,
-       listeners : {
-               beforeload : function() {},
-               load : function(store, records) {
-                       var defaultColumn = TYPO3.Workspaces.WorkspaceGrid.colModel.getColumnById('label_Workspace');
-                       if (defaultColumn) {
-                               defaultColumn.width = defaultColumn.defaultWidth + this.getMaximumCollectionLevel() * defaultColumn.levelWidth;
-                       }
-               },
-               datachanged : function(store) {}
-       },
-       getMaximumCollectionLevel: function() {
-               var maximumCollectionLevel = 0;
-               Ext.each(this.data.items, function(item) {
-                       if (item.json.Workspaces_CollectionLevel > maximumCollectionLevel) {
-                               maximumCollectionLevel = item.json.Workspaces_CollectionLevel;
-                       }
-               });
-               return maximumCollectionLevel;
-       }
-});
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Workspaces.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Workspaces.js
new file mode 100644 (file)
index 0000000..60e20ef
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+/*
+ * 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!
+ */
+
+/**
+ * RequireJS module for Workspaces
+ */
+define([
+       'jquery',
+       'TYPO3/CMS/Backend/Severity',
+       'TYPO3/CMS/Backend/Modal'
+], function($, Severity, Modal) {
+       'use strict';
+
+       var Workspaces = {};
+
+       /**
+        * 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'])
+                       );
+
+                       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', 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.sendExtDirectRequest(
+                       Workspaces.generateExtDirectPayload('checkIntegrity', payload)
+               );
+       };
+
+       /**
+        * Sends an AJAX request compatible to ExtDirect
+        * This method is intended to be dropped once we don't the ExtDirect stuff anymore.
+        *
+        * @param {Object} payload
+        * @return {$}
+        */
+       Workspaces.sendExtDirectRequest = function(payload) {
+               return $.ajax({
+                       url: TYPO3.settings.ajaxUrls['ext_direct_route'] + '&namespace=TYPO3.Workspaces',
+                       method: 'POST',
+                       contentType: 'application/json; charset=utf-8',
+                       dataType: 'json',
+                       data: JSON.stringify(payload)
+               });
+       };
+
+       /**
+        * Generates the payload for ExtDirect
+        *
+        * @param {String} method
+        * @param {Object} data
+        * @return {{action, data, method, type}}
+        */
+       Workspaces.generateExtDirectPayload = function(method, data) {
+               if (typeof data === 'undefined') {
+                       data = {};
+               }
+               return Workspaces.generateExtDirectPayloadBody('ExtDirect', method, data);
+       };
+
+       /**
+        * Generates the payload for ExtDirectMassActions
+        *
+        * @param {String} method
+        * @param {Object} data
+        * @return {{action, data, method, type}}
+        */
+       Workspaces.generateExtDirectMassActionsPayload = function(method, data) {
+               if (typeof data === 'undefined') {
+                       data = {};
+               }
+               return Workspaces.generateExtDirectPayloadBody('ExtDirectMassActions', method, data);
+       };
+
+       /**
+        * Generates the payload for ExtDirectActions
+        *
+        * @param {String} method
+        * @param {Object} data
+        * @return {{action, data, method, type}}
+        */
+       Workspaces.generateExtDirectActionsPayload = function(method, data) {
+               if (typeof data === 'undefined') {
+                       data = [];
+               }
+               return Workspaces.generateExtDirectPayloadBody('ExtDirectActions', 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.generateExtDirectPayloadBody = 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'
+               };
+       };
+
+       /**
+        * 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;
+});
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js
deleted file mode 100644 (file)
index 13e2ecd..0000000
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * 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!
- */
-
-
-Ext.ns('TYPO3.Workspaces');
-
-TYPO3.Workspaces.Actions = {
-
-       runningMassAction: null,
-       currentSendToMode: 'next',
-
-       checkIntegrity: function(parameters, callbackFunction, callbackArguments) {
-               TYPO3.Workspaces.ExtDirect.checkIntegrity(
-                               parameters,
-                               function (response) {
-                                       switch (response.result) {
-                                               case 'error':
-                                                       top.TYPO3.Dialog.ErrorDialog({
-                                                               minWidth: 400,
-                                                               title: 'Error',
-                                                               msg: '<div class="scope">' + TYPO3.l10n.localize('integrity.hasIssuesDescription') + '</div>'
-                                                       });
-                                                       break;
-                                               case 'warning':
-                                                       top.TYPO3.Dialog.QuestionDialog({
-                                                               minWidth: 400,
-                                                               title: 'Warning',
-                                                               msg: '<div class="scope">' + TYPO3.l10n.localize('integrity.hasIssuesDescription') + '</div>' +
-                                                                       '<div class="question">' + TYPO3.l10n.localize('integrity.hasIssuesQuestion') + '</div>',
-                                                               fn: function(result) {
-                                                                       if (result == 'yes') {
-                                                                               callbackFunction.call(this, callbackArguments)
-                                                                       }
-                                                               }
-                                                       });
-                                                       break;
-                                               default:
-                                                       callbackFunction.call(this, callbackArguments);
-                                       }
-                               }
-               )
-       },
-
-       triggerMassAction: function(action, language) {
-               switch (action) {
-                       case 'publish':
-                       case 'swap':
-                               this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.publishWorkspace;
-                               break;
-                       case 'discard':
-                               this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.flushWorkspace;
-                               break;
-               }
-
-               // Publishing large amount of changes may require a longer timeout
-               Ext.Ajax.timeout = 3600000;
-
-               this.runMassAction({
-                       init: true,
-                       total:0,
-                       processed:0,
-                       language: language,
-                       swap: (action == 'swap')
-               });
-       },
-
-       runMassAction: function(parameters) {
-               if (parameters.init) {
-                       top.Ext.getCmp('executeMassActionForm').hide();
-                       top.Ext.getCmp('executeMassActionProgressBar').show();
-                       top.Ext.getCmp('executeMassActionOkButton').disable();
-               }
-
-               var progress = parameters.total > 0 ? parameters.processed / parameters.total : 0;
-               var label = parameters.total > 0 ? parameters.processed + '/' + parameters.total : TYPO3.l10n.localize('runMassAction.init');
-               top.Ext.getCmp('executeMassActionProgressBar').updateProgress(progress, label, true);
-
-               this.runningMassAction(parameters, TYPO3.Workspaces.Actions.runMassActionCallback);
-       },
-
-       runMassActionCallback: function(response) {
-               if (response.error) {
-                       top.Ext.getCmp('executeMassActionProgressBar').hide();
-                       top.Ext.getCmp('executeMassActionOkButton').hide();
-                       top.Ext.getCmp('executeMassActionCancelButton').setText(TYPO3.l10n.localize('close'));
-                       top.Ext.getCmp('executeMassActionForm').show();
-                       top.Ext.getCmp('executeMassActionForm').update(response.error);
-                       TYPO3.Workspaces.Helpers.refreshPageTree();
-               } else {
-                       if (response.total > response.processed) {
-                               TYPO3.Workspaces.Actions.runMassAction(response);
-                       } else {
-                               top.Ext.getCmp('executeMassActionProgressBar').hide();
-                               top.Ext.getCmp('executeMassActionOkButton').hide();
-                               top.Ext.getCmp('executeMassActionCancelButton').setText(TYPO3.l10n.localize('close'));
-                               top.Ext.getCmp('executeMassActionForm').show();
-                               top.Ext.getCmp('executeMassActionForm').update(TYPO3.l10n.localize('runMassAction.done').replace('%d', response.total));
-                               TYPO3.Workspaces.Helpers.refreshPageTree();
-                       }
-               }
-       },
-       generateWorkspacePreviewLink: function() {
-               TYPO3.Workspaces.ExtDirectActions.generateWorkspacePreviewLink(TYPO3.settings.Workspaces.id, function(response) {
-                       top.TYPO3.Dialog.InformationDialog({
-                               title: TYPO3.l10n.localize('previewLink'),
-                               msg: String.format('<a href="{0}" target="_blank">{0}</a>', response)
-                       });
-               });
-       },
-       swapSingleRecord: function(table, t3ver_oid, orig_uid) {
-               TYPO3.Workspaces.ExtDirectActions.swapSingleRecord(table, t3ver_oid, orig_uid, function(response) {
-                       TYPO3.Workspaces.MainStore.load();
-               });
-       },
-       deleteSingleRecord: function(table, uid) {
-               TYPO3.Workspaces.ExtDirectActions.deleteSingleRecord(table, uid, function(response) {
-                       TYPO3.Workspaces.Helpers.refreshPageTree();
-                       TYPO3.Workspaces.MainStore.load();
-               });
-       },
-       viewSingleRecord: function(table, uid) {
-               TYPO3.Workspaces.ExtDirectActions.viewSingleRecord(table, uid, function(response) {
-                       eval(response);
-               });
-       },
-       sendToStageWindow: function(response, selection) {
-               if (Ext.isObject(response.error)) {
-                       TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
-               } else {
-                       var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
-                               title: response.title,
-                               items: response.items,
-                               executeHandler: function(event) {
-                                       var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
-                                       affects = response.affects;
-                                       affects.elements = TYPO3.Workspaces.Helpers.getElementsArrayOfSelection(selection);
-                                       var parameters = {
-                                               affects: affects,
-                                               receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
-                                               additional: values.additional,
-                                               comments: values.comments
-                                       };
-
-                                       TYPO3.Workspaces.Actions.sendToStageExecute(parameters);
-                                       top.TYPO3.Windows.close('sendToStageWindow');
-                                       TYPO3.Workspaces.MainStore.reload();
-                                       TYPO3.Workspaces.Helpers.refreshPageTree();
-                               }
-                       });
-               }
-       },
-       sendToNextStageWindow: function(table, uid, t3ver_oid) {
-               TYPO3.Workspaces.ExtDirectActions.sendToNextStageWindow(uid, table, t3ver_oid, function(response) {
-                       TYPO3.Workspaces.Actions.currentSendToMode = 'next';
-                       TYPO3.Workspaces.Actions.sendToStageWindow(response);
-               });
-       },
-       sendToPrevStageWindow: function(table, uid) {
-               TYPO3.Workspaces.ExtDirectActions.sendToPrevStageWindow(uid, table, function(response) {
-                       TYPO3.Workspaces.Actions.currentSendToMode = 'prev';
-                       TYPO3.Workspaces.Actions.sendToStageWindow(response);
-               });
-       },
-       sendToSpecificStageWindow: function(selection, nextStage) {
-               var elements = [];
-
-               Ext.each(selection, function(row) {
-                       elements.push({table: row.json.table, uid: row.json.uid})
-               });
-
-               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageWindow(nextStage, elements, function(response) {
-                       TYPO3.Workspaces.Actions.currentSendToMode = 'specific';
-                       TYPO3.Workspaces.Actions.sendToStageWindow(response, selection);
-               });
-       },
-       sendToStageExecute: function (parameters) {
-               switch (TYPO3.Workspaces.Actions.currentSendToMode) {
-                       case 'next':
-                               TYPO3.Workspaces.ExtDirectActions.sendToNextStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
-                       break;
-                       case 'prev':
-                               TYPO3.Workspaces.ExtDirectActions.sendToPrevStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
-                       break;
-                       case 'specific':
-                               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
-                       break;
-               }
-
-       },
-       updateColModel: function(colModel) {
-               var dataArray = [];
-               for (var i = 0; i < colModel.config.length; i++) {
-                       if (colModel.config[i].dataIndex !== '') {
-                               dataArray.push({
-                                       'position': i,
-                                       'column': colModel.config[i].dataIndex,
-                                       'hidden': colModel.config[i].hidden ? 1 : 0
-                               });
-                       }
-               }
-               TYPO3.Workspaces.ExtDirectActions.saveColumnModel(dataArray);
-       },
-       loadColModel: function(grid) {
-               TYPO3.Workspaces.ExtDirectActions.loadColumnModel(function(response) {
-                       var colModel = grid.getColumnModel();
-                       for (var field in response) {
-                               var colIndex = colModel.getIndexById(field);
-                               if (colIndex != -1) {
-                                       colModel.setHidden(colModel.getIndexById(field), (response[field].hidden == 1 ? true : false));
-                                       colModel.moveColumn(colModel.getIndexById(field), response[field].position);
-                               }
-                       }
-               });
-       },
-       handlerResponseOnExecuteAction: function(response) {
-               if (!Ext.isObject(response)) {
-                       response = { error: { message: TYPO3.l10n.localize('error.noResponse') }};
-               }
-
-               if (Ext.isObject(response.error)) {
-                       var error = response.error;
-                       var code = (error.code ? ' #' + error.code : '');
-                       top.TYPO3.Dialog.ErrorDialog({ title: 'Error' + code, msg: error.message });
-               }
-       },
-
-       /**
-        * Process "send to next stage" action.
-        *
-        * This method is used in the split frontend preview part.
-        *
-        * @return void
-        */
-       sendPageToNextStage: function () {
-               TYPO3.Workspaces.ExtDirectActions.sendPageToNextStage(TYPO3.settings.Workspaces.id, function (response) {
-                       if (Ext.isObject(response.error)) {
-                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
-                       } else {
-                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
-                                       title: TYPO3.l10n.localize('nextStage'),
-                                       items: response.items.items,
-                                       executeHandler: function(event) {
-                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
-                                               affects = response.affects;
-                                               var parameters = {
-                                                       affects: affects,
-                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
-                                                       additional: values.additional,
-                                                       comments: values.comments,
-                                                       stageId: response.stageId
-                                               };
-                                               TYPO3.Workspaces.ExtDirectActions.sentCollectionToStage(parameters, function (response) {
-                                                       TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
-                                                       TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons);
-
-                                                       if (response.refreshLivePanel == true) {
-                                                               Ext.getCmp('livePanel').refresh();
-                                                               Ext.getCmp('livePanel-hbox').refresh();
-                                                               Ext.getCmp('livePanel-vbox').refresh();
-                                                       }
-                                               });
-                                               top.TYPO3.Windows.close('sendToStageWindow');
-                                       }
-                               });
-                       }
-               });
-       },
-
-       /**
-        * Process "send to previous stage" action.
-        *
-        * This method is used in the split frontend preview part.
-        *
-        * @return void
-        */
-       sendPageToPrevStage: function () {
-               TYPO3.Workspaces.ExtDirectActions.sendPageToPreviousStage(TYPO3.settings.Workspaces.id, function (response) {
-                       if (Ext.isObject(response.error)) {
-                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
-                       } else {
-                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
-                                       title: TYPO3.l10n.localize('nextStage'),
-                                       items: response.items.items,
-                                       executeHandler: function(event) {
-     &