diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 753cd72116..dcd7eb9d05 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core public static class Aliases { /// - /// CheckBox List. + /// Block List. /// public const string BlockList = "Umbraco.BlockList"; diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 59e8bf6c05..a807d63f5f 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -17,7 +17,7 @@ module.exports = { installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" }, nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"}, preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" }, - umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" }, + umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" }, rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" } }, diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index 24a6e65540..a94314abd6 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -9,9 +9,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -//const { less } = require('./less'); -//const { views } = require('./views'); - +var {js} = require('./js'); function watchTask(cb) { @@ -37,8 +35,15 @@ function watchTask(cb) { if(group.watch !== false) { viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }); viewWatcher.on('change', function(path, stats) { + console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder); - src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); + + return MergeStream( + src(group.files) + .pipe( dest(config.root + config.targets.views + group.folder) ) + , js() + ); + }); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js index d8dbcc1012..dda5c51175 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js @@ -2,13 +2,20 @@ angular.module("umbraco.directives").directive('focusWhen', function ($timeout) return { restrict: 'A', link: function (scope, elm, attrs, ctrl) { + + var delayTimer; + attrs.$observe("focusWhen", function (newValue) { - if (newValue === "true") { - $timeout(function () { - elm.trigger("focus"); - }); + if (newValue === "true" && document.activeelement !== elm[0]) { + delayTimer = $timeout(function () { + elm[0].focus(); + }); } }); + + scope.$on('$destroy', function() { + $timeout.cancel(delayTimer); + }); } }; }); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 0921f46aac..695b456fd6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -197,6 +197,10 @@ @import "components/umbemailmarketing.less"; +// Property Editors +@import "../views/propertyeditors/blocklist/blocklist.component.less"; + + // Utilities @import "utilities/layout/_display.less"; @import "utilities/theme/_opacity.less"; @@ -220,6 +224,11 @@ // Used for prevalue editors @import "components/prevalues/multivalues.less"; +// Block Elements +@import "../views/blockelements/labelblock/labelblock.editor.less"; +@import "../views/blockelements/textareablock/textareablock.editor.less"; +@import "../views/blockelements/imageblock/imageblock.editor.less"; + // Dashboards @import "dashboards/getstarted.less"; @import "dashboards/umbraco-forms.less"; diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 883907d1dc..d026cdb305 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -32,13 +32,17 @@ function MainController($scope, $location, appState, treeService, notificationsS // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 function handleFirstTab(evt) { if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); + enableTabbingActive(); } } + function enableTabbingActive() { + $scope.tabbingActive = true; + $scope.$digest(); + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + } + function disableTabbingActive(evt) { $scope.tabbingActive = false; $scope.$digest(); @@ -48,6 +52,12 @@ function MainController($scope, $location, appState, treeService, notificationsS window.addEventListener("keydown", handleFirstTab); + $scope.$on("showFocusOutline", function() { + $scope.tabbingActive = true; + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + }); + $scope.removeNotification = function (index) { notificationsService.remove(index); diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html new file mode 100644 index 0000000000..c77f19ba67 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html @@ -0,0 +1,3 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.less new file mode 100644 index 0000000000..99b1bb53f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.less @@ -0,0 +1,12 @@ +.blockelement-imageblock-editor { + + padding-top: 2px; + padding-bottom: 2px; + + img { + width: 100%; + border: none; + resize: none; + border-radius: @baseBorderRadius; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html new file mode 100644 index 0000000000..f0678c76e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html @@ -0,0 +1,4 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.less new file mode 100644 index 0000000000..610773528b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.less @@ -0,0 +1,34 @@ +.blockelement-labelblock-editor { + + width: 100%; + min-height: 48px; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + + color: @ui-action-discreet-type; + + text-align: left; + padding-left: 20px; + padding-bottom: 2px; + margin-bottom: 2px; + margin-top: 2px; + + user-select: none; + + transition: border-color 120ms; + + i { + font-size: 22px; + display: inline-block; + vertical-align: middle; + } + span { + display: inline-block; + vertical-align: middle; + } + + &:hover { + color: @ui-action-discreet-type-hover; + border-color: @gray-8; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.controller.js new file mode 100644 index 0000000000..761fc57e03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.controller.js @@ -0,0 +1,29 @@ +//used for the media picker dialog +angular.module("umbraco") +.controller("Umbraco.Editors.TextAreaBlockElementEditorController", + function ($scope) { + + var vm = this; + + vm.firstProperty = $scope.block.content.tabs[0].properties[0]; +/* + vm.submitOnEnter = function($event) { + if($event && $event.keyCode === 13 && !$event.shiftKey && !$event.ctrlKey) { + var target = $event.target; + if(target.selectionStart === target.selectionEnd && target.selectionEnd === target.textLength) { + //&& (target.textLength === 0 || /\r|\n/.test(target.value.charAt(target.textLength - 1))) + $scope.$emit("showFocusOutline"); + $scope.blockApi.showCreateOptionsFor($scope.block, $event); + } + } + } +*/ + vm.onBlur = function() { + if (vm.firstProperty.value === null || vm.firstProperty.value === "") { + $scope.blockApi.deleteBlock($scope.block); + } + } + + } + +); diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html new file mode 100644 index 0000000000..405c8634b4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html @@ -0,0 +1,15 @@ + +
+ + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.less b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.less new file mode 100644 index 0000000000..492025274a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.less @@ -0,0 +1,27 @@ +.blockelement-textareablock-editor { + + width: 100%; + padding-bottom: 24px; + padding-top: 24px; + + padding-left: 24px; + padding-right: 24px; + + min-height: 64px; + box-sizing: border-box; + + textarea { + display: block; + width: 100%; + max-width: 640px; + margin-left: auto; + margin-right: auto; + border: none; + resize: none; + overflow: auto; + + font-size: 18px; + font-family: Georgia,Cambria,"Times New Roman",Times,serif; + line-height: 1.25; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.controller.js new file mode 100644 index 0000000000..d421418297 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.controller.js @@ -0,0 +1,23 @@ +//used for the media picker dialog +angular.module("umbraco") +.controller("Umbraco.Editors.ElementEditorController", + function ($scope) { + + var vm = this; + + vm.content = $scope.model.block.content; + + vm.saveAndClose = function() { + if ($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + } + + vm.close = function() { + if ($scope.model && $scope.model.close) { + $scope.model.close($scope.model); + } + } + + } +); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.html new file mode 100644 index 0000000000..71aab124e6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/elementeditor/elementeditor.html @@ -0,0 +1,84 @@ +
+ + + + + + +
+ + + +
+ +
+
+ +
+ +
+
{{ group.label }}
+
+ +
+ + +
+ + +
+ +
+
+ +
+ + + + + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html new file mode 100644 index 0000000000..e38858c8b7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html @@ -0,0 +1,104 @@ +
+ + +
+ +
+ +
+ + + + +
+ +
+
+
+ +

No editor

+
+ +
+ + +
+ +
+ +
+
+ + + + + +
+ + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js new file mode 100644 index 0000000000..5fc17fcafd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js @@ -0,0 +1,602 @@ +(function () { + 'use strict'; + + angular + .module('umbraco') + .component('blockListPropertyEditor', { + templateUrl: 'views/propertyeditors/blocklist/blocklist.component.html', + controller: BlockListController, + controllerAs: 'vm', + bindings: { + + }, + require: { + umbProperty: '?^umbProperty', + propertyForm: '?^propertyForm' + } + }); + + function BlockListController($scope, $interpolate, editorService, clipboardService, localizationService, overlayService) { + + var vm = this; + var model = $scope.$parent.$parent.model; + + $scope.moveFocusToBlock = null; + + vm.quickMenuVisible = false; + vm.quickMenuIndex = 0; + + vm.quickMenuAddNewBlock = function(type) { + addNewBlock(vm.quickMenuIndex, type); + vm.quickMenuVisible = false; + } + + vm.availableBlockTypes = [ + { + alias: "pageModule", + name: "Module", + icon: "icon-document", + prototype_paste_data: { + + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "{{pageTitle | truncate:true:36}}", + labelInterpolate: $interpolate("{{pageTitle | truncate:true:36}}"), + editor: "views/blockelements/labelblock/labelblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ] + } + } + }, + { + alias: "contentTypeAlias", + name: "Text", + icon: "icon-info", + prototype_paste_data: { + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "Label", + editor: "views/blockelements/textareablock/textareablock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ] + } + } + }, + { + alias: "contentTypeAlias", + name: "Image", + icon: "icon-picture", + prototype_paste_data: { + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "Label", + editor: "views/blockelements/imageblock/imageblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "Let's have a chat", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ], + temp_image: "/umbraco/assets/img/login.jpg" + } + } + }, + { + alias: "contentTypeAlias", + name: "Inline editing", + icon: "icon-picture", + prototype_paste_data: { + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "Label", + editor: "views/blockelements/imageblock/imageblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "Let's have a chat", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ], + temp_image: "/umbraco/assets/img/demo.png" + } + } + } + ]; + + // var defaultBlockType... + + // TODO: get icon, properties etc. from available types? + vm.blocks = [ + { + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "{{pageTitle | truncate:true:36}}", + labelInterpolate: $interpolate("{{pageTitle | truncate:true:36}}"), + key: 1, + editor: "views/blockelements/labelblock/labelblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "The purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ] + } + }, + { + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "{{pageTitle | truncate:true:36}}", + labelInterpolate: $interpolate("{{pageTitle | truncate:true:36}}"), + key: 2, + editor: "views/blockelements/labelblock/labelblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ] + } + }, + { + + elementType: { + alias: 'contentTypeAlias', + icon: "icon-document", + label: "Text" + }, + label: "{{pageTitle | truncate:true:36}}", + labelInterpolate: $interpolate("{{pageTitle | truncate:true:36}}"), + key: 3, + editor: "views/blockelements/labelblock/labelblock.editor.html", + content: { + variants: [ + { + language: { + isDefault: true + } + } + ], + tabs: [ + { + id: 1234, + label: "Group 1", + properties: [ + { + label: "Page Title", + description: "The title of the page", + view: "textbox", + config: {maxChars: 500}, + hideLabel: false, + validation: {mandatory: true, mandatoryMessage: "", pattern: null, patternMessage: ""}, + readonly: false, + id: 441, + dataTypeKey: "0cc0eba1-9960-42c9-bf9b-60e150b429ae", + value: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + alias: "pageTitle", + editor: "Umbraco.TextBox", + isSensitive: false, + culture: null, + segment: null + } + ] + } + ] + } + } + ]; + + function setDirty() { + if (vm.propertyForm) { + vm.propertyForm.$setDirty(); + } + }; + + function addNewBlock(index, type) { + + var block = angular.copy(type.prototype_paste_data); + + vm.blocks.splice(index, 0, block); + $scope.moveFocusToBlock = block; + + } + /* + function moveFocusToNextBlock(blockModel, $event) { + var index = vm.blocks.indexOf(blockModel); + if(index < vm.blocks.length) { + var nextBlock = vm.blocks[index+1]; + $scope.moveFocusToBlock = nextBlock; + } else { + showCreateOptions(blockModel, $event); + } + } + */ + + vm.showCreateOptionsFor = function(blockModel, $event) { + var index = vm.blocks.indexOf(blockModel); + $event.preventDefault(); + showCreateOptionsAt(index); + } + function showCreateOptionsAt(index) { + vm.quickMenuIndex = index; + vm.quickMenuVisible = true; + window.addEventListener("keydown", handleTypingInCreateOptions); + } + + function handleTypingInCreateOptions(event) { + if (event.ctrlKey || event.metaKey || event.altKey) + return; + + if ( + (event.keyCode === 13) // enter + || + (event.keyCode >= 48 && event.keyCode <= 90)// 0 to z + || + (event.keyCode >= 96 && event.keyCode <= 111)// numpads + || + (event.keyCode >= 186 && event.keyCode <= 222)// semi-colon and a lot of other special characters + ) { + // Continue writting... needs to know default text-element. if we have one. + } + } + + function hideCreateOptions() { + vm.quickMenuVisible = false; + window.removeEventListener("keydown", handleTypingInCreateOptions); + } + + vm.onCreateOptionsBlur = function($event) { + + if(!$($event.relatedTarget).is(".umb-block-list__block--create-bar > button")) { + hideCreateOptions(); + } + + } + + vm.getBlockLabel = function(block) { + + var name = ""; + + 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); + if(index !== -1) { + vm.blocks.splice(index, 1); + } + if(vm.quickMenuIndex > index) { + vm.quickMenuIndex--; + } + } + + vm.editBlock = function(blockModel) { + + var elementEditor = { + block: blockModel, + view: "views/common/infiniteeditors/elementeditor/elementeditor.html", + size: "large", + submit: function(model) { + blockModel.content = model.block.content; + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + + // open property settings editor + editorService.open(elementEditor); + } + + vm.showCreateDialog = function (createIndex, $event) { + + if (vm.blockTypePicker) { + return; + } + + if (vm.availableBlockTypes.length === 0) { + return; + } + + vm.blockTypePicker = { + show: true, + size: vm.availableBlockTypes.length > 6 ? "medium" : "small", + filter: vm.availableBlockTypes.length > 12 ? true : false, + orderBy: "$index", + view: "itempicker", + event: $event, + availableItems: vm.availableBlockTypes, + submit: function (model) { + if (model && model.selectedItem) { + addNewBlock(createIndex, model.selectedItem); + } + vm.blockTypePicker.close(); + }, + close: function () { + vm.blockTypePicker.show = false; + vm.blockTypePicker = null; + } + }; + + }; + + vm.requestCopyBlock = function(block) { + console.log("copy") + } + vm.requestDeleteBlock = function(block) { + localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) { + const overlay = { + title: data[1], + content: data[0], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + vm.deleteBlock(block); + overlayService.close(); + } + }; + + overlayService.open(overlay); + }); + } + + vm.showCopy = clipboardService.isSupported(); + + + + vm.sorting = false; + vm.sortableOptions = { + axis: "y", + cursor: "grabbing", + handle: '.umb-block-list__block', + cancel: 'input,textarea,select,option', + classes: '.blockelement--dragging', + distance: 5, + tolerance: "pointer", + scroll: true, + start: function (ev, ui) { + $scope.$apply(function () { + vm.sorting = true; + }); + }, + update: function (ev, ui) { + setDirty(); + }, + stop: function (ev, ui) { + $scope.$apply(function () { + vm.sorting = false; + }); + } + }; + + $scope.blockApi = { + showCreateOptionsFor: vm.showCreateOptionsFor, + removeBlock: vm.removeBlock + } + + + var copyAllEntriesAction = { + labelKey: 'clipboard_labelForCopyAllEntries', + labelTokens: [model.label], + icon: 'documents', + method: function () {}, + isDisabled: true + } + + var propertyActions = [ + copyAllEntriesAction + ]; + + this.$onInit = function () { + if (this.umbProperty) { + this.umbProperty.setPropertyActions(propertyActions); + } + }; + + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.less new file mode 100644 index 0000000000..a26dda4202 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.less @@ -0,0 +1,217 @@ +@umb-block-list__item_minimum_height: 48px; + +.umb-block-list { + padding-bottom:10px; +} + +.umb-block-list__wrapper { + position: relative; + max-width: 1024px; + > .ui-sortable > .ui-sortable-helper > .umb-block-list__block > .umb-block-list__block--content > * { + box-shadow: 0px 5px 10px 0 rgba(0,0,0,.2); + } +} + +.umb-block-list__block { + position: relative; + width: 100%; + cursor: grab; + + .umb-block-list__block--head { + opacity: 0; + transition: opacity 120ms; + } + .umb-block-list__block--actions { + opacity: 0; + transition: opacity 120ms; + } + + &:hover, &:focus, &:focus-within { + .umb-block-list__block--head { + opacity: 1; + } + + .umb-block-list__block--actions { + opacity: 1; + } + } + + &:focus, &:focus-within { + .umb-block-list__block--head { + &::before { + background-color: @blueMid; + } + } + } +} + +.umb-block-list__block--head { + position: absolute; + top: 0; + left: -180px;// 160px from control-header + 20px from spacing. + bottom: 0; + width: 180px;// 160px from control-header + 20px from spacing. + user-select: none; + padding-top: 6px; + padding-right: 14px; + box-sizing: border-box; + color: @gray-5; + background-color: rgba(255, 255, 255, .96); + box-shadow: 0 0 6px 6px rgba(255, 255, 255, .96); + text-align: right; + &::before { + content: ''; + position: absolute; + top: 6px; + bottom: 6px; + right: 4px; + width: 1px; + background-color: @gray-10; + } + + small { + text-align: left; + margin-left: 4px; + margin-bottom: 4px; + } +} +label.umb-block-list__block--head { + cursor: grab; +} + +.umb-block-list__block--actions { + position: absolute; + top: 10px; + right: 10px; + font-size: 0; + background-color: rgba(255, 255, 255, .96); + border-radius: 14px; + padding-left: 5px; + padding-right: 5px; + .action { + display: inline-block; + color: @ui-action-discreet-type; + font-size: 18px; + padding: 5px; + &:hover { + color: @ui-action-discreet-type-hover; + } + } +} + +.umb-block-list__block--content { + position: relative; + width: 100%; + min-height: @umb-block-list__item_minimum_height; + background-color: @white; + border-radius: @baseBorderRadius; +} + + +.umb-block-list__block--create-button { + position: absolute; + width: 100%; + z-index:1; + opacity: 0; + outline: none; + height: 20px; + margin-top: -10px; + padding-top: 10px; + margin-bottom: -10px; + transition: opacity 240ms; + + &::before { + content: ''; + position: absolute; + background-color: @ui-outline; + border-radius: 2px; + top:9px; + right: 0; + left: 0; + height: 2px; + animation: umb-block-list__block--create-button 800ms ease-in-out infinite; + @keyframes umb-block-list__block--create-button { + 0% { opacity: 0.5; } + 50% { opacity: 1; } + 100% { opacity: 0.5; } + } + } + &::after { + content: "+"; + margin-left: auto; + margin-right: auto; + margin-top: -16px; + width: 28px; + height: 25px; + padding-bottom: 3px; + border-radius: 3em; + border: 2px solid @ui-outline; + display: flex; + justify-content: center; + align-items: center; + color: @ui-outline; + font-size: 20px; + font-weight: 800; + background-color: rgba(255, 255, 255, .96); + box-shadow: 0 0 0 2px rgba(255, 255, 255, .96); + transform: scale(0); + transition: transform 240ms ease-in; + } + &:focus { + &::after { + border: 2px solid @ui-outline; + } + } + &:hover, &:focus { + opacity: 1; + transition-duration: 120ms; + &::after { + transform: scale(1); + transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + } +} +/* +.umb-block-list__block--create-bar { + button { + display: inline-block; + width: 120px; + height: 120px; + border-radius: @baseBorderRadius; + text-align: center; + font-size: 12px; + i { + font-size: 30px; + line-height: 20px; + margin-bottom: 10px; + display: block; + } + } +} +*/ +.umb-block-list__create-button { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + border: 1px dashed @ui-action-discreet-border; + color: @ui-action-discreet-type; + font-weight: bold; + margin: 2px 0; + padding: 5px 15px; + box-sizing: border-box; + border-radius: @baseBorderRadius; +} + +.umb-block-list__create-button:hover { + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + text-decoration: none; +} + +.umb-block-list__create-button.--disabled, +.umb-block-list__create-button.--disabled:hover { + color: @gray-7; + border-color: @gray-7; + cursor: default; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html new file mode 100644 index 0000000000..198dab4f5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs index f0148f7a9a..9c4e2f460f 100644 --- a/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs @@ -5,17 +5,26 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - + /// + /// Represents a block list property editor. + /// [DataEditor( Constants.PropertyEditors.Aliases.BlockList, "Block List", "blocklist", - Icon = "icon-list", - Group = Constants.PropertyEditors.Groups.Lists)] + ValueType = ValueTypes.Json, + Group = Constants.PropertyEditors.Groups.Lists, + Icon = "icon-thumbnail-list")] public class BlockListPropertyEditor : BlockEditorPropertyEditor { - public BlockListPropertyEditor(ILogger logger) : base(logger) - { - } + public BlockListPropertyEditor(ILogger logger) + : base(logger) + { } + + #region Pre Value Editor + //protected override IConfigurationEditor CreateConfigurationEditor() => new BlockEditorListConfigurationEditor(); + + #endregion + } }