diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index f7341d112b..217719e144 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -19,6 +19,12 @@ namespace Umbraco.Core.Persistence.Repositories /// Current version is first, and then versions are ordered with most recent first. IEnumerable GetAllVersions(int nodeId); + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take); + /// /// Gets version identifiers. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 34bc3713f3..36b7ab07f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -53,6 +53,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets all versions, current first public abstract IEnumerable GetAllVersions(int nodeId); + // gets all versions, current first + public virtual IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + => GetAllVersions(nodeId).Skip(skip).Take(take); + // gets all version ids, current first public virtual IEnumerable GetVersionIds(int nodeId, int maxRows) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index df389c738a..322ea5814d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -220,6 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return MapDtosToContent(Database.Fetch(sql), true); } + public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + { + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); + + return MapDtosToContent(Database.Fetch(sql), true, true); + } + public override IContent GetVersion(int versionId) { var sql = GetBaseQuery(QueryType.Single, false) @@ -911,7 +921,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return base.ApplySystemOrdering(ref sql, ordering); } - private IEnumerable MapDtosToContent(List dtos, bool withCache = false) + private IEnumerable MapDtosToContent(List dtos, bool withCache = false, bool slim = false) { var temps = new List>(); var contentTypes = new Dictionary(); @@ -944,18 +954,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - // need templates - var templateId = dto.DocumentVersionDto.TemplateId; - if (templateId.HasValue && templateId.Value > 0) - templateIds.Add(templateId.Value); - if (dto.Published) + if (!slim) { - templateId = dto.PublishedVersionDto.TemplateId; + // need templates + var templateId = dto.DocumentVersionDto.TemplateId; if (templateId.HasValue && templateId.Value > 0) templateIds.Add(templateId.Value); + if (dto.Published) + { + templateId = dto.PublishedVersionDto.TemplateId; + if (templateId.HasValue && templateId.Value > 0) + templateIds.Add(templateId.Value); + } } - // need properties + // need temps, for properties, templates and variations var versionId = dto.DocumentVersionDto.Id; var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType, c) @@ -966,25 +979,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement temps.Add(temp); } - // load all required templates in 1 query, and index - var templates = _templateRepository.GetMany(templateIds.ToArray()) - .ToDictionary(x => x.Id, x => x); - - // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - - // assign templates and properties - foreach (var temp in temps) + if (!slim) { - // complete the item - if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) - temp.Content.Template = template; - if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) - temp.Content.PublishTemplate = template; - temp.Content.Properties = properties[temp.VersionId]; + // load all required templates in 1 query, and index + var templates = _templateRepository.GetMany(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); - // reset dirty initial properties (U4-1946) - temp.Content.ResetDirtyProperties(false); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); + + // assign templates and properties + foreach (var temp in temps) + { + // complete the item + if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) + temp.Content.Template = template; + if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) + temp.Content.PublishTemplate = template; + temp.Content.Properties = properties[temp.VersionId]; + + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); + } } // set variations, if varying diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 022bee8b41..64877e393e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -134,6 +134,12 @@ namespace Umbraco.Core.Services /// Versions are ordered with current first, then most recent first. IEnumerable GetVersions(int id); + /// + /// Gets all versions of a document. + /// + /// Versions are ordered with current first, then most recent first. + IEnumerable GetVersionsSlim(int id, int skip, int take); + /// /// Gets top versions of a document. /// @@ -461,5 +467,21 @@ namespace Umbraco.Core.Services IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); #endregion + + #region Rollback + + /// + /// Rolls back the content to a specific version. + /// + /// The id of the content node. + /// The version id to roll back to. + /// An optional culture to roll back. + /// The identifier of the user who is performing the roll back. + /// + /// When no culture is specified, all cultures are rolled back. + /// + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..92ffba952f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -474,6 +474,19 @@ namespace Umbraco.Core.Services.Implement } } + /// + /// Gets a collection of an objects versions by Id + /// + /// An Enumerable list of objects + public IEnumerable GetVersionsSlim(int id, int skip, int take) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _documentRepository.GetAllVersionsSlim(id, skip, take); + } + } + /// /// Gets a list of all version Ids for the given content item ordered so latest is first /// @@ -2490,5 +2503,68 @@ namespace Umbraco.Core.Services.Implement } #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + //Get the current copy of the node + var content = GetById(id); + + //Get the version + var version = GetVersion(versionId); + + //Good ole null checks + if (content == null || version == null) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + //Store the result of doing the save of content for the rollback + OperationResult rollbackSaveResult; + + using (var scope = ScopeProvider.CreateScope()) + { + var rollbackEventArgs = new RollbackEventArgs(content); + + //Emit RollingBack event aka before + if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } + + //Copy the changes from the version + content.CopyFrom(version, culture); + + //Save the content for the rollback + rollbackSaveResult = Save(content, userId); + + //Depending on the save result - is what we log & audit along with what we return + if (rollbackSaveResult.Success == false) + { + //Log the error/warning + Logger.Error("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + } + else + { + //Emit RolledBack event aka after + rollbackEventArgs.CanCancel = false; + scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + + //Logging & Audit message + Logger.Info("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id); + } + + scope.Complete(); + } + + return rollbackSaveResult; + } + + #endregion } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index ef34f4bc24..3d22f4c04e 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2330,6 +2330,22 @@ namespace Umbraco.Tests.Services } Console.WriteLine("-"); + var versionsSlim = ServiceContext.ContentService.GetVersionsSlim(page.Id, 0, 50).ToArray(); + Assert.AreEqual(5, versionsSlim.Length); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versionsSlim[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versionsSlim[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versionsSlim[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // what we do in the controller to get rollback versions + var versionsSlimFr = versionsSlim.Where(x => x.UpdateDate == x.GetUpdateDate("fr")).ToArray(); + Assert.AreEqual(3, versionsSlimFr.Length); + // alas, at the moment we do *not* properly track 'dirty' for cultures, meaning // that we cannot synchronize dates the way we do with publish dates - and so this // would fail - the version UpdateDate is greater than the cultures'. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 98e02f3d55..6670a41ac4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -103,6 +103,17 @@ loadContent(); } })); + + evts.push(eventsService.on("editors.content.reload", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if(args && args.node && args.node.key === $scope.content.key) { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } + })); + } /** diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b2e64983d6..e2292c50d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -101,6 +101,22 @@ scope.node.template = templateAlias; }; + scope.openRollback = function() { + + var rollback = { + node: scope.node, + submit: function(model) { + const args = { node: scope.node }; + eventsService.emit("editors.content.reload", args); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.rollback(rollback); + }; + function loadAuditTrail() { scope.loadingAuditTrail = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..71cbe3b8d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -808,8 +808,106 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to create blueprint from content with id " + contentId ); - } + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns an array of previous version id's, given a node id and a culture + * + * ##usage + *
+          * contentResource.getRollbackVersions(id, culture)
+          *    .then(function(versions) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the versions + * + */ + getRollbackVersions: function (contentId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { + contentId: contentId, + culture: culture + }) + ), + "Failed to get rollback versions for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersion + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a previous version of a content item + * + * ##usage + *
+          * contentResource.getRollbackVersion(versionId, culture)
+          *    .then(function(version) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the version + * + */ + getRollbackVersion: function (versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { + versionId: versionId, + culture: culture + }) + ), + "Failed to get version for content item with id " + versionId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#rollback + * @methodOf umbraco.resources.contentResource + * + * @description + * Roll backs a content item to a previous version + * + * ##usage + *
+          * contentResource.rollback(contentId, versionId, culture)
+          *    .then(function() {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object + * + */ + rollback: function (contentId, versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostRollbackContent", { + contentId: contentId, versionId:versionId, culture:culture + }) + ), + "Failed to roll back content item with id " + contentId + ); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index eab167c2ec..7d1ef3e9b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -180,6 +180,25 @@ open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#rollback + * @methodOf umbraco.services.editorService + * + * @description + * Opens a rollback editor in infinite editing. + * @param {String} editor.node The node to rollback + * @param {Callback} editor.submit Saves, submits, and closes the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + + function rollback(editor) { + editor.view = "views/common/infiniteeditors/rollback/rollback.html"; + editor.size = "small"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#linkPicker @@ -481,6 +500,7 @@ copy: copy, move: move, embed: embed, + rollback: rollback, linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4b670ab781..d3f2fee5d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -104,7 +104,7 @@ } .umb-button--xs { - padding: 5px 16px; + padding: 5px 13px; font-size: 14px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index f2cacc26b3..fb83504a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -8,6 +8,9 @@ .umb-box-header { padding: 10px 20px; border-bottom: 1px solid @gray-9; + display: flex; + align-items: center; + justify-content: space-between; } .umb-box-header-title { diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less index b8fb5ca5db..5767b0b474 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less @@ -7,6 +7,8 @@ .ws-normal { white-space: normal; } .nowrap { white-space: nowrap; } .pre { white-space: pre; } +.pre-wrap { white-space: pre-wrap; } +.pre-line { white-space: pre-line; } .truncate { white-space: nowrap; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js new file mode 100644 index 0000000000..6b8462b583 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -0,0 +1,177 @@ +(function () { + "use strict"; + + function RollbackController($scope, contentResource, localizationService, assetsService) { + + var vm = this; + + vm.rollback = rollback; + vm.changeLanguage = changeLanguage; + vm.changeVersion = changeVersion; + vm.submit = submit; + vm.close = close; + + ////////// + + function onInit() { + + vm.loading = true; + vm.variantVersions = []; + vm.diff = null; + vm.currentVersion = null; + vm.rollbackButtonDisabled = true; + + // find the current version for invariant nodes + if($scope.model.node.variants.length === 1) { + vm.currentVersion = $scope.model.node.variants[0]; + } + + // find the current version for nodes with variants + if($scope.model.node.variants.length > 1) { + var active = _.find($scope.model.node.variants, function (v) { + return v.active; + }); + + // preselect the language in the dropdown + if(active) { + vm.selectedLanguage = active; + vm.currentVersion = active; + } + } + + // set default title + if(!$scope.model.title) { + localizationService.localize("actions_rollback").then(function(value){ + $scope.model.title = value; + }); + } + + // Load in diff library + assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { + + getVersions().then(function(){ + vm.loading = false; + }); + + }); + + } + + function changeLanguage(language) { + vm.currentVersion = language; + getVersions(); + } + + function changeVersion(version) { + + if(version && version.versionId) { + + 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; + createDiff(vm.currentVersion, vm.previousVersion); + vm.rollbackButtonDisabled = false; + }); + + } else { + vm.diff = null; + vm.rollbackButtonDisabled = true; + } + } + + function getVersions() { + + const nodeId = $scope.model.node.id; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + vm.previousVersions = data.map(version => { + version.displayValue = version.versionDate + " - " + version.versionAuthorName; + return version; + }); + }); + } + + /** + * This will load in a new version + */ + function createDiff(currentVersion, previousVersion) { + + vm.diff = {}; + vm.diff.properties = []; + + // find diff in name + vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name); + + // extract all properties from the tabs and create new object for the diff + currentVersion.tabs.forEach((tab, tabIndex) => { + tab.properties.forEach((property, propertyIndex) => { + var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; + + // we have to make properties storing values as object into strings (Grid, nested content, etc.) + if(property.value instanceof Object) { + property.value = JSON.stringify(property.value, null, 1); + property.isObject = true; + } + + if(oldProperty.value instanceof Object) { + oldProperty.value = JSON.stringify(oldProperty.value, null, 1); + oldProperty.isObject = true; + } + + // create new property object used in the diff table + var diffProperty = { + "alias": property.alias, + "label": property.label, + "diff": (property.value || oldProperty.value) ? JsDiff.diffWords(property.value, oldProperty.value) : "", + "isObject": (property.isObject || oldProperty.isObject) ? true : false + }; + + vm.diff.properties.push(diffProperty); + + }); + }); + + } + + function rollback() { + + vm.rollbackButtonState = "busy"; + + const nodeId = $scope.model.node.id; + const versionId = vm.previousVersion.versionId; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.rollback(nodeId, versionId, culture) + .then(data => { + vm.rollbackButtonState = "success"; + submit(); + }, error => { + vm.rollbackButtonState = "error"; + }); + + } + + function submit() { + if($scope.model.submit) { + $scope.model.submit($scope.model.submit); + } + } + + function close() { + if($scope.model.close) { + $scope.model.close(); + } + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html new file mode 100644 index 0000000000..f7ab78b7a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -0,0 +1,104 @@ +
+ + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

+ +
+ +
+ +
+ +
Changes
+ + + + + + + + + + + + + +
Name + + {{part.value}} + {{part.value}} + {{part.value}} + +
{{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
+ +
+ +
+
+
+ + + + + + + + + + +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 03d4318ba2..d8bc08491a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,19 @@ - + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html index 8c59061788..e9f771f09c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html @@ -1,10 +1,13 @@ -
-
- - {{title}} -
-
- - {{description}} +
+
+
+ + {{title}} +
+
+ + {{description}} +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js deleted file mode 100644 index 9da43b1430..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js +++ /dev/null @@ -1,117 +0,0 @@ -(function () { - "use strict"; - - function ContentRollbackController($scope, assetsService) { - - var vm = this; - - vm.rollback = rollback; - vm.loadVersion = loadVersion; - vm.closeDialog = closeDialog; - - function onInit() { - - vm.loading = true; - vm.variantVersions = []; - vm.diff = null; - - // Load in diff library - assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; - - vm.loading = false; - - }); - - } - - function rollback() { - console.log("rollback"); - } - - /** - * This will load in a new version - */ - function loadVersion(id) { - - // fake load version - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.diff = {}; - vm.diff.properties = []; - - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); - - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; - var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) - }; - vm.diff.properties.push(diffProperty); - }); - - } - - /** - * This will close the dialog - */ - function closeDialog() { - $scope.nav.hideDialog(); - } - - onInit(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Content.RollbackController", ContentRollbackController); - -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html b/src/Umbraco.Web.UI.Client/src/views/content/rollback.html deleted file mode 100644 index 389b8f30db..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html +++ /dev/null @@ -1,80 +0,0 @@ -
- -
- - - -
- -
-
Rollback {{ currentNode.name }}
- -
-

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

- -
- -
- -
- -
Changes
- - - - - - - - - - - - - -
Name - - {{part.value}} - {{part.value}} - {{part.value}} - -
{{property.label}} - - {{part.value}} - {{part.value}} - {{part.value}} - -
- -
- -
-
- - - -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index de52021220..d18dab1987 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,7 +348,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 84b2cc5cb8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,98 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - -
- - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
-
-

- -

-
- - - -
-
-
-
- - -
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index adc2a78804..cce9a2d2ed 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1702,5 +1702,93 @@ namespace Umbraco.Web.Editors Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions); } + [HttpGet] + public IEnumerable GetRollbackVersions(int contentId, string culture = null) + { + var rollbackVersions = new List(); + var writerIds = new HashSet(); + + //Return a list of all versions of a specific content node + // fixme - cap at 50 versions for now? + var versions = Services.ContentService.GetVersionsSlim(contentId, 0, 50); + + //Not all nodes are variants & thus culture can be null + if (culture != null) + { + //Get cultures that were published with the version = their update date is equal to the version's + versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); + } + + //First item is our current item/state (cant rollback to ourselves) + versions = versions.Skip(1); + + foreach (var version in versions) + { + var rollbackVersion = new RollbackVersion + { + VersionId = version.VersionId, + VersionDate = version.UpdateDate, + VersionAuthorId = version.WriterId + }; + + rollbackVersions.Add(rollbackVersion); + + writerIds.Add(version.WriterId); + } + + var users = Services.UserService + .GetUsersById(writerIds.ToArray()) + .ToDictionary(x => x.Id, x => x.Name); + + foreach (var rollbackVersion in rollbackVersions) + { + if (users.TryGetValue(rollbackVersion.VersionAuthorId, out var userName)) + rollbackVersion.VersionAuthorName = userName; + } + + return rollbackVersions; + } + + [HttpGet] + public ContentVariantDisplay GetRollbackVersion(int versionId, string culture = null) + { + var version = Services.ContentService.GetVersion(versionId); + var content = MapToDisplay(version); + + return culture == null + ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + } + + [HttpPost] + public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") + { + var rollbackResult = Services.ContentService.Rollback(contentId, versionId, culture, Security.GetUserId().ResultOr(0)); + + if (rollbackResult.Success) + return Request.CreateResponse(HttpStatusCode.OK); + + var notificationModel = new SimpleNotificationModel(); + + switch (rollbackResult.Result) + { + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; + } + + return Request.CreateValidationErrorResponse(notificationModel); + } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs new file mode 100644 index 0000000000..dad1341a8c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "rollbackVersion", Namespace = "")] + public class RollbackVersion + { + [DataMember(Name = "versionId")] + public int VersionId { get; set; } + + [DataMember(Name = "versionDate")] + public DateTime VersionDate { get; set; } + + [DataMember(Name = "versionAuthorId")] + public int VersionAuthorId { get; set; } + + [DataMember(Name = "versionAuthorName")] + public string VersionAuthorName { get; set; } + } +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 3a990a7741..436c65d95d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -238,7 +238,6 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, true); - AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index faf08849ce..63e497a1c4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -147,6 +147,7 @@ + @@ -1311,12 +1312,6 @@ editPackage.aspx - - rollBack.aspx - - - rollBack.aspx - @@ -1401,9 +1396,6 @@ ASPXCodeBehind - - ASPXCodeBehind - @@ -1457,10 +1449,7 @@ umbraco_org_umbraco_update_CheckForUpgrade - - - - +