Initial work on Block List Prevalue Editor
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name umbraco.resources.elementTypeResource
|
||||
* @description Loads in data for element types
|
||||
**/
|
||||
function elementTypeResource($q, $http, umbRequestHelper) {
|
||||
|
||||
return {
|
||||
|
||||
getAll: function () {
|
||||
|
||||
// TODO: Change this into a real api (ElementTypeApi). This is a temporary fix to get data.
|
||||
var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/UmbracoApi/NestedContent/GetContentTypes";
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(url),
|
||||
'Failed to retrieve content types'
|
||||
);
|
||||
|
||||
/*
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"elementTypeApiBaseUrl",
|
||||
"GetAll")),
|
||||
"Failed to retrieve data");
|
||||
*/
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
angular.module("umbraco.resources").factory("elementTypeResource", elementTypeResource);
|
||||
@@ -199,6 +199,7 @@
|
||||
|
||||
// Property Editors
|
||||
@import "../views/propertyeditors/blocklist/blocklist.component.less";
|
||||
@import "../views/propertyeditors/blocklist/prevalue/blocklist.elementtypepicker.less";
|
||||
|
||||
|
||||
// Utilities
|
||||
|
||||
@@ -42,5 +42,13 @@
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li ng-if="model.createNewItem">
|
||||
<button class="umb-card-grid-item btn-reset --creator" ng-click="model.createNewItem.action()">
|
||||
<span>
|
||||
<i class="{{ model.createNewItem.icon }}"></i>
|
||||
{{ model.createNewItem.name }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name Umbraco.Editors.PropertySettingsController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The controller for the content type editor property settings dialog
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function ElementTypePickerController($scope, elementTypeResource, overlayService, localizationService, editorService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.enableAddEntry = true;
|
||||
|
||||
function evaluateStatus() {
|
||||
|
||||
if (!vm.elementTypes) return;// cancel if elementTypes isnt loaded jet.
|
||||
|
||||
vm.enableAddEntry = vm.getAvailableElementTypes().length > 0;
|
||||
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = [];
|
||||
}
|
||||
|
||||
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
|
||||
//selectElementTypeModalTitle = value;
|
||||
});
|
||||
|
||||
loadElementTypes();
|
||||
|
||||
}
|
||||
|
||||
function loadElementTypes() {
|
||||
return elementTypeResource.getAll().then(function (elementTypes) {
|
||||
vm.elementTypes = elementTypes;
|
||||
console.log("vm.elementTypes:", vm.elementTypes)
|
||||
evaluateStatus();
|
||||
});
|
||||
}
|
||||
|
||||
vm.removeEntryByIndex = function (index) {
|
||||
$scope.model.value.splice(index, 1);
|
||||
};
|
||||
|
||||
vm.sortableOptions = {
|
||||
axis: "y",
|
||||
cursor: "grabbing",
|
||||
placeholder: 'sortable-placeholder',
|
||||
forcePlaceholderSize: true
|
||||
};
|
||||
|
||||
|
||||
vm.getAvailableElementTypes = function () {
|
||||
return vm.elementTypes.filter(function (type) {
|
||||
return !$scope.model.value.find(function (entry) {
|
||||
return type.alias === entry.elementTypeAlias;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.getElementTypeByAlias = function(alias) {
|
||||
return _.find(vm.elementTypes, function (type) {
|
||||
return type.alias === alias;
|
||||
});
|
||||
};
|
||||
|
||||
vm.openAddDialog = function ($event, entry) {
|
||||
|
||||
//we have to add the alias to the objects (they are stored as elementTypeAlias)
|
||||
var selectedItems = _.each($scope.model.value, function (obj) {
|
||||
obj.alias = obj.elementTypeAlias;
|
||||
return obj;
|
||||
});
|
||||
|
||||
var availableItems = vm.getAvailableElementTypes()
|
||||
|
||||
var elemTypeSelectorOverlay = {
|
||||
view: "itempicker",
|
||||
title: "no title jet",
|
||||
availableItems: availableItems,
|
||||
selectedItems: selectedItems,
|
||||
createNewItem: {
|
||||
action: function() {
|
||||
overlayService.close();
|
||||
vm.createElementTypeAndAdd(vm.addEntryFromElementTypeAlias);
|
||||
},
|
||||
icon: "icon-add",
|
||||
name: "Create new"
|
||||
},
|
||||
position: "target",
|
||||
event: $event,
|
||||
size: availableItems.length < 7 ? "small" : "medium",
|
||||
submit: function (overlay) {
|
||||
vm.addEntryFromElementTypeAlias(overlay.selectedItem.alias);
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(elemTypeSelectorOverlay);
|
||||
};
|
||||
|
||||
vm.createElementTypeAndAdd = function(callback) {
|
||||
const editor = {
|
||||
create: true,
|
||||
infiniteMode: true,
|
||||
isElement: true,
|
||||
submit: function (model) {
|
||||
console.log(model)
|
||||
loadElementTypes().then( function () {
|
||||
callback(model.documentTypeAlias);
|
||||
});
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.documentTypeEditor(editor);
|
||||
}
|
||||
|
||||
vm.addEntryFromElementTypeAlias = function(alias) {
|
||||
|
||||
var entry = {
|
||||
"elementTypeAlias": alias,
|
||||
"view": null,
|
||||
"labelTemplate": "",
|
||||
"settingsElementTypeAlias": null
|
||||
};
|
||||
|
||||
$scope.model.value.push(entry);
|
||||
};
|
||||
|
||||
vm.removeSettingsForEntry = function(entry) {
|
||||
entry.settingsElementTypeAlias = null;
|
||||
};
|
||||
|
||||
vm.openPickSettingsDialog = function ($event, entry) {
|
||||
|
||||
var elemTypeSelectorOverlay = {
|
||||
view: "itempicker",
|
||||
title: "Pick settings (missing translation)",
|
||||
availableItems: vm.elementTypes,
|
||||
position: "target",
|
||||
event: $event,
|
||||
size: vm.elementTypes.length < 7 ? "small" : "medium",
|
||||
createNewItem: {
|
||||
action: function() {
|
||||
overlayService.close();
|
||||
vm.createElementTypeAndAdd((alias) => {
|
||||
vm.addSettingsAtEntry(entry, alias);
|
||||
});
|
||||
},
|
||||
icon: "icon-add",
|
||||
name: "Create new"
|
||||
},
|
||||
submit: function (overlay) {
|
||||
vm.addSettingsAtEntry(entry, overlay.selectedItem.alias);
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(elemTypeSelectorOverlay);
|
||||
};
|
||||
vm.addSettingsAtEntry = function(entry, alias) {
|
||||
entry.settingsElementTypeAlias = alias;
|
||||
};
|
||||
|
||||
vm.openElementType = function(elementTypeAlias) {
|
||||
var elementTypeId = vm.getElementTypeByAlias(elementTypeAlias).id;
|
||||
const editor = {
|
||||
id: elementTypeId,
|
||||
submit: function (model) {
|
||||
loadElementTypes();
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.documentTypeEditor(editor);
|
||||
}
|
||||
|
||||
vm.removeViewForEntry = function(entry) {
|
||||
entry.view = null;
|
||||
};
|
||||
vm.addViewForEntry = function(entry) {
|
||||
const viewPicker = {
|
||||
title: "Pick view (TODO need translation)",
|
||||
section: "settings",
|
||||
treeAlias: "partialView",
|
||||
entityType: "partialView",
|
||||
onlyInitialized: false,
|
||||
filter: function (i) {
|
||||
if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
filterCssClass: "not-allowed",
|
||||
select: function (node) {
|
||||
console.log(node);
|
||||
//entry.view = node.name;
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.treePicker(viewPicker);
|
||||
}
|
||||
|
||||
|
||||
onInit();
|
||||
|
||||
$scope.$watchCollection('model.value', function(newVal, oldVal) {
|
||||
evaluateStatus();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.ElementTypePickerController", ElementTypePickerController);
|
||||
|
||||
})();
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<div id="{{model.alias}}" class="umb-block-list-element-type-picker" ng-controller="Umbraco.PropertyEditors.BlockList.ElementTypePickerController as vm">
|
||||
<div>
|
||||
<div class="umb-table">
|
||||
<div class="umb-table-head">
|
||||
<div class="umb-table-row">
|
||||
<div class="umb-table-cell not-fixed umb-table__name">
|
||||
<localize key="blockEditor_labelContentElementType">Custom view</localize>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<localize key="blockEditor_labelLabelTemplate">Label</localize>
|
||||
<button type="button" class="btn-reset">
|
||||
<i class="icon icon-help-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<localize key="blockEditor_labelCustomView">Custom view</localize>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<localize key="blockEditor_labelSettingsElementType">Custom view</localize>
|
||||
</div>
|
||||
<div class="umb-table-cell action-cell">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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); "" }}
|
||||
<umb-node-preview icon="contentPreview.icon" name="contentPreview.name"></umb-node-preview>
|
||||
<button type="button" class="btn-reset cell-btn --open umb-outline" ng-click="vm.openElementType(entry.elementTypeAlias)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<input type="text" ng-model="entry.labelTemplate" class="text-input"/>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<div class="settings-input --hasValue" ng-if="entry.view !== null" >
|
||||
|
||||
<umb-node-preview icon="'icon-document'" name="entry.view"></umb-node-preview>
|
||||
|
||||
<button type="button" class="btn-reset cell-btn --remove umb-outline" ng-click="vm.removeViewForEntry(entry)">
|
||||
<i class="icon icon-wrong"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn-reset settings-input --noValue umb-outline" ng-if="entry.view === null" ng-click="vm.addViewForEntry(entry)">
|
||||
<localize key="blockEditor_addCustomView">Add custom view</localize>
|
||||
</button>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<div class="settings-input --hasValue" ng-if="entry.settingsElementTypeAlias !== null" >
|
||||
{{ settingsPreview = vm.getElementTypeByAlias(entry.settingsElementTypeAlias); "" }}
|
||||
<umb-node-preview icon="settingsPreview.icon" name="settingsPreview.name"></umb-node-preview>
|
||||
|
||||
<button type="button" class="btn-reset cell-btn --open umb-outline" ng-click="vm.openElementType(entry.settingsElementTypeAlias)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-reset cell-btn --remove umb-outline" ng-click="vm.removeSettingsForEntry(entry)">
|
||||
<i class="icon icon-wrong"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn-reset settings-input --noValue umb-outline" ng-if="entry.settingsElementTypeAlias === null" ng-click="vm.openPickSettingsDialog($event, entry)">
|
||||
<localize key="blockEditor_addSettingsElementType">Add settings</localize>
|
||||
</button>
|
||||
</div>
|
||||
<div class="umb-table-cell action-cell --noOverflow">
|
||||
<button type="button" class="btn-icon umb-outline" ng-click="vm.removeEntryByIndex($index)">
|
||||
<i class="icon icon-trash" aria-hidden="true"></i>
|
||||
<localize key="general_delete" class="sr-only">Delete</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn-reset add-button" ng-click="vm.openAddDialog($event)" ng-disabled="!vm.enableAddEntry">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
.umb-block-list-element-type-picker {
|
||||
|
||||
.block-entry {
|
||||
cursor: grab;
|
||||
background-color: white;
|
||||
border-radius: @baseBorderRadius;
|
||||
}
|
||||
|
||||
.umb-table-head {
|
||||
button {
|
||||
margin-left: 5px;
|
||||
color: @ui-action-discreet-type;
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-table-cell {
|
||||
padding-left: 10px;
|
||||
padding-right: 0;
|
||||
&.action-cell {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
flex: 0 0 30px;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.umb-node-preview {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cell-btn {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
color: @ui-action-discreet-type;
|
||||
height: 30px;
|
||||
width: 26px;
|
||||
margin-top: 1px;
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
.umb-table-cell:hover,
|
||||
.umb-table-cell:focus,
|
||||
.umb-table-cell:focus-within {
|
||||
.cell-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
position: relative;
|
||||
padding: 5px 8px;
|
||||
color: @ui-action-discreet-type;
|
||||
border: 1px dashed @ui-action-discreet-border;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
||||
localize {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.umb-node-preview {
|
||||
padding: 3px 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&.--noValue {
|
||||
text-align: center;
|
||||
border-radius: @baseBorderRadius;
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
border-color: @ui-action-discreet-border-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.--hasValue {
|
||||
border: 1px solid @inputBorder;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width:100%;
|
||||
color: @ui-action-discreet-type;
|
||||
border: 1px dashed @ui-action-discreet-border;
|
||||
border-radius: @baseBorderRadius;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 15px;
|
||||
box-sizing: border-box;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
border-color: @ui-action-discreet-border-hover;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2393,4 +2393,13 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="formsHeadline">Umbraco Forms</key>
|
||||
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
|
||||
</area>
|
||||
<area alias="blockEditor">
|
||||
<key alias="labelContentElementType">Content model</key>
|
||||
<key alias="labelLabelTemplate">Label</key>
|
||||
<key alias="labelCustomView">Custom view</key>
|
||||
<key alias="labelSettingsElementType">Settings model</key>
|
||||
<key alias="addCustomView">Add custom view</key>
|
||||
<key alias="addSettingsElementType">Add settings</key>
|
||||
<key alias="labelTemplatePlaceholder">Overwrite label template</key>
|
||||
</area>
|
||||
</language>
|
||||
|
||||
@@ -2405,4 +2405,13 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="formsHeadline">Umbraco Forms</key>
|
||||
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
|
||||
</area>
|
||||
<area alias="blockEditor">
|
||||
<key alias="labelContentElementType">Content model</key>
|
||||
<key alias="labelLabelTemplate">Label</key>
|
||||
<key alias="labelCustomView">Custom view</key>
|
||||
<key alias="labelSettingsElementType">Settings model</key>
|
||||
<key alias="addCustomView">Add custom view</key>
|
||||
<key alias="addSettingsElementType">Add settings</key>
|
||||
<key alias="labelTemplatePlaceholder">Overwrite label template</key>
|
||||
</area>
|
||||
</language>
|
||||
|
||||
@@ -9,16 +9,35 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// </summary>
|
||||
public class BlockListConfiguration
|
||||
{
|
||||
[ConfigurationField("elementTypes", "Element Types", "views/propertyeditors/blocklist/blocklist.elementtypepicker.html", Description = "Select the Element Types to use as models for the items.")]
|
||||
|
||||
// TODO: rename this to blockDefinitions, cause its not elementTypes, its a dictionary of objects that define blocks, part of a block is the elementType used as content model.
|
||||
[ConfigurationField("elementTypes", "Available Blocks", "views/propertyeditors/blocklist/prevalue/blocklist.elementtypepicker.html", Description = "Define the available blocks.")]
|
||||
public ElementType[] ElementTypes { get; set; }
|
||||
|
||||
// TODO: Fill me in
|
||||
[ConfigurationField("minNumber", "Minimum amount", "number")]
|
||||
public int MinNumber { get; set; }
|
||||
|
||||
[ConfigurationField("maxNumber", "Maximum amount", "number")]
|
||||
public int MaxNumber { get; set; }
|
||||
|
||||
public class ElementType
|
||||
{
|
||||
// TODO: rename this to contentElementTypeAlias, I would like this to be specific, since we have the settings.
|
||||
[JsonProperty("elementTypeAlias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[JsonProperty("settingsElementTypeAlias")]
|
||||
public string SettingsElementTypeAlias { get; set; }
|
||||
|
||||
[JsonProperty("view")]
|
||||
public string View { get; set; }
|
||||
|
||||
[JsonProperty("labelTemplate")]
|
||||
public string Template { get; set; }
|
||||
}
|
||||
|
||||
[ConfigurationField("useAccordionsAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view")]
|
||||
public bool useInlineEditingAsDefault { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
internal class BlockListConfigurationEditor : ConfigurationEditor<BlockListConfiguration>
|
||||
{
|
||||
public BlockListConfigurationEditor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ namespace Umbraco.Web.PropertyEditors
|
||||
{ }
|
||||
|
||||
#region Pre Value Editor
|
||||
//protected override IConfigurationEditor CreateConfigurationEditor() => new BlockEditorListConfigurationEditor();
|
||||
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() => new BlockListConfigurationEditor();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
Executable → Regular
+1
@@ -235,6 +235,7 @@
|
||||
<Compile Include="Profiling\WebProfilingController.cs" />
|
||||
<Compile Include="PropertyEditors\BlockEditorPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\BlockListConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\BlockListConfigurationEditor.cs" />
|
||||
<Compile Include="PropertyEditors\BlockListPropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\ParameterEditors\MultipleMediaPickerParameterEditor.cs" />
|
||||
<Compile Include="PropertyEditors\RichTextEditorPastedImages.cs" />
|
||||
|
||||
Reference in New Issue
Block a user