Merge branch 'v11/dev' into v11/contrib

# Conflicts:
#	.gitignore
#	src/Umbraco.Cms.ManagementApi/ManagementApiComposer.cs
#	src/Umbraco.Cms.ManagementApi/OpenApi.json
This commit is contained in:
Sebastiaan Janssen
2022-11-17 09:26:44 +01:00
223 changed files with 2489 additions and 10107 deletions

View File

@@ -24,7 +24,8 @@
// optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/)
propertyAlias: "@",
showInherit: "<",
inheritsFrom: "<"
inheritsFrom: "<",
hideLabel: "<?"
}
});

View File

@@ -5,7 +5,9 @@
* A component to render the property action toggle
*/
function umbPropertyActionsController(keyboardService, localizationService) {
function umbPropertyActionsController(keyboardService, localizationService, $scope) {
var unsubscribe = [];
var vm = this;
@@ -56,6 +58,9 @@
}
function onDestroy() {
for (var i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]();
}
if (vm.isOpen === true) {
destroyDropDown();
}
@@ -72,26 +77,36 @@
vm.labels.openText = values[0];
vm.labels.closeText = values[1];
});
unsubscribe.push($scope.$watchCollection("vm.actions",
function (newValue, oldValue) {
if (newValue !== oldValue) {
updateActions();
}
}
));
}
function onChanges(simpleChanges) {
if (simpleChanges.actions) {
let actions = simpleChanges.actions.currentValue || [];
Utilities.forEach(actions, action => {
if (action.labelKey) {
localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => {
action.label = data;
});
action.useLegacyIcon = action.useLegacyIcon === false ? false : true;
action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon;
}
});
updateActions();
}
}
function updateActions() {
Utilities.forEach(vm.actions || [], action => {
if (action.labelKey) {
localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => {
action.label = data;
});
action.useLegacyIcon = action.useLegacyIcon === false ? false : true;
action.icon = (action.useLegacyIcon && action.icon.indexOf('icon-') !== 0 ? 'icon-' : '') + action.icon;
}
});
}
}
var umbPropertyActionsComponent = {

View File

@@ -0,0 +1,65 @@
(function () {
"use strict";
function mediaItemResolverFilterService(mediaResource, eventsService) {
var mediaKeysRequest = [];
var mediaItemCache = [];
var service = {
getByKey: function (key) {
// Is it cached, then get that:
const cachedMediaItem = mediaItemCache.find(cache => key === cache.key);
if(cachedMediaItem) {
return cachedMediaItem;
}
// check its not already being loaded, and then start loading:
if(mediaKeysRequest.indexOf(key) === -1) {
mediaKeysRequest.push(key);
mediaResource.getById(key).then(function (mediaItem) {
if(mediaItem) {
mediaItemCache.push(mediaItem);
}
});
}
return null;
}
};
eventsService.on("editors.media.saved", function (name, args) {
const index = mediaItemCache.findIndex(cache => cache.key === args.media.key);
if(index !== -1) {
mediaItemCache[index] = args.media;
}
});
return service;
}
angular.module("umbraco.filters").factory("mediaItemResolverFilterService", mediaItemResolverFilterService);
// Filter loads Media Item Model from a Media Key.
// Usage: {{ myMediaProperty[0].mediaKey | mediaItemResolver }}
angular.module("umbraco.filters").filter("mediaItemResolver", function (mediaItemResolverFilterService) {
mediaItemResolverFilter.$stateful = true;
function mediaItemResolverFilter(input) {
// Check we have a value at all
if (typeof input === 'string' && input.length > 0) {
return mediaItemResolverFilterService.getByKey(input);
}
return null;
}
return mediaItemResolverFilter;
});
})();

View File

@@ -347,18 +347,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
plugins.push("autoresize");
var modeInline = false;
var toolbar = args.toolbar.join(" ");
var toolbarActions = args.toolbar.join(" ");
var toolbar = toolbarActions;
var quickbar = toolbarActions;
// Based on mode set
// classic = Theme: modern, inline: false
// inline = Theme: modern, inline: true,
// distraction-free = Theme: inlite, inline: true
if (args.mode === "inline") {
modeInline = true;
}
else if (args.mode === "distraction-free") {
// distraction-free = Same as inline - kept for legacy reasons due to older versions of Umbraco having this as a mode
if (args.mode === "inline" || args.mode === 'distraction-free') {
modeInline = true;
toolbar = false;
plugins.push('quickbars');
}
//create a baseline Config to extend upon
@@ -379,6 +379,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
//this would be for a theme other than inlite
toolbar: toolbar,
quickbars_insert_toolbar: quickbar,
quickbars_selection_toolbar: quickbar,
body_class: "umb-rte",

View File

@@ -20,6 +20,12 @@
background-color: @white;
border-color: @white;
}
.umb-editor-sub-header--blue {
background-color: @ui-selected-border;
border-color: @ui-selected-border;
color: @white;
border-radius: 3px;
}
.umb-editor-sub-header.--state-selection {
padding-left: 10px;

View File

@@ -20,7 +20,7 @@
}
.umb-rte.--initialized .umb-rte-editor-con {
height:auto;
min-height: 100px;
min-height: 95px;
visibility: visible;
}
@@ -140,6 +140,12 @@
}
}
.tox {
&.tox-tinymce-aux {
z-index: 65535;
}
}
.tox-tinymce-inline {
z-index: 999;
}

View File

@@ -1,13 +1,13 @@
<div class="umb-property">
<ng-form name="propertyForm">
<div class="control-group umb-control-group"
ng-class="{'hidelabel':vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
ng-class="{'hidelabel':vm.hideLabel || vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
<val-property-msg></val-property-msg>
<div class="umb-el-wrap">
<div class="control-header" ng-hide="vm.property.hideLabel === true">
<div class="control-header" ng-hide="(vm.hideLabel || vm.property.hideLabel) === true">
<label data-element="property-label-{{vm.property.alias}}" class="control-label" for="{{vm.property.alias}}" ng-attr-title="{{vm.controlLabelTitle}}">{{vm.property.label}}<span ng-if="vm.property.validation.mandatory || vm.property.ncMandatory"><strong class="umb-control-required">*</strong></span></label>

View File

@@ -53,6 +53,23 @@
padding-top:2px;
padding-bottom:2px;
}
:host {
--inherited--column-gap: var(--umb-block-grid--column-gap, 10px);
--inherited--row-gap: var(--umb-block-grid--row-gap, 10px);
--inherited--areas-column-gap: var(--umb-block-grid--areas-column-gap, 10px);
--inherited--areas-row-gap: var(--umb-block-grid--areas-row-gap, 10px);
}
[part='area-container'] {
box-sizing: border-box;
padding: 10px;
--umb-block-grid--column-gap: var(--inherited--column-gap, 10px);
--umb-block-grid--row-gap: var(--inherited--row-gap, 10px);
--umb-block-grid--areas-column-gap: var(--inherited--areas-column-gap, 10px);
--umb-block-grid--areas-row-gap: var(--inherited--areas-row-gap, 10px);
}
</style>
<div class="blockelement-gridblock-editor" ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
@@ -64,6 +81,6 @@
<span>{{block.label}}</span>
</button>
<slot name="area-container" part="area-container"></slot>
<umb-block-grid-render-area-slots ng-if="block.layout.areas.length > 0"></umb-block-grid-render-area-slots>
</div>

View File

@@ -1,27 +1,71 @@
(function () {
'use strict';
function GridInlineBlockEditor($scope, $element) {
function GridInlineBlockEditor($scope, $compile, $element) {
const vm = this;
var propertyEditorElement;
vm.$onInit = function() {
const host = $element[0].getRootNode();
vm.property = $scope.block.content.variants[0].tabs[0]?.properties[0];
console.log(document.styleSheets)
if (vm.property) {
vm.propertySlotName = "umbBlockGridProxy_" + vm.property.alias + "_" + String.CreateGuid();
propertyEditorElement = $('<div slot="{{vm.propertySlotName}}"></div>');
propertyEditorElement.html(
`
<umb-property
data-element="grid-block-property-{{vm.property.alias}}"
property="vm.property"
node="$scope.block.content"
hide-label="true">
for (const stylesheet of document.styleSheets) {
<umb-property-editor
model="vm.property"
preview="$scope.api.internal.readonly"
ng-attr-readonly="{{$scope.api.internal.readonly || undefined}}">
</umb-property-editor>
console.log(stylesheet);
const styleEl = document.createElement('link');
styleEl.setAttribute('rel', 'stylesheet');
styleEl.setAttribute('type', stylesheet.type);
styleEl.setAttribute('href', stylesheet.href);
</umb-property>
`
);
$element[0].addEventListener('umb-rte-focus', onRteFocus);
$element[0].addEventListener('umb-rte-blur', onRteBlur);
host.appendChild(styleEl);
const connectedCallback = () => {
$compile(propertyEditorElement)($scope)
};
const event = new CustomEvent("UmbBlockGrid_AppendProperty", {composed: true, bubbles: true, detail: {'property': propertyEditorElement[0], 'contentUdi': $scope.block.layout.contentUdi, 'slotName': vm.propertySlotName, 'connectedCallback':connectedCallback}});
$element[0].dispatchEvent(event);
}
}
function onRteFocus() {
$element[0].classList.add('umb-block-grid--force-focus');
}
function onRteBlur() {
$element[0].classList.remove('umb-block-grid--force-focus');
}
vm.$onDestroy = function() {
if (vm.property) {
const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': vm.propertySlotName}});
$element[0].dispatchEvent(event);
}
$element[0].removeEventListener('umb-rte-focus', onRteFocus);
$element[0].removeEventListener('umb-rte-blur', onRteBlur);
propertyEditorElement = null;
}
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.GridInlineBlockEditor", GridInlineBlockEditor);

View File

@@ -1,24 +1,6 @@
<div class="blockelement-inlineblock-editor"
ng-controller="Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor as vm"
ng-class="{ '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
<button type="button" class="btn-reset umb-outline blockelement__draggable-element"
ng-click="vm.openBlock(block)"
ng-focus="block.focus">
<span class="caret"></span>
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
<span class="name">{{block.label}}</span>
</button>
<div class="blockelement-inlineblock-editor__inner" ng-class="{'--singleGroup':block.content.variants[0].tabs.length === 1}" ng-if="block.active === true">
</div>
</div>
<style>
.umb-icon {
display: inline-block;
width: 1em;
@@ -42,52 +24,82 @@
background-color: white;
}
.blockelement-gridinlineblock-editor > button {
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
position: relative;
display: flex;
align-items: center;
width: 100%;
min-height: 48px;
cursor: pointer;
color: #1b264f;
background-color: white;
text-align: left;
padding: 0 20px;
user-select: none;
border: none;
transition: border-color 120ms, background-color 120ms;
box-sizing: border-box;
transition: min-height 120ms;
}
.blockelement-gridinlineblock-editor > button:hover {
color: #2152A3;
}
.blockelement-gridinlineblock-editor .icon {
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label > .icon {
font-size: 22px;
margin-right: 10px;
display: inline-block;
}
.blockelement-gridinlineblock-editor > button > span {
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label > span {
position: relative;
display: inline-block;
padding-top:2px;
padding-bottom:2px;
}
.blockelement-gridinlineblock-editor > button {
cursor: pointer;
transition: border-color 120ms, background-color 120ms;
}
.blockelement-gridinlineblock-editor > button:hover {
color: #2152A3;
}
:host .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
min-height: 0;
height: 0;
overflow: hidden;
}
.blockelement-gridinlineblock-editor.umb-block-grid--force-focus > .blockelement-gridinlineblock-label,
:host(:focus) .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label,
:host(:focus-within) .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
min-height: 48px;
height: auto;
}
</style>
<div
class="blockelement-gridinlineblock-editor"
ng-controller="Umbraco.PropertyEditors.BlockEditor.GridInlineBlockEditor as vm"
ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
<button type="button"
<button
ng-if="!block.config.forceHideContentEditorInOverlay"
type="button"
ng-click="block.edit()"
ng-focus="block.focus"
class="blockelement-gridinlineblock-label"
val-server-property-class="">
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
<span>{{block.label}}</span>
</button>
<div
ng-if="block.config.forceHideContentEditorInOverlay"
class="blockelement-gridinlineblock-label"
val-server-property-class="">
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
<span>{{block.label}}</span>
</div>
<umb-element-editor-content model="block.content"></umb-element-editor-content>
<slot name="{{vm.propertySlotName}}"></slot>
<slot name="area-container" part="area-container"></slot>
<umb-block-grid-render-area-slots></umb-block-grid-render-area-slots>
</div>

View File

@@ -1,173 +0,0 @@
.blockelement-inlineblock-editor {
display: block;
margin-bottom: 4px;
margin-top: 4px;
border: 1px solid @gray-9;
border-radius: @baseBorderRadius;
transition: border-color 120ms, background-color 120ms;
.umb-block-list__block:not(.--active) &:hover {
border-color: @gray-8;
}
.umb-editor-tab-bar {
margin: 0;
position: static;
padding: 0;
}
> button {
width: 100%;
min-height: 48px;
cursor: pointer;
color: @ui-action-discreet-type;
text-align: left;
padding-left: 10px;
padding-bottom: 2px;
user-select: none;
background-color: white;
.caret {
vertical-align: middle;
transform: rotate(-90deg);
transition: transform 80ms ease-out;
}
.icon {
font-size: 1.1rem;
display: inline-block;
vertical-align: middle;
}
span.name {
position: relative;
display: inline-block;
vertical-align: middle;
}
&:hover {
color: @ui-action-discreet-type-hover;
border-color: @gray-8;
}
}
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
> button {
color: @formErrorText;
.show-validation-type-warning & {
color: @formWarningText;
}
span.caret {
border-top-color: @formErrorText;
.show-validation-type-warning & {
border-top-color: @formWarningText;
}
}
}
}
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & {
> button {
span.name {
&::after {
content: "!";
text-align: center;
position: absolute;
top: -6px;
right: -15px;
min-width: 10px;
color: @white;
background-color: @ui-active-type;
border: 2px solid @white;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
padding: 2px;
line-height: 10px;
background-color: @formErrorText;
.show-validation-type-warning & {
background-color: @formWarningText;
}
font-weight: 900;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-name: blockelement-inlineblock-editor--badge-bounce;
animation-timing-function: ease;
@keyframes blockelement-inlineblock-editor--badge-bounce {
0% {
transform: translateY(0);
}
20% {
transform: translateY(-4px);
}
40% {
transform: translateY(0);
}
55% {
transform: translateY(-2px);
}
70% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}
}
}
}
}
}
.umb-block-list__block.--active {
border-color: @gray-8;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.05);
> .umb-block-list__block--content {
> .umb-block-list__block--view {
> .blockelement-inlineblock-editor {
> button {
> .caret {
transform: rotate(0deg);
}
}
}
}
}
}
.blockelement-inlineblock-editor__inner {
border-top: 1px solid @gray-8;
background-color: @gray-12;
> * > * > * > .umb-group-panel {
background-color: transparent;
box-shadow: none;
margin-top: 10px;
margin-bottom: 0;
> .umb-group-panel__content .umb-property {
margin-bottom: 20px;
}
}
.umb-group-panel + .umb-group-panel {
margin-top: 20px;
}
&.--singleGroup > * > * > * > .umb-group-panel {
margin-top: 0;
> .umb-group-panel__header {
display: none;
}
}
}

View File

@@ -0,0 +1,92 @@
<style>
@keyframes umb-icon-jump {
0%, 100% { transform: rotate(6deg); }
50% { transform: rotate(-6deg); }
}
.umb-icon {
display: inline-block;
width: 1em;
height: 1em;
flex-shrink: 0;
animation: umb-icon-jump 1s infinite ease-in-out;
}
.umb-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
animation: inherit;
}
.blockelement-gridsortblock-editor {
position: relative;
border-radius: 3px;
height: 100%;/* Add this to fill the cell fully */
box-sizing: border-box;
border: 1px solid #E9E9EB;
background-color: white;
}
.blockelement-gridsortblock-editor > button {
position: relative;
display: flex;
align-items: center;
width: 100%;
min-height: 48px;
cursor: pointer;
color: #1b264f;
background-color: white;
text-align: left;
padding: 0 20px;
user-select: none;
border: none;
transition: border-color 120ms, background-color 120ms;
}
.blockelement-gridsortblock-editor > button:hover {
color: #2152A3;
}
.blockelement-gridsortblock-editor .icon {
font-size: 22px;
margin-right: 10px;
display: inline-block;
}
.blockelement-gridsortblock-editor > button > span {
position: relative;
display: inline-block;
padding-top:2px;
padding-bottom:2px;
}
:host {
--inherited--column-gap: var(--umb-block-grid--column-gap, 10px);
--inherited--row-gap: var(--umb-block-grid--row-gap, 10px);
--inherited--areas-column-gap: var(--umb-block-grid--areas-column-gap, 10px);
--inherited--areas-row-gap: var(--umb-block-grid--areas-row-gap, 10px);
}
[part='area-container'] {
box-sizing: border-box;
padding: 10px;
--umb-block-grid--column-gap: var(--inherited--column-gap, 10px);
--umb-block-grid--row-gap: var(--inherited--row-gap, 10px);
--umb-block-grid--areas-column-gap: var(--inherited--areas-column-gap, 10px);
--umb-block-grid--areas-row-gap: var(--inherited--areas-row-gap, 10px);
}
</style>
<div class="blockelement-gridsortblock-editor" ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
<button type="button"
ng-click="block.showContent ? block.edit() : null"
ng-focus="block.focus"
val-server-property-class="">
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
<span>{{block.label}}</span>
</button>
<umb-block-grid-render-area-slots ng-if="block.layout.areas.length > 0"></umb-block-grid-render-area-slots>
</div>

View File

@@ -1,45 +0,0 @@
(function () {
'use strict';
function HeroBlockEditor($scope, mediaResource, mediaHelper) {
var unsubscribe = [];
const bc = this;
$scope.$watch("block.data.image", function(newValue, oldValue) {
if (newValue !== oldValue) {
bc.retrieveMedia();
}
}, true);
bc.retrieveMedia = function() {
if($scope.block.data.image && $scope.block.data.image.length > 0) {
mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) {
var mediaPath = mediaEntity.mediaLink;
//set a property on the 'scope' for the returned media object.
bc.mediaName = mediaEntity.name;
bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath);
bc.imageSource = mediaPath;
});
}
}
bc.retrieveMedia();
$scope.$on("$destroy", function () {
for (const subscription of unsubscribe) {
subscription();
}
});
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.HeroBlockEditor", HeroBlockEditor);
})();

View File

@@ -1,47 +0,0 @@
(function () {
'use strict';
function MediaBlockEditor($scope, mediaResource, mediaHelper) {
var unsubscribe = [];
const bc = this;
$scope.$watch("block.data.image", function(newValue, oldValue) {
if (newValue !== oldValue) {
bc.retrieveMedia();
}
}, true);
bc.retrieveMedia = function() {
if($scope.block.data.image && $scope.block.data.image.length > 0) {
mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) {
var mediaPath = mediaEntity.mediaLink;
//set a property on the 'scope' for the returned media object
bc.icon = mediaEntity.contentType.icon;
bc.mediaName = mediaEntity.name;
bc.fileExtension = mediaHelper.getFileExtension(mediaPath);
bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath);
bc.imageSource = mediaHelper.getThumbnailFromPath(mediaPath);
});
}
}
bc.retrieveMedia();
$scope.$on("$destroy", function () {
for (const subscription of unsubscribe) {
subscription();
}
});
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.MediaBlockEditor", MediaBlockEditor);
})();

View File

@@ -21,13 +21,16 @@
.umb-block-grid__layout-item {
position: relative;
&:hover {
z-index: 3;
/*
> .umb-block-grid__force-left,
> .umb-block-grid__force-right {
> ng-form > .umb-block-grid__block--context {
z-index: 4;
}
*/
> ng-form > .umb-block-grid__block--inline-create-button,
> ng-form > .umb-block-grid__block--validation-border,
> ng-form > .umb-block-grid__block--actions {
z-index: 3;
}
}
}
@@ -49,52 +52,12 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
pointer-events: none;
}
/*.umb-block-grid__block--validation-badge {
display:none;
}
ng-form.ng-invalid-val-server-match-settings > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge,
ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge {
display:block;
text-align: center;
position: absolute;
top: -9px;
right: -9px;
min-width: 10px;
color: @white;
border: 2px solid @white;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
padding: 2px;
line-height: 10px;
background-color: @formErrorText;
.show-validation-type-warning & {
background-color: @formWarningText;
}
font-weight: 900;
pointer-events: none;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-name: blockelement-inlineblock-editor--badge-bounce;
animation-timing-function: ease;
@keyframes blockelement-inlineblock-editor--badge-bounce {
0% { transform: translateY(0); }
20% { transform: translateY(-4px); }
40% { transform: translateY(0); }
55% { transform: translateY(-2px); }
70% { transform: translateY(0); }
100% { transform: translateY(0); }
}
}
*/
.umb-block-grid__block {
position: relative;
width: 100%;
height: 100%;
--umb-block-grid__block--show-ui: 0;// Publicly available.
--umb-block-grid--block-ui-opacity: 0;
--umb-block-grid--hint-area-ui: 0;
&::after {
@@ -115,11 +78,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
opacity: 0;
transition: opacity 120ms;
}
> .umb-block-grid__force-left,
> .umb-block-grid__force-right {
opacity: 0;
transition: opacity 120ms;
}
> .umb-block-grid__block--context {
opacity: 0;
transition: opacity 120ms;
@@ -136,7 +94,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
--umb-block-grid--hint-area-ui: 1;
&::after {
/*border-color: @blueDark;*/
display: var(--umb-block-grid--block-ui-display, block);
animation: umb-block-grid__block__border-pulse 400ms ease-in-out alternate infinite;
@keyframes umb-block-grid__block__border-pulse {
@@ -185,10 +142,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
> .umb-block-grid__scale-label {
opacity: 1;
}
> .umb-block-grid__force-left,
> .umb-block-grid__force-right {
opacity: 1;
}
}
/** make sure to hide child block ui: */
@@ -205,24 +158,23 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
> .umb-block-grid__block--actions {
display: none;
}
> .umb-block-grid__force-left,
> .umb-block-grid__force-right {
display: none;
}
}
&.--block-ui-visible {
> .umb-block-grid__block--context {
/* take full width to prevent interaction with elements behind.*/
left: 0;
}
.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) {
--umb-block-grid--block-ui-display: none;
.umb-block-grid__layout-item {
pointer-events: none;
}
.umb-block-grid__block {
pointer-events: none;
}
pointer-events: none;
}
.umb-block-grid__layout-item {
pointer-events: none;
}
.umb-block-grid__block {
pointer-events: none;
--umb-block-grid--block-ui-opacity: 0;
}
}
@@ -236,48 +188,30 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
&.--active {
/** Avoid displaying hover when dragging-mode */
--umb-block-grid--block_ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0));
--umb-block-grid--block-ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0));
> .umb-block-grid__block--context {
opacity: var(--umb-block-grid--block_ui-opacity);
opacity: var(--umb-block-grid--block-ui-opacity);
}
&:not(.--scale-mode) {
> .umb-block-grid__block--actions {
opacity: var(--umb-block-grid--block_ui-opacity);
opacity: var(--umb-block-grid--block-ui-opacity);
}
> umb-block-grid-block > umb-block-grid-entries > .umb-block-grid__layout-container > .umb-block-grid__area-actions {
opacity: var(--umb-block-grid--block_ui-opacity);
opacity: var(--umb-block-grid--block-ui-opacity);
}
}
> .umb-block-grid__scale-handler {
opacity: var(--umb-block-grid--block_ui-opacity);
}
> .umb-block-grid__force-left,
> .umb-block-grid__force-right {
opacity: var(--umb-block-grid--block_ui-opacity);
opacity: var(--umb-block-grid--block-ui-opacity);
}
}
/*
&.--show-validation {
ng-form.ng-invalid-val-server-match-content > & {
border: 2px solid @formErrorText;
border-radius: @baseBorderRadius;
}
}
*/
}
ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--actions {
opacity: 1;
}
/*
ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--context {
opacity: 1;
}
*/
.umb-block-grid__block--view {
height: 100%;
@@ -291,10 +225,20 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
top: -20px;
right: 0;
font-size: 12px;
z-index: 2;
z-index: 4;
display: var(--umb-block-grid--block-ui-display, flex);
justify-content: end;
/** prevent interaction with inline-create button just beneath the context-bar: */
::after {
content: '';
position: absolute;
top: 100%;
left: 0;
right: 0;
height: 12px;
}
.__context-bar {
padding: 0 9px;
padding-top: 1px;
@@ -409,127 +353,6 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
}
}
.umb-block-grid__force-left,
.umb-block-grid__force-right {
position: absolute;
z-index: 2;
top: 50%;
height: 30px;
width: 15px;
margin-top:-15px;
background-color: transparent;
color: @blueDark;
border: 1px solid rgba(255, 255, 255, .2);
font-size: 12px;
padding: 0;
cursor: pointer;
box-sizing: border-box;
border-radius: 8px;
display: var(--umb-block-grid--block-ui-display, flex);
justify-content: center;
align-items: center;
pointer-events: all;
opacity: 0;
transition: background-color 120ms, border-color 120ms, color 120ms, opacity 120ms;
.icon {
position: relative;
display: inline-block;
pointer-events: none;
opacity: 0;
transition: transform 120ms ease-in-out, opacity 120ms;
::before {
content: '';
position: absolute;
background-color:currentColor;
width:2px;
height: 8px;
top: 2px;
transition: transform 120ms ease-in-out;
}
}
&:hover {
opacity: 1;
color: @blueDark;
background-color: @white;
}
&:hover,
&.--active {
.icon {
opacity: 1;
transform: translateX(0);
::before {
transform: translateX(0);
}
}
}
&.--active {
background-color: @blueDark;
color: white;
&:hover {
color: white;
}
}
}
.umb-block-grid__force-left {
left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.icon {
transform: translateX(3px);
::before {
left: 2px;
transform: translateX(-3px);
}
}
&:hover,
&.--active {
border-left-color: @blueDark;
}
}
.umb-block-grid__force-right {
right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.icon {
margin-right: 1px;
transform: translateX(-3px);
::before {
right: 2px;
transform: translateX(3px);
}
}
&:hover,
&.--active {
border-right-color: @blueDark;
}
}
/*
umb-block-grid-block {
> div {
position: relative;
width: 100%;
min-height: @umb-block-grid__item_minimum_height;
background-color: @white;
border-radius: @baseBorderRadius;
box-sizing: border-box;
}
}
*/
/*
.blockelement__draggable-element {
cursor: grab;
}
*/
.umb-block-grid__scale-handler {
cursor: nwse-resize;
@@ -586,7 +409,7 @@ umb-block-grid-block {
.umb-block-grid__block--inline-create-button {
top: 0px;
position: absolute;
z-index: 1;
z-index: 1; /** overwritten for the first one of an area. */
/** Avoid showing inline-create in dragging-mode */
opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0));
@@ -594,6 +417,12 @@ umb-block-grid-block {
.umb-block-grid__block--inline-create-button.--above {
left: 0;
width: 100%;
top: calc(var(--umb-block-grid--row-gap, 0px) * -0.5);
}
.umb-block-grid__layout-item:first-of-type .umb-block-grid__block--inline-create-button.--above {
/* Do not use row-gap if the first one. */
top: 0;
}
.umb-block-grid__block--inline-create-button.--above.--at-root {
/* If at root, and full-width then become 40px wider: */
@@ -601,14 +430,9 @@ umb-block-grid-block {
left: calc(-20px * var(--calc));
width: calc(100% + 40px * var(--calc));
}
.umb-block-grid__block--inline-create-button.--after {
right: 1px;
}
.umb-block-grid__block--inline-create-button.--after.--detector {
width: 10px;
margin-right: -10px;
height: 100%;
z-index: 0;
right: calc(1px - (var(--umb-block-grid--column-gap, 0px) * 0.5));
}
.umb-block-grid__block--inline-create-button.--after.--at-root {
/* If at root, and full-width then move a little out to the right: */
@@ -624,8 +448,8 @@ umb-block-grid-block {
pointer-events: none;
}
.umb-block-grid__block--after-inline-create-button {
z-index:2;
.umb-block-grid__block--last-inline-create-button {
z-index:4;
width: 100%;
/* Move inline create button slightly up, to avoid collision with others*/
margin-bottom: -7px;
@@ -746,13 +570,11 @@ umb-block-grid-block {
align-items: center;
justify-content: center;
/* TODO: dont use --umb-text-color, its temporary to inherit UI */
color: var(--umb-text-color, @ui-action-discreet-type);
color: var(--umb-block-grid--text-color, @ui-action-discreet-type);
font-weight: bold;
padding: 5px 15px;
/* TODO: dont use --umb-text-color, its temporary to inherit UI */
border: 1px dashed var(--umb-text-color, @ui-action-discreet-border);
border: 1px dashed var(--umb-block-grid--text-color, @ui-action-discreet-border);
border-radius: @baseBorderRadius;
box-sizing: border-box;
@@ -760,24 +582,14 @@ umb-block-grid-block {
height: 100%;
&:hover {
color: var(--umb-text-color, @ui-action-discreet-type-hover);
border-color: var(--umb-text-color, @ui-action-discreet-border-hover);
color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-type-hover);
border-color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-border-hover);
text-decoration: none;
z-index: 1;
}
}
}
/** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/
/*
.umb-block-grid__layout-container.--droppable-indication {
.umb-block-grid__area-actions {
display: none;
}
}
*/
.umb-block-grid__layout-item-placeholder {
background: transparent;
border-radius: 3px;
@@ -814,36 +626,6 @@ umb-block-grid-block {
100% { background-color: rgba(@blueMidLight, 0.22); }
}
}
.umb-block-grid__layout-item-placeholder .indicateForceLeft,
.umb-block-grid__layout-item-placeholder .indicateForceRight {
position:absolute;
z-index: 2;
height: 100%;
width: 15px;
background-color: @blueDark;
background-position: center center;
background-repeat: no-repeat;
display: block !important;
animation: umb-block-grid__indicateForce__pulse 400ms ease-in-out alternate infinite;
}
.umb-block-grid__layout-item-placeholder .indicateForceLeft {
left:0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='10'><polygon points='0,0 2,0 2,5 6,1 6,9 2,5 2,10 0,10' style='fill:white;'/></svg>");
}
.umb-block-grid__layout-item-placeholder .indicateForceRight {
right:0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='10'><polygon points='8,0 6,0 6,5 2,1 2,9 6,5 6,10 8,10' style='fill:white;'/></svg>");
}
@keyframes umb-block-grid__indicateForce__pulse {
0% { background-color: rgba(@blueDark, 1); }
100% { background-color: rgba(@blueDark, 0.5); }
}
.umb-block-grid__area {
@@ -859,16 +641,19 @@ umb-block-grid-block {
content: '';
position: absolute;
inset: 0;
/* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */
top:2px;
bottom: 2px;
top:0;
bottom: 0;
border-radius: 3px;
border: 1px solid rgba(@gray-5, 0.3);
pointer-events: none;
opacity: var(--umb-block-grid--show-area-ui, 0);
transition: opacity 240ms;
z-index:3;
}
.umb-block-grid__area.--highlight::after {
/* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */
top:2px;
bottom: 2px;
/** Avoid displaying highlight when in dragging-mode */
opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0));
border-color: @blueDark;
@@ -899,31 +684,10 @@ umb-block-grid-block {
z-index: 1;
cursor: nwse-resize;
}
/*
.umb-block-grid__scalebox {
position: absolute;
top:0;
left:0;
z-index: 10;
cursor: nwse-resize;
transition: background-color 240ms ease-in;
animation: umb-block-grid__scalebox__pulse 400ms ease-in-out alternate infinite;
@keyframes umb-block-grid__scalebox__pulse {
0% { background-color: rgba(@blueMidLight, 0.33); }
100% { background-color: rgba(@blueMidLight, 0.22); }
}
}
*/
/*
.umb-block-grid__layout-container {
}
*/
/** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/
@@ -934,8 +698,10 @@ umb-block-grid-block {
}
.umb-block-grid__layout-container .umb-block-grid__layout-item:not([depth='0']):first-of-type .umb-block-grid__block--inline-create-button.--above {
/* Move first above inline create button slightly up, to avoid collision with others*/
/* Move the above inline create button slightly down, to avoid collision with others*/
margin-top: -7px;
z-index:4;
}
.umb-block-grid__not-allowed-box {

View File

@@ -25,13 +25,25 @@
value: {min:vm.area.minAllowed, max:vm.area.maxAllowed}
}
unsubscribe.push($scope.$watch('vm.area.alias', (newVal, oldVal) => {
$scope.model.updateTitle();
if($scope.blockGridBlockConfigurationAreaForm.alias) {
$scope.blockGridBlockConfigurationAreaForm.alias.$setValidity("alias", $scope.model.otherAreaAliases.indexOf(newVal) === -1);
}
}));
vm.submit = function() {
if($scope.blockGridBlockConfigurationAreaForm.$valid === false) {
$scope.submitButtonState = "error";
return;
}
if ($scope.model && $scope.model.submit) {
// Transfer minMaxModel to area:
vm.area.minAllowed = vm.minMaxModel.value.min;
vm.area.maxAllowed = vm.minMaxModel.value.max;
$scope.submitButtonState = "success";
$scope.model.submit($scope.model);
}
};

View File

@@ -27,11 +27,18 @@
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="alias"><localize key="general_alias">Alias</localize></label>
<strong class="umb-control-required">*</strong>
<umb-property-info-button button-title-key="general_readMore">
<localize key="blockEditor_areaAliasHelp">The alias will be printed by GetBlockGridHTML(), use the alias to target the Element representing this area. Ex. .umb-block-grid__area[data-area-alias="MyAreaAlias"] { ... }</localize>
</umb-property-info-button>
<div class="controls">
<input type="text" name="alias" ng-model="vm.area.alias" style="width:100%" umb-auto-focus/>
<input type="text" name="alias" ng-model="vm.area.alias" val-server="alias" style="width:100%" required umb-auto-focus/>
</div>
<div ng-messages="blockGridBlockConfigurationAreaForm.alias.$error" class="red">
<div ng-message="alias">
<localize key="blockEditor_areaAliasIsNotUnique" tokens="[vm.area.alias]" watch-tokens="true">This Areas Alias must be unique compared to the other Areas of this Block.</localize>
</div>
<span ng-message="valServer" ng-bind-html="blockGridBlockConfigurationAreaForm.alias.errorMsg"></span>
</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@
<umb-button action="vm.setupSample()" button-style="primary" state="vm.sampleButtonState" label-key="blockEditor_getSampleButton" type="button"></umb-button>
</div>
<div class="umb-block-card-group">
<div class="umb-block-card-grid" ui-sortable="vm.blockSortableOptions" ng-model="model.value">
@@ -30,9 +31,14 @@
</div>
</umb-block-card>
<button id="{{model.alias}}" type="button" class="btn-reset __add-button" ng-click="vm.openAddDialog()">
<uui-button id="{{model.alias}}" look="{{(model.value.length === 0 && vm.blockGroups.length === 0) ? 'primary' : 'placeholder'}}" color="primary" ng-click="vm.openAddDialog()">
<localize key="blockEditor_addBlockType">Add Block</localize>
</button>
</uui-button>
<uui-button ng-if="!vm.showSampleDataCTA && model.value.length === 0 && vm.blockGroups.length === 0" look="placeholder" color="primary" ng-click="vm.setupSample()">
<localize key="blockEditor_getSampleHeadline">Install demo Blocks</localize>
</uui-button>
</div>
</div>
@@ -82,9 +88,9 @@
</div>
</umb-block-card>
<button type="button" class="btn-reset __add-button" ng-click="vm.openAddDialog(blockGroup.key)">
<uui-button id="{{model.alias}}" look="placeholder" color="primary" ng-click="vm.openAddDialog(blockGroup.key)">
<localize key="blockEditor_addBlockType">Add Block</localize>
</button>
</uui-button>
</div>
</div>

View File

@@ -2,29 +2,10 @@
margin-bottom: 20px;
.__add-button {
position: relative;
display: inline-flex;
width: 100%;
height: 100%;
margin-right: 20px;
margin-bottom: 20px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
border-radius: @doubleBorderRadius;
align-items: center;
justify-content: center;
padding: 5px 15px;
box-sizing: border-box;
font-weight: bold;
}
.__add-button:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
uui-button {
font-weight: 700;
--uui-button-border-radius: 6px;
min-height: 80px;
}
.__get-sample-box {

View File

@@ -355,12 +355,25 @@
</div>
</div>
<!-- inlineEditing -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label ng-attr-disabled="{{(vm.block.view !== null) || undefined}}" class="control-label" for="inlineEditing"><localize key="blockEditor_gridInlineEditing">Inline editing</localize></label>
<umb-property-info-button ng-if="vm.block.view === null" button-title-key="general_readMore">
<localize key="blockEditor_gridInlineEditingHelp">Hide the content edit button and the content editor from the Block Editor overlay.</localize>
</umb-property-info-button>
<div class="controls">
<umb-toggle checked="(vm.block.view === null ? vm.block.inlineEditing : false)" disabled="vm.block.view !== null" on-click="vm.block.inlineEditing = vm.block.inlineEditing != true"></umb-toggle>
</div>
</div>
</div>
<!-- forceHideContentEditorInOverlay -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="forceHideContentEditorInOverlay"><localize key="blockEditor_forceHideContentEditor">Hide content editor</localize></label>
<umb-property-info-button button-title-key="general_readMore">
<localize key="blockEditor_forceHideContentEditorHelp">Define the range of layout rows this block is allowed to span across.</localize>
<localize key="blockEditor_forceHideContentEditorHelp">Hide the content edit button and the content editor from the Block Editor overlay.</localize>
</umb-property-info-button>
<div class="controls">
<umb-toggle checked="vm.block.forceHideContentEditorInOverlay" on-click="vm.block.forceHideContentEditorInOverlay = vm.block.forceHideContentEditorInOverlay != true"></umb-toggle>

View File

@@ -1,5 +1,5 @@
<div class="umb-block-grid-block-configuration controls" ng-controller="Umbraco.PropertyEditors.BlockGrid.GroupConfigurationController as vm">
<button type="button" class="btn-reset __add-button" ng-click="vm.addGroup($event)">
<div class="controls" ng-controller="Umbraco.PropertyEditors.BlockGrid.GroupConfigurationController as vm">
<uui-button look="placeholder" color="primary" ng-click="vm.addGroup($event)" style="font-weight: 700; width: 100%;">
<localize key="blockEditor_addBlockGroup">Add group</localize>
</button>
</uui-button>
</div>

View File

@@ -2,13 +2,11 @@
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div ng-show="vm.loading !== true" class="__list" ng-class="{'--disabled': vm.disabled}">
<div ng-show="vm.loading !== true" class="__list">
<div ng-repeat="allowance in vm.model track by allowance.$key" class="umb-block-grid-area-allowance-editor__entry">
<select
ng-model="allowance.$chosenValue"
ng-disabled="vm.disabled"
localize="title"
title="blockEditor_pickSpecificAllowance"
required
@@ -19,7 +17,6 @@
</select>
<input
type="number"
ng-disabled="vm.disabled"
name="label"
min="0"
ng-max="model.value.max"
@@ -31,7 +28,6 @@
<span></span>
<input
type="number"
ng-disabled="vm.disabled"
name="label"
ng-model="allowance.maxAllowed"
placeholder="∞"
@@ -44,8 +40,7 @@
class="btn-reset umb-outline"
localize="title"
title="actions_delete"
ng-click="vm.deleteAllowance(allowance);"
ng-disabled="vm.disabled">
ng-click="vm.deleteAllowance(allowance);">
<umb-icon icon="icon-trash" class="icon"></umb-icon>
<span class="sr-only">
<localize key="actions_delete">Delete</localize>
@@ -56,12 +51,15 @@
<button
type="button"
ng-disabled="vm.disabled"
class="btn-reset umb-block-grid-area-editor__create-button umb-outline"
ng-click="vm.onNewAllowanceClick()">
<localize key="general_add">Add</localize>
</button>
<div ng-if="vm.model.length === 0" class="__empty-label">
<localize key="blockEditor_areaAllowedBlocksEmpty">When empty all Blocks allowed for Areas can be created.</localize>
</div>
</div>

View File

@@ -5,8 +5,11 @@
width: 100%;
}
.umb-block-grid-area-allowance-editor .__list.--disabled {
.umb-block-grid-area-allowance-editor .__empty-label {
font-size: 12px;
color: @gray-6;
line-height: 1.5em;
padding-top: 5px;
}
.umb-block-grid-area-allowance-editor__entry {

View File

@@ -56,15 +56,32 @@
function initializeSortable() {
const gridLayoutContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper');
function _sync(evt) {
const sortable = Sortable.create(gridLayoutContainerEl, {
const oldIndex = evt.oldIndex,
newIndex = evt.newIndex;
vm.model.splice(newIndex, 0, vm.model.splice(oldIndex, 1)[0]);
}
const gridContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper');
const sortable = Sortable.create(gridContainerEl, {
sort: true, // sorting inside list
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation. Defaults to null. See https://easings.net/ for examples.
cancel: '',
draggable: ".umb-block-grid-area-editor__area", // Specifies which items inside the element should be draggable
ghostClass: "umb-block-grid-area-editor__area-placeholder"
ghostClass: "umb-block-grid-area-editor__area-placeholder",
onAdd: function (evt) {
_sync(evt);
$scope.$evalAsync();
},
onUpdate: function (evt) {
_sync(evt);
$scope.$evalAsync();
}
});
// TODO: setDirty if sort has happend.
@@ -130,14 +147,23 @@
vm.openAreaOverlay = function (area) {
// TODO: use the right localization key:
localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [area.alias]).then(function (localized) {
localizationService.localize("blockEditor_blockConfigurationOverlayTitle").then(function (localized) {
var clonedAreaData = Utilities.copy(area);
vm.openArea = area;
function updateTitle() {
overlayModel.title = localizationService.tokenReplace(localized, [clonedAreaData.alias]);
}
const areaIndex = vm.model.indexOf(area);
const otherAreas = [...vm.model];
otherAreas.splice(areaIndex, 1);
var overlayModel = {
otherAreaAliases: otherAreas.map(x => x.alias),
area: clonedAreaData,
title: localized,
updateTitle: updateTitle,
allBlockTypes: vm.allBlockTypes,
allBlockGroups: vm.allBlockGroups,
loadedElementTypes: vm.loadedElementTypes,
@@ -154,6 +180,8 @@
}
};
updateTitle();
// open property settings editor
editorService.open(overlayModel);

View File

@@ -14,8 +14,6 @@
data-element-udi="{{layoutEntry.contentUdi}}"
data-col-span="{{layoutEntry.columnSpan}}"
data-row-span="{{layoutEntry.rowSpan}}"
ng-attr-data-force-left="{{layoutEntry.forceLeft || undefined}}"
ng-attr-data-force-right="{{layoutEntry.forceRight || undefined}}"
style="
--umb-block-grid--item-column-span: {{layoutEntry.columnSpan}};
--umb-block-grid--item-row-span: {{layoutEntry.rowSpan}};

View File

@@ -14,8 +14,6 @@
data-element-udi="{{layoutEntry.contentUdi}}"
data-col-span="{{layoutEntry.columnSpan}}"
data-row-span="{{layoutEntry.rowSpan}}"
ng-attr-data-force-left="{{layoutEntry.forceLeft || undefined}}"
ng-attr-data-force-right="{{layoutEntry.forceRight || undefined}}"
style="
--umb-block-grid--item-column-span: {{layoutEntry.columnSpan}};
--umb-block-grid--item-row-span: {{layoutEntry.rowSpan}};
@@ -60,7 +58,7 @@
<uui-button-inline-create
ng-if="!vm.blockEditorApi.readonly && vm.depth !== '0' && !(vm.entries.length === 0 || !vm.entriesForm.areaMinCount.$valid)"
class="umb-block-grid__block--after-inline-create-button"
class="umb-block-grid__block--last-inline-create-button"
ng-mouseover="vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey)"
ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)"
ng-click="vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.entries.length, $event)">
@@ -119,7 +117,7 @@
key="{{(invalidBlockType.amount < invalidBlockType.minRequirement) ? 'blockEditor_areaValidationEntriesShort' : 'blockEditor_areaValidationEntriesExceed'}}"
tokens="[invalidBlockType.name, invalidBlockType.amount, invalidBlockType.minRequirement, invalidBlockType.maxRequirement]"
watch-tokens="true"
>%0% must be present between %2% %3% times.</localize>
>%0% must be present between %2%%3% times.</localize>
</div>
</div>
<span ng-message="valServer" ng-bind-html="vm.entriesForm.areaTypeRequirements.errorMsg"></span>

View File

@@ -1,10 +1,11 @@
<uui-button-inline-create
ng-if="!vm.blockEditorApi.readonly"
ng-if="!vm.blockEditorApi.readonly && !vm.hideInlineCreateAbove"
class="umb-block-grid__block--inline-create-button --above"
style="width: {{vm.inlineCreateAboveWidth}};"
ng-class="{'--at-root': vm.depth === '0'}"
ng-click="vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index, $event)"
ng-mouseover="vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey)"
ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)">
ng-click="vm.clickInlineCreateAbove()"
ng-mouseover="vm.mouseOverInlineCreate()"
ng-mouseleave="vm.mouseOutInlineCreate()">
</uui-button-inline-create>
<ng-form name="vm.blockForm" val-server-match="{ 'contains' : { 'valServerMatchContent': vm.layoutEntry.$block.content.key, 'valServerMatchSettings': vm.layoutEntry.$block.settings.key } }">
@@ -29,9 +30,15 @@
parent-form="vm.blockForm"
style="--umb-block-grid--area-grid-columns: {{vm.areaGridColumns}}"
>
<slot
ng-repeat="proxyProp in vm.proxyProperties track by proxyProp.slotName"
data-is-property-editor-proxy
name="{{proxyProp.slotName}}"
slot="{{proxyProp.slotName}}">
</slot>
<umb-block-grid-entries
slot="area-container"
ng-repeat="areaEntry in vm.layoutEntry.areas track by areaEntry.key"
slot="{{::areaEntry.$config.alias}}"
class="umb-block-grid__area"
ng-class="{'--highlight': areaEntry.$highlight}"
data-area-col-span="{{::areaEntry.$config.columnSpan}}"
@@ -58,7 +65,6 @@
</umb-block-grid-block>
<div class="umb-block-grid__block--validation-border"></div>
<!--<div class="umb-block-grid__block--validation-badge">!</div>-->
<div class="umb-block-grid__block--context">
<div class="__context-bar">
@@ -127,39 +133,7 @@
</div>
<button
ng-if="vm.blockEditorApi.readonly !== true && (vm.layoutColumnsInt !== vm.layoutEntry.columnSpan || (vm.layoutColumnsInt === vm.layoutEntry.columnSpan && vm.layoutEntry.forceLeft))"
type="button"
aria-labelledby="forceLeftLabel"
title="@blockEditor_forceLeftButton"
localize="title"
class="umb-block-grid__force-left"
ng-class="{ '--active': vm.layoutEntry.forceLeft }"
ng-click="vm.toggleForceLeft()">
<span class="sr-only">
<localize ng-if="!vm.layoutEntry.forceLeft" id="forceLeftLabel" key="blockEditor_forceLeftLabel">Force placement at left side</localize>
<localize ng-if="vm.layoutEntry.forceLeft" id="forceLeftLabel" key="blockEditor_unforceLeftLabel">Remove forced placement at left side</localize>
</span>
<umb-icon icon="icon-navigation-left" class="icon"></umb-icon>
</button>
<button
ng-if="vm.blockEditorApi.readonly !== true && (vm.layoutColumnsInt !== vm.layoutEntry.columnSpan || (vm.layoutColumnsInt === vm.layoutEntry.columnSpan && vm.layoutEntry.forceRight))"
type="button"
aria-labelledby="forceRightLabel"
title="@blockEditor_forceRightButton"
localize="title"
class="umb-block-grid__force-right"
ng-class="{ '--active': vm.layoutEntry.forceRight }"
ng-click="vm.toggleForceRight()">
<span class="sr-only">
<localize ng-if="!vm.layoutEntry.forceRight" id="forceRightLabel" key="blockEditor_forceRightLabel">Force placement at right side</localize>
<localize ng-if="vm.layoutEntry.forceRight" id="forceRightLabel" key="blockEditor_unforceRightLabel">Remove forced placement at right side</localize>
</span>
<umb-icon icon="icon-navigation-right" class="icon"></umb-icon>
</button>
<button
ng-if="::!vm.blockEditorApi.readonly && (vm.layoutEntry.$block.config.columnSpanOptions.length > 1 || (vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan))"
ng-if="::!vm.blockEditorApi.readonly && vm.canScale"
type="button"
title="@blockEditor_scaleHandlerButtonTitle"
localize="title"
@@ -167,7 +141,7 @@
ng-mousedown="vm.scaleHandlerMouseDown($event)"
ng-keyup="vm.scaleHandlerKeyUp($event)">
</button>
<div ng-if="::!vm.blockEditorApi.readonly && (vm.layoutEntry.$block.config.columnSpanOptions.length > 1 || (vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan))"
<div ng-if="::!vm.blockEditorApi.readonly && vm.canScale"
class="umb-block-grid__scale-label">
{{vm.layoutEntry.columnSpan}} x {{vm.layoutEntry.rowSpan}}
</div>
@@ -180,10 +154,7 @@
class="umb-block-grid__block--inline-create-button --after"
ng-class="{'--at-root': vm.depth === '0'}"
ng-click="vm.clickInlineCreateAfter($event)"
ng-mouseover="vm.mouseOverInlineCreateAfter()"
ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)"
ng-mouseover="vm.mouseOverInlineCreate()"
ng-mouseleave="vm.mouseOutInlineCreate()"
vertical>
</uui-button-inline-create>
<div ng-if="vm.hideInlineCreateAfter"
class="umb-block-grid__block--inline-create-button --after --detector" ng-mouseover="vm.mouseOverInlineCreateAfter()">
</div>
</uui-button-inline-create>

View File

@@ -4,7 +4,19 @@
<div class="umb-block-grid__wrapper" ng-style="vm.editorWrapperStyles">
<div ng-if="vm.loading !== true">
<umb-editor-sub-header ng-if="vm.sortMode" appearance="blue">
<umb-editor-sub-header-content-right>
<umb-button
type="button"
icon="icon-delete"
button-style="primary"
label-key="blockEditor_actionExitSortMode"
action="vm.exitSortMode()">
</umb-button>
</umb-editor-sub-header-content-right>
</umb-editor-sub-header>
<div ng-show="vm.loading !== true">
<umb-block-grid-root
grid-columns="{{::vm.gridColumns}}"
@@ -12,11 +24,12 @@
stylesheet="{{::vm.layoutStylesheet}}"
block-editor-api="vm.blockEditorApi"
property-editor-form="vm.propertyForm"
entries="vm.layout">
entries="vm.layout"
loading="vm.loading"
>
</umb-block-grid-root>
</div>
<input type="hidden" name="minCount" ng-model="vm.layout" val-server="minCount" />
<input type="hidden" name="maxCount" ng-model="vm.layout" val-server="maxCount" />

View File

@@ -1,4 +1,8 @@
.umb-block-grid__wrapper {
position: relative;
max-width: 1200px;
}
.umb-block-grid__wrapper .umb-rte {
max-width: 100%;
}

View File

@@ -0,0 +1,3 @@
<div part="area-container">
<slot ng-repeat="area in ::block.layout.areas" name="{{::area.$config.alias}}"></slot>
</div>

View File

@@ -13,6 +13,31 @@
return null;
}
function closestColumnSpanOption(target, map, max) {
if(map.length > 0) {
const result = map.reduce((a, b) => {
if (a.columnSpan > max) {
return b;
}
let aDiff = Math.abs(a.columnSpan - target);
let bDiff = Math.abs(b.columnSpan - target);
if (aDiff === bDiff) {
return a.columnSpan < b.columnSpan ? a : b;
} else {
return bDiff < aDiff ? b : a;
}
});
if(result) {
return result;
}
}
return null;
}
const DefaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/";
/**
* @ngdoc directive
@@ -44,14 +69,20 @@
var unsubscribe = [];
var modelObject;
var gridRootEl;
// Property actions:
var propertyActions = null;
var enterSortModeAction = null;
var exitSortModeAction = null;
var copyAllBlocksAction = null;
var deleteAllBlocksAction = null;
var liveEditing = true;
var shadowRoot;
var firstLayoutContainer;
var vm = this;
@@ -107,6 +138,8 @@
vm.options = {
createFlow: false
};
vm.sortMode = false;
vm.sortModeView = DefaultViewFolderPath + "gridsortblock/gridsortblock.editor.html";;
localizationService.localizeMany(["grid_addElement", "content_createEmpty", "blockEditor_addThis"]).then(function (data) {
vm.labels.grid_addElement = data[0];
@@ -114,8 +147,23 @@
vm.labels.blockEditor_addThis = data[2]
});
vm.onAppendProxyProperty = (event) => {
event.stopPropagation();
gridRootEl.appendChild(event.detail.property);
event.detail.connectedCallback();
};
vm.onRemoveProxyProperty = (event) => {
event.stopPropagation();
const el = gridRootEl.querySelector(`:scope > [slot='${event.detail.slotName}']`);
gridRootEl.removeChild(el);
};
vm.$onInit = function() {
gridRootEl = $element[0].querySelector('umb-block-grid-root');
$element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty);
$element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty);
//listen for form validation changes
vm.valFormManager.onValidationStatusChanged(function (evt, args) {
@@ -177,6 +225,19 @@
scopeOfExistence = vm.umbElementEditorContent.getScope();
}
enterSortModeAction = {
labelKey: 'blockEditor_actionEnterSortMode',
icon: 'navigation-vertical',
method: enableSortMode,
isDisabled: false
};
exitSortModeAction = {
labelKey: 'blockEditor_actionExitSortMode',
icon: 'navigation-vertical',
method: exitSortMode,
isDisabled: false
};
copyAllBlocksAction = {
labelKey: "clipboard_labelForCopyAllEntries",
labelTokens: [vm.model.label],
@@ -187,13 +248,13 @@
deleteAllBlocksAction = {
labelKey: 'clipboard_labelForRemoveAllEntries',
labelTokens: [],
icon: 'trash',
method: requestDeleteAllBlocks,
isDisabled: true
};
var propertyActions = [
propertyActions = [
enterSortModeAction,
copyAllBlocksAction,
deleteAllBlocksAction
];
@@ -223,7 +284,6 @@
}
function onLoaded() {
// Store a reference to the layout model, because we need to maintain this model.
@@ -241,6 +301,7 @@
window.requestAnimationFrame(() => {
shadowRoot = $element[0].querySelector('umb-block-grid-root').shadowRoot;
firstLayoutContainer = shadowRoot.querySelector('.umb-block-grid__layout-container');
})
}
@@ -314,21 +375,30 @@
}
}
// if no columnSpan, then we set one:
if (!layoutEntry.columnSpan) {
// Ensure Areas are ordered like the area configuration is:
layoutEntry.areas.sort((left, right) => {
return block.config.areas?.findIndex(config => config.key === left.key) < block.config.areas?.findIndex(config => config.key === right.key) ? -1 : 1;
});
const contextColumns = getContextColumns(parentBlock, areaKey)
if (block.config.columnSpanOptions.length > 0) {
// set columnSpan to minimum allowed span for this BlockType:
const minimumColumnSpan = block.config.columnSpanOptions.reduce((prev, option) => Math.min(prev, option.columnSpan), vm.gridColumns);
const contextColumns = getContextColumns(parentBlock, areaKey);
const relevantColumnSpanOptions = block.config.columnSpanOptions.filter(option => option.columnSpan <= contextColumns);
// If minimumColumnSpan is larger than contextColumns, then we will make it fit within context anyway:
layoutEntry.columnSpan = Math.min(minimumColumnSpan, contextColumns)
// if no columnSpan or no columnSpanOptions configured, then we set(or rewrite) one:
if (!layoutEntry.columnSpan || layoutEntry.columnSpan > contextColumns || relevantColumnSpanOptions.length === 0) {
if (relevantColumnSpanOptions.length > 0) {
// Find greatest columnSpanOption within contextColumns, or fallback to contextColumns.
layoutEntry.columnSpan = relevantColumnSpanOptions.reduce((prev, option) => Math.max(prev, option.columnSpan), 0) || contextColumns;
} else {
layoutEntry.columnSpan = contextColumns;
}
} else {
// Check that columnSpanOption still is available or equal contextColumns, or find closest option fitting:
if (relevantColumnSpanOptions.find(option => option.columnSpan === layoutEntry.columnSpan) === undefined || layoutEntry.columnSpan !== contextColumns) {
layoutEntry.columnSpan = closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns;
}
}
// if no rowSpan, then we set one:
if (!layoutEntry.rowSpan) {
layoutEntry.rowSpan = 1;
@@ -375,12 +445,12 @@
function applyDefaultViewForBlock(block) {
var defaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/";
if (block.config.unsupported === true) {
block.view = defaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html";
block.view = DefaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html";
} else if (block.config.inlineEditing) {
block.view = DefaultViewFolderPath + "gridinlineblock/gridinlineblock.editor.html";
} else {
block.view = defaultViewFolderPath + "gridblock/gridblock.editor.html";
block.view = DefaultViewFolderPath + "gridblock/gridblock.editor.html";
}
}
@@ -430,9 +500,11 @@
block.showCopy = vm.supportCopy && block.config.contentElementTypeKey != null;
block.blockUiVisibility = false;
block.showBlockUI = function () {
block.showBlockUI = () => {
delete block.__timeout;
shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"});
$timeout(() => {
shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] > ng-form > .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"});
}, 100);
block.blockUiVisibility = true;
};
block.onMouseLeave = function () {
@@ -778,6 +850,8 @@
vm.requestShowCreate = requestShowCreate;
function requestShowCreate(parentBlock, areaKey, createIndex, mouseEvent, options) {
vm.hideAreaHighlight(parentBlock, areaKey);
if (vm.blockTypePickerIsOpen === true) {
return;
}
@@ -1254,6 +1328,38 @@
}
}
function enableSortMode() {
vm.sortMode = true;
propertyActions.splice(propertyActions.indexOf(enterSortModeAction), 1, exitSortModeAction);
if (vm.umbProperty) {
vm.umbProperty.setPropertyActions(propertyActions);
}
}
vm.exitSortMode = exitSortMode;
function exitSortMode() {
vm.sortMode = false;
propertyActions.splice(propertyActions.indexOf(exitSortModeAction), 1, enterSortModeAction);
if (vm.umbProperty) {
vm.umbProperty.setPropertyActions(propertyActions);
}
}
vm.startDraggingMode = startDraggingMode;
function startDraggingMode() {
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1);
firstLayoutContainer.style.minHeight = firstLayoutContainer.getBoundingClientRect().height + "px";
}
vm.exitDraggingMode = exitDraggingMode;
function exitDraggingMode() {
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0);
firstLayoutContainer.style.minHeight = "";
}
function onAmountOfBlocksChanged() {
// enable/disable property actions
@@ -1278,9 +1384,16 @@
unsubscribe.push($scope.$watch(() => vm.layout.length, onAmountOfBlocksChanged));
$scope.$on("$destroy", function () {
$element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty);
$element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty);
for (const subscription of unsubscribe) {
subscription();
}
firstLayoutContainer = null;
gridRootEl = null;
});
}

View File

@@ -58,7 +58,7 @@
<div
style="display:contents;"
ng-class="{'show-validation': vm.blockEditorApi.internal.showValidation}"
ng-include="'${model.view}'"></div>
ng-include="api.internal.sortMode ? api.internal.sortModeView : '${model.view}'"></div>
`;
$compile(shadowRoot)($scope);

View File

@@ -76,6 +76,7 @@
vm.movingLayoutEntry = null;
vm.layoutColumnsInt = 0;
vm.containedPropertyEditorProxies = [];
vm.$onInit = function () {
initializeSortable();
@@ -93,7 +94,6 @@
vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10);
}));
function onLocalAmountOfBlocksChanged() {
if (vm.entriesForm && vm.areaConfig) {
@@ -153,6 +153,11 @@
}
}
vm.notifyVisualUpdate = function () {
$scope.$broadcast("blockGridEditorVisualUpdate", {areaKey: vm.areaKey});
}
vm.acceptBlock = function(contentTypeKey) {
return vm.blockEditorApi.internal.isElementTypeKeyAllowedAt(vm.parentBlock, vm.areaKey, contentTypeKey);
}
@@ -198,9 +203,6 @@
var dragY = 0;
var dragOffsetX = 0;
var ghostElIndicateForceLeft = null;
var ghostElIndicateForceRight = null;
var approvedContainerEl = null;
// Setup DOM method for communication between sortables:
@@ -210,6 +212,11 @@
var nextSibling;
function _removePropertyProxy(eventTarget, slotName) {
const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': slotName}});
eventTarget.dispatchEvent(event);
}
// Borrowed concept from, its not identical as more has been implemented: https://github.com/SortableJS/angular-legacy-sortablejs/blob/master/angular-legacy-sortable.js
function _sync(evt) {
@@ -222,8 +229,15 @@
const prevEntries = fromCtrl.entries;
const syncEntry = prevEntries[oldIndex];
// Perform the transfer:
// Make sure Property Editor Proxies are destroyed, as we need to establish new when moving context:
// unregister all property editor proxies via events:
fromCtrl.containedPropertyEditorProxies.forEach(slotName => {
_removePropertyProxy(evt.from, slotName);
});
// Perform the transfer:
if (Sortable.active && Sortable.active.lastPullMode === 'clone') {
syncEntry = Utilities.copy(syncEntry);
prevEntries.splice(Sortable.utils.index(evt.clone, sortable.options.draggable), 0, prevEntries.splice(oldIndex, 1)[0]);
@@ -231,7 +245,6 @@
else {
prevEntries.splice(oldIndex, 1);
}
vm.entries.splice(newIndex, 0, syncEntry);
const contextColumns = vm.blockEditorApi.internal.getContextColumns(vm.parentBlock, vm.areaKey);
@@ -246,12 +259,6 @@
} else {
syncEntry.columnSpan = contextColumns;
}
if(syncEntry.columnSpan === contextColumns) {
// If we are full width, then reset forceLeft/right.
syncEntry.forceLeft = false;
syncEntry.forceRight = false;
}
}
else {
@@ -261,6 +268,7 @@
function _indication(contextVM, movingEl) {
// Remove old indication:
if(_lastIndicationContainerVM !== contextVM && _lastIndicationContainerVM !== null) {
_lastIndicationContainerVM.hideNotAllowed();
_lastIndicationContainerVM.revertIndicateDroppable();
@@ -269,7 +277,7 @@
if(contextVM.acceptBlock(movingEl.dataset.contentElementTypeKey) === true) {
_lastIndicationContainerVM.hideNotAllowed();
_lastIndicationContainerVM.indicateDroppable();// This block is accepted to we will indicate a good drop.
_lastIndicationContainerVM.indicateDroppable();// This block is accepted so we will indicate a good drop.
return true;
}
@@ -382,55 +390,40 @@
}
let verticalDirection = false;
if (ghostEl.dataset.forceLeft) {
placeAfter = true;
} else if (ghostEl.dataset.forceRight) {
placeAfter = true;
} else {
// TODO: move calculations out so they can be persisted a bit longer?
//const approvedContainerRect = approvedContainerEl.getBoundingClientRect();
const approvedContainerComputedStyles = getComputedStyle(approvedContainerEl);
const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue("--umb-block-grid--grid-columns"), 10);
// if the related element is forceLeft and we are in the left side, we will set vertical direction, to correct placeAfter.
if (foundRelatedEl.dataset.forceLeft && placeAfter === false) {
verticalDirection = true;
} else
// if the related element is forceRight and we are in the right side, we will set vertical direction, to correct placeAfter.
if (foundRelatedEl.dataset.forceRight && placeAfter === true) {
verticalDirection = true;
} else {
const relatedColumns = parseInt(foundRelatedEl.dataset.colSpan, 10);
const ghostColumns = parseInt(ghostEl.dataset.colSpan, 10);
// TODO: move calculations out so they can be persisted a bit longer?
//const approvedContainerRect = approvedContainerEl.getBoundingClientRect();
const approvedContainerComputedStyles = getComputedStyle(approvedContainerEl);
const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue("--umb-block-grid--grid-columns"), 10);
// Get grid template:
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0);
const relatedColumns = parseInt(foundRelatedEl.dataset.colSpan, 10);
const ghostColumns = parseInt(ghostEl.dataset.colSpan, 10);
// Get grid template:
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0);
// ensure all columns are there.
// This will also ensure handling non-css-grid mode,
// use container width divided by amount of columns( or the item width divided by its amount of columnSpan)
let amountOfColumnsInWeightMap = approvedContainerGridColumns.length;
const amountOfUnknownColumns = gridColumnNumber-amountOfColumnsInWeightMap;
if(amountOfUnknownColumns > 0) {
let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
const layoutWidth = approvedContainerRect.width;
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
approvedContainerGridColumns.push(missingColumnWidth);
}
}
const relatedStartX = foundRelatedElRect.left - approvedContainerRect.left;
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
if(relatedStartCol + relatedColumns + ghostColumns > gridColumnNumber) {
verticalDirection = true;
}
// ensure all columns are there.
// This will also ensure handling non-css-grid mode,
// use container width divided by amount of columns( or the item width divided by its amount of columnSpan)
let amountOfColumnsInWeightMap = approvedContainerGridColumns.length;
const amountOfUnknownColumns = gridColumnNumber-amountOfColumnsInWeightMap;
if(amountOfUnknownColumns > 0) {
let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
const layoutWidth = approvedContainerRect.width;
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
approvedContainerGridColumns.push(missingColumnWidth);
}
}
const relatedStartX = foundRelatedElRect.left - approvedContainerRect.left;
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
if(relatedStartCol + relatedColumns + ghostColumns > gridColumnNumber) {
verticalDirection = true;
}
if (verticalDirection) {
placeAfter = (dragY > foundRelatedElRect.top + (foundRelatedElRect.height*.5));
}
@@ -485,56 +478,6 @@
rqaId = requestAnimationFrame(_moveGhostElement);
}
}
if(vm.movingLayoutEntry.columnSpan !== vm.layoutColumnsInt) {
const oldForceLeft = vm.movingLayoutEntry.forceLeft;
const oldForceRight = vm.movingLayoutEntry.forceRight;
var newValue = (dragX < targetRect.left);
if(newValue !== oldForceLeft) {
vm.movingLayoutEntry.forceLeft = newValue;
if(oldForceRight) {
vm.movingLayoutEntry.forceRight = false;
if(ghostElIndicateForceRight) {
ghostEl.removeChild(ghostElIndicateForceRight);
ghostElIndicateForceRight = null;
}
}
vm.blockEditorApi.internal.setDirty();
vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated
$scope.$evalAsync();
// Append element for indication, as angularJS lost connection:
if(newValue === true) {
ghostElIndicateForceLeft = document.createElement("div");
ghostElIndicateForceLeft.className = "indicateForceLeft";
ghostEl.appendChild(ghostElIndicateForceLeft);
} else if(ghostElIndicateForceLeft) {
ghostEl.removeChild(ghostElIndicateForceLeft);
ghostElIndicateForceLeft = null;
}
}
newValue = (dragX > targetRect.right) && (vm.movingLayoutEntry.forceLeft !== true);
if(newValue !== oldForceRight) {
vm.movingLayoutEntry.forceRight = newValue;
vm.blockEditorApi.internal.setDirty();
vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated
$scope.$evalAsync();
// Append element for indication, as angularJS lost connection:
if(newValue === true) {
ghostElIndicateForceRight = document.createElement("div");
ghostElIndicateForceRight.className = "indicateForceRight";
ghostEl.appendChild(ghostElIndicateForceRight);
} else if(ghostElIndicateForceRight) {
ghostEl.removeChild(ghostElIndicateForceRight);
ghostElIndicateForceRight = null;
}
}
}
}
}
@@ -557,6 +500,10 @@
forceAutoScrollFallback: true,
onStart: function (evt) {
// TODO: This does not work correctly jet with SortableJS. With the replacement we should be able to call this before DOM is changed.
vm.blockEditorApi.internal.startDraggingMode();
nextSibling = evt.from === evt.item.parentNode ? evt.item.nextSibling : evt.clone.nextSibling;
var contextVM = vm;
@@ -568,15 +515,9 @@
const oldIndex = evt.oldIndex;
vm.movingLayoutEntry = contextVM.getLayoutEntryByIndex(oldIndex);
if(vm.movingLayoutEntry.forceLeft || vm.movingLayoutEntry.forceRight) {
// if one of these where true before, then we made a change here:
vm.blockEditorApi.internal.setDirty();
}
vm.movingLayoutEntry.forceLeft = false;
vm.movingLayoutEntry.forceRight = false;
vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated
ghostEl = evt.item;
vm.containedPropertyEditorProxies = Array.from(ghostEl.querySelectorAll('slot[data-is-property-editor-proxy]')).map(x => x.getAttribute('name'));
targetRect = evt.to.getBoundingClientRect();
ghostRect = ghostEl.getBoundingClientRect();
@@ -587,8 +528,6 @@
window.addEventListener('drag', _onDragMove);
window.addEventListener('dragover', _onDragMove);
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1);
$scope.$evalAsync();
},
// Called by any change to the list (add / update / remove)
@@ -619,16 +558,7 @@
}
window.removeEventListener('drag', _onDragMove);
window.removeEventListener('dragover', _onDragMove);
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0);
if(ghostElIndicateForceLeft) {
ghostEl.removeChild(ghostElIndicateForceLeft);
ghostElIndicateForceLeft = null;
}
if(ghostElIndicateForceRight) {
ghostEl.removeChild(ghostElIndicateForceRight);
ghostElIndicateForceRight = null;
}
vm.blockEditorApi.internal.exitDraggingMode();
// ensure not-allowed indication is removed.
if(_lastIndicationContainerVM) {
@@ -643,6 +573,9 @@
ghostRect = null;
ghostEl = null;
relatedEl = null;
vm.containedPropertyEditorProxies = [];
vm.notifyVisualUpdate();
}
});

View File

@@ -44,23 +44,25 @@
}
function closestColumnSpanOption(target, map, max) {
const result = map.reduce((a, b) => {
if (a.columnSpan > max) {
return b;
if(map.length > 0) {
const result = map.reduce((a, b) => {
if (a.columnSpan > max) {
return b;
}
let aDiff = Math.abs(a.columnSpan - target);
let bDiff = Math.abs(b.columnSpan - target);
if (aDiff === bDiff) {
return a.columnSpan < b.columnSpan ? a : b;
} else {
return bDiff < aDiff ? b : a;
}
});
if(result) {
return result;
}
let aDiff = Math.abs(a.columnSpan - target);
let bDiff = Math.abs(b.columnSpan - target);
if (aDiff === bDiff) {
return a.columnSpan < b.columnSpan ? a : b;
} else {
return bDiff < aDiff ? b : a;
}
});
if(result) {
return result;
}
return max;
return null;
}
@@ -86,11 +88,17 @@
areaKey: "<",
propertyEditorForm: "<?",
depth: "@"
},
require: {
umbBlockGridEntries: "?^^umbBlockGridEntries"
}
}
);
function BlockGridEntryController($scope, $element) {
function BlockGridEntryController($scope, $element, $timeout) {
let updateInlineCreateTimeout;
let updateInlineCreateRaf;
const unsubscribe = [];
const vm = this;
@@ -98,10 +106,37 @@
vm.isHoveringArea = false;
vm.isScaleMode = false;
vm.layoutColumnsInt = 0;
vm.inlineCreateAboveWidth = "";
vm.hideInlineCreateAbove = true;
vm.hideInlineCreateAfter = true;
vm.canScale = false;
vm.proxyProperties = [];
vm.onAppendProxyProperty = (event) => {
// Only insert a proxy slot for the direct Block of this entry (as all the blocks share the same ShadowDom though they are slotted into each other when nested through areas.)
if (event.detail.contentUdi === vm.layoutEntry.contentUdi) {
vm.proxyProperties.push({
slotName: event.detail.slotName
});
$scope.$evalAsync();
}
};
vm.onRemoveProxyProperty = (event) => {
// Only react to proxies from the direct Block of this entry:
if (event.detail.contentUdi === vm.layoutEntry.contentUdi) {
const index = vm.proxyProperties.findIndex(x => x.slotName === event.detail.slotName);
if(index !== -1) {
vm.proxyProperties.splice(index, 1);
}
$scope.$evalAsync();
}
};
vm.$onInit = function() {
$element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty);
$element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty);
vm.childDepth = parseInt(vm.depth) + 1;
if(vm.layoutEntry.$block.config.areaGridColumns) {
@@ -110,13 +145,29 @@
vm.areaGridColumns = vm.blockEditorApi.internal.gridColumns.toString();
}
vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10)
vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10);
vm.relevantColumnSpanOptions = vm.layoutEntry.$block.config.columnSpanOptions.filter(x => x.columnSpan <= vm.layoutColumnsInt).sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0));
const hasRelevantColumnSpanOptions = vm.relevantColumnSpanOptions.length > 1;
const hasRowSpanOptions = vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan;
vm.canScale = (hasRelevantColumnSpanOptions || hasRowSpanOptions);
unsubscribe.push(vm.layoutEntry.$block.__scope.$watch(() => vm.layoutEntry.$block.index, visualUpdateCallback));
unsubscribe.push($scope.$on("blockGridEditorVisualUpdate", (evt, data) => {if(data.areaKey === vm.areaKey) { visualUpdateCallback()}}));
updateInlineCreateTimeout = $timeout(updateInlineCreate, 500);
$scope.$evalAsync();
}
unsubscribe.push($scope.$watch("depth", (newVal, oldVal) => {
vm.childDepth = parseInt(vm.depth) + 1;
}));
function visualUpdateCallback() {
cancelAnimationFrame(updateInlineCreateRaf);
updateInlineCreateRaf = requestAnimationFrame(updateInlineCreate);
}
/**
* We want to only show the validation errors on the specific Block, not the parent blocks.
* So we need to avoid having a Block as the parent to the Block Form.
@@ -139,27 +190,16 @@
vm.mouseLeaveArea = function() {
vm.isHoveringArea = false;
}
vm.toggleForceLeft = function() {
vm.layoutEntry.forceLeft = !vm.layoutEntry.forceLeft;
if(vm.layoutEntry.forceLeft) {
vm.layoutEntry.forceRight = false;
}
vm.blockEditorApi.internal.setDirty();
}
vm.toggleForceRight = function() {
vm.layoutEntry.forceRight = !vm.layoutEntry.forceRight;
if(vm.layoutEntry.forceRight) {
vm.layoutEntry.forceLeft = false;
}
vm.blockEditorApi.internal.setDirty();
}
// Block sizing functionality:
let layoutContainer = null;
let gridColumns = null;
let columnGap = 0;
let rowGap = 0;
let gridRows = null;
let lockedGridRows = 0;
let scaleBoxBackdropEl = null;
let raf = null;
function getNewSpans(startX, startY, endX, endY) {
@@ -171,7 +211,8 @@
let newColumnSpan = Math.max(blockEndCol-blockStartCol, 1);
// Find nearest allowed Column:
newColumnSpan = closestColumnSpanOption(newColumnSpan , vm.layoutEntry.$block.config.columnSpanOptions, gridColumns.length - blockStartCol).columnSpan;
const bestColumnSpanOption = closestColumnSpanOption(newColumnSpan , vm.relevantColumnSpanOptions, vm.layoutColumnsInt - blockStartCol)
newColumnSpan = bestColumnSpanOption ? bestColumnSpanOption.columnSpan : vm.layoutColumnsInt;
let newRowSpan = Math.round(Math.max(blockEndRow-blockStartRow, vm.layoutEntry.$block.config.rowMinSpan || 1));
if(vm.layoutEntry.$block.config.rowMaxSpan != null) {
@@ -181,10 +222,14 @@
return {'columnSpan': newColumnSpan, 'rowSpan': newRowSpan, 'startCol': blockStartCol, 'startRow': blockStartRow};
}
function updateGridLayoutData(layoutContainerRect, layoutItemRect) {
function updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate) {
const computedStyles = window.getComputedStyle(layoutContainer);
columnGap = Number(computedStyles.columnGap.split("px")[0]) || 0;
rowGap = Number(computedStyles.rowGap.split("px")[0]) || 0;
gridColumns = computedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x));
gridRows = computedStyles.gridTemplateRows.trim().split("px").map(x => Number(x));
@@ -192,6 +237,18 @@
gridColumns = gridColumns.filter(n => n > 0);
gridRows = gridRows.filter(n => n > 0);
// We use this code to lock the templateRows, while scaling. otherwise scaling Rows is too crazy.
if(updateRowTemplate || gridRows.length > lockedGridRows) {
lockedGridRows = gridRows.length;
layoutContainer.style.gridTemplateRows = computedStyles.gridTemplateRows;
}
// add gaps:
const gridColumnsLen = gridColumns.length;
gridColumns = gridColumns.map((n, i) => gridColumnsLen === i ? n : n + columnGap);
const gridRowsLen = gridRows.length;
gridRows = gridRows.map((n, i) => gridRowsLen === i ? n : n + rowGap);
// ensure all columns are there.
// This will also ensure handling non-css-grid mode,
// use container width divided by amount of columns( or the item width divided by its amount of columnSpan)
@@ -226,25 +283,28 @@
gridRows.push(50);
gridRows.push(50);
gridRows.push(50);
gridRows.push(50);
gridRows.push(50);
}
vm.scaleHandlerMouseDown = function($event) {
$event.originalEvent.preventDefault();
layoutContainer = $element[0].closest('.umb-block-grid__layout-container');
if(!layoutContainer) {
console.error($element[0], 'could not find parent layout-container');
return;
}
vm.isScaleMode = true;
window.addEventListener('mousemove', vm.onMouseMove);
window.addEventListener('mouseup', vm.onMouseUp);
window.addEventListener('mouseleave', vm.onMouseUp);
layoutContainer = $element[0].closest('.umb-block-grid__layout-container');
if(!layoutContainer) {
console.error($element[0], 'could not find parent layout-container');
}
const layoutContainerRect = layoutContainer.getBoundingClientRect();
const layoutItemRect = $element[0].getBoundingClientRect();
updateGridLayoutData(layoutContainerRect, layoutItemRect);
updateGridLayoutData(layoutContainerRect, layoutItemRect, true);
scaleBoxBackdropEl = document.createElement('div');
@@ -256,7 +316,6 @@
const layoutContainerRect = layoutContainer.getBoundingClientRect();
const layoutItemRect = $element[0].getBoundingClientRect();
updateGridLayoutData(layoutContainerRect, layoutItemRect);
const startX = layoutItemRect.left - layoutContainerRect.left;
@@ -266,6 +325,18 @@
const newSpans = getNewSpans(startX, startY, endX, endY);
const updateRowTemplate = vm.layoutEntry.columnSpan !== newSpans.columnSpan;
if(updateRowTemplate) {
// If we like to update we need to first remove the lock, make the browser render onces and then update.
layoutContainer.style.gridTemplateRows = "";
}
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
// As mentioned above we need to wait until the browser has rendered DOM without the lock of gridTemplateRows.
updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate);
})
// update as we go:
vm.layoutEntry.columnSpan = newSpans.columnSpan;
vm.layoutEntry.rowSpan = newSpans.rowSpan;
@@ -275,7 +346,13 @@
vm.onMouseUp = function(e) {
vm.isScaleMode = false;
cancelAnimationFrame(raf);
// Remove listeners:
window.removeEventListener('mousemove', vm.onMouseMove);
window.removeEventListener('mouseup', vm.onMouseUp);
window.removeEventListener('mouseleave', vm.onMouseUp);
const layoutContainerRect = layoutContainer.getBoundingClientRect();
const layoutItemRect = $element[0].getBoundingClientRect();
@@ -287,22 +364,24 @@
const newSpans = getNewSpans(startX, startY, endX, endY);
// Remove listeners:
window.removeEventListener('mousemove', vm.onMouseMove);
window.removeEventListener('mouseup', vm.onMouseUp);
window.removeEventListener('mouseleave', vm.onMouseUp);
// release the lock of gridTemplateRows:
layoutContainer.removeChild(scaleBoxBackdropEl);
layoutContainer.style.gridTemplateRows = "";
vm.isScaleMode = false;
// Clean up variables:
layoutContainer = null;
gridColumns = null;
gridRows = null;
lockedGridRows = 0;
scaleBoxBackdropEl = null;
// Update block size:
vm.layoutEntry.columnSpan = newSpans.columnSpan;
vm.layoutEntry.rowSpan = newSpans.rowSpan;
vm.umbBlockGridEntries.notifyVisualUpdate();
vm.blockEditorApi.internal.setDirty();
$scope.$evalAsync();
}
@@ -331,8 +410,8 @@
}
if(addColIndex !== 0) {
if (vm.layoutEntry.$block.config.columnSpanOptions.length > 0) {
const sortOptions = vm.layoutEntry.$block.config.columnSpanOptions.sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0));
if (vm.relevantColumnSpanOptions.length > 0) {
const sortOptions = vm.relevantColumnSpanOptions;
const currentColIndex = sortOptions.findIndex(x => x.columnSpan === vm.layoutEntry.columnSpan);
const newColIndex = Math.min(Math.max(currentColIndex + addColIndex, 0), sortOptions.length-1);
vm.layoutEntry.columnSpan = sortOptions[newColIndex].columnSpan;
@@ -346,18 +425,30 @@
}
vm.layoutEntry.rowSpan = newRowSpan;
vm.umbBlockGridEntries.notifyVisualUpdate();
vm.blockEditorApi.internal.setDirty();
$event.originalEvent.stopPropagation();
}
vm.clickInlineCreateAbove = function($event) {
if(vm.hideInlineCreateAbove === false) {
vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index, $event);
}
}
vm.clickInlineCreateAfter = function($event) {
if(vm.hideInlineCreateAfter === false) {
vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index+1, $event, {'fitInRow': true});
}
}
vm.mouseOverInlineCreateAfter = function() {
vm.mouseOverInlineCreate = function() {
vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey);
}
vm.mouseOutInlineCreate = function() {
vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey);
}
function updateInlineCreate() {
layoutContainer = $element[0].closest('.umb-block-grid__layout-container');
if(!layoutContainer) {
return;
@@ -366,17 +457,39 @@
const layoutContainerRect = layoutContainer.getBoundingClientRect();
const layoutItemRect = $element[0].getBoundingClientRect();
if(layoutItemRect.right > layoutContainerRect.right - 5) {
if(layoutContainerRect.width === 0) {
$timeout.cancel(updateInlineCreateTimeout);
vm.hideInlineCreateAbove = true;
vm.hideInlineCreateAfter = true;
vm.inlineCreateAboveWidth = "";
$scope.$evalAsync();
updateInlineCreateTimeout = $timeout(updateInlineCreate, 500);
return;
}
vm.hideInlineCreateAfter = false;
vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey);
if(layoutItemRect.right > layoutContainerRect.right - 5) {
vm.hideInlineCreateAfter = true;
} else {
vm.hideInlineCreateAfter = false;
}
if(layoutItemRect.left > layoutContainerRect.left + 5) {
vm.hideInlineCreateAbove = true;
vm.inlineCreateAboveWidth = "";
} else {
vm.inlineCreateAboveWidth = getComputedStyle(layoutContainer).width;
vm.hideInlineCreateAbove = false;
}
$scope.$evalAsync();
}
$scope.$on("$destroy", function () {
$timeout.cancel(updateInlineCreateTimeout);
$element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty);
$element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty);
for (const subscription of unsubscribe) {
subscription();
}

View File

@@ -0,0 +1,20 @@
(function () {
'use strict';
function UmbBlockGridRenderAreaSlots() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html',
scope: false
};
return directive;
}
angular.module('umbraco.directives').directive('umbBlockGridRenderAreaSlots', UmbBlockGridRenderAreaSlots);
})();

View File

@@ -20,7 +20,8 @@
stylesheet: "@",
blockEditorApi: "<",
propertyEditorForm: "<?",
entries: "<"
entries: "<",
loading: "<"
}
}
);
@@ -41,6 +42,7 @@
}
</style>
<div
ng-if="vm.loading !== true"
class="umb-block-grid"
ng-class="{'show-validation': vm.blockEditorApi.internal.showValidation}"
data-grid-columns="${vm.gridColumns}"

View File

@@ -10,18 +10,6 @@
--umb-block-grid__layout-item-calc: calc(var(--umb-block-grid--item-column-span) / var(--umb-block-grid--grid-columns));
width: calc(var(--umb-block-grid__layout-item-calc) * 100%);
}
.umb-block-grid__layout-item[data-force-left] {
align-self: flex-start;
}
.umb-block-grid__layout-item[data-force-left]::before {
content: '';
flex-basis: 100%;
height: 0;
}
.umb-block-grid__layout-item[data-force-right] {
margin-left: auto;
align-self: flex-end;
}
.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) {

View File

@@ -2,9 +2,11 @@
position: relative;
display: grid;
grid-template-columns: repeat(var(--umb-block-grid--grid-columns, 1), minmax(0, 1fr));
grid-gap: 0px;
grid-auto-flow: row;
grid-auto-rows: minmax(50px, min-content);
column-gap: var(--umb-block-grid--column-gap, 0);
row-gap: var(--umb-block-grid--row-gap, 0);
}
.umb-block-grid__layout-item {
position: relative;
@@ -12,22 +14,17 @@
grid-column-end: span min(calc(var(--umb-block-grid--item-column-span, 1) * 3), var(--umb-block-grid--grid-columns));
grid-row: span var(--umb-block-grid--item-row-span, 1);
}
.umb-block-grid__layout-item[data-force-left] {
grid-column-start: 1;
}
.umb-block-grid__layout-item[data-force-right] {
grid-column-start: calc(1 + var(--umb-block-grid--grid-columns) - var(--umb-block-grid--item-column-span));
}
.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) {
position: relative;
display: grid;
grid-template-columns: repeat(var(--umb-block-grid--area-grid-columns, var(--umb-block-grid--grid-columns, 1)), minmax(0, 1fr));
grid-gap: 0px;
grid-auto-flow: row;
grid-auto-rows: minmax(50px, min-content);
width: 100%;
column-gap: var(--umb-block-grid--areas-column-gap, 0);
row-gap: var(--umb-block-grid--areas-row-gap, 0);
}
.umb-block-grid__area {
position: relative;

View File

@@ -1,9 +1,10 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.RTEController",
function ($scope, $q, assetsService, $timeout, tinyMceService, angularHelper, tinyMceAssets) {
function ($scope, $q, assetsService, $timeout, tinyMceService, angularHelper, tinyMceAssets, $element) {
// TODO: A lot of the code below should be shared between the grid rte and the normal rte
var unsubscribe = [];
$scope.isLoading = true;
//To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
@@ -19,9 +20,9 @@ angular.module("umbraco")
var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null;
var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null;
$scope.containerWidth = editorConfig.mode === "distraction-free" ? (width ? width : "auto") : "auto";
$scope.containerHeight = editorConfig.mode === "distraction-free" ? (height ? height : "auto") : "auto";
$scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit";
$scope.containerWidth = "auto";
$scope.containerHeight = "auto";
$scope.containerOverflow = "inherit";
var promises = [];
@@ -73,6 +74,12 @@ angular.module("umbraco")
$scope.isLoading = false;
});
});
tinyMceEditor.on("focus", function () {
$element[0].dispatchEvent(new CustomEvent('umb-rte-focus', {composed: true, bubbles: true}));
});
tinyMceEditor.on("blur", function () {
$element[0].dispatchEvent(new CustomEvent('umb-rte-blur', {composed: true, bubbles: true}));
});
//initialize the standard editor functionality for Umbraco
tinyMceService.initializeEditor({
@@ -96,11 +103,11 @@ angular.module("umbraco")
}, 150);
//listen for formSubmitting event (the result is callback used to remove the event subscription)
var unsubscribe = $scope.$on("formSubmitting", function () {
unsubscribe.push($scope.$on("formSubmitting", function () {
if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
$scope.model.value = tinyMceEditor.getContent();
}
});
}));
$scope.focus = function () {
tinyMceEditor.focus();
@@ -110,8 +117,13 @@ angular.module("umbraco")
// NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
$scope.$on('$destroy', function () {
unsubscribe();
for (var i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]();
}
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
if($element) {
$element[0]?.dispatchEvent(new CustomEvent('blur', {composed: true, bubbles: true}));
}
tinyMceEditor.destroy()
}
});

View File

@@ -22,6 +22,10 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
if (!$scope.model.value.mode) {
$scope.model.value.mode = "classic";
}
else if ($scope.model.value.mode === 'distraction-free') {
// Due to legacy reasons, the older 'distraction-free' mode is kept and remapped to 'inline'
$scope.model.value.mode = 'inline';
}
tinyMceService.configuration().then(function(config){
$scope.tinyMceConfig = config;

View File

@@ -40,9 +40,8 @@
<umb-control-group label="Mode" description="Select mode">
<div class="vertical-align-items">
<select ng-model="model.value.mode">
<option value="classic">Classic</option> <!-- Theme:silver & inline:false-->
<option value="inline">Inline</option> <!-- Theme:silver & inline:true-->
<option value="distraction-free">Distraction Free</option> <!-- Theme:silver & toolbars:null & inline:true -->
<option value="classic">Classic</option> <!-- Theme:modern & inline:false-->
<option value="inline">Inline</option> <!-- Theme:modern & inline:true-->
</select>
</div>
</umb-control-group>

View File

@@ -1,5 +1,5 @@
describe('RTE controller tests', function () {
var scope, controllerFactory;
var scope, controllerFactory, element;
//mock tinymce globals
if ((typeof tinymce) === "undefined") {
@@ -23,6 +23,7 @@ describe('RTE controller tests', function () {
controllerFactory = $controller;
scope = $rootScope.$new();
scope.model = {value: "<p>hello</p>"};
element = $("<div></div>");
}));
@@ -31,7 +32,8 @@ describe('RTE controller tests', function () {
it('should define the default properties on construction', function () {
controllerFactory('Umbraco.PropertyEditors.RTEController', {
$scope: scope,
$routeParams: routeParams
$routeParams: routeParams,
$element: element
});
});