ContentVersion cleanup backoffice UI (#11637)
* init rollback ui prototype * add busy state to button, deselect version, add pagination status * add localisation * style current version * disable rollback button when nothing is selected * stop click event * Endpoints for paginated content versions. Light on tests, tight on time. * Endpoints to "pin" content versions * camel case json output. Not sure why json formatter not set for controller, bit risky to add it now * wire up paging * wire up pin/unpin * rename getPagedRollbackVersions to getPagedContentVersions * prevent selection of current version and current draft * add current draft and current version to UI * remove pointer if the row is not selectable * Improve warning for globally disabled cleanup feature. * Fix current loses prevent cleanup state on publish. * Added umbracoLog audit entries for "pin" / "unpin" * Match v9 defaults for keepVersions settings * Fix - losing preventCleanup on save current with content changes * update pin/unpin button labels * fix pagination bug * add missing " * always send culture when a doc type can vary Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
@@ -1343,6 +1343,77 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
),
|
||||
"Failed to remove public access for content item with id " + contentId
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#getPagedContentVersions
|
||||
* @methodOf umbraco.resources.contentResource
|
||||
*
|
||||
* @description
|
||||
* Returns a paged array of previous version id's, given a node id, pageNumber, pageSize and a culture
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* contentResource.getPagedContentVersions(id, pageNumber, pageSize, culture)
|
||||
* .then(function(versions) {
|
||||
* alert('its here!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Int} id Id of node
|
||||
* @param {Int} pageNumber page number
|
||||
* @param {Int} pageSize page size
|
||||
* @param {Int} culture if provided, the results will be for this specific culture/variant
|
||||
* @returns {Promise} resourcePromise object containing the versions
|
||||
*
|
||||
*/
|
||||
getPagedContentVersions: function (contentId, pageNumber, pageSize, culture) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPagedContentVersions", {
|
||||
contentId: contentId,
|
||||
pageNumber: pageNumber,
|
||||
pageSize: pageSize,
|
||||
culture: culture
|
||||
})
|
||||
),
|
||||
"Failed to get versions for content item with id " + contentId
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#contentVersionPreventCleanup
|
||||
* @methodOf umbraco.resources.contentResource
|
||||
*
|
||||
* @description
|
||||
* Enables or disabled clean up of a version
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* contentResource.contentVersionPreventCleanup(contentId, versionId, preventCleanup)
|
||||
* .then(function() {
|
||||
* // do your thing
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Int} contentId Id of node
|
||||
* @param {Int} versionId Id of version
|
||||
* @param {Int} preventCleanup Boolean to toggle clean up prevention
|
||||
*
|
||||
*/
|
||||
contentVersionPreventCleanup: function (contentId, versionId, preventCleanup) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSetContentVersionPreventCleanup", {
|
||||
contentId: contentId,
|
||||
versionId: versionId,
|
||||
preventCleanup: preventCleanup
|
||||
})
|
||||
),
|
||||
"Failed to toggle prevent cleanup of version with id " + versionId
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -527,7 +527,7 @@ When building a custom infinite editor view you can use the same components as a
|
||||
*/
|
||||
function rollback(editor) {
|
||||
editor.view = "views/common/infiniteeditors/rollback/rollback.html";
|
||||
if (!editor.size) editor.size = "medium";
|
||||
if (!editor.size) editor.size = "";
|
||||
open(editor);
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,8 @@
|
||||
|
||||
@import "components/umbemailmarketing.less";
|
||||
|
||||
// Editors
|
||||
@import "../views/common/infiniteeditors/rollback/rollback.less";
|
||||
|
||||
// Property Editors
|
||||
@import "../views/components/blockcard/umb-block-card-grid.less";
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
color: @white;
|
||||
}
|
||||
|
||||
.umb-button__success.-black,
|
||||
.umb-button__error.-black {
|
||||
color: @black;
|
||||
}
|
||||
|
||||
.umb-button__overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService) {
|
||||
function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService, notificationsService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
vm.changeVersion = changeVersion;
|
||||
vm.submit = submit;
|
||||
vm.close = close;
|
||||
vm.pinVersion = pinVersion;
|
||||
vm.goToPage = goToPage;
|
||||
vm.paginationCount = { from: 0, to: 0, total: 0 };
|
||||
|
||||
//////////
|
||||
|
||||
@@ -22,6 +25,11 @@
|
||||
vm.rollbackButtonDisabled = true;
|
||||
vm.labels = {};
|
||||
|
||||
vm.pageSize = 15;
|
||||
vm.pageNumber = 1;
|
||||
vm.totalPages = 1;
|
||||
vm.totalItems = 0;
|
||||
|
||||
// find the current version for invariant nodes
|
||||
if($scope.model.node.variants.length === 1) {
|
||||
vm.currentVersion = $scope.model.node.variants[0];
|
||||
@@ -61,27 +69,40 @@
|
||||
|
||||
function changeLanguage(language) {
|
||||
vm.currentVersion = language;
|
||||
vm.pageNumber = 1;
|
||||
getVersions();
|
||||
}
|
||||
|
||||
function changeVersion(version) {
|
||||
|
||||
if(version && version.versionId) {
|
||||
const canRollback = !version.currentDraftVersion && !version.currentPublishedVersion;
|
||||
|
||||
vm.loading = true;
|
||||
if (canRollback === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vm.previousVersion && version && vm.previousVersion.versionId === version.versionId) {
|
||||
vm.previousVersion = null;
|
||||
vm.diff = null;
|
||||
vm.rollbackButtonDisabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (version && version.versionId) {
|
||||
vm.loadingDiff = true;
|
||||
const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
|
||||
|
||||
contentResource.getRollbackVersion(version.versionId, culture)
|
||||
.then(function(data) {
|
||||
vm.previousVersion = data;
|
||||
vm.previousVersion.versionId = version.versionId;
|
||||
vm.previousVersion.displayValue = version.displayValue + ' - ' + version.username;
|
||||
createDiff(vm.currentVersion, vm.previousVersion);
|
||||
|
||||
vm.loading = false;
|
||||
vm.loadingDiff = false;
|
||||
vm.rollbackButtonDisabled = false;
|
||||
}, function () {
|
||||
vm.loading = false;
|
||||
vm.loadingDiff = false;
|
||||
});
|
||||
|
||||
} else {
|
||||
@@ -93,15 +114,26 @@
|
||||
function getVersions() {
|
||||
|
||||
const nodeId = $scope.model.node.id;
|
||||
const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
|
||||
const culture = vm.currentVersion.language ? vm.currentVersion.language.culture : null;
|
||||
|
||||
return contentResource.getRollbackVersions(nodeId, culture)
|
||||
return contentResource.getPagedContentVersions(nodeId, vm.pageNumber, vm.pageSize, culture)
|
||||
.then(function (data) {
|
||||
vm.totalPages = data.totalPages;
|
||||
vm.totalItems = data.totalItems;
|
||||
|
||||
const possibleTotalItems = vm.pageNumber * vm.pageSize;
|
||||
|
||||
vm.paginationCount = {
|
||||
from: (vm.pageNumber * vm.pageSize - vm.pageSize) + 1,
|
||||
to: vm.totalItems < possibleTotalItems ? vm.totalItems : possibleTotalItems,
|
||||
total: vm.totalItems
|
||||
};
|
||||
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
vm.previousVersions = data.map(version => {
|
||||
vm.previousVersions = data.items.map(version => {
|
||||
var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL');
|
||||
version.displayValue = timestampFormatted + ' - ' + version.versionAuthorName;
|
||||
version.displayValue = timestampFormatted;
|
||||
return version;
|
||||
});
|
||||
});
|
||||
@@ -200,6 +232,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
function pinVersion (version, event) {
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
|
||||
version.pinningState = 'busy';
|
||||
|
||||
const nodeId = $scope.model.node.id;
|
||||
const versionId = version.versionId;
|
||||
const preventCleanup = !version.preventCleanup;
|
||||
|
||||
contentResource.contentVersionPreventCleanup(nodeId, versionId, preventCleanup)
|
||||
.then(() => {
|
||||
version.pinningState = 'success';
|
||||
version.preventCleanup = preventCleanup;
|
||||
}, () => {
|
||||
version.pinningState = 'error';
|
||||
|
||||
const localizationKey = preventCleanup ? 'speechBubbles_preventCleanupEnableError' : 'speechBubbles_preventCleanupDisableError';
|
||||
|
||||
localizationService.localize(localizationKey).then(value => {
|
||||
notificationsService.error(value);
|
||||
});
|
||||
});
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function goToPage (pageNumber) {
|
||||
vm.pageNumber = pageNumber;
|
||||
getVersions();
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div ng-controller="Umbraco.Editors.RollbackController as vm">
|
||||
<div class="editor-rollback" ng-controller="Umbraco.Editors.RollbackController as vm">
|
||||
|
||||
<umb-editor-view>
|
||||
|
||||
@@ -16,75 +16,138 @@
|
||||
ng-if="vm.loading">
|
||||
</umb-load-indicator>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-content>
|
||||
<div class="main-content">
|
||||
|
||||
<div ng-if="model.node.variants.length > 1">
|
||||
<h5><localize key="general_language">Language</localize></h5>
|
||||
<select
|
||||
class="input-block-level"
|
||||
ng-model="vm.selectedLanguage"
|
||||
ng-options="variant as variant.displayName for variant in model.node.variants track by variant.language.culture"
|
||||
ng-change="vm.changeLanguage(vm.selectedLanguage)">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<h5><localize key="rollback_currentVersion">Current version</localize></h5>
|
||||
<p>{{vm.currentVersion.name}} (<localize key="rollback_created">Created</localize>: {{vm.currentVersion.createDate}})</p>
|
||||
|
||||
<h5><localize key="rollback_rollbackTo">Rollback to</localize></h5>
|
||||
<select
|
||||
class="input-block-level"
|
||||
ng-model="vm.selectedVersion"
|
||||
ng-options="version.displayValue for version in vm.previousVersions track by version.versionId"
|
||||
ng-change="vm.changeVersion(vm.selectedVersion)">
|
||||
<option value="">{{vm.labels.choose}}...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div ng-if="vm.diff && !vm.loading" class="diff" style="border-top: 1px solid #e9e9eb; margin-top: 30px;">
|
||||
|
||||
<h5><localize key="rollback_changes">Changes</localize></h5>
|
||||
<small style="margin-bottom: 15px; display: block;">
|
||||
<localize key="rollback_diffHelp">
|
||||
This shows the differences between the current version and the selected version<br /><del>Red text</del> will be
|
||||
removed in the selected version, <ins>green text</ins> will be added
|
||||
<umb-box class="side-panel">
|
||||
<umb-box-header title-key="rollback_versions">
|
||||
<small>
|
||||
<localize
|
||||
key="rollback_pagination"
|
||||
tokens="[vm.paginationCount.from, vm.paginationCount.to, vm.paginationCount.total]"
|
||||
watch-tokens="true">
|
||||
</localize>
|
||||
</small>
|
||||
</umb-box-header>
|
||||
|
||||
<table class="table table-condensed table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="bold">
|
||||
<localize key="general_name">Name</localize>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-repeat="part in vm.diff.name track by $id(part)">
|
||||
<ins ng-if="part.added">{{part.value}}</ins>
|
||||
<del ng-if="part.removed">{{part.value}}</del>
|
||||
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="property in vm.diff.properties track by property.alias">
|
||||
<td class="bold">{{property.label}}</td>
|
||||
<td ng-class="{'pre-line': property.isObject, 'word-wrap': !property.isObject}">
|
||||
<span ng-repeat="part in property.diff track by $id(part)">
|
||||
<ins ng-if="part.added">{{part.value}}</ins>
|
||||
<del ng-if="part.removed">{{part.value}}</del>
|
||||
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<umb-box-content>
|
||||
|
||||
</div>
|
||||
<div ng-if="model.node.variants.length > 1">
|
||||
<h5 class="mt0"><localize key="general_language">Language</localize></h5>
|
||||
<select
|
||||
class="culture-select input-block-level"
|
||||
ng-model="vm.selectedLanguage"
|
||||
ng-options="variant as variant.displayName for variant in model.node.variants track by variant.language.culture"
|
||||
ng-change="vm.changeLanguage(vm.selectedLanguage)">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
<div>
|
||||
<div class="current-version">
|
||||
<span class="bold"><localize key="rollback_currentVersion">Current version</localize>: </span>
|
||||
<span>{{vm.currentVersion.name}} (<localize key="rollback_created">Created</localize>: {{vm.currentVersion.createDate}})</span>
|
||||
</div>
|
||||
|
||||
<div class="umb-table umb-table--condensed">
|
||||
|
||||
<div class="umb-table-body">
|
||||
<div class="umb-table-row umb-outline"
|
||||
ng-repeat="version in vm.previousVersions track by version.versionId"
|
||||
ng-class="{'-selectable cursor-pointer': !version.currentDraftVersion && !version.currentPublishedVersion,'-selected': version.versionId === vm.previousVersion.versionId }"
|
||||
ng-click="vm.changeVersion(version)">
|
||||
<div class="umb-table-cell dn"></div>
|
||||
<div class="umb-table-cell black">
|
||||
<div class="flex flex-column">
|
||||
<div>{{ version.displayValue }}</div>
|
||||
<div class="version-details">{{version.username}}</div>
|
||||
<div class="version-details">
|
||||
<localize ng-if="version.currentPublishedVersion" key="rollback_currentPublishedVersion">Current version</localize>
|
||||
<localize ng-if="version.currentDraftVersion" key="rollback_currentDraftVersion">Current version</localize>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-table-cell umb-table-cell--auto-width">
|
||||
<umb-button
|
||||
style="margin-left: auto;"
|
||||
button-style="outline"
|
||||
type="button"
|
||||
size="xxs"
|
||||
state="version.pinningState"
|
||||
action="vm.pinVersion(version, $event)"
|
||||
label="{{ version.preventCleanup ? 'Enable cleanup' : 'Prevent cleanup' }}">
|
||||
</umb-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<umb-pagination
|
||||
ng-if="vm.totalPages > 0 && !vm.loading"
|
||||
page-number="vm.pageNumber"
|
||||
total-pages="vm.totalPages"
|
||||
on-change="vm.goToPage(pageNumber)">
|
||||
</umb-pagination>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box class="compare-panel">
|
||||
|
||||
<umb-box-header ng-if="vm.previousVersion" title="{{vm.previousVersion.displayValue}}"></umb-box-header>
|
||||
|
||||
<umb-box-content>
|
||||
|
||||
<umb-load-indicator ng-if="vm.loadingDiff"></umb-load-indicator>
|
||||
|
||||
<umb-empty-state
|
||||
ng-if="!vm.diff"
|
||||
position="center">
|
||||
<localize key="rollback_headline"></localize>
|
||||
</umb-empty-state>
|
||||
|
||||
<div ng-if="vm.diff && !vm.loadingDiff" class="diff">
|
||||
|
||||
<small class="db" style="margin-bottom: 15px;">
|
||||
<localize key="rollback_diffHelp">
|
||||
This shows the differences between the current version and the selected version<br /><del>Red text</del> will be
|
||||
removed in the selected version, <ins>green text</ins> will be added
|
||||
</localize>
|
||||
</small>
|
||||
|
||||
<table class="table table-condensed table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="bold">
|
||||
<localize key="general_name">Name</localize>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-repeat="part in vm.diff.name track by $id(part)">
|
||||
<ins ng-if="part.added">{{part.value}}</ins>
|
||||
<del ng-if="part.removed">{{part.value}}</del>
|
||||
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="property in vm.diff.properties track by property.alias">
|
||||
<td class="bold">{{property.label}}</td>
|
||||
<td ng-class="{'pre-line': property.isObject, 'word-wrap': !property.isObject}">
|
||||
<span ng-repeat="part in property.diff track by $id(part)">
|
||||
<ins ng-if="part.added">{{part.value}}</ins>
|
||||
<del ng-if="part.removed">{{part.value}}</del>
|
||||
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
</div>
|
||||
</umb-editor-container>
|
||||
|
||||
<umb-editor-footer>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.editor-rollback {
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
flex: 0 0 33%;
|
||||
}
|
||||
|
||||
.compare-panel {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.current-version {
|
||||
background: @gray-10;
|
||||
padding: 15px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.culture-select {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.version-details {
|
||||
color: @gray-5;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="umb-button" ng-class="{'ml0': vm.generalActions, 'umb-button--block': vm.blockElement}" data-element="{{ vm.alias ? 'button-' + vm.alias : '' }}">
|
||||
|
||||
<div ng-if="vm.innerState">
|
||||
<umb-icon icon="icon-check" class="umb-button__success" ng-class="{'-hidden': vm.innerState !== 'success', '-white': vm.isPrimaryButtonStyle}"></umb-icon>
|
||||
<umb-icon icon="icon-delete" class="umb-button__error" ng-class="{'-hidden': vm.innerState !== 'error', '-white': vm.isPrimaryButtonStyle}"></umb-icon>
|
||||
<div class="umb-button__progress" ng-class="{'-hidden': vm.innerState !== 'busy', '-white': vm.isPrimaryButtonStyle}"></div>
|
||||
<umb-icon icon="icon-check" class="umb-button__success" ng-class="{'-hidden': vm.innerState !== 'success', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></umb-icon>
|
||||
<umb-icon icon="icon-delete" class="umb-button__error" ng-class="{'-hidden': vm.innerState !== 'error', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></umb-icon>
|
||||
<div class="umb-button__progress" ng-class="{'-hidden': vm.innerState !== 'busy', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></div>
|
||||
<div ng-if="vm.innerState !== 'init'" class="umb-button__overlay"></div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user