Merge remote-tracking branch 'origin/temp8' into temp8-IAction-cleanup

# Conflicts:
#	src/Umbraco.Tests/Composing/ActionCollectionTests.cs
#	src/Umbraco.Web/Models/Trees/MenuItem.cs
#	src/Umbraco.Web/_Legacy/Actions/Action.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionBrowse.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionChangeDocType.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionCopy.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionCreateBlueprintFromContent.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionDelete.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionEmptyTranscan.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionExport.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionImport.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionMove.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionNew.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionNotify.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionNull.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionPackage.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionPackageCreate.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionProtect.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionPublish.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionRePublish.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionRefresh.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionRights.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionRollback.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionSort.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionToPublish.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionTranslate.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionUnPublish.cs
#	src/Umbraco.Web/_Legacy/Actions/ActionUpdate.cs
#	src/Umbraco.Web/_Legacy/Actions/ContextMenuSeperator.cs
#	src/Umbraco.Web/_Legacy/Actions/IAction.cs
#	src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionDeleteRelationType.cs
#	src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/TreeMenu/ActionNewRelationType.cs
This commit is contained in:
Shannon
2018-10-29 23:35:24 +11:00
41 changed files with 552 additions and 85 deletions
@@ -28,11 +28,11 @@ namespace Umbraco.Core.Models
/// Initializes a new instance of the <see cref="ContentCultureInfos"/> class.
/// </summary>
/// <remarks>Used for cloning, without change tracking.</remarks>
private ContentCultureInfos(string culture, string name, DateTime date)
: this(culture)
internal ContentCultureInfos(ContentCultureInfos other)
: this(other.Culture)
{
_name = name;
_date = date;
_name = other.Name;
_date = other.Date;
}
/// <summary>
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models
/// <inheritdoc />
public object DeepClone()
{
return new ContentCultureInfos(Culture, Name, Date);
return new ContentCultureInfos(this);
}
/// <inheritdoc />
@@ -24,8 +24,12 @@ namespace Umbraco.Core.Models
public ContentCultureInfosCollection(IEnumerable<ContentCultureInfos> items)
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{
// make sure to add *copies* and not the original items,
// as items can be modified by AddOrUpdate, and therefore
// the new collection would be impacted by changes made
// to the old collection
foreach (var item in items)
Add(item);
Add(new ContentCultureInfos(item));
}
/// <summary>
@@ -368,6 +368,11 @@ namespace Umbraco.Core.Services
/// <para>A publishing document is a document with values that are being published, i.e.
/// that have been published or cleared via <see cref="IContent.PublishCulture"/> and
/// <see cref="IContent.UnpublishCulture"/>.</para>
/// <para>When one needs to publish or unpublish a single culture, or all cultures, using <see cref="SaveAndPublish"/>
/// and <see cref="Unpublish"/> is the way to go. But if one needs to, say, publish two cultures and unpublish a third
/// one, in one go, then one needs to invoke <see cref="IContent.PublishCulture"/> and <see cref="IContent.UnpublishCulture"/>
/// on the content itself - this prepares the content, but does not commit anything - and then, invoke
/// <see cref="SavePublishing"/> to actually commit the changes to the database.</para>
/// <para>The document is *always* saved, even when publishing fails.</para>
/// </remarks>
PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true);
@@ -375,11 +380,30 @@ namespace Umbraco.Core.Services
/// <summary>
/// Saves and publishes a document branch.
/// </summary>
/// <remarks>
/// <para>Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
/// that one culture, see the other overload of this method.</para>
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
/// published.</para>
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0);
/// <summary>
/// Saves and publishes a document branch.
/// </summary>
/// <remarks>
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
/// published.</para>
/// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
/// values to publish (else there is no need to publish it). If one wants to publish only a selection of
/// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
/// cultures may trigger an unwanted republish.</para>
/// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
/// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
/// whether the cultures could be published.</para>
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, Func<IContent, bool> editing, Func<IContent, bool> publishCultures, int userId = 0);
/// <summary>
@@ -1272,12 +1272,49 @@ namespace Umbraco.Core.Services.Implement
bool IsEditing(IContent c, string l)
=> c.PublishName != c.Name ||
c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture == l).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
c.PublishedCultures.Where(x => x.InvariantEquals(l)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture.InvariantEquals(l)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
return SaveAndPublishBranch(content, force, document => IsEditing(document, culture), document => document.PublishCulture(culture), userId);
}
// fixme - make this public once we know it works + document
private IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0)
{
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
// and not to == them, else we would be comparing references, and that is a bad thing
cultures = cultures ?? Array.Empty<string>();
// determines whether the document is edited, and thus needs to be published,
// for the specified cultures (it may be edited for other cultures and that
// should not trigger a publish).
bool IsEdited(IContent c)
{
if (cultures.Length == 0)
{
// nothing = everything
return c.PublishName != c.Name ||
c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
c.Properties.Any(x => x.Values.Any(y => !y.EditedValue.Equals(y.PublishedValue)));
}
return c.PublishName != c.Name ||
c.PublishedCultures.Where(x => cultures.Contains(x, StringComparer.InvariantCultureIgnoreCase)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
c.Properties.Any(x => x.Values.Where(y => cultures.Contains(y.Culture, StringComparer.InvariantCultureIgnoreCase)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
}
// publish the specified cultures
bool PublishCultures(IContent c)
{
return cultures.Length == 0
? c.PublishCulture() // nothing = everything
: cultures.All(c.PublishCulture);
}
return SaveAndPublishBranch(content, force, IsEdited, PublishCultures, userId);
}
/// <inheritdoc />
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
Func<IContent, bool> editing, Func<IContent, bool> publishCultures, int userId = 0)
+8 -1
View File
@@ -203,7 +203,14 @@ namespace Umbraco.Tests.Persistence
Assert.IsNotNull(e1);
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
Assert.IsNull(e2);
// the assertion below depends on timing conditions - on a fast enough environment,
// thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow
// environment (CI) both threads can end up dying due to deadlock - so, cannot test
// that e2 is null - but if it's not, can test that it's a timeout
//
//Assert.IsNull(e2);
if (e2 != null)
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
}
private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception)
@@ -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 {
@@ -51,6 +51,15 @@
text-decoration: none;
}
.umb-action {
&.-opens-dialog {
.menu-label:after {
// adds an ellipsis (...) after the menu label for actions that open a dialog
content: '\2026';
}
}
}
.umb-actions-child {
.umb-action {
@@ -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; }
@@ -16,24 +16,23 @@
<umb-box-content>
<div class="umb-insert-code-boxes">
<div ng-if="model.allowedTypes.umbracoField" class="umb-insert-code-box" ng-click="vm.openPageFieldOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertPageField" /></div>
<div class="umb-insert-code-box__title"><localize key="template_insertPageField" />...</div>
<div class="umb-insert-code-box__description"><localize key="template_insertPageFieldDesc" /></div>
</div>
<div ng-if="model.allowedTypes.partial" class="umb-insert-code-box" ng-click="vm.openPartialOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertPartialView" /></div>
<div class="umb-insert-code-box__title"><localize key="template_insertPartialView" />...</div>
<div class="umb-insert-code-box__description">
<localize key="template_insertPartialViewDesc" />
</div>
</div>
<div ng-if="model.allowedTypes.macro" class="umb-insert-code-box" ng-click="vm.openMacroPicker()">
<div class="umb-insert-code-box__title"><localize key="template_insertMacro" /></div>
<div class="umb-insert-code-box__title"><localize key="template_insertMacro" />...</div>
<div class="umb-insert-code-box__description">
<localize key="template_insertMacroDesc" />
</div>
</div>
<div ng-if="model.allowedTypes.dictionary" class="umb-insert-code-box" ng-click="vm.openDictionaryItemOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertDictionaryItem" /></div>
<div class="umb-insert-code-box__title"><localize key="template_insertDictionaryItem" />...</div>
<div class="umb-insert-code-box__description"><localize key="template_insertDictionaryItemDesc" /></div>
</div>
</div>
@@ -5,7 +5,7 @@
<div class='umb-modalcolumn-body'>
<ul class="umb-actions">
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.seperator}" ng-repeat="action in menuActions">
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.seperator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
<a class="umb-action-link" prevent-default>
<i class="icon icon-{{action.cssclass}}"></i>
<span class="menu-label">{{action.name}}</span>
@@ -13,4 +13,4 @@
</li>
</ul>
</div>
</div>
</div>
@@ -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>
@@ -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>
@@ -72,7 +72,7 @@
<umb-editor-menu
data-element="editor-actions"
current-node="menu.currentNode"
current-section="{{menu.currentSection}}">
current-section="{{menu.currentNode.section}}">
</umb-editor-menu>
</div>
@@ -8,7 +8,7 @@
<!-- actions -->
<ul class="dropdown-menu umb-actions" role="menu" aria-labelledby="dLabel">
<li class="umb-action" ng-class="{sep:action.seperator}" ng-repeat="action in actions">
<li class="umb-action" ng-class="{'sep':action.seperatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
<!-- How does this reference executeMenuItem() i really don't think that this is very clear -->
<a prevent-default
@@ -17,7 +17,6 @@
<i class="icon icon-{{action.cssclass}}"></i>
<span class="menu-label">{{action.name}}</span>
</a>
</li>
</ul>
</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>
@@ -13,7 +13,8 @@
label-key="contentTypeEditor_compositions"
icon="icon-merge"
action="openCompositionsDialog()"
size="xs">
size="xs"
add-ellipsis="true">
</umb-button>
<umb-button
@@ -157,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">
@@ -229,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>
@@ -16,7 +16,7 @@
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label">
<localize key="create_newFolder">New folder</localize>
<localize key="create_newFolder">New folder</localize>...
</span>
</a>
</li>
@@ -25,7 +25,7 @@
<a href="" ng-click="showCreateDocTypeCollection()" class="umb-action-link">
<i class="large icon icon-thumbnail-list"></i>
<span class="menu-label">
Document Type Collection
Document Type Collection...
<!-- <localize key="content_documentType_collection">Document Type Collection</localize>-->
</span>
</a>
@@ -33,7 +33,7 @@
<li data-element="action-folder" ng-if="model.allowCreateFolder" class="umb-action">
<a href="" ng-click="showCreateFolder()" class="umb-action-link">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</li>
</ul>
@@ -48,7 +48,8 @@
"shortcuts_toggleListView",
"shortcuts_toggleAllowAsRoot",
"shortcuts_addChildNode",
"shortcuts_addTemplate"
"shortcuts_addTemplate",
"shortcuts_toggleAllowCultureVariants"
];
onInit();
@@ -81,6 +82,7 @@
vm.labels.allowAsRoot = values[11];
vm.labels.addChildNode = values[12];
vm.labels.addTemplate = values[13];
vm.labels.allowCultureVariants = values[14];
var buttons = [
{
@@ -161,6 +163,10 @@
{
"description": vm.labels.addChildNode,
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
},
{
"description": vm.labels.allowCultureVariants,
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "v" }]
}
]
},
@@ -23,7 +23,8 @@
vm.addChild = addChild;
vm.removeChild = removeChild;
vm.toggle = toggle;
vm.toggleAllowAsRoot = toggleAllowAsRoot;
vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
/* ---------- INIT ---------- */
@@ -86,7 +87,7 @@
/**
* Toggle the $scope.model.allowAsRoot value to either true or false
*/
function toggle(){
function toggleAllowAsRoot(){
if($scope.model.allowAsRoot){
$scope.model.allowAsRoot = false;
return;
@@ -95,6 +96,15 @@
$scope.model.allowAsRoot = true;
}
function toggleAllowCultureVariants() {
if ($scope.model.allowCultureVariant) {
$scope.model.allowCultureVariant = false;
return;
}
$scope.model.allowCultureVariant = true;
}
}
angular.module("umbraco").controller("Umbraco.Editors.DocumentType.PermissionsController", PermissionsController);
@@ -10,11 +10,10 @@
<small><localize key="contentTypeEditor_allowAsRootDescription" /></small>
</div>
<div class="sub-view-column-right">
<umb-toggle
data-element="permissions-allow-as-root"
checked="model.allowAsRoot"
on-click="vm.toggle()"
hotkey="alt+shift+r">
<umb-toggle data-element="permissions-allow-as-root"
checked="model.allowAsRoot"
on-click="vm.toggleAllowAsRoot()"
hotkey="alt+shift+r">
</umb-toggle>
</div>
@@ -28,14 +27,13 @@
</div>
<div class="sub-view-column-right">
<umb-child-selector
selected-children="vm.selectedChildren"
available-children="vm.contentTypes"
parent-name="model.name"
parent-icon="model.icon"
parent-id="model.id"
on-add="vm.addChild"
on-remove="vm.removeChild">
<umb-child-selector selected-children="vm.selectedChildren"
available-children="vm.contentTypes"
parent-name="model.name"
parent-icon="model.icon"
parent-id="model.id"
on-add="vm.addChild"
on-remove="vm.removeChild">
</umb-child-selector>
</div>
@@ -44,18 +42,20 @@
<div class="sub-view-columns">
<div class="sub-view-column-left">
<h5>Content Type Variation</h5>
<small>Define the rules for how this content type's properties can be varied</small>
</div>
<div class="sub-view-column-right">
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.allowCultureVariant" />
Allow varying by Culture
</label>
<h5><localize key="contentTypeEditor_variantsHeading" /></h5>
<small><localize key="contentTypeEditor_variantsDescription" /></small>
</div>
<div class="sub-view-column-right">
<umb-toggle data-element="permissions-allow-culture-variant"
checked="model.allowCultureVariant"
on-click="vm.toggleAllowCultureVariants()"
hotkey="alt+shift+v">
</umb-toggle>
</div>
</div>
</umb-box-content>
</umb-box>
</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>
@@ -16,7 +16,7 @@
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</li>
</ul>
@@ -25,13 +25,13 @@
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewMacroFromSnippet">>New partial view macro from snippet</localize></span>
<span class="menu-label"><localize key="create_newPartialViewMacroFromSnippet">>New partial view macro from snippet</localize>...</span>
</a>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</li>
</ul>
@@ -18,13 +18,13 @@
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewFromSnippet">New partial view from snippet</localize></span>
<span class="menu-label"><localize key="create_newPartialViewFromSnippet">New partial view from snippet</localize>...</span>
</a>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</li>
</ul>
@@ -13,7 +13,7 @@
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</li>
</ul>
@@ -681,6 +681,7 @@
<key alias="moveLineDown">Move Lines Down</key>
<key alias="generalHeader">General</key>
<key alias="editorHeader">Editor</key>
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
</area>
<area alias="graphicheadline">
<key alias="backgroundcolor">Background colour</key>
@@ -1484,6 +1485,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="tabHasNoSortOrder">tab has no sort order</key>
<key alias="compositionUsageHeading">Where is this composition used?</key>
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
<key alias="variantsHeading">Allow varying by culture</key>
<key alias="variantsDescription">Allow editors to create content of this type in different languages</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
</area>
<area alias="modelsBuilder">
<key alias="buildingModels">Building models</key>
@@ -701,6 +701,7 @@
<key alias="moveLineDown">Move Lines Down</key>
<key alias="generalHeader">General</key>
<key alias="editorHeader">Editor</key>
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
</area>
<area alias="graphicheadline">
<key alias="backgroundcolor">Background color</key>
@@ -1506,6 +1507,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="tabHasNoSortOrder">tab has no sort order</key>
<key alias="compositionUsageHeading">Where is this composition used?</key>
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
<key alias="variantsHeading">Allow varying by culture</key>
<key alias="variantsDescription">Allow editors to create content of this type in different languages</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
</area>
<area alias="languages">
<key alias="addLanguage">Add language</key>
@@ -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;
}
}
}
+5
View File
@@ -52,6 +52,7 @@ namespace Umbraco.Web.Models.Trees
SeperatorBefore = false;
Icon = action.Icon;
Action = action;
OpensDialog = legacyMenu.OpensDialog;
}
#endregion
@@ -85,6 +86,10 @@ namespace Umbraco.Web.Models.Trees
[DataMember(Name = "cssclass")]
public string Icon { get; set; }
[DataMember(Name = "opensDialog")]
public bool OpensDialog { get; set; }
#endregion
#region Constants