implement block list editor
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
function blockEditorService($interpolate, udiService) {
|
||||
|
||||
|
||||
function applyModelToScaffold(scaffold, contentModel) {
|
||||
|
||||
scaffold.key = contentModel.key;
|
||||
|
||||
var variant = scaffold.variants[0];
|
||||
|
||||
for (var t = 0; t < variant.tabs.length; t++) {
|
||||
var tab = variant.tabs[t];
|
||||
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
if (contentModel[prop.propertyAlias]) {
|
||||
prop.value = contentModel[prop.propertyAlias];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name umbraco.factory.BlockEditorModelObject
|
||||
* @description A model object used to handle Block Editor data.
|
||||
**/
|
||||
function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations) {
|
||||
|
||||
if (!propertyModelValue) {
|
||||
throw new Error("propertyModelValue cannot be undefined, to ensure we keep the binding to the angular model we need minimum an empty object.");
|
||||
}
|
||||
|
||||
// ensure basic part of data-structure is in place:
|
||||
this.value = propertyModelValue;
|
||||
this.value.layout = this.value.layout || [];
|
||||
this.value.data = this.value.data || [];
|
||||
|
||||
this.propertyEditorAlias = propertyEditorAlias;
|
||||
this.blockConfigurations = blockConfigurations;
|
||||
|
||||
this.scaffolds = [];
|
||||
|
||||
};
|
||||
|
||||
BlockEditorModelObject.prototype = {
|
||||
|
||||
getBlockConfiguration: function(alias) {
|
||||
return this.blockConfigurations.find(blockConfiguration => blockConfiguration.contentTypeAlias === alias);
|
||||
},
|
||||
|
||||
loadScaffolds: function(contentResource) {
|
||||
var tasks = [];
|
||||
|
||||
var scaffoldAliases = [];
|
||||
|
||||
this.blockConfigurations.forEach(blockConfiguration => {
|
||||
scaffoldAliases.push(blockConfiguration.contentTypeAlias);
|
||||
if (blockConfiguration.settingsElementTypeAlias != null) {
|
||||
scaffoldAliases.push(elementType.settingsElementTypeAlias);
|
||||
}
|
||||
});
|
||||
|
||||
// remove dublicates.
|
||||
scaffoldAliases = scaffoldAliases.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
scaffoldAliases.forEach((elementTypeAlias => {
|
||||
tasks.push(contentResource.getScaffold(-20, elementTypeAlias).then(scaffold => {
|
||||
console.log(scaffold);
|
||||
this.scaffolds.push(scaffold);
|
||||
}));
|
||||
}));
|
||||
|
||||
return Promise.all(tasks);
|
||||
},
|
||||
|
||||
getAvailableBlocksForItemPicker: function() {
|
||||
|
||||
var blocks = [];
|
||||
|
||||
this.blockConfigurations.forEach(blockConfiguration => {
|
||||
|
||||
var scaffold = this.getScaffoldFor(blockConfiguration.contentTypeAlias);
|
||||
|
||||
blocks.push({
|
||||
alias: scaffold.contentTypeAlias,
|
||||
name: scaffold.contentTypeName,
|
||||
icon: scaffold.icon
|
||||
});
|
||||
});
|
||||
|
||||
return blocks;
|
||||
},
|
||||
|
||||
getScaffoldFor: function(contentTypeAlias, data) {
|
||||
return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve editing model of a layout entry
|
||||
* @return {Object} Scaffolded Block Content object.
|
||||
*/
|
||||
getEditingModel: function(layoutEntry) {
|
||||
|
||||
var contentModel = this.getContentByUdi(layoutEntry.udi);
|
||||
|
||||
var blockConfiguration = this.getBlockConfiguration(contentModel.contentTypeAlias);
|
||||
|
||||
// TODO: make blockConfiguration the base for model, remeber to make a copy.
|
||||
var model = {
|
||||
label: "",
|
||||
labelInterpolate: $interpolate(blockConfiguration.label),
|
||||
editor: blockConfiguration.view,
|
||||
overlaySize: "medium"
|
||||
};
|
||||
|
||||
var scaffold = this.getScaffoldFor(blockConfiguration.contentTypeAlias);
|
||||
if(scaffold === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
model.content = angular.copy(scaffold);
|
||||
applyModelToScaffold(model.content, contentModel);
|
||||
|
||||
// TODO: settings
|
||||
|
||||
return model;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve layout data
|
||||
* @return layout object.
|
||||
*/
|
||||
getLayout: function() {
|
||||
if (!this.value.layout[this.propertyEditorAlias]) {
|
||||
this.value.layout[this.propertyEditorAlias] = [];
|
||||
}
|
||||
return this.value.layout[this.propertyEditorAlias];
|
||||
},
|
||||
|
||||
/**
|
||||
* Create layout entry
|
||||
* @param {object} blockConfiguration
|
||||
* @return layout entry, to be added in the layout.
|
||||
*/
|
||||
createLayoutEntry: function(contentTypeAlias) {
|
||||
|
||||
var blockConfiguration = this.getBlockConfiguration(contentTypeAlias);
|
||||
|
||||
var entry = {
|
||||
udi: this.createContent(contentTypeAlias)
|
||||
}
|
||||
|
||||
if (blockConfiguration.settingsElementTypeAlias != null) {
|
||||
// TODO: Settings.
|
||||
}
|
||||
|
||||
return entry;
|
||||
},
|
||||
|
||||
// You make entries in your layout your self.
|
||||
|
||||
getContentByUdi: function(udi) {
|
||||
return this.value.data.find(entry => entry.udi === udi);
|
||||
},
|
||||
|
||||
createContent: function(elementTypeAlias) {
|
||||
var content = {
|
||||
contentTypeAlias: elementTypeAlias,
|
||||
udi: udiService.create("element")
|
||||
};
|
||||
this.value.data.push(content);
|
||||
return content.udi;
|
||||
},
|
||||
|
||||
removeContent: function(entry) {
|
||||
const index = this.value.data.indexOf(entry)
|
||||
if (index > -1) {
|
||||
this.value.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
removeContentByUdi: function(udi) {
|
||||
const index = this.value.data.findIndex(o => o.udi === udi);
|
||||
if (index > -1) {
|
||||
this.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createModelObject: function(propertyModelValue, propertyEditorAlias, blockConfigurations) {
|
||||
return new BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations);
|
||||
},
|
||||
getBlockLabel: function(blockModelObject, labelIndex) {
|
||||
|
||||
console.log("getBlockLabel", blockModelObject);
|
||||
|
||||
// TODO: we should do something about this for performance.
|
||||
|
||||
var vars = new Object();
|
||||
vars["$index"] = labelIndex;
|
||||
|
||||
var variant = blockModelObject.content.variants[0];
|
||||
var tab = variant.tabs[0];
|
||||
// TODO: need to look up all tabs...
|
||||
for(const property of tab.properties) {
|
||||
vars[property.alias] = property.value;
|
||||
}
|
||||
|
||||
if(blockModelObject.labelInterpolate) {
|
||||
return blockModelObject.labelInterpolate(vars);
|
||||
}
|
||||
|
||||
return blockModelObject.contentTypeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('umbraco.services').service('blockEditorService', blockEditorService);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,32 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name umbraco.services.udiService
|
||||
* @description A service for UDIs
|
||||
**/
|
||||
function udiService() {
|
||||
return {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.udiService#parse
|
||||
* @methodOf umbraco.services.udiService
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Generates a Udi string.
|
||||
*
|
||||
* @param {string} entityType The entityType as a string.
|
||||
* @returns {string} The generated UDI
|
||||
*/
|
||||
create: function(entityType) {
|
||||
return "umb://" + entityType + "/" + String.CreateGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
angular.module("umbraco.services").factory("udiService", udiService);
|
||||
|
||||
})();
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<div class="blockelement-inlineblock-editor" ng-class="{'--open': bc.isOpen}" ng-controller="Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor as bc">
|
||||
<button type="button" class="btn-reset umb-outline blockelement__draggable-element" ng-click="bc.openBlock(block)" focus-when="{{moveFocusToBlock === block}}">
|
||||
<span class="caret"></span>
|
||||
<i class="icon {{block.elementType.icon}}"></i>
|
||||
<i class="icon {{block.content.icon}}"></i>
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
<div class="blockelement-inlineblock-editor__inner" ng-if="bc.isOpen === true">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<button type="button" class="btn-reset umb-outline blockelement-labelblock-editor blockelement__draggable-element" ng-click="vm.editBlock(block)" focus-when="{{moveFocusToBlock === block}}">
|
||||
<i class="icon {{block.elementType.icon}}"></i>
|
||||
<i class="icon {{block.content.icon}}"></i>
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ angular.module("umbraco")
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.firstProperty = $scope.block.content.tabs[0].properties[0];
|
||||
vm.firstProperty = $scope.block.content.variants[0].tabs[0].properties[0];
|
||||
/*
|
||||
vm.onBlur = function() {
|
||||
if (vm.firstProperty.value === null || vm.firstProperty.value === "") {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
angular
|
||||
.module('umbraco.directives')
|
||||
.component('umbElementContentEditor', {
|
||||
templateUrl: 'views/common/infiniteeditors/elementeditor/elementeditor.component.html',
|
||||
templateUrl: 'views/common/infiniteeditors/elementeditor/elementContentEditor.component.html',
|
||||
controller: ElementEditorComponentController,
|
||||
controllerAs: 'vm',
|
||||
bindings: {
|
||||
|
||||
+47
-27
@@ -15,7 +15,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function BlockListController($scope, $interpolate, editorService, clipboardService, localizationService, overlayService) {
|
||||
function BlockListController($scope, $interpolate, editorService, clipboardService, localizationService, overlayService, blockEditorService, contentResource) {
|
||||
|
||||
var unsubscribe = [];
|
||||
var vm = this;
|
||||
@@ -24,13 +24,15 @@
|
||||
|
||||
$scope.moveFocusToBlock = null;
|
||||
|
||||
console.log("model JSON:", JSON.stringify(model));
|
||||
console.log("model:", model);
|
||||
console.log("config:", model.config);
|
||||
|
||||
vm.validationLimit = model.config.validationLimit;
|
||||
|
||||
console.log("value:", model.value);
|
||||
|
||||
|
||||
/*
|
||||
vm.availableBlockTypes = [
|
||||
{
|
||||
alias: "pageModule",
|
||||
@@ -856,7 +858,34 @@
|
||||
];
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
model.value = model.value || {};
|
||||
|
||||
var modelObject = blockEditorService.createModelObject(model.value, model.editor, model.config.blocks);
|
||||
|
||||
modelObject.loadScaffolds(contentResource).then(loaded);
|
||||
|
||||
vm.layout = [];
|
||||
vm.blocks = [];
|
||||
vm.availableBlockTypes = [];
|
||||
|
||||
function loaded() {
|
||||
|
||||
console.log("Loading done!!!");
|
||||
console.log(modelObject);
|
||||
|
||||
|
||||
vm.layout = modelObject.getLayout();
|
||||
vm.layout.forEach(entry => {
|
||||
vm.blocks.push(modelObject.getEditingModel(entry));
|
||||
});
|
||||
|
||||
vm.availableBlockTypes = modelObject.getAvailableBlocksForItemPicker();
|
||||
console.log(vm.availableBlockTypes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function setDirty() {
|
||||
@@ -865,33 +894,23 @@
|
||||
}
|
||||
};
|
||||
|
||||
function addNewBlock(index, type) {
|
||||
function addNewBlock(index, contentTypeAlias) {
|
||||
|
||||
var block = angular.copy(type.prototype_paste_data);
|
||||
// Create layout entry.
|
||||
var layoutEntry = modelObject.createLayoutEntry(contentTypeAlias);
|
||||
// add layout entry a decired location in layout.
|
||||
vm.layout.splice(index, 0, layoutEntry);
|
||||
|
||||
vm.blocks.splice(index, 0, block);
|
||||
$scope.moveFocusToBlock = block;
|
||||
// make editing object
|
||||
var blockEditingObject = modelObject.getEditingModel(layoutEntry);
|
||||
// apply editing model at decired location in editing model.
|
||||
vm.blocks.splice(index, 0, blockEditingObject);
|
||||
|
||||
$scope.moveFocusToBlock = blockEditingObject;
|
||||
|
||||
}
|
||||
|
||||
function getBlockLabel(block) {
|
||||
|
||||
// TODO: we should do something about this for performance.
|
||||
|
||||
var props = new Object();
|
||||
|
||||
var tab = block.content.tabs[0];
|
||||
// TODO: need to look up all tabs...
|
||||
for(const property of tab.properties) {
|
||||
props[property.alias] = property.value;
|
||||
}
|
||||
|
||||
if(block.labelInterpolate) {
|
||||
return block.labelInterpolate(props);
|
||||
}
|
||||
|
||||
return "block.label";
|
||||
}
|
||||
|
||||
|
||||
vm.deleteBlock = function(block) {
|
||||
var index = vm.blocks.indexOf(block);
|
||||
@@ -942,7 +961,7 @@
|
||||
availableItems: vm.availableBlockTypes,
|
||||
submit: function (model) {
|
||||
if (model && model.selectedItem) {
|
||||
addNewBlock(createIndex, model.selectedItem);
|
||||
addNewBlock(createIndex, model.selectedItem.alias);
|
||||
}
|
||||
vm.blockTypePicker.close();
|
||||
},
|
||||
@@ -1050,9 +1069,10 @@
|
||||
|
||||
// TODO: We need to investigate if we can do a specific watch on each block, so we dont re-render all blocks.
|
||||
unsubscribe.push($scope.$watch("vm.blocks", onBlocksUpdated, true));
|
||||
function onBlocksUpdated(newVal, oldVal){
|
||||
function onBlocksUpdated(newVal, oldVal) {
|
||||
var labelIndex = 1;
|
||||
for(const block of vm.blocks) {
|
||||
block.label = getBlockLabel(block);
|
||||
block.label = blockEditorService.getBlockLabel(block, labelIndex++);
|
||||
}
|
||||
}
|
||||
unsubscribe.push($scope.$watch(() => vm.blocks.length, validateLimits));
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@
|
||||
|
||||
vm.requestRemoveEntryByIndex = function (index) {
|
||||
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockMessage", "blockEditor_confirmDeleteBlockNotice"]).then(function (data) {
|
||||
var contentElementType = vm.getElementTypeByAlias($scope.model.value[index].elementTypeAlias);
|
||||
var contentElementType = vm.getElementTypeByAlias($scope.model.value[index].contentTypeAlias);
|
||||
overlayService.confirmDelete({
|
||||
title: data[0],
|
||||
content: localizationService.tokenReplace(data[1], [contentElementType.name]),
|
||||
@@ -78,7 +78,7 @@
|
||||
vm.getAvailableElementTypes = function () {
|
||||
return vm.elementTypes.filter(function (type) {
|
||||
return !$scope.model.value.find(function (entry) {
|
||||
return type.alias === entry.elementTypeAlias;
|
||||
return type.alias === entry.contentTypeAlias;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -91,9 +91,9 @@
|
||||
|
||||
vm.openAddDialog = function ($event, entry) {
|
||||
|
||||
//we have to add the alias to the objects (they are stored as elementTypeAlias)
|
||||
//we have to add the alias to the objects (they are stored as contentTypeAlias)
|
||||
var selectedItems = _.each($scope.model.value, function (obj) {
|
||||
obj.alias = obj.elementTypeAlias;
|
||||
obj.alias = obj.contentTypeAlias;
|
||||
return obj;
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
vm.addEntryFromElementTypeAlias = function(alias) {
|
||||
|
||||
var entry = {
|
||||
"elementTypeAlias": alias,
|
||||
"contentTypeAlias": alias,
|
||||
"view": null,
|
||||
"labelTemplate": "",
|
||||
"settingsElementTypeAlias": null
|
||||
|
||||
+3
-3
@@ -24,16 +24,16 @@
|
||||
<div class="umb-table-body" ui-sortable="vm.sortableOptions" ng-model="model.value">
|
||||
<div class="umb-table-row block-entry" ng-repeat="entry in model.value">
|
||||
<div class="umb-table-cell not-fixed umb-table__name">
|
||||
{{ contentPreview = vm.getElementTypeByAlias(entry.elementTypeAlias); "" }}
|
||||
{{ contentPreview = vm.getElementTypeByAlias(entry.contentTypeAlias); "" }}
|
||||
<umb-node-preview icon="contentPreview.icon" name="contentPreview.name"></umb-node-preview>
|
||||
<div class="cell-actions">
|
||||
<button type="button" class="btn-reset cell-actions-btn --open umb-outline" ng-click="vm.openElementType(entry.elementTypeAlias)">
|
||||
<button type="button" class="btn-reset cell-actions-btn --open umb-outline" ng-click="vm.openElementType(entry.contentTypeAlias)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<input type="text" ng-model="entry.labelTemplate" class="text-input"/>
|
||||
<input type="text" ng-model="entry.label" class="text-input"/>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<div class="settings-input --hasValue" ng-if="entry.view !== null" >
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public class BlockConfiguration
|
||||
{
|
||||
// TODO: rename this to contentElementTypeAlias, I would like this to be specific, since we have the settings.
|
||||
[JsonProperty("elementTypeAlias")]
|
||||
[JsonProperty("contentTypeAlias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[JsonProperty("settingsElementTypeAlias")]
|
||||
@@ -38,8 +38,8 @@ namespace Umbraco.Web.PropertyEditors
|
||||
[JsonProperty("view")]
|
||||
public string View { get; set; }
|
||||
|
||||
[JsonProperty("labelTemplate")]
|
||||
public string Template { get; set; }
|
||||
[JsonProperty("label")]
|
||||
public string Label { get; set; }
|
||||
}
|
||||
|
||||
[ConfigurationField("useAccordionsAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view")]
|
||||
|
||||
Reference in New Issue
Block a user