Compare commits

...

13 Commits

Author SHA1 Message Date
Warren 26c148dccc Merge remote-tracking branch 'origin/temp8-135-ChangeDocTypeDialog' into temp8-135-ChangeDocTypeDialog 2018-08-13 15:12:48 +01:00
Warren 8497056185 Fix up camelCasing stuff 2018-08-13 15:12:23 +01:00
Mads Rasmussen 5498e2c2bb Don't show property select if there are no allowed properties 2018-08-13 15:25:09 +02:00
Mads Rasmussen 602a8ce08c add property map + wire up save 2018-08-13 14:48:18 +02:00
Warren 7e82524c4e Camel case properties to make Mads not have a OCD issue with the server responses :) 2018-08-13 12:18:42 +01:00
Warren 783973b9da Fix up the Content Resource to send the correct params to the Controller endpoint 2018-08-13 11:53:14 +01:00
Mads Rasmussen 096cd1ef73 wip change doc type UI 2018-08-13 12:25:36 +02:00
Warren 07b01fa318 Updates resource with getAvailableProperties(fromAlias, toAlias) to call our new HTTP endpoint 2018-08-13 10:58:40 +01:00
Warren 0ba253c61d New Controller endpoint for Mads to get back a JSON object of allowed properties that he can use easier in the clientside for this dialog 2018-08-13 10:40:52 +01:00
Warren 5da7f034e9 WIP - Need some help with JS 2018-08-10 13:34:42 +01:00
Warren 8eebd4ccae WIP - UI needs finishing
* Adds in the GET & POST Controller methods along with new Models
* Adds new HTML view & Angular Controller
* Updates Angular Content Resource to call our new C# controller endpoints
2018-08-09 18:18:27 +01:00
Warren c47e254efe Remove the Legacy menu conversion on ChangeDocType action 2018-08-09 09:54:39 +01:00
Warren 3f35749619 * Remove actionNotify as notifications dialog already merged in
* Remove actionChangeDocType
2018-08-09 09:53:31 +01:00
9 changed files with 554 additions and 14 deletions
@@ -787,9 +787,91 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
),
"Failed to create blueprint from content with id " + contentId
);
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#getAvailableContentTypesToChangeTo
* @methodOf umbraco.resources.contentResource
*
* @description
* Returns a list of available content types that it can be changed by checking against a nodeID
*
* @param {Int} id id of content item to query for available ContentTypes to change to
* @returns {Promise} resourcePromise object.
*
*/
getAvailableContentTypesToChangeTo: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetAvailableContentTypesToChangeTo",
[{ currentNodeId: id }])),
'Failed to get available content types to change to for item ' + id);
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#getAvailableProperties
* @methodOf umbraco.resources.contentResource
*
* @description
* Returns a list of available properties that a content types that it can be changed to
*
* @param {String} fromAlias alias of current ContentType
* @param {String} toAlias alias of new ContentType changing to
* @returns {Promise} resourcePromise object.
*
*/
getAvailableProperties: function (fromAlias, toAlias) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetAvailableProperties",
[{ fromPropertyAlias: fromAlias}, { toPropertyAlias: toAlias }])),
'Failed to get available propeties for item ' + fromAlias);
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#postContentTypeChange
* @methodOf umbraco.resources.contentResource
*
* @description
* Sorts all children below a given parent node id, based on a collection of node-ids
*
* @param {Object} args arguments object
* @param {Int} args.contentNodeId the ID of the current node to change doctype
* @param {Int} args.newContentTypeId the ID of the pew ContentType to change to
* @param {Int} args.newTemplateId the ID of the new Template to change to
* @param {Array} args.fieldMap array of node IDs as they should be sorted
* @returns {Promise} resourcePromise object.
*
*/
postContentTypeChange: function (args) {
if (!args) {
throw "args cannot be null";
}
if (!args.contentNodeId) {
throw "args.contentNodeId cannot be null";
}
if (!args.newContentTypeId) {
throw "args.newContentTypeId cannot be null";
}
if (!args.newTemplateId) {
throw "args.newTemplateId cannot be null";
}
if (!args.fieldMap) {
throw "args.fieldMap cannot be null";
}
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostContentTypeChange"),
args),
'Failed to change content type content');
}
};
}
@@ -0,0 +1,100 @@
<div ng-controller="Umbraco.Editors.Content.ChangeDocTypeController as vm" ng-cloak>
<div class="umb-dialog-body with-footer">
<div class="umb-pane">
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<div ng-if="!vm.loading">
<div ng-if="vm.allowedContentTypes.length > 0" class="block-form">
<p class="help"><localize key="changeDocType_changeDocTypeInstruction"></localize></p>
<div class="umb-el-wrap" style="margin-bottom: 15px;">
<label class="control-label"><localize key="changeDocType_selectedContent"></localize></label>
<div class="controls controls-row">{{ currentNode.name }}</div>
</div>
<div class="umb-el-wrap" style="margin-bottom: 15px;">
<label class="control-label"><localize key="changeDocType_currentType"></localize></label>
<div class="controls controls-row">{{ vm.currentContentType.name }}</div>
</div>
<!-- Allowed Doctypes to switch to -->
<div class="umb-el-wrap" style="margin-bottom: 15px;">
<label class="control-label"><localize key="changeDocType_newType"></localize></label>
<div class="controls controls-row">
<select
ng-change="vm.changeAllowedContentType(vm.newContentType)"
ng-options="option.name for option in vm.allowedContentTypes"
ng-model="vm.newContentType">
</select>
</div>
</div>
<!-- Allowed Templates to switch to -->
<div ng-if="vm.allowedTemplates.length > 0" class="umb-el-wrap" style="margin-bottom: 15px;">
<label class="control-label"><localize key="changeDocType_newTemplate"></localize></label>
<div class="controls controls-row">
<select
ng-options="option.name for option in vm.allowedTemplates"
ng-model="vm.newTemplate">
</select>
</div>
</div>
<!-- Map properties -->
<table class="table table-condensed table-bordered" ng-if="vm.propertiesMap.length > 0">
<thead>
<tr>
<th>Current Property</th>
<th>Map to Property</th>
</th>
</thead>
<tbody>
<tr ng-repeat="currentProperty in vm.propertiesMap">
<td>
<span class="bold">{{currentProperty.name}}</span>
</td>
<td>
<p ng-if="currentProperty.allowed.length === 0">None</p>
<select
ng-if="currentProperty.allowed.length > 0"
style="margin-bottom: 0;"
ng-options="option.alias as option.name for option in currentProperty.allowed"
ng-model="selected"
ng-change="vm.changeProperty(currentProperty, selected)">
</select>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Display warning if no types available to swap to -->
<umb-empty-state ng-if="vm.allowedContentTypes.length === 0" position="center">
<localize key="changeDocType_docTypeCannotBeChanged"></localize>
</umb-empty-state>
</div>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
<umb-button
type="button"
button-style="link"
action="nav.hideDialog()"
label-key="general_cancel">
</umb-button>
<umb-button
ng-if="vm.allowedContentTypes.length > 0"
type="button"
action="vm.save()"
state="vm.saveButtonState"
button-style="success"
label-key="buttons_save">
</umb-button>
</div>
</div>
@@ -0,0 +1,84 @@
(function () {
"use strict";
function ChangeDocTypeController($scope, contentResource, navigationService) {
var vm = this;
var id = $scope.currentNode.id;
vm.currentContentType = {};
vm.allowedContentTypes = [];
vm.allowedTemplates = [];
vm.propertiesMap = [];
// the new models
vm.newContentType = {};
vm.newTemplate = {};
// bind function to view model
vm.changeAllowedContentType = changeAllowedContentType;
vm.changeProperty = changeProperty;
vm.save = save;
function onInit() {
vm.loading = true;
contentResource.getAvailableContentTypesToChangeTo(id)
.then(function(data){
vm.currentContentType = data.currentContentType;
vm.allowedContentTypes = data.contentTypes;
vm.loading = false;
});
}
function changeAllowedContentType(newContentType) {
contentResource.getAvailableProperties(vm.currentContentType.alias, newContentType.alias)
.then(function(data){
vm.allowedTemplates = data.templates;
vm.propertiesMap = data.currentProperties;
});
}
function changeProperty(currentProperty, selected) {
currentProperty.selectedAlias = selected;
}
function save() {
vm.saveButtonState = "busy";
var fieldMap = [];
angular.forEach(vm.propertiesMap, function(property) {
if(property.selectedAlias) {
var map = {};
map.fromAlias = property.alias;
map.toAlias = property.selectedAlias;
fieldMap.push(map);
}
});
var args = {
"contentNodeId": id,
"newContentTypeId": vm.newContentType.id,
"newTemplateId": vm.newTemplate.id,
"fieldMap": fieldMap
};
contentResource.postContentTypeChange(args).then(function(){
console.log('args', args);
//Sync tree?
//Reload node?
vm.saveButtonState = "success";
}, function(error) {
vm.error = error;
vm.saveButtonState = "error";
});
}
onInit();
}
angular.module("umbraco").controller("Umbraco.Editors.Content.ChangeDocTypeController", ChangeDocTypeController);
})();
@@ -1539,5 +1539,196 @@ namespace Umbraco.Web.Editors
Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions);
}
[HttpGet]
public AvailableContentTypes GetAvailableContentTypesToChangeTo(int currentNodeId)
{
var content = Services.ContentService.GetById(currentNodeId);
// Start with all content types
var contentTypes = Services.ContentTypeService.GetAll().ToArray();
//Remove invalid ones from list of potential alternatives
//Remove CurrentDoctype
contentTypes = contentTypes.Where(x => x.Id != content.ContentTypeId).ToArray();
//Remove Invalid Parent Doctypes
if (content.ParentId == -1)
{
// Root content, only include those that have been selected as allowed at root
contentTypes = contentTypes.Where(x => x.AllowedAsRoot).ToArray();
}
else
{
// Below root, so only include those allowed as sub-nodes for the parent
var parentNode = Services.ContentService.GetById(content.ParentId);
contentTypes = contentTypes
.Where(x => parentNode.ContentType.AllowedContentTypes
.Select(y => y.Id.Value)
.Contains(x.Id)).ToArray();
}
//Remove invalid children doctypes
var docTypeIdsOfChildren = content.Children(Services.ContentService)
.Select(x => x.ContentType.Id)
.Distinct()
.ToList();
contentTypes = contentTypes
.Where(x => x.AllowedContentTypes
.Select(y => y.Id.Value)
.ContainsAll(docTypeIdsOfChildren)).ToArray();
//Return a friendlier model down the wire
var basicContentTypes = new List<ContentTypeBasic>();
foreach (var type in contentTypes)
{
basicContentTypes.Add(Mapper.Map<IContentType, ContentTypeBasic>(type));
}
return new AvailableContentTypes
{
ContentTypes = basicContentTypes,
CurrentNodeName = content.Name,
CurrentContentType = Mapper.Map<IContentType, ContentTypeBasic>(content.ContentType)
};
}
[HttpGet]
public AvailableProperties GetAvailableProperties(string fromPropertyAlias, string toPropertyAlias)
{
//Get the new property type we are changing to
var newContentType = Services.ContentTypeService.Get(toPropertyAlias);
//Get the current property type that the content node is using
var currentContentType = Services.ContentTypeService.Get(fromPropertyAlias);
var properties = new List<CurrentProperty>();
//Create a list of current properties the current doctype has
//With each property in this list specifying what new properties from the doctype we are changing to
//Ensuring they use the same underlying property editor alias
foreach (var currentProp in currentContentType.PropertyTypes)
{
var propertyToAdd = new CurrentProperty
{
Name = currentProp.Name,
Alias = currentProp.Alias
};
var allowedProps = new List<NewProperty>();
foreach (var newProp in newContentType.PropertyTypes.Where(x => x.PropertyEditorAlias == currentProp.PropertyEditorAlias))
{
var allowedProp = new NewProperty
{
Name = newProp.Name,
Alias = newProp.Alias
};
allowedProps.Add(allowedProp);
}
propertyToAdd.Allowed = allowedProps;
properties.Add(propertyToAdd);
}
var templates = new List<TemplateDisplay>();
foreach (var template in newContentType.AllowedTemplates)
{
templates.Add(Mapper.Map<ITemplate, TemplateDisplay>(template));
}
return new AvailableProperties
{
Templates = templates,
CurrentProperties = properties
};
}
/*
{
"contentNodeId": 1234,
"newContentTypeId": 80,
"newTemplateId": 40,
"fieldMap": [
{
"fromAlias": "siteTitle",
"toAlias": "newsTitle"
},
{
"fromAlias": "bodyText",
"toAlias": null
}
]
}
*/
[HttpPost]
public HttpResponseMessage PostContentTypeChange(ChangeContentType model)
{
var content = Services.ContentService.GetById(model.ContentNodeId);
if (content == null)
{
//throw some ex
return Request.CreateNotificationValidationErrorResponse("Content is null");
}
//Check that field mappings (do not have something selected twice & is unique)
if (model.FieldMap.GroupBy(x => x.ToAlias).Any(g => g.Count() > 1))
{
//Throw Error
return Request.CreateNotificationValidationErrorResponse(Services.TextService.Localize("changeDocType/validationErrorPropertyWithMoreThanOneMapping"));
}
// For all properties to be mapped, save the current values to a temporary list
var propertyMappings = new List<FieldMapValue>();
foreach (var map in model.FieldMap)
{
//If the ToAlias is not empty
if (!string.IsNullOrEmpty(map.ToAlias))
{
// Mapping property, get current property value from alias
var sourceAlias = map.FromAlias;
var sourcePropertyValue = content.GetValue(sourceAlias);
// Add to list
propertyMappings.Add(new FieldMapValue
{
ToAlias = map.ToAlias,
CurrentValue = sourcePropertyValue
});
}
}
// Get flag for if content already published
var wasPublished = content.Published;
// Change the document type passing flag to clear the properties
var newContentType = Services.ContentTypeService.Get(model.NewContentTypeId);
content.ChangeContentType(newContentType, true);
//Set the template if one has been selected
content.Template = model.NewTemplateId > 0 ? Services.FileService.GetTemplate(model.NewTemplateId) : null;
// Port across the property values to the new properties
foreach (var propertyMapping in propertyMappings)
{
content.SetValue(propertyMapping.ToAlias, propertyMapping.CurrentValue);
}
// Save the changes
var user = Security.CurrentUser;
Services.ContentService.Save(content, user.Id);
// Publish if the content was already published
if (wasPublished)
{
// no values to publish, really
Services.ContentService.SaveAndPublish(content, userId: user.Id);
}
//All OK - return a 200
return Request.CreateNotificationSuccessResponse("DOCTYPE CHANGED");
}
}
}
@@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
[DataContract(Name = "changeContentType", Namespace = "")]
public class ChangeContentType
{
[DataMember(Name = "contentNodeId")]
public int ContentNodeId { get; set; }
[DataMember(Name = "newContentTypeId")]
public int NewContentTypeId { get; set; }
[DataMember(Name = "newTemplateId")]
public int NewTemplateId { get; set; }
[DataMember(Name = "fieldMap")]
public IEnumerable<FieldMap> FieldMap { get; set; }
}
[DataContract(Name = "fieldMap", Namespace = "")]
public class FieldMap
{
[DataMember(Name = "fromAlias")]
public string FromAlias { get; set; }
[DataMember(Name = "toAlias")]
public string ToAlias { get; set; }
}
public class FieldMapValue
{
public string ToAlias { get; set; }
public object CurrentValue { get; set; }
}
[DataContract(Name = "availableContentTypes", Namespace = "")]
public class AvailableContentTypes
{
[DataMember(Name = "currentNodeName")]
public string CurrentNodeName { get; set; }
[DataMember(Name = "currentContentType")]
public ContentTypeBasic CurrentContentType { get; set; }
[DataMember(Name = "contentTypes")]
public IEnumerable<ContentTypeBasic> ContentTypes { get; set; }
}
[DataContract(Name = "availableProperties", Namespace = "")]
public class AvailableProperties
{
[DataMember(Name = "templates")]
public IEnumerable<TemplateDisplay> Templates { get; set; }
[DataMember(Name = "currentProperties")]
public IEnumerable<CurrentProperty> CurrentProperties { get; set; }
}
[DataContract(Name = "currentProperty", Namespace = "")]
public class CurrentProperty
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "allowed")]
public IEnumerable<NewProperty> Allowed { get; set; }
}
[DataContract(Name = "newProperty", Namespace = "")]
public class NewProperty
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
}
}
@@ -226,7 +226,7 @@ namespace Umbraco.Web.Trees
//need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized.
AddActionNode<ActionMove>(item, menu, true);
AddActionNode<ActionCopy>(item, menu);
AddActionNode<ActionChangeDocType>(item, menu, convert: true);
AddActionNode<ActionChangeDocType>(item, menu);
AddActionNode<ActionSort>(item, menu, true);
@@ -211,46 +211,43 @@ namespace Umbraco.Web.Trees
new LegacyUrlAction(
"create.aspx?nodeId=" + nodeId + "&nodeType=" + nodeType + "&nodeName=" + nodeName + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/create")));
case "UmbClientMgr.appActions().actionNewFolder()":
return Attempt.Succeed(
new LegacyUrlAction(
"createFolder.aspx?nodeId=" + nodeId + "&nodeType=" + nodeType + "&nodeName=" + nodeName + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/create")));
case "UmbClientMgr.appActions().actionProtect()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/protectPage.aspx?mode=cut&nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/protect")));
case "UmbClientMgr.appActions().actionRollback()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/rollback.aspx?nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/rollback")));
case "UmbClientMgr.appActions().actionNotify()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/notifications.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/notify")));
case "UmbClientMgr.appActions().actionPublish()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/publish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/publish")));
case "UmbClientMgr.appActions().actionChangeDocType()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/ChangeDocType.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/changeDocType")));
case "UmbClientMgr.appActions().actionToPublish()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/SendPublish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/sendtopublish")));
case "UmbClientMgr.appActions().actionRePublish()":
return Attempt.Succeed(
new LegacyUrlAction(
"dialogs/republish.aspx?rnd=" + DateTime.UtcNow.Ticks,
Current.Services.TextService.Localize("actions/republish")));
case "UmbClientMgr.appActions().actionSendToTranslate()":
return Attempt.Succeed(
new LegacyUrlAction(
+1
View File
@@ -115,6 +115,7 @@
<Compile Include="Editors\BackOfficeAssetsController.cs" />
<Compile Include="Editors\BackOfficeModel.cs" />
<Compile Include="Editors\BackOfficeServerVariables.cs" />
<Compile Include="Models\ContentEditing\ChangeContentType.cs" />
<Compile Include="Models\ContentEditing\ContentSavedState.cs" />
<Compile Include="WebApi\HttpActionContextExtensions.cs" />
<Compile Include="Models\ContentEditing\IContentSave.cs" />
@@ -36,7 +36,7 @@ namespace Umbraco.Web._Legacy.Actions
{
get
{
return string.Format("{0}.actionChangeDocType()", ClientTools.Scripts.GetAppActions);
return null;
}
}