Merge pull request #3432 from umbraco/temp8-153-invariant-properties
Temp8 153 invariant properties
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -7,7 +7,9 @@ angular.module("umbraco.directives")
|
||||
.directive('umbProperty', function (umbPropEditorHelper, userService) {
|
||||
return {
|
||||
scope: {
|
||||
property: "="
|
||||
property: "=",
|
||||
showInherit: "<",
|
||||
inheritsFrom: "<"
|
||||
},
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
|
||||
@@ -12,7 +12,7 @@ function umbPropEditor(umbPropEditorHelper) {
|
||||
scope: {
|
||||
model: "=",
|
||||
isPreValue: "@",
|
||||
preview: "@"
|
||||
preview: "<"
|
||||
},
|
||||
|
||||
require: "^^form",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -201,7 +201,6 @@ a.umb-variant-switcher__toggle {
|
||||
|
||||
.umb-variant-switcher__name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.umb-variant-switcher__state {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.umb-property-editor.-not-clickable {
|
||||
.umb-property-editor--preview {
|
||||
pointer-events: none;
|
||||
}
|
||||
user-select: none;
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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"> </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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user