From 56e282946f3b727e42c10c222302e52078e4b9de Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Fri, 19 Aug 2022 10:19:36 +0200 Subject: [PATCH] Added copy functionality for data types (#11867) * Added copy functionality for data types * Fix errors * Add logic to default interface * PR Feedback * PR feedback * Fix error --- src/Umbraco.Core/Models/IDataType.cs | 24 +- src/Umbraco.Core/Services/DataTypeService.cs | 33 + src/Umbraco.Core/Services/IDataTypeService.cs | 11 + .../Controllers/DataTypeController.cs | 24 + .../Trees/DataTypeTreeController.cs | 5 +- .../src/common/resources/datatype.resource.js | 691 ++++++++++-------- .../src/views/dataTypes/copy.controller.js | 62 ++ .../src/views/dataTypes/copy.html | 53 ++ 8 files changed, 571 insertions(+), 332 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs index 6f0002c779..64ffe6489d 100644 --- a/src/Umbraco.Core/Models/IDataType.cs +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -28,14 +28,26 @@ public interface IDataType : IUmbracoEntity, IRememberBeingDirty ValueStorageType DatabaseType { get; set; } /// - /// Gets or sets the configuration object. + /// Gets or sets the configuration object. /// /// - /// The configuration object is serialized to Json and stored into the database. - /// - /// The serialized Json is deserialized by the property editor, which by default should - /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. - /// + /// The configuration object is serialized to Json and stored into the database. + /// The serialized Json is deserialized by the property editor, which by default should + /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. /// object? Configuration { get; set; } + + /// + /// Creates a deep clone of the current entity with its identity/alias reset + /// We have the default implementation here to avoid breaking changes for the user + /// + /// + IDataType DeepCloneWithResetIdentities() + { + var clone = (DataType)DeepClone(); + clone.Key = Guid.Empty; + clone.ResetIdentity(); + return clone; + } } + diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 1fdbb4a79b..d310954985 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -436,6 +436,39 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); } + public Attempt?> Copy(IDataType copying, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + IDataType copy; + using (var scope = ScopeProvider.CreateCoreScope()) + { + try + { + if (containerId > 0) + { + var container = _dataTypeContainerRepository.Get(containerId); + if (container is null) + { + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + } + } + copy = copying.DeepCloneWithResetIdentities(); + + copy.Name += " (copy)"; // might not be unique + copy.ParentId = containerId; + _dataTypeRepository.Save(copy); + scope.Complete(); + } + catch (DataOperationException ex) + { + return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback + } + } + + return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy); + } + /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index effb4573b4..02bc51a8f6 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -100,4 +100,15 @@ public interface IDataTypeService : IService IEnumerable GetByEditorAlias(string propertyEditorAlias); Attempt?> Move(IDataType toMove, int parentId); + + /// + /// Copies the give to a given container + /// We have the default implementation here to avoid breaking changes for the user + /// + /// The data type that will be copied + /// The container ID under where the data type will be copied + /// + /// + Attempt?> Copy(IDataType copying, int containerId) => throw new NotImplementedException(); + } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index a524062d36..7de06dfe6d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -383,6 +383,30 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } + public IActionResult PostCopy(MoveOrCopy copy) + { + var toCopy = _dataTypeService.GetDataType(copy.Id); + if (toCopy is null) + { + return NotFound(); + } + + Attempt?> result = _dataTypeService.Copy(toCopy, copy.ParentId); + if (result.Success) + { + return Content(toCopy.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); + } + + return result.Result?.Result switch + { + MoveOperationStatusType.FailedParentNotFound => NotFound(), + MoveOperationStatusType.FailedCancelledByEvent => ValidationProblem(), + MoveOperationStatusType.FailedNotAllowedByPath => ValidationProblem( + _localizedTextService.Localize("moveOrCopy", "notAllowedByPath")), + _ => throw new ArgumentOutOfRangeException() + }; + } + public IActionResult PostRenameContainer(int id, string name) { var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 84a32d3659..d0164afa06 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -181,8 +181,9 @@ public class DataTypeTreeController : TreeController, ISearchableTree menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); } - menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); - } + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + } return menu; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 8f5308ce22..91d74e1efd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -5,7 +5,7 @@ **/ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { - return { + return { /** * @ngdoc method @@ -30,358 +30,401 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { */ getPreValues: function (editorAlias, dataTypeId) { - if (!dataTypeId) { - dataTypeId = -1; - } + if (!dataTypeId) { + dataTypeId = -1; + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetPreValues", - [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - "Failed to retrieve pre values for editor alias " + editorAlias); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetPreValues", + [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), + "Failed to retrieve pre values for editor alias " + editorAlias); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getReferences - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Retrieves references of a given data type. - * - * @param {Int} id id of datatype to retrieve references for - * @returns {Promise} resourcePromise object. - * - */ - getReferences: function (id) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getReferences + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Retrieves references of a given data type. + * + * @param {Int} id id of datatype to retrieve references for + * @returns {Promise} resourcePromise object. + * + */ + getReferences: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetReferences", - { id: id })), - "Failed to retrieve usages for data type of id " + id); - - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetReferences", + { id: id })), + "Failed to retrieve usages for data type of id " + id); - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given id - * - * ##usage - *
-         * dataTypeResource.getById(1234)
-         *    .then(function(datatype) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getById: function (id) { + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetById", - [{ id: id }])), - "Failed to retrieve data for data type id " + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given id + * + * ##usage + *
+     * dataTypeResource.getById(1234)
+     *    .then(function(datatype) {
+     *        alert('its here!');
+     *    });
+     * 
+ * + * @param {Int} id id of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getById: function (id) { - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getByName - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given name - * - * ##usage - *
-         * dataTypeResource.getByName("upload")
-         *    .then(function(datatype) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {String} name Name of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getByName: function (name) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetById", + [{ id: id }])), + "Failed to retrieve data for data type id " + id); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetByName", - [{ name: name }])), - "Failed to retrieve data for data type with name: " + name); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getByName + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given name + * + * ##usage + *
+     * dataTypeResource.getByName("upload")
+     *    .then(function(datatype) {
+     *        alert('its here!');
+     *    });
+     * 
+ * + * @param {String} name Name of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByName: function (name) { - getAll: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetByName", + [{ name: name }])), + "Failed to retrieve data for data type with name: " + name); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAll")), - "Failed to retrieve data"); - }, + getAll: function () { - getGroupedDataTypes: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedDataTypes")), - "Failed to retrieve data"); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAll")), + "Failed to retrieve data"); + }, - getGroupedPropertyEditors: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedPropertyEditors")), - "Failed to retrieve data"); - }, + getGroupedDataTypes: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedDataTypes")), + "Failed to retrieve data"); + }, - getAllPropertyEditors: function () { + getGroupedPropertyEditors: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedPropertyEditors")), + "Failed to retrieve data"); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAllPropertyEditors")), - "Failed to retrieve data"); - }, + getAllPropertyEditors: function () { - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty data type item - * - * The scaffold is used to build editors for data types that has not yet been populated with data. - * - * ##usage - *
-         * dataTypeResource.getScaffold()
-         *    .then(function(scaffold) {
-         *        var myType = scaffold;
-         *        myType.name = "My new data type";
-         *
-         *        dataTypeResource.save(myType, myType.preValues, true)
-         *            .then(function(type){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object containing the data type scaffold. - * - */ - getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAllPropertyEditors")), + "Failed to retrieve data"); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - "Failed to retrieve data for empty datatype"); - }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#deleteById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Deletes a data type with a given id - * - * ##usage - *
-         * dataTypeResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - "Failed to delete item " + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty data type item + * + * The scaffold is used to build editors for data types that has not yet been populated with data. + * + * ##usage + *
+     * dataTypeResource.getScaffold()
+     *    .then(function(scaffold) {
+     *        var myType = scaffold;
+     *        myType.name = "My new data type";
+     *
+     *        dataTypeResource.save(myType, myType.preValues, true)
+     *            .then(function(type){
+     *                alert("Retrieved, updated and saved again");
+     *            });
+     *    });
+     * 
+ * + * @returns {Promise} resourcePromise object containing the data type scaffold. + * + */ + getScaffold: function (parentId) { - deleteContainerById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + "Failed to retrieve data for empty datatype"); + }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#deleteById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Deletes a data type with a given id + * + * ##usage + *
+     * dataTypeResource.deleteById(1234)
+     *    .then(function() {
+     *        alert('its gone!');
+     *    });
+     * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + "Failed to delete item " + id); + }, - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Returns a custom listview, given a content types alias - * - * - * ##usage - *
-         * dataTypeResource.getCustomListView("home")
-         *    .then(function(listview) {
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object containing the listview datatype. - * - */ + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Returns a custom listview, given a content types alias + * + * + * ##usage + *
+     * dataTypeResource.getCustomListView("home")
+     *    .then(function(listview) {
+     *    });
+     * 
+ * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ - getCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to retrieve data for custom listview datatype"); - }, + getCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to retrieve data for custom listview datatype"); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#createCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Creates and returns a custom listview, given a content types alias - * - * ##usage - *
-        * dataTypeResource.createCustomListView("home")
-        *    .then(function(listview) {
-        *    });
-        * 
- * - * @returns {Promise} resourcePromise object containing the listview datatype. - * - */ - createCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to create a custom listview datatype"); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#createCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Creates and returns a custom listview, given a content types alias + * + * ##usage + *
+    * dataTypeResource.createCustomListView("home")
+    *    .then(function(listview) {
+    *    });
+    * 
+ * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to create a custom listview datatype"); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#save - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Saves or update a data type - * - * @param {Object} dataType data type object to create/update - * @param {Array} preValues collection of prevalues on the datatype - * @param {Bool} isNew set to true if type should be create instead of updated - * @returns {Promise} resourcePromise object. - * - */ - save: function (dataType, preValues, isNew) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#save + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Saves or update a data type + * + * @param {Object} dataType data type object to create/update + * @param {Array} preValues collection of prevalues on the datatype + * @param {Bool} isNew set to true if type should be create instead of updated + * @returns {Promise} resourcePromise object. + * + */ + save: function (dataType, preValues, isNew) { - var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), - "Failed to save data for data type id " + dataType.id); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), + "Failed to save data for data type id " + dataType.id); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#move - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * dataTypeResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#move + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+     * dataTypeResource.move({ parentId: 1244, id: 123 })
+     *    .then(function() {
+     *        alert("node was moved");
+     *    }, function(err){
+     *      alert("node didnt move:" + err.data.Message); 
+     *    });
+     * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }, { responseType: 'text' }), - 'Failed to move content'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' }), + 'Failed to move content'); + }, - createContainer: function (parentId, name) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#copy + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+     * dataTypeResource.copy({ parentId: 1244, id: 123 })
+     *    .then(function() {
+     *        alert("node has been copied");
+     *    }, function(err){
+     *      alert("node didnt copy:" + err.data.Message);
+     *    });
+     * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: encodeURIComponent(name) })), - 'Failed to create a folder under parent id ' + parentId); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' }), + 'Failed to copy content'); + }, + + createContainer: function (parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: encodeURIComponent(name) })), + 'Failed to create a folder under parent id ' + parentId); + }, renameContainer: function (id, name) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js new file mode 100644 index 0000000000..3f7634b50b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js @@ -0,0 +1,62 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.DataType.CopyController", + function ($scope, dataTypeResource, treeService, navigationService, appState) { + + $scope.dialogTreeApi = {}; + $scope.source = _.clone($scope.currentNode); + + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.copy = function () { + + $scope.busy = true; + $scope.error = false; + + dataTypeResource.copy({ parentId: $scope.target.id, id: $scope.source.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the copied content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was copied!!) + + navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + + }); + }; + + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + } + + $scope.close = function () { + navigationService.hideDialog(); + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html new file mode 100644 index 0000000000..64f815148b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html @@ -0,0 +1,53 @@ +
+ +
+
+ +

+ Select the folder to copy {{source.name}} to in the tree structure below +

+ + + +
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+ +
+
+ {{source.name}} was copied underneath {{target.name}} +
+ +
+ +
+ +
+ + +
+ +
+
+
+ + +