Merge pull request #3432 from umbraco/temp8-153-invariant-properties

Temp8 153 invariant properties
This commit is contained in:
Robert
2018-10-29 12:39:48 +01:00
committed by GitHub
20 changed files with 389 additions and 31 deletions

View File

@@ -13,6 +13,18 @@
//expose the property/methods for other directives to use
this.content = $scope.content;
$scope.activeVariant = _.find(this.content.variants, variant => {
return variant.active;
});
$scope.defaultVariant = _.find(this.content.variants, variant => {
return variant.language.isDefault;
});
$scope.unlockInvariantValue = function(property) {
property.unlockInvariantValue = !property.unlockInvariantValue;
};
$scope.$watch("tabbedContentForm.$dirty",
function (newValue, oldValue) {
if (newValue === true) {

View File

@@ -7,7 +7,9 @@ angular.module("umbraco.directives")
.directive('umbProperty', function (umbPropEditorHelper, userService) {
return {
scope: {
property: "="
property: "=",
showInherit: "<",
inheritsFrom: "<"
},
transclude: true,
restrict: 'E',

View File

@@ -12,7 +12,7 @@ function umbPropEditor(umbPropEditorHelper) {
scope: {
model: "=",
isPreValue: "@",
preview: "@"
preview: "<"
},
require: "^^form",

View File

@@ -0,0 +1,46 @@
angular.module("umbraco.directives")
.directive('disableTabindex', function (tabbableService) {
return {
restrict: 'A', //Can only be used as an attribute,
scope: {
"disableTabindex": "<"
},
link: function (scope, element, attrs) {
if(scope.disableTabindex) {
//Select the node that will be observed for mutations (native DOM element not jQLite version)
var targetNode = element[0];
//Watch for DOM changes - so when the property editor subview loads in
//We can be notified its updated the child elements inside the DIV we are watching
var observer = new MutationObserver(domChange);
// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true, subtree: false };
function domChange(mutationsList, observer){
for(var mutation of mutationsList) {
//DOM items have been added or removed
if (mutation.type == 'childList') {
//Check if any child items in mutation.target contain an input
var childInputs = tabbableService.tabbable(mutation.target);
//For each item in childInputs - override or set HTML attribute tabindex="-1"
angular.forEach(childInputs, function(element){
angular.element(element).attr('tabindex', '-1');
});
}
}
}
// Start observing the target node for configured mutations
//GO GO GO
observer.observe(targetNode, config);
}
}
};
});

View File

@@ -0,0 +1,223 @@
//tabbable JS Lib (Wrapped in angular service)
//https://github.com/davidtheclark/tabbable
(function() {
'use strict';
function tabbableService() {
var candidateSelectors = [
'input',
'select',
'textarea',
'a[href]',
'button',
'[tabindex]',
'audio[controls]',
'video[controls]',
'[contenteditable]:not([contenteditable="false"])'
];
var candidateSelector = candidateSelectors.join(',');
var matches = typeof Element === 'undefined'
? function () {}
: Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
function tabbable(el, options) {
options = options || {};
var elementDocument = el.ownerDocument || el;
var regularTabbables = [];
var orderedTabbables = [];
var untouchabilityChecker = new UntouchabilityChecker(elementDocument);
var candidates = el.querySelectorAll(candidateSelector);
if (options.includeContainer) {
if (matches.call(el, candidateSelector)) {
candidates = Array.prototype.slice.apply(candidates);
candidates.unshift(el);
}
}
var i, candidate, candidateTabindex;
for (i = 0; i < candidates.length; i++) {
candidate = candidates[i];
if (!isNodeMatchingSelectorTabbable(candidate, untouchabilityChecker)) continue;
candidateTabindex = getTabindex(candidate);
if (candidateTabindex === 0) {
regularTabbables.push(candidate);
} else {
orderedTabbables.push({
documentOrder: i,
tabIndex: candidateTabindex,
node: candidate
});
}
}
var tabbableNodes = orderedTabbables
.sort(sortOrderedTabbables)
.map(function(a) { return a.node })
.concat(regularTabbables);
return tabbableNodes;
}
tabbable.isTabbable = isTabbable;
tabbable.isFocusable = isFocusable;
function isNodeMatchingSelectorTabbable(node, untouchabilityChecker) {
if (
!isNodeMatchingSelectorFocusable(node, untouchabilityChecker)
|| isNonTabbableRadio(node)
|| getTabindex(node) < 0
) {
return false;
}
return true;
}
function isTabbable(node, untouchabilityChecker) {
if (!node) throw new Error('No node provided');
if (matches.call(node, candidateSelector) === false) return false;
return isNodeMatchingSelectorTabbable(node, untouchabilityChecker);
}
function isNodeMatchingSelectorFocusable(node, untouchabilityChecker) {
untouchabilityChecker = untouchabilityChecker || new UntouchabilityChecker(node.ownerDocument || node);
if (
node.disabled
|| isHiddenInput(node)
|| untouchabilityChecker.isUntouchable(node)
) {
return false;
}
return true;
}
var focusableCandidateSelector = candidateSelectors.concat('iframe').join(',');
function isFocusable(node, untouchabilityChecker) {
if (!node) throw new Error('No node provided');
if (matches.call(node, focusableCandidateSelector) === false) return false;
return isNodeMatchingSelectorFocusable(node, untouchabilityChecker);
}
function getTabindex(node) {
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
if (!isNaN(tabindexAttr)) return tabindexAttr;
// Browsers do not return `tabIndex` correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if (isContentEditable(node)) return 0;
return node.tabIndex;
}
function sortOrderedTabbables(a, b) {
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
}
// Array.prototype.find not available in IE.
function find(list, predicate) {
for (var i = 0, length = list.length; i < length; i++) {
if (predicate(list[i])) return list[i];
}
}
function isContentEditable(node) {
return node.contentEditable === 'true';
}
function isInput(node) {
return node.tagName === 'INPUT';
}
function isHiddenInput(node) {
return isInput(node) && node.type === 'hidden';
}
function isRadio(node) {
return isInput(node) && node.type === 'radio';
}
function isNonTabbableRadio(node) {
return isRadio(node) && !isTabbableRadio(node);
}
function getCheckedRadio(nodes) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked) {
return nodes[i];
}
}
}
function isTabbableRadio(node) {
if (!node.name) return true;
// This won't account for the edge case where you have radio groups with the same
// in separate forms on the same page.
var radioSet = node.ownerDocument.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
var checked = getCheckedRadio(radioSet);
return !checked || checked === node;
}
// An element is "untouchable" if *it or one of its ancestors* has
// `visibility: hidden` or `display: none`.
function UntouchabilityChecker(elementDocument) {
this.doc = elementDocument;
// Node cache must be refreshed on every check, in case
// the content of the element has changed. The cache contains tuples
// mapping nodes to their boolean result.
this.cache = [];
}
// getComputedStyle accurately reflects `visibility: hidden` of ancestors
// but not `display: none`, so we need to recursively check parents.
UntouchabilityChecker.prototype.hasDisplayNone = function hasDisplayNone(node, nodeComputedStyle) {
if (node === this.doc.documentElement) return false;
// Search for a cached result.
var cached = find(this.cache, function(item) {
return item === node;
});
if (cached) return cached[1];
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
var result = false;
if (nodeComputedStyle.display === 'none') {
result = true;
} else if (node.parentNode) {
result = this.hasDisplayNone(node.parentNode);
}
this.cache.push([node, result]);
return result;
}
UntouchabilityChecker.prototype.isUntouchable = function isUntouchable(node) {
if (node === this.doc.documentElement) return false;
var computedStyle = this.doc.defaultView.getComputedStyle(node);
if (this.hasDisplayNone(node, computedStyle)) return true;
return computedStyle.visibility === 'hidden';
}
//module.exports = tabbable;
////////////
var service = {
tabbable: tabbable,
isTabbable: isTabbable,
isFocusable: isFocusable
};
return service;
}
angular.module('umbraco.services').factory('tabbableService', tabbableService);
})();

View File

@@ -194,7 +194,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
$rootScope.emptySection = false;
}
}
}));
//Listen for section state changes
@@ -222,10 +222,21 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
});
}));
evts.push(eventsService.on("editors.languages.languageCreated", function (e, args) {
loadLanguages().then(function (languages) {
$scope.languages = languages;
});
//Emitted when a language is created or an existing one saved/edited
evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) {
console.log('lang event listen args', args);
if(args.isNew){
//A new language has been created - reload languages for tree
loadLanguages().then(function (languages) {
$scope.languages = languages;
});
}
else if(args.language.isDefault){
//A language was saved and was set to be the new default (refresh the tree, so its at the top)
loadLanguages().then(function (languages) {
$scope.languages = languages;
});
}
}));
//This reacts to clicks passed to the body element which emits a global call to close all dialogs

View File

@@ -170,6 +170,7 @@
// Utilities
@import "utilities/layout/_display.less";
@import "utilities/theme/_opacity.less";
@import "utilities/typography/_text-decoration.less";
@import "utilities/typography/_white-space.less";
@import "utilities/_flexbox.less";

View File

@@ -201,7 +201,6 @@ a.umb-variant-switcher__toggle {
.umb-variant-switcher__name {
display: block;
font-weight: bold;
}
.umb-variant-switcher__state {

View File

@@ -1,3 +1,4 @@
.umb-property-editor.-not-clickable {
.umb-property-editor--preview {
pointer-events: none;
}
user-select: none;
}

View File

@@ -0,0 +1,19 @@
/*
Opacity
*/
.o-100 { opacity: 1; }
.o-90 { opacity: 0.9; }
.o-80 { opacity: 0.8; }
.o-70 { opacity: 0.7; }
.o-60 { opacity: 0.6; }
.o-50 { opacity: 0.5; }
.o-40 { opacity: 0.4; }
.o-30 { opacity: 0.3; }
.o-20 { opacity: 0.2; }
.o-10 { opacity: 0.1; }
.o-05 { opacity: 0.05; }
.o-025 { opacity: 0.025; }
.o-0 { opacity: 0; }

View File

@@ -11,7 +11,7 @@
<ins class="umb-language-picker__expand" ng-class="{'icon-navigation-down': !page.languageSelectorIsOpen, 'icon-navigation-up': page.languageSelectorIsOpen}" class="icon-navigation-right">&nbsp;</ins>
</div>
<div class="umb-language-picker__dropdown" ng-if="page.languageSelectorIsOpen">
<a class="umb-language-picker__dropdown-item" ng-class="{'umb-language-picker__dropdown-item--current': language.active}" ng-click="selectLanguage(language)" ng-repeat="language in languages" href="">{{language.name}}</a>
<a class="umb-language-picker__dropdown-item" ng-class="{'umb-language-picker__dropdown-item--current': language.active, 'bold': language.isDefault}" ng-click="selectLanguage(language)" ng-repeat="language in languages" href="">{{language.name}}</a>
</div>
</div>

View File

@@ -8,8 +8,20 @@
</div>
<div class="umb-expansion-panel__content" ng-show="group.open">
<umb-property data-element="property-{{property.alias}}" ng-repeat="property in group.properties track by property.alias" property="property">
<umb-property-editor model="property"></umb-property-editor>
<umb-property
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
property="property"
show-inherit="content.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
inherits-from="defaultVariant.language.name">
<div ng-class="{'o-40 cursor-not-allowed': content.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
<umb-property-editor
model="property"
preview="content.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
</umb-property-editor>
</div>
</umb-property>
</div>

View File

@@ -41,7 +41,7 @@
<umb-dropdown ng-if="vm.dropdownOpen" style="width: 100%; max-height: 250px; overflow-y: scroll; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.active, 'umb-variant-switcher_item--not-allowed': variantIsOpen(variant.language.culture)}" ng-repeat="variant in variants">
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name">{{variant.language.name}}</span>
<span class="umb-variant-switcher__name" ng-class="{'bold': variant.language.isDefault}">{{variant.language.name}}</span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
</a>
<div ng-if="splitViewOpen !== true && !variant.active" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>

View File

@@ -1,3 +1,5 @@
<div class="umb-property-editor db" ng-class="{'-not-clickable': preview}">
<div ng-include="propertyEditorView"></div>
<div class="umb-property-editor db" ng-class="{'umb-property-editor--preview': preview}">
<div disable-tabindex="preview">
<div ng-include="propertyEditorView"></div>
</div>
</div>

View File

@@ -7,7 +7,13 @@
<div class="umb-el-wrap">
<label class="control-label" ng-hide="property.hideLabel" for="{{property.alias}}" ng-attr-title="{{propertyAlias}}">
<small ng-if="showInherit" class="db" style="padding-top: 0; margin-bottom: 5px;">
<localize key="contentTypeEditor_inheritedFrom"></localize> {{inheritsFrom}}
</small>
{{property.label}}
<span ng-if="property.validation.mandatory">
<strong class="umb-control-required">*</strong>
</span>

View File

@@ -158,7 +158,7 @@
<div class="umb-validation-label" ng-message="valServerField">{{propertyTypeForm.groupName.errorMsg}}</div>
<div class="umb-validation-label" ng-message="required"><localize key="contentTypeEditor_requiredLabel"></localize></div>
</div>
</div>
<div class="umb-group-builder__property-meta-description">
@@ -230,11 +230,11 @@
</div>
<ng-form name="propertyEditorPreviewForm" umb-disable-form-validation>
<umb-property-editor
ng-if="property.view !== undefined"
model="property"
preview="true">
</umb-property-editor>
<umb-property-editor
ng-if="property.view !== undefined"
model="property"
preview="true">
</umb-property-editor>
</ng-form>
</div>

View File

@@ -113,11 +113,9 @@
notificationsService.success(value);
});
// emit event when language is created
if($routeParams.create) {
var args = { language: lang };
eventsService.emit("editors.languages.languageCreated", args);
}
// emit event when language is created or updated/saved
var args = { language: lang, isNew: $routeParams.create ? true : false };
eventsService.emit("editors.languages.languageSaved", args);
back();
@@ -129,7 +127,7 @@
});
}
}
function back() {
@@ -145,7 +143,7 @@
}
function toggleDefault() {
// it shouldn't be possible to uncheck the default language
if(vm.initIsDefault) {
return;

View File

@@ -41,7 +41,7 @@
<tr ng-repeat="language in vm.languages" ng-click="vm.editLanguage(language)" style="cursor: pointer;">
<td>
<i class="icon-globe" style="color: #BBBABF; margin-right: 5px;"></i>
<span class="bold">{{ language.name }}</span>
<span ng-class="{'bold': language.isDefault}">{{ language.name }}</span>
</td>
<td>
<span>{{ language.culture }}</span>

View File

@@ -52,6 +52,18 @@ namespace Umbraco.Web.Models.Mapping
variant.Name = source.GetCultureName(x.IsoCode);
}
//Put the default language first in the list & then sort rest by a-z
var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
//Remove the default lang from the list for now
variants.Remove(defaultLang);
//Sort the remaining languages a-z
variants = variants.OrderBy(x => x.Name).ToList();
//Insert the default lang as the first item
variants.Insert(0, defaultLang);
return variants;
}
return result;

View File

@@ -28,7 +28,21 @@ namespace Umbraco.Web.Models.Mapping
{
public IEnumerable<Language> Convert(IEnumerable<ILanguage> source, IEnumerable<Language> destination, ResolutionContext context)
{
return source.Select(x => context.Mapper.Map<ILanguage, Language>(x, null, context)).OrderBy(x => x.Name);
var langs = source.Select(x => context.Mapper.Map<ILanguage, Language>(x, null, context)).ToList();
//Put the default language first in the list & then sort rest by a-z
var defaultLang = langs.SingleOrDefault(x => x.IsDefault);
//Remove the default lang from the list for now
langs.Remove(defaultLang);
//Sort the remaining languages a-z
langs = langs.OrderBy(x => x.Name).ToList();
//Insert the default lang as the first item
langs.Insert(0, defaultLang);
return langs;
}
}
}