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
19 changed files with 633 additions and 149 deletions
+5 -5
View File
@@ -955,12 +955,12 @@
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
"is-extendable": "0.1.1"
},
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
}
}
},
@@ -1670,9 +1670,9 @@
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
"dev": true,
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
}
}
},
@@ -158,10 +158,6 @@
});
contentApp.viewModel = variant;
// emit variant change event so content apps can update content
var args = { "node": $scope.content, "variant": variant };
eventsService.emit("editors.content.changeVariant", args);
return variant;
}
@@ -6,7 +6,7 @@
function link(scope, element, attrs, ctrl) {
var evts = [];
var isInfoApp = false;
var isInfoTab = false;
var labels = {};
scope.publishStatus = [];
@@ -16,11 +16,6 @@
function onInit() {
//get the current variant
scope.activeVariant = _.find(scope.node.variants, function (variant) {
return variant.active;
});
userService.getCurrentUser().then(function(user){
// only allow change of media type if user has access to the settings sections
angular.forEach(user.sections, function(section){
@@ -47,7 +42,7 @@
labels.publishedPendingChanges = data[3];
labels.notCreated = data[4];
setPublishState(scope.node, scope.activeVariant);
setNodePublishStatus(scope.node);
});
@@ -177,40 +172,49 @@
});
}
function setPublishState(node, variant) {
function setNodePublishStatus(node) {
var state = {};
if (node.trashed) {
// deleted node
state.label = labels.deleted;
state.color = "danger";
// deleted node
if (node.trashed === true) {
scope.publishStatus.push({
label: labels.deleted,
color: "danger"
});
return;
}
if (variant.state === "NotCreated") {
// not created variant
state.label = labels.notCreated;
state.color = "gray";
}
else if (variant.state === "Draft") {
// draft variant
state.label = labels.unpublished;
state.color = "gray";
}
else if (variant.state === "Published") {
// published variant
state.label = labels.published;
state.color = "success";
}
else if (variant.state === "PublishedPendingChanges") {
// published variant with pending changes
state.label = labels.publishedPendingChanges;
state.color = "success";
}
if (node.variants) {
for (var i = 0; i < node.variants.length; i++) {
scope.variantState = state;
var variant = node.variants[i];
var status = {
culture: variant.language ? variant.language.culture : null
};
if (variant.state === "NotCreated") {
status.label = labels.notCreated;
status.color = "gray";
}
else if (variant.state === "Draft") {
// draft node
status.label = labels.unpublished;
status.color = "gray";
}
else if (variant.state === "Published") {
// published node
status.label = labels.published;
status.color = "success";
}
else if (variant.state === "PublishedPendingChanges") {
// published node with pending changes
status.label = labels.publishedPendingChanges;
status.color = "success";
}
scope.publishStatus.push(status);
}
}
}
function setPublishDate(date) {
@@ -288,7 +292,7 @@
function formatDatesToLocal() {
// get current backoffice user and format dates
userService.getCurrentUser().then(function (currentUser) {
scope.activeVariant.createDateFormatted = dateHelper.getLocalDate(scope.activeVariant.createDate, currentUser.locale, 'LLL');
scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL');
scope.node.releaseDateYear = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'YYYY')) : null;
scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null;
@@ -308,30 +312,24 @@
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function(){
if (args.alias === "info") {
isInfoApp = true;
isInfoTab = true;
loadAuditTrail();
} else {
isInfoApp = false;
isInfoTab = false;
}
});
}));
// listen for variant change so we can update the content
evts.push(eventsService.on("editors.content.changeVariant", function (event, args) {
setPublishState(args.node, args.variant);
formatDatesToLocal();
}));
// watch for content updates - reload content when node is saved, published etc.
scope.$watch('node.updateDate', function(newValue, oldValue){
if(!newValue) { return; }
if(newValue === oldValue) { return; }
if(newValue === oldValue) { return; }
if(isInfoTab) {
loadAuditTrail();
formatDatesToLocal();
setPublishState(scope.node);
setNodePublishStatus(scope.node);
}
});
@@ -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');
}
};
}
@@ -519,32 +519,25 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var allNewProps = this.getAllProps(savedVariant);
//check for changed properties of the content
for (var k = 0; k < allOrigProps.length; k++) {
var origProp = allOrigProps[k];
var alias = origProp.alias;
for (var p in allOrigProps) {
var alias = allOrigProps[p].alias;
var newProp = getNewProp(alias, allNewProps);
if (newProp && !_.isEqual(origProp.value, newProp.value)) {
if (newProp && !_.isEqual(alias, newProp.value)) {
//they have changed so set the origContent prop to the new one
var origVal = origProp.value;
origProp.value = newProp.value;
var origVal = allOrigProps[p].value;
allOrigProps[p].value = newProp.value;
//instead of having a property editor $watch their expression to check if it has
// been updated, instead we'll check for the existence of a special method on their model
// and just call it.
if (angular.isFunction(origProp.onValueChanged)) {
if (angular.isFunction(allOrigProps[p].onValueChanged)) {
//send the newVal + oldVal
origProp.onValueChanged(origProp.value, origVal);
allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal);
}
changed.push(origProp);
changed.push(allOrigProps[p]);
}
}
for (var p in allOrigProps) {
}
}
@@ -159,18 +159,10 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
//Check if this is for content properties - specific to content/media/member editors because those are special
// user defined properties with custom controls.
if (parts.length > 1 && parts[0] === "_Properties") {
if (parts.length > 2 && parts[0] === "_Properties") {
var propertyAlias = parts[1];
var culture = null;
if (parts.length > 2) {
culture = parts[2];
//special check in case the string is formatted this way
if (culture === "null") {
culture = null;
}
}
var culture = parts[2];
//if it contains 3 '.' then we will wire it up to a property's html field
if (parts.length > 3) {
@@ -140,9 +140,8 @@ function serverValidationManager($timeout) {
}
}
else if (propertyAlias !== undefined) {
//normalize culture to null
if (!culture) {
culture = null;
culture = null; // if empty or null, always make null
}
//don't add it if it already exists
var exists2 = _.find(callbacks, function (item) {
@@ -166,9 +165,8 @@ function serverValidationManager($timeout) {
}
else if (propertyAlias !== undefined) {
//normalize culture to null
if (!culture) {
culture = null;
culture = null; // if empty or null, always make null
}
//remove all callbacks for the content property
@@ -194,9 +192,8 @@ function serverValidationManager($timeout) {
*/
getPropertyCallbacks: function (propertyAlias, culture, fieldName) {
//normalize culture to null
if (!culture) {
culture = null;
culture = null; // if empty or null, always make null
}
var found = _.filter(callbacks, function (item) {
@@ -271,9 +268,8 @@ function serverValidationManager($timeout) {
return;
}
//normalize culture to null
if (!culture) {
culture = null;
culture = null; // if empty or null, always make null
}
//only add the item if it doesn't exist
@@ -310,12 +306,6 @@ function serverValidationManager($timeout) {
if (!propertyAlias) {
return;
}
//normalize culture to null
if (!culture) {
culture = null;
}
//remove the item
this.items = _.reject(this.items, function (item) {
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
@@ -364,12 +354,6 @@ function serverValidationManager($timeout) {
* Gets the error message for the content property
*/
getPropertyError: function (propertyAlias, culture, fieldName) {
//normalize culture to null
if (!culture) {
culture = null;
}
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
@@ -404,12 +388,6 @@ function serverValidationManager($timeout) {
* Checks if the content property + culture + field name combo has an error
*/
hasPropertyError: function (propertyAlias, culture, fieldName) {
//normalize culture to null
if (!culture) {
culture = null;
}
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
@@ -435,4 +435,4 @@ angular.module("umbraco")
onInit();
});
});
@@ -169,11 +169,16 @@
<umb-box-content class="block-form">
<umb-control-group data-element="node-info-status" label="@general_status">
<umb-badge size="xs" color="{{variantState.color}}">{{variantState.label}}</umb-badge>
<div ng-repeat="status in publishStatus" style="margin-bottom: 5px;">
<span ng-if="status.culture"><em>{{status.culture}}: </em></span>
<umb-badge size="xs" color="{{status.color}}">
{{status.label}}
</umb-badge>
</div>
</umb-control-group>
<umb-control-group data-element="node-info-create-date" label="@template_createdDate">
{{activeVariant.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
{{node.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
</umb-control-group>
<umb-control-group data-element="node-info-document-type" label="@content_documentType">
@@ -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);
})();
@@ -123,8 +123,7 @@ describe('contentEditingHelper tests', function () {
var allProps = contentEditingHelper.getAllProps(content);
//act
//note the null, that's because culture is null
formHelper.handleServerValidation({ "_Properties.bodyText.null.value": ["Required"] });
formHelper.handleServerValidation({ "_Properties.bodyText.value": ["Required"] });
//assert
expect(serverValidationManager.items.length).toBe(1);
@@ -144,8 +143,7 @@ describe('contentEditingHelper tests', function () {
{
"Name": ["Required"],
"UpdateDate": ["Invalid date"],
//note the null, that's because culture is null
"_Properties.bodyText.null.value": ["Required field"],
"_Properties.bodyText.value": ["Required field"],
"_Properties.textarea": ["Invalid format"]
});
@@ -228,7 +226,6 @@ describe('contentEditingHelper tests', function () {
//act
var changed = contentEditingHelper.reBindChangedProperties(origContent, newContent);
//assert
expect(changed.length).toBe(2);
expect(changed[0].alias).toBe("grid");
@@ -10,39 +10,24 @@ describe('file manager tests', function () {
describe('file management', function () {
it('adding a file adds to the collection', function () {
fileManager.setFiles({
propertyAlias: 'testProp',
files: ["testFile"]
});
fileManager.setFiles('testProp', ["testFile"]);
expect(fileManager.getFiles().length).toBe(1);
});
it('adding a file with the same property id replaces the existing one', function () {
fileManager.setFiles({
propertyAlias: 'testProp',
files: ["testFile"]
});
fileManager.setFiles({
propertyAlias: 'testProp',
files: ["testFile2"]
});
fileManager.setFiles('testProp', ["testFile"]);
fileManager.setFiles('testProp', ["testFile2"]);
expect(fileManager.getFiles().length).toBe(1);
expect(fileManager.getFiles()[0].file).toBe("testFile2");
});
it('clears all files', function () {
fileManager.setFiles({
propertyAlias: 'testProp',
files: ["testFile"]
});
fileManager.setFiles({
propertyAlias: 'testProp2',
files: ["testFile"]
});
fileManager.setFiles('testProp1', ["testFile"]);
fileManager.setFiles('testProp2', ["testFile"]);
expect(fileManager.getFiles().length).toBe(2);
fileManager.clearFiles();
expect(fileManager.getFiles().length).toBe(0);
});
});
});
});
@@ -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;
}
}