Merge pull request #6072 from callumbwhyte/v8/feature/block-editor

WIP - block editor prototype
This commit is contained in:
Shannon Deminick
2019-08-09 17:05:21 +10:00
committed by GitHub
27 changed files with 819 additions and 69 deletions
@@ -268,7 +268,7 @@ AnotherContentFinder
public void GetDataEditors()
{
var types = _typeLoader.GetDataEditors();
Assert.AreEqual(38, types.Count());
Assert.AreEqual(39, types.Count());
}
/// <summary>
+25 -44
View File
@@ -1222,7 +1222,7 @@
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
"integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=",
"dev": true
},
"asap": {
@@ -1284,7 +1284,7 @@
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
"integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=",
"dev": true
},
"asynckit": {
@@ -1535,7 +1535,7 @@
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"requires": {
@@ -2516,7 +2516,7 @@
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true
},
"continuable-cache": {
@@ -4045,7 +4045,7 @@
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true,
"requires": {
"ms": "2.0.0"
@@ -4081,7 +4081,7 @@
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true,
"requires": {
"ms": "2.0.0"
@@ -4476,7 +4476,7 @@
"eventemitter3": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
"integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=",
"dev": true
},
"exec-buffer": {
@@ -5309,8 +5309,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@@ -5331,14 +5330,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5353,20 +5350,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5483,8 +5477,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -5496,7 +5489,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5511,7 +5503,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5519,14 +5510,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -5545,7 +5534,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5626,8 +5614,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -5639,7 +5626,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -5725,8 +5711,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5762,7 +5747,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5782,7 +5766,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5826,14 +5809,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -7575,7 +7556,7 @@
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=",
"dev": true,
"requires": {
"isarray": "2.0.1"
@@ -7778,7 +7759,7 @@
"http-proxy": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
"integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=",
"dev": true,
"requires": {
"eventemitter3": "^3.0.0",
@@ -13616,7 +13597,7 @@
"qjobs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
"integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
"integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=",
"dev": true
},
"qs": {
@@ -14787,7 +14768,7 @@
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true,
"requires": {
"ms": "2.0.0"
@@ -15860,7 +15841,7 @@
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
"integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=",
"dev": true,
"requires": {
"media-typer": "0.3.0",
@@ -15902,7 +15883,7 @@
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=",
"dev": true
},
"unc-path-regex": {
@@ -16505,7 +16486,7 @@
"ws": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=",
"dev": true,
"requires": {
"async-limiter": "~1.0.0",
@@ -0,0 +1,65 @@
(function () {
"use strict";
/**
* A component used to render a list of blocks
*/
var umbBlockList = {
templateUrl: 'views/components/blockeditor/umb-block-list.html',
bindings: {
blocks: "<",
config: "<",
scaffolds: "<",
onEdit: "&",
onRemove: "&",
onSettings: "&",
onSort: "&?"
},
controllerAs: 'vm',
controller: umbBlockListController
};
function umbBlockListController() {
var vm = this;
vm.sortableOptions = {
axis: "y",
cursor: "move",
handle: ".handle",
tolerance: 'pointer',
update: function (e, ui) {
if (vm.onSort) {
vm.onSort({ e: e, ui: ui });
}
}
};
vm.editContent = function (block) {
var scaffold = _.findWhere(vm.scaffolds, {
udi: block.udi
});
var element = angular.copy(scaffold);
_.each(element.variants[0].tabs, function (tab) {
_.each(tab.properties, function (property) {
if (block.content[property.alias]) {
property.value = block.content[property.alias];
}
});
});
vm.onEdit({ element: element, block: block });
}
vm.remove = function (block) {
// this should be replaced by a custom dialog (pending some PRs)
if (confirm("TODO: Are you sure?")) {
vm.onRemove({ block: block });
}
}
}
angular.module('umbraco').component('umbBlockList', umbBlockList);
})();
@@ -509,6 +509,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
});
},
getScaffoldByUdi: function (parentId, udi) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmpty",
[{ udi: udi }, { parentId: parentId }])),
'Failed to retrieve data for empty content item with udi ' + udi)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
});
},
getBlueprintScaffold: function (parentId, blueprintId) {
return umbRequestHelper.resourcePromise(
@@ -132,6 +132,10 @@
&.umb-overlay--medium {
width: 480px;
}
&.umb-overlay--large {
width:640px;
}
}
.umb-overlay.umb-overlay-target .umb-overlay-header {
@@ -0,0 +1,18 @@
<ul class="unstyled" ui-sortable="vm.sortableOptions" ng-model="vm.blocks">
<li ng-repeat="block in vm.blocks">
<div class="handle">
<div class="umb-node-preview umb-node-preview--sortable">
<i ng-if="block.icon" class="umb-node-preview__icon {{ block.icon }}"></i>
<div class="umb-node-preview__content">
<div class="umb-node-preview__name" ng-attr-title="{{block.alias}}">TODO: Figure out how to name a block</div>
<div class="umb-node-preview__description" ng-if="block.description">{{ block.description }}</div>
</div>
<div class="umb-node-preview__actions">
<a class="umb-node-preview__action" title="Edit" href="" ng-click="vm.editContent(block)"><localize key="general_edit">Edit</localize></a>
<a class="umb-node-preview__action" title="Settings" href="" ng-click="vm.onSettings({block: block})"><localize key="general_settings">Settings</localize></a>
<a class="umb-node-preview__action umb-node-preview__action--red" title="Remove" ng-href="" ng-click="vm.remove(block)"><localize key="general_remove">Remove</localize></a>
</div>
</div>
</div>
</li>
</ul>
@@ -94,6 +94,11 @@ function contentCreateController($scope,
navigationService.hideDialog(showMenu);
};
$scope.editContentType = function() {
$location.path("/settings/documenttypes/edit/" + $scope.contentTypeId).search("view", "permissions");
close();
}
$scope.createBlank = createBlank;
$scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
$scope.createFromBlueprint = createFromBlueprint;
@@ -10,7 +10,7 @@
<p class="abstract" ng-if="!hasSettingsAccess"><localize key="create_noDocumentTypesWithNoSettingsAccess"/></p>
<div ng-if="hasSettingsAccess">
<p class="abstract"><localize key="create_noDocumentTypes" /></p>
<button class="btn umb-outline" href="#settings/documentTypes/edit/{{contentTypeId}}?view=permissions" ng-click="close()">
<button class="btn umb-outline" ng-click="editContentType()">
<localize key="create_noDocumentTypesEditPermissions"/>
</button>
</div>
@@ -0,0 +1,90 @@
<div>
<umb-editor-view>
<umb-editor-header name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container class="block-form">
<umb-box>
<umb-box-content>
<div class="content-type-editor-dialog edit-property-settings">
<div class="umb-control-group" ng-if="!model.property.locked">
<div class="control-group">
<textarea class="editor-label"
data-element="property-name"
name="propertyLabel"
ng-model="model.property.label"
localize="placeholder"
placeholder="@placeholders_entername"
umb-auto-focus
focus-on-filled="true"
umb-auto-resize
required
ng-keypress="submitOnEnter($event)">
</textarea>
</div>
<div class="control-group -no-margin">
<umb-generate-alias enable-lock="true" alias-from="model.property.label" alias="model.property.alias"></umb-generate-alias>
</div>
</div>
<div class="editor-wrapper umb-control-group control-group" ng-model="model.property.editor" val-require-component ng-if="!model.property.locked">
<a data-element="editor-add" href="" ng-if="!model.property.editor" class="editor-placeholder" hotkey="alt+shift+e" ng-click="openDataTypePicker(model.property)">
<localize key="shortcuts_addEditor"></localize>
</a>
<div class="editor clearfix" ng-if="model.property.editor">
<a href="" class="editor-icon-wrapper" ng-click="openDataTypePicker(model.property)">
<i class="icon {{ model.property.dataTypeIcon }}" ng-class="{'icon-autofill': model.property.dataTypeIcon == null}"></i>
</a>
<div class="editor-details">
<a href="" class="editor-name" ng-click="openDataTypePicker(model.property)" ng-bind="model.property.dataTypeName"></a>
<a href="" class="editor-editor" ng-click="openDataTypePicker(model.property)" ng-bind="model.property.editor"></a>
</div>
<a href class="editor-settings-icon pull-right"
ng-click="vm.openDataTypeSettings(model.property)"
hotkey="alt+shift+d"
ng-if="model.property.editor">
<i class="icon icon-settings"></i>
</a>
</div>
</div>
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="close()">
</umb-button>
<umb-button type="button"
button-style="success"
label-key="general_submit"
state="saveButtonState"
action="submit(model)">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</div>
@@ -0,0 +1,204 @@
(function () {
"use strict";
function BlockEditorController($scope, contentResource, editorService, iconHelper, clipboardService) {
var vm = this;
vm.scaffolds = [];
vm.loading = true;
let allowedElements = [];
// it would be awesome if we could load all scaffolds in one go... however we need to have an eye out for performance,
// oddly enough it's been shown to actually be slower to load them all at once instead of one at a time
var scaffoldsLoaded = 0;
function init() {
_.each($scope.model.config.blocks, function (config) {
contentResource.getScaffoldByUdi(-20, config.elementType).then(function (scaffold) {
if (scaffold.isElement) {
// the scaffold udi is not the same as the element type udi, but we need it to be for comparison
scaffold.udi = config.elementType;
vm.scaffolds.push(scaffold);
}
scaffoldsLoaded++;
initIfAllScaffoldsHaveLoaded();
}, function (error) {
scaffoldsLoaded++;
initIfAllScaffoldsHaveLoaded();
});
});
}
function initIfAllScaffoldsHaveLoaded() {
// Initialize when all scaffolds have loaded
if ($scope.model.config.blocks.length === scaffoldsLoaded) {
vm.scaffolds = _.sortBy(vm.scaffolds, function (scaffold) {
return _.findIndex($scope.model.config.blocks, function (blockConfig) {
return blockConfig.elementType === scaffold.udi;
});
});
_.each($scope.model.value, function (block) {
applyFakeSettings(block);
});
allowedElements = vm.scaffolds.map(x => x.contentTypeAlias);
vm.loading = false;
}
}
vm.openNodeTypePicker = function ($event) {
vm.overlayMenu = {
show: false,
style: {},
filter: vm.scaffolds.length > 12 ? true : false,
orderBy: "$index",
view: "itempicker",
event: $event,
clickPasteItem: function (item) {
vm.pasteFromClipboard(item.data);
vm.overlayMenu.show = false;
vm.overlayMenu = null;
},
submit: function (model) {
if (model && model.selectedItem) {
vm.add(vm.scaffolds.filter(x => x.contentTypeAlias === model.selectedItem.alias)[0]);
}
vm.overlayMenu.show = false;
vm.overlayMenu = null;
},
close: function () {
vm.overlayMenu.show = false;
vm.overlayMenu = null;
}
};
// this could be used for future limiting on node types
vm.overlayMenu.availableItems = [];
vm.scaffolds.forEach(function (scaffold) {
vm.overlayMenu.availableItems.push({
alias: scaffold.contentTypeAlias,
name: scaffold.contentTypeName,
icon: iconHelper.convertFromLegacyIcon(scaffold.icon),
description: scaffold.documentType.description
});
});
if (vm.overlayMenu.availableItems.length === 0) {
return;
}
//TODO: If we support larger thumbnails of some sort one day, this could be large
vm.overlayMenu.size = vm.overlayMenu.availableItems.length > 6 ? "medium" : "small";
vm.overlayMenu.pasteItems = [];
var availableNodesForPaste = clipboardService.retriveDataOfType("elementType", allowedElements);
availableNodesForPaste.forEach(function (node) {
vm.overlayMenu.pasteItems.push({
alias: node.contentTypeAlias,
name: node.name, //contentTypeName
data: node,
icon: iconHelper.convertFromLegacyIcon(node.icon)
});
});
vm.overlayMenu.clickClearPaste = function ($event) {
$event.stopPropagation();
$event.preventDefault();
clipboardService.clearEntriesOfType("elementType", allowedElements);
vm.overlayMenu.pasteItems = []; // This dialog is not connected via the clipboardService events, so we need to update manually.
};
if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) {
// only one scaffold type - no need to display the picker
vm.add(vm.scaffolds[0]);
return;
}
vm.overlayMenu.show = true;
}
vm.add = function (scaffold) {
var element = angular.copy(scaffold);
var block = {
udi: element.udi,
icon: element.icon,
description: element.documentType.description, // probably don't want to persist these.
content: {},
settings: {
view: 'views/propertyeditors/blockeditor/blockeditor.block.html'
}
};
openContent(element, block);
}
vm.editSettings = function (block) {
var options = {
settings: block.settings,
title: "Edit settings",
view: "views/propertyeditors/blockeditor/blockeditor.editsettings.html",
size: "small",
submit: function (model) {
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.open(options);
}
vm.onRemove = function (block) {
$scope.model.value.splice($scope.model.value.indexOf(block), 1);
}
vm.onEdit = function (element, block) {
openContent(element, block);
}
function openContent(element, block) {
var options = {
element: element,
title: 'Edit block',
//fixme: This isn't really a component if its strongly tied to views in the property editor :/
view: "views/propertyeditors/blockeditor/blockeditor.editcontent.html",
submit: function (model) {
_.each(element.variants[0].tabs, function (tab) {
_.each(tab.properties, function (property) {
block.content[property.alias] = property.value;
});
});
if ($scope.model.value.indexOf(block) < 0) {
$scope.model.value.push(block);
applyFakeSettings(block);
}
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.open(options);
}
// TODO: remove this (only for testing)
// currently pushes random values for setting a css grid location
function applyFakeSettings(block) {
block.settings["col"] = 1 + Math.floor(Math.random() * 7);
block.settings["row"] = 1 + Math.floor(Math.random() * 7);
block.settings["w"] = 1 + Math.floor(Math.random() * 3);
block.settings["h"] = 1 + Math.floor(Math.random() * 3);
}
init();
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditorController", BlockEditorController);
})();
@@ -0,0 +1,34 @@
/**
* @ngdoc controller
* @name Umbraco.PropertyEditors.BlockEditor.EditContentController
* @function
*
* @description
* The controller for editing content with the block editor
*/
function BlockEditorEditContentController($scope) {
var vm = this;
vm.submit = submit;
vm.close = close;
vm.content = $scope.model.element.variants[0];
function init() {
}
function submit() {
if($scope.model.submit) {
$scope.model.submit($scope.model);
}
}
function close() {
if($scope.model.close) {
$scope.model.close();
}
}
init();
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.EditContentController", BlockEditorEditContentController);
@@ -0,0 +1,38 @@
<div ng-controller="Umbraco.PropertyEditors.BlockEditor.EditContentController as vm">
<umb-editor-view>
<umb-editor-header name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-load-indicator ng-if="vm.loading" />
<div ng-if="!loading">
<ng-form name="editBlockForm" val-form-manager>
<umb-tabbed-content content="vm.content" />
</ng-form>
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button type="button"
button-style="link"
label-key="general_cancel"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button type="button"
button-style="success"
label-key="general_ok"
action="vm.submit()">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</div>
@@ -0,0 +1,26 @@
/**
* @ngdoc controller
* @name Umbraco.PropertyEditors.BlockEditor.EditSettingsController
* @function
*
* @description
* The controller for editing the configuration of an individual block
*/
//fixme: Need to figure out the name of a block settings, is it block config or block settings?
function BlockEditorEditSettingsController($scope) {
this.submit = () => {
if($scope.model.submit) {
$scope.model.submit($scope.model);
}
}
this.close = () => {
if($scope.model.close) {
$scope.model.close();
}
}
}
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.BlockEditor.EditSettingsController", ['$scope', BlockEditorEditSettingsController]);
@@ -0,0 +1,44 @@
<div ng-controller="Umbraco.PropertyEditors.BlockEditor.EditSettingsController as vm">
<umb-editor-view>
<umb-editor-header name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-load-indicator ng-if="vm.loading" />
<div ng-if="!loading">
<umb-box>
<umb-box-content>
<ng-form name="editBlockForm" class="form-horizontal" val-form-manager>
<umb-control-group label="{{key}}" description="" ng-repeat="(key, value) in model.settings">
<input type="text" ng-model="model.settings[key]" ng-model-options="{ debounce: 5000 }"/>
</umb-control-group>
</ng-form>
</umb-box-content>
</umb-box>
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button type="button"
button-style="link"
label-key="general_cancel"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button type="button"
button-style="success"
label-key="general_ok"
action="vm.submit()">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</div>
@@ -0,0 +1,31 @@
<div ng-controller="Umbraco.PropertyEditors.BlockEditorController as vm">
<div style="position:relative" ng-if="vm.loading">
<umb-load-indicator />
</div>
<div ng-if="!vm.loading">
<umb-block-list blocks="model.value"
config="model.config.blocks"
scaffolds="vm.scaffolds"
on-edit="vm.onEdit(element, block)"
on-remove="vm.onRemove(element, block)"
on-settings="vm.editSettings(block)">
</umb-block-list>
</div>
<div ng-if="!vm.loading" class="umb-nested-content__footer-bar">
<a href class="umb-nested-content__add-content" ng-class="{ '--disabled': !vm.scaffolds.length }" ng-click="vm.openNodeTypePicker($event)" prevent-default>
<localize key="grid_addElement" ng-if="vm.scaffolds.length > 1"></localize>
<span ng-if="vm.scaffolds.length === 1">
<localize key="general_add"></localize> {{ vm.scaffolds[0].contentTypeName }}
</span>
</a>
</div>
<umb-overlay ng-if="vm.overlayMenu.show"
position="target"
size="vm.overlayMenu.size"
view="vm.overlayMenu.view"
model="vm.overlayMenu">
</umb-overlay>
</div>
@@ -0,0 +1,96 @@
/**
* @ngdoc controller
* @name Umbraco.PropertyEditors.BlockEditor.SettingsController
* @function
*
* @description
* The controller for the block editor's data type configuration
*/
//fixme: Rename this! it shouldn't be called Settings, it should be called Configuration because that is what we call pre-values in v8 (data type config)
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.BlockEditor.SettingsController", [
"$scope",
"contentTypeResource",
"editorService",
function ($scope, contentTypeResource, editorService) {
$scope.blocks = [];
$scope.elementTypes = [];
if ($scope.model.value) {
$scope.blocks = $scope.model.value;
} else {
$scope.model.value = [];
}
contentTypeResource.getAll()
.then(function (contentType) {
$scope.elementTypes.push(contentType);
});
$scope.$on("formSubmitting", function () {
$scope.model.value = $scope.blocks;
});
//fixme: This shouldn't be a method, using methods to return values in angular is a performance issue
$scope.getBlockName = function(udi) {
return 'TODO: Get block name from somewhere...';
}
$scope.addBlock = function () {
var block = {};
var pickerOptions = {
title: "Pick an element type",
view: "views/common/infiniteeditors/treepicker/treepicker.html",
size: "small",
multiPicker: false,
section: "settings",
treeAlias: "documentTypes",
entityType: "documentType",
submit: function (model) {
_.each(model.selection, function (elementType) {
block.elementType = elementType.udi;
block.settings = {
view: 'views/propertyeditors/blockeditor/blockeditor.block.html'
}
$scope.model.value.push(block);
});
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.open(pickerOptions);
}
$scope.removeBlock = function (index) {
$scope.model.value.splice(index, 1);
};
//fixme: This currently doesn't work or do anything
$scope.editSettings = function (block) {
var pickerOptions = {
title: "Block settings",
//fixme: This file needs to be renamed since it's inconsistent with other blockeditor file names, probably blockeditor.blocksettings.html
view: "views/propertyeditors/blockeditor/block.settings.html",
size: "small",
submit: function (model) {
block.settings = model;
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.open(pickerOptions);
}
}
]);
@@ -0,0 +1,20 @@
<div ng-controller="Umbraco.PropertyEditors.BlockEditor.SettingsController">
<umb-node-preview ng-repeat="block in blocks"
name="getBlockName(block)"
alias="block.elementType"
icon="icon-folder"
allow-edit="true"
on-edit="editSettings(block)"
allow-remove="true"
on-remove="removeBlock($index)">
</umb-node-preview>
<a href="" class="umb-node-preview-add" ng-click="addBlock()">
<localize key="general_add">Add</localize>
</a>
<hr />
<pre>{{model.value | json}}</pre>
</div>
@@ -0,0 +1,9 @@
function BlockEditorSimpleListController($scope) {
$scope.sortableOptions = {
axis: "y",
cursor: "move",
handle: ".handle",
tolerance: 'pointer'
};
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.SimpleListController", ['$scope', BlockEditorSimpleListController]);
@@ -307,22 +307,22 @@ angular.module("umbraco")
$scope.openEditorOverlay = function (event, area, index, key) {
var title = "";
localizationService.localize("grid_insertControl").then(function (value) {
title = value;
$scope.editorOverlay = {
view: "itempicker",
filter: area.$allowedEditors.length > 15,
title: title,
availableItems: area.$allowedEditors,
event: event,
show: true,
submit: function (model) {
if (model.selectedItem) {
$scope.addControl(model.selectedItem, area, index);
$scope.editorOverlay.show = false;
$scope.editorOverlay = null;
title = value;
$scope.editorOverlay = {
view: "itempicker",
filter: area.$allowedEditors.length > 15,
title: title,
availableItems: area.$allowedEditors,
event: event,
show: true,
submit: function (model) {
if (model.selectedItem) {
$scope.addControl(model.selectedItem, area, index);
$scope.editorOverlay.show = false;
$scope.editorOverlay = null;
}
}
}
};
};
});
};
@@ -18,7 +18,7 @@
$scope.remove = function (index) {
$scope.model.value.splice(index, 1);
}
$scope.sortableOptions = {
axis: "y",
cursor: "move",
@@ -184,7 +184,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$scope.overlayMenu.availableItems.push({
alias: scaffold.contentTypeAlias,
name: scaffold.contentTypeName,
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
icon: iconHelper.convertFromLegacyIcon(scaffold.icon),
description: scaffold.documentType.description
});
});
@@ -192,7 +193,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
return;
}
$scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small";
$scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small";
$scope.overlayMenu.pasteItems = [];
var availableNodesForPaste = clipboardService.retriveDataOfType("elementType", contentTypeAliases);
@@ -40,7 +40,8 @@
<div class="umb-nested-content__footer-bar" ng-hide="hasContentTypes === false || nodes.length >= maxItems">
<a href class="umb-nested-content__add-content" ng-class="{ '--disabled': !scaffolds.length }" ng-click="openNodeTypePicker($event)" prevent-default>
<localize key="grid_addElement"></localize>
<localize key="grid_addElement" ng-if="scaffolds.length > 1"></localize>
<span ng-if="scaffolds.length === 1"><localize key="general_add"></localize> {{ scaffolds[0].contentTypeName }}</span>
</a>
</div>
@@ -60,7 +60,7 @@
<security>
<!-- set to true to auto update login interval (and there by disabling the lock screen -->
<keepUserLoggedIn>false</keepUserLoggedIn>
<keepUserLoggedIn>true</keepUserLoggedIn>
<!-- by default this is true and if not specified in config will be true. set to false to always show a separate username field in the back office user editor -->
<usernameIsEmail>true</usernameIsEmail>
<!-- change in 4.8: Disabled users are now showed dimmed and last in the tree. If you prefer not to display them set this to true -->
+23 -1
View File
@@ -354,12 +354,34 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return EmptyContent(parentId, contentType);
}
/// <summary>
/// Gets an empty content item for the
/// </summary>
/// <param name="udi"></param>
/// <param name="parentId"></param>
[OutgoingEditorModelEvent]
public ContentItemDisplay GetEmpty(GuidUdi udi, int parentId)
{
var contentType = Services.ContentTypeService.Get(udi.Guid);
if(contentType == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return EmptyContent(parentId, contentType);
}
private ContentItemDisplay EmptyContent(int parentId, IContentType contentType)
{
var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0));
var mapped = MapToDisplay(emptyContent);
// translate the content type name if applicable
mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName);
// if your user type doesn't have access to the Settings section it would not get this property mapped
if (mapped.DocumentType != null)
if(mapped.DocumentType != null)
mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name);
//remove the listview app if it exists
@@ -0,0 +1,36 @@
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
public class BlockEditorConfiguration
{
[ConfigurationField("blocks", "Blocks", "views/propertyeditors/blockeditor/blockeditor.settings.html")]
public Block[] Blocks { get; set; }
[ConfigurationField("view", "View", "textstring", Description = "The path to a custom view for rendering the editor")]
public string View { get; set; }
public class Block
{
[JsonProperty("elementType")]
public Udi ElementType { get; set; }
[JsonProperty("settings")]
public BlockSetting Settings { get; set; }
}
public class BlockSetting
{
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("alias")]
public string Alias { get; set; }
[JsonProperty("dataType")]
public Udi DataType { get; set; }
}
}
}
@@ -0,0 +1,9 @@
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
public class BlockEditorConfigurationEditor : ConfigurationEditor<BlockEditorConfiguration>
{
}
}
@@ -4,14 +4,14 @@ using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(Constants.PropertyEditors.Aliases.BlockEditor, "Block Editor", ValueType = ValueTypes.Json, Group="rich content")]
[DataEditor(Constants.PropertyEditors.Aliases.BlockEditor, EditorType.PropertyValue , "Block Editor", "blockeditor", ValueType = ValueTypes.Json, Group="rich content", Icon="icon-application-window-alt")]
public class BlockEditorPropertyEditor : DataEditor
{
public BlockEditorPropertyEditor(ILogger logger)
: base(logger)
{ }
protected override IConfigurationEditor CreateConfigurationEditor() => base.CreateConfigurationEditor();
protected override IConfigurationEditor CreateConfigurationEditor() => new BlockEditorConfigurationEditor();
protected override IDataValueEditor CreateValueEditor() => base.CreateValueEditor();
}
+2
View File
@@ -222,6 +222,8 @@
<Compile Include="Mvc\HttpUmbracoFormRouteStringException.cs" />
<Compile Include="Mvc\ModelBindingExceptionFilter.cs" />
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
<Compile Include="PropertyEditors\BlockEditorConfiguration.cs" />
<Compile Include="PropertyEditors\BlockEditorConfigurationEditor.cs" />
<Compile Include="PropertyEditors\BlockEditorPropertyEditor.cs" />
<Compile Include="Mvc\ValidateUmbracoFormRouteStringAttribute.cs" />
<Compile Include="Profiling\WebProfilingController.cs" />