Merge branch 'v8/dev' into v8/contrib

This commit is contained in:
Sebastiaan Janssen
2020-05-11 14:22:07 +02:00
105 changed files with 2667 additions and 1681 deletions
@@ -134,15 +134,16 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
var isInvariantValue = propertyValue.Culture == null;
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
var isInvariantValue = propertyValue.Culture == null && propertyValue.Segment == null;
var isCultureValue = propertyValue.Culture != null;
var isSegmentValue = propertyValue.Segment != null;
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
if ((propertyValue.PublishedValue != null || isSegmentValue) && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
// deal with edit value
if (propertyValue.EditedValue != null)
if (propertyValue.EditedValue != null || isSegmentValue)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
@@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property
Assert.AreEqual("_Properties.headerImage.invariant", ms.Keys.First());
Assert.AreEqual("_Properties.headerImage.invariant.null", ms.Keys.First());
}
[Test]
@@ -73,9 +73,57 @@ namespace Umbraco.Tests.Web
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //invariant property
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //variant property
Assert.AreEqual("_Properties.headerImage.en-US", ms.Keys.First());
Assert.AreEqual("_Properties.headerImage.en-US.null", ms.Keys.First());
}
[Test]
public void Add_Invariant_Segment_Property_Error()
{
var ms = new ModelStateDictionary();
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); //invariant/segment property
Assert.AreEqual("_Properties.headerImage.invariant.mySegment", ms.Keys.First());
}
[Test]
public void Add_Variant_Segment_Property_Error()
{
var ms = new ModelStateDictionary();
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); //variant/segment property
Assert.AreEqual("_Properties.headerImage.en-US.mySegment", ms.Keys.First());
}
[Test]
public void Add_Invariant_Segment_Field_Property_Error()
{
var ms = new ModelStateDictionary();
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); //invariant/segment property
Assert.AreEqual("_Properties.headerImage.invariant.mySegment.myField", ms.Keys.First());
}
[Test]
public void Add_Variant_Segment_Field_Property_Error()
{
var ms = new ModelStateDictionary();
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); //variant/segment property
Assert.AreEqual("_Properties.headerImage.en-US.mySegment.myField", ms.Keys.First());
}
}
}
@@ -31,7 +31,7 @@
$scope.page.hideActionsMenu = infiniteMode ? true : false;
$scope.page.hideChangeVariant = false;
$scope.allowOpen = true;
$scope.app = null;
$scope.activeApp = null;
//initializes any watches
function startWatches(content) {
@@ -74,31 +74,23 @@
var isAppPresent = false;
// on first init, we dont have any apps. but if we are re-initializing, we do, but ...
if ($scope.app) {
if ($scope.activeApp) {
// lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
_.forEach(content.apps, function (app) {
if (app === $scope.app) {
if (app.alias === $scope.activeApp.alias) {
isAppPresent = true;
$scope.appChanged(app);
}
});
// if we did reload our DocType, but still have the same app we will try to find it by the alias.
if (isAppPresent === false) {
_.forEach(content.apps, function (app) {
if (app.alias === $scope.app.alias) {
isAppPresent = true;
app.active = true;
$scope.appChanged(app);
}
});
// active app does not exist anymore.
$scope.activeApp = null;
}
}
// if we still dont have a app, lets show the first one:
if (isAppPresent === false && content.apps.length) {
content.apps[0].active = true;
if ($scope.activeApp === null && content.apps.length) {
$scope.appChanged(content.apps[0]);
}
// otherwise make sure the save options are up to date with the current content state
@@ -151,8 +143,8 @@
}
/** Returns true if the content item varies by culture */
function isContentCultureVariant() {
return $scope.content.variants.length > 1;
function hasVariants(content) {
return content.variants.length > 1;
}
function reload() {
@@ -215,6 +207,13 @@
}));
}
function appendRuntimeData() {
$scope.content.variants.forEach((variant) => {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
});
}
/**
* This does the content loading and initializes everything, called on first load
*/
@@ -226,6 +225,7 @@
$scope.content = data;
appendRuntimeData();
init();
syncTreeNode($scope.content, $scope.content.path, true);
@@ -251,6 +251,7 @@
$scope.content = data;
appendRuntimeData();
init();
startWatches($scope.content);
@@ -274,7 +275,7 @@
$scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info";
// only create the save/publish/preview buttons if the
// content app is "Conent"
if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo" && $scope.app.alias !== "umbListView") {
if ($scope.activeApp && $scope.activeApp.alias !== "umbContent" && $scope.activeApp.alias !== "umbInfo" && $scope.activeApp.alias !== "umbListView") {
$scope.defaultButton = null;
$scope.subButtons = null;
$scope.page.showSaveButton = false;
@@ -589,7 +590,7 @@
$scope.sendToPublish = function () {
clearNotifications($scope.content);
if (isContentCultureVariant()) {
if (hasVariants($scope.content)) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
@@ -649,7 +650,7 @@
$scope.saveAndPublish = function () {
clearNotifications($scope.content);
if (isContentCultureVariant()) {
if (hasVariants($scope.content)) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
var dialog = {
@@ -711,7 +712,7 @@
$scope.save = function () {
clearNotifications($scope.content);
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (isContentCultureVariant()) {
if (hasVariants($scope.content)) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) {
@@ -776,7 +777,7 @@
clearNotifications($scope.content);
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "schedule" })) {
if (!isContentCultureVariant()) {
if (!hasVariants($scope.content)) {
//ensure the flags are set
$scope.content.variants[0].save = true;
}
@@ -813,7 +814,7 @@
}, function (err) {
clearDirtyState($scope.content.variants);
//if this is invariant, show the notification errors, else they'll be shown inline with the variant
if (!isContentCultureVariant()) {
if (!hasVariants($scope.content)) {
formHelper.showNotifications(err.data);
}
model.submitButtonState = "error";
@@ -840,7 +841,7 @@
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) {
if (!isContentCultureVariant()) {
if (!hasVariants($scope.content)) {
//ensure the flags are set
$scope.content.variants[0].save = true;
$scope.content.variants[0].publish = true;
@@ -873,7 +874,7 @@
}, function (err) {
clearDirtyState($scope.content.variants);
//if this is invariant, show the notification errors, else they'll be shown inline with the variant
if (!isContentCultureVariant()) {
if (!hasVariants($scope.content)) {
formHelper.showNotifications(err.data);
}
model.submitButtonState = "error";
@@ -963,11 +964,18 @@
* Call back when a content app changes
* @param {any} app
*/
$scope.appChanged = function (app) {
$scope.appChanged = function (activeApp) {
$scope.app = app;
$scope.activeApp = activeApp;
_.forEach($scope.content.apps, function (app) {
app.active = false;
if (app.alias === $scope.activeApp.alias) {
app.active = true;
}
});
$scope.$broadcast("editors.apps.appChanged", { app: app });
$scope.$broadcast("editors.apps.appChanged", { app: activeApp });
createButtons($scope.content);
@@ -1029,6 +1037,7 @@
getMethod: "&",
getScaffoldMethod: "&?",
culture: "=?",
segment: "=?",
infiniteModel: "=?"
}
};
@@ -323,7 +323,7 @@
// find the urls for the currently selected language
if (scope.node.variants.length > 1) {
// nodes with variants
scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture);
scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture));
} else {
// invariant nodes
scope.currentUrls = scope.node.urls;
@@ -4,7 +4,7 @@
/** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */
function tabbedContentDirective($timeout) {
function link($scope, $element, $attrs) {
function link($scope, $element) {
var appRootNode = $element[0];
@@ -115,21 +115,18 @@
}
function controller($scope, $element, $attrs) {
function controller($scope) {
//expose the property/methods for other directives to use
this.content = $scope.content;
this.activeVariant = _.find(this.content.variants, variant => {
return variant.active;
});
$scope.activeVariant = this.activeVariant;
$scope.defaultVariant = _.find(this.content.variants, variant => {
return variant.language.isDefault;
});
if($scope.contentNodeModel) {
$scope.defaultVariant = _.find($scope.contentNodeModel.variants, variant => {
// defaultVariant will never have segment. Wether it has a language or not depends on the setup.
return !variant.segment && ((variant.language && variant.language.isDefault) || (!variant.language));
});
}
$scope.unlockInvariantValue = function(property) {
property.unlockInvariantValue = !property.unlockInvariantValue;
};
@@ -141,6 +138,24 @@
}
}
);
$scope.propertyEditorDisabled = function (property) {
if (property.unlockInvariantValue) {
return false;
}
var contentLanguage = $scope.content.language;
var canEditCulture = !contentLanguage ||
// If the property culture equals the content culture it can be edited
property.culture === contentLanguage.culture ||
// A culture-invariant property can only be edited by the default language variant
(property.culture == null && contentLanguage.isDefault);
var canEditSegment = property.segment === $scope.content.segment;
return !canEditCulture || !canEditSegment;
}
}
var directive = {
@@ -150,7 +165,8 @@
controller: controller,
link: link,
scope: {
content: "="
content: "=", // in this context the content is the variant model.
contentNodeModel: "=?" //contentNodeModel is the content model for the node,
}
};
@@ -12,7 +12,6 @@
editor: "<",
editorIndex: "<",
editorCount: "<",
openVariants: "<",
onCloseSplitView: "&",
onSelectVariant: "&",
onOpenSplitView: "&",
@@ -25,7 +24,7 @@
controller: umbVariantContentController
};
function umbVariantContentController($scope, $element, $location) {
function umbVariantContentController($scope) {
var unsubscribe = [];
@@ -42,13 +41,14 @@
vm.showBackButton = showBackButton;
function onInit() {
// disable the name field if the active content app is not "Content"
vm.nameDisabled = false;
angular.forEach(vm.editor.content.apps, function(app){
if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") {
vm.nameDisabled = true;
}
});
// Make copy of apps, so we can have a variant specific model for the App. (needed for validation etc.)
vm.editor.variantApps = Utilities.copy(vm.content.apps);
var activeApp = vm.content.apps.find((app) => app.active);
onAppChanged(activeApp);
}
function showBackButton() {
@@ -94,14 +94,23 @@
}
$scope.$on("editors.apps.appChanged", function($event, $args) {
var app = $args.app;
// disable the name field if the active content app is not "Content" or "Info"
vm.nameDisabled = false;
if(app && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") {
vm.nameDisabled = true;
}
var activeApp = $args.app;
// sync varaintApps active with new active.
_.forEach(vm.editor.variantApps, function (app) {
app.active = (app.alias === activeApp.alias);
});
onAppChanged(activeApp);
});
function onAppChanged(activeApp) {
// disable the name field if the active content app is not "Content" or "Info"
vm.nameDisabled = (activeApp && activeApp.alias !== "umbContent" && activeApp.alias !== "umbInfo" && activeApp.alias !== "umbListView");
}
/**
* Used to proxy a callback
* @param {any} item
@@ -8,8 +8,9 @@
templateUrl: 'views/components/content/umb-variant-content-editors.html',
bindings: {
page: "<",
content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant
content: "<",
culture: "<",
segment: "<",
onSelectApp: "&?",
onSelectAppAnchor: "&?",
onBack: "&?",
@@ -19,12 +20,11 @@
controller: umbVariantContentEditorsController
};
function umbVariantContentEditorsController($scope, $location, $timeout) {
function umbVariantContentEditorsController($scope, $location, contentEditingHelper) {
var prevContentDateUpdated = null;
var vm = this;
var activeAppAlias = null;
vm.$onInit = onInit;
vm.$onChanges = onChanges;
@@ -39,13 +39,11 @@
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
vm.editors = [];
//Used to track the open variants across the split views
vm.openVariants = [];
/** Called when the component initializes */
function onInit() {
prevContentDateUpdated = Utilities.copy(vm.content.updateDate);
setActiveCulture();
setActiveVariant();
}
/** Called when the component has linked all elements, this is when the form controller is available */
@@ -60,14 +58,16 @@
function onChanges(changes) {
if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) {
setActiveCulture();
setActiveVariant();
} else if (changes.segment && !changes.segment.isFirstChange() && changes.segment.currentValue !== changes.segment.previousValue) {
setActiveVariant();
}
}
/** Allows us to deep watch whatever we want - executes on every digest cycle */
function doCheck() {
if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) {
setActiveCulture();
setActiveVariant();
prevContentDateUpdated = Utilities.copy(vm.content.updateDate);
}
}
@@ -79,37 +79,32 @@
}
/**
* Set the active variant based on the current culture (query string)
* Set the active variant based on the current culture or segment (query string)
*/
function setActiveCulture() {
function setActiveVariant() {
// set the active variant
var activeVariant = null;
_.each(vm.content.variants, function (v) {
if (v.language && v.language.culture === vm.culture) {
v.active = true;
if ((vm.culture === "invariant" || v.language && v.language.culture === vm.culture) && v.segment === vm.segment) {
activeVariant = v;
}
else {
v.active = false;
}
});
if (!activeVariant) {
// Set the first variant to active if we can't find it.
// If the content item is invariant, then only one item exists in the array.
vm.content.variants[0].active = true;
activeVariant = vm.content.variants[0];
}
insertVariantEditor(0, initVariant(activeVariant, 0));
insertVariantEditor(0, activeVariant);
if (vm.editors.length > 1) {
//now re-sync any other editor content (i.e. if split view is open)
for (var s = 1; s < vm.editors.length; s++) {
//get the variant from the scope model
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === vm.editors[s].content.language.culture;
return (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment;
});
vm.editors[s].content = initVariant(variant, s);
vm.editors[s].content = variant;
}
}
@@ -122,157 +117,84 @@
*/
function insertVariantEditor(index, variant) {
if (vm.editors[index]) {
if (vm.editors[index].content === variant) {
// This variant is already the content of the editor in this index.
return;
}
vm.editors[index].content.active = false;
}
variant.active = true;
var variantCulture = variant.language ? variant.language.culture : "invariant";
var variantSegment = variant.segment;
//check if the culture at the index is the same, if it's null an editor will be added
var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture;
var currentCulture = index < vm.editors.length ? vm.editors[index].culture : null;
var currentSegment = index < vm.editors.length ? vm.editors[index].segment : null;
// if index not already exists or if the culture or segment isnt identical then we do a replacement.
if (index >= vm.editors.length || currentCulture !== variantCulture || currentSegment !== variantSegment) {
if (currentCulture !== variantCulture) {
//Not the current culture which means we need to modify the array.
//Not the current culture or segment which means we need to modify the array.
//NOTE: It is not good enough to just replace the `content` object at a given index in the array
// since that would mean that directives are not re-initialized.
vm.editors.splice(index, 1, {
compositeId: variant.compositeId,
content: variant,
//used for "track-by" ng-repeat
culture: variantCulture
culture: variantCulture,
segment: variantSegment
});
}
else {
//replace the editor for the same culture
//replace the content of the editor, since the culture and segment is the same.
vm.editors[index].content = variant;
}
}
function initVariant(variant, editorIndex) {
//The model that is assigned to the editor contains the current content variant along
//with a copy of the contentApps. This is required because each editor renders it's own
//header and content apps section and the content apps contains the view for editing content itself
//and we need to assign a view model to the subView so that it is scoped to the current
//editor so that split views work.
//copy the apps from the main model if not assigned yet to the variant
if (!variant.apps) {
variant.apps = Utilities.copy(vm.content.apps);
}
//if this is a variant has a culture/language than we need to assign the language drop down info
if (variant.language) {
//if the variant list that defines the header drop down isn't assigned to the variant then assign it now
if (!variant.variants) {
variant.variants = _.map(vm.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
});
}
else {
//merge the scope variants on top of the header variants collection (handy when needing to refresh)
angular.extend(variant.variants,
_.map(vm.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
}));
}
//ensure the current culture is set as the active one
for (var i = 0; i < variant.variants.length; i++) {
if (variant.variants[i].language.culture === variant.language.culture) {
variant.variants[i].active = true;
}
else {
variant.variants[i].active = false;
}
}
// keep track of the open variants across the different split views
// push the first variant then update the variant index based on the editor index
if (vm.openVariants && vm.openVariants.length === 0) {
vm.openVariants.push(variant.language.culture);
} else {
vm.openVariants[editorIndex] = variant.language.culture;
}
}
//then assign the variant to a view model to the content app
var contentApp = _.find(variant.apps, function (a) {
return a.alias === "umbContent";
});
if (contentApp) {
//The view model for the content app is simply the index of the variant being edited
var variantIndex = vm.content.variants.indexOf(variant);
contentApp.viewModel = variantIndex;
}
// make sure the same app it set to active in the new variant
if (activeAppAlias) {
angular.forEach(variant.apps, function (app) {
app.active = false;
if (app.alias === activeAppAlias) {
app.active = true;
}
});
}
return variant;
}
/**
* Adds a new editor to the editors array to show content in a split view
* @param {any} selectedVariant
*/
function openSplitView(selectedVariant) {
var selectedCulture = selectedVariant.language.culture;
// enforce content contentApp in splitview.
var contentApp = vm.content.apps.find((app) => app.alias === "umbContent");
if(contentApp) {
selectApp(contentApp);
}
insertVariantEditor(vm.editors.length, selectedVariant);
splitViewChanged();
}
$scope.$on("editors.content.splitViewRequest", function(event, args) {requestSplitView(args);});
vm.requestSplitView = requestSplitView;
function requestSplitView(args) {
var culture = args.culture;
var segment = args.segment;
//Find the whole variant model based on the culture that was chosen
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === selectedCulture;
return (!v.language || v.language.culture === culture) && v.segment === segment;
});
insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length));
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
//to the "editors" we need to update this across all editors
for (var e = 0; e < vm.editors.length; e++) {
var editor = vm.editors[e];
for (var i = 0; i < editor.content.apps.length; i++) {
var app = editor.content.apps[i];
if (app.alias === "umbContent") {
app.active = true;
// tell the world that the app has changed (but do it only once)
if (e === 0) {
selectApp(app);
}
}
else {
app.active = false;
}
}
if (variant != null) {
openSplitView(variant);
}
// TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
editor.collapsed = true;
editor.loading = true;
$timeout(function () {
editor.collapsed = false;
editor.loading = false;
splitViewChanged();
}, 100);
}
/** Closes the split view */
function closeSplitView(editorIndex) {
// TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
var editor = vm.editors[editorIndex];
editor.loading = true;
editor.collapsed = true;
$timeout(function () {
vm.editors.splice(editorIndex, 1);
//remove variant from open variants
vm.openVariants.splice(editorIndex, 1);
//update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant)
$location.search("cculture", vm.openVariants[0]);
splitViewChanged();
}, 400);
vm.editors.splice(editorIndex, 1);
editor.content.active = false;
//update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant)
$location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment});
splitViewChanged();
}
/**
@@ -282,38 +204,26 @@
*/
function selectVariant(variant, editorIndex) {
// prevent variants already open in a split view to be opened
if (vm.openVariants.indexOf(variant.language.culture) !== -1) {
var variantCulture = variant.language ? variant.language.culture : "invariant";
var variantSegment = variant.segment || null;
// Check if we already have this editor open, if so, do nothing.
if (vm.editors.find((editor) => (!editor.content.language || editor.content.language.culture === variantCulture) && editor.content.segment === variantSegment)) {
return;
}
//if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part
//of a 2nd split view editor then update the model directly.
if (editorIndex === 0) {
//If we've made it this far, then update the query string.
//The editor will respond to this query string changing.
$location.search("cculture", variant.language.culture);
$location.search("cculture", variantCulture).search("csegment", variantSegment);
}
else {
//Update the 'active' variant for this editor
var editor = vm.editors[editorIndex];
//set all variant drop down items as inactive for this editor and then set the selected one as active
for (var i = 0; i < editor.content.variants.length; i++) {
editor.content.variants[i].active = false;
}
variant.active = true;
//get the variant content model and initialize the editor with that
var contentVariant = _.find(vm.content.variants,
function (v) {
return v.language.culture === variant.language.culture;
});
editor.content = initVariant(contentVariant, editorIndex);
//update the editors collection
insertVariantEditor(editorIndex, contentVariant);
insertVariantEditor(editorIndex, variant);
}
}
@@ -322,25 +232,17 @@
* @param {any} app This is the model of the selected app
*/
function selectApp(app) {
if (vm.onSelectApp) {
vm.onSelectApp({ "app": app });
if(vm.onSelectApp) {
vm.onSelectApp({"app": app});
}
}
function selectAppAnchor(app, anchor) {
if (vm.onSelectAppAnchor) {
vm.onSelectAppAnchor({ "app": app, "anchor": anchor });
if(vm.onSelectAppAnchor) {
vm.onSelectAppAnchor({"app": app, "anchor": anchor});
}
}
$scope.$on("editors.apps.appChanged", function ($event, $args) {
var app = $args.app;
if (app && app.alias) {
activeAppAlias = app.alias;
}
});
}
angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors);
@@ -73,7 +73,7 @@ Use this directive to generate a list of breadcrumbs.
var path = scope.pathTo(ancestor);
$location.path(path);
navigationService.clearSearch(["cculture"]);
navigationService.clearSearch(["cculture", "csegment"]);
}
scope.pathTo = function (ancestor) {
@@ -2,11 +2,10 @@
'use strict';
function EditorContentHeader(serverValidationManager, localizationService, editorState) {
function link(scope) {
function link(scope, el, attr, ctrl) {
var unsubscribe = [];
if (!scope.serverValidationNameField) {
scope.serverValidationNameField = "Name";
}
@@ -14,19 +13,20 @@
scope.serverValidationAliasField = "Alias";
}
scope.isNew = scope.content.state == "NotCreated";
localizationService.localizeMany([
scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
"visuallyHiddenTexts_name",
scope.isNew ? "general_new" : "general_edit"]
).then(function (data) {
scope.isNew = scope.editor.content.state == "NotCreated";
localizationService.localizeMany(
[
scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit",
"placeholders_a11yName",
scope.isNew ? "general_new" : "general_edit"
]
).then(function (data) {
scope.a11yMessage = data[0];
scope.a11yName = data[1];
var title = data[2] + ": ";
if (!scope.isNew) {
scope.a11yMessage += " " + scope.content.name;
scope.a11yMessage += " " + scope.editor.content.name;
title += scope.content.name;
} else {
var name = editorState.current.contentTypeName;
@@ -34,199 +34,212 @@
scope.a11yName = name + " " + scope.a11yName;
title += name;
}
scope.$emit("$changeTitle", title);
});
scope.vm = {};
scope.vm.hasVariants = false;
scope.vm.hasSubVariants = false;
scope.vm.hasCulture = false;
scope.vm.hasSegments = false;
scope.vm.dropdownOpen = false;
scope.vm.currentVariant = "";
scope.vm.variantsWithError = [];
scope.vm.defaultVariant = null;
scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors.
function updateVaraintErrors() {
scope.content.variants.forEach( function (variant) {
variant.hasError = scope.variantHasError(variant);
});
checkErrorsOnOtherVariants();
}
function checkErrorsOnOtherVariants() {
var check = false;
angular.forEach(scope.content.variants, function (variant) {
if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) {
scope.content.variants.forEach( function (variant) {
if (variant.active !== true && variant.hasError) {
check = true;
}
});
scope.vm.errorsOnOtherVariants = check;
}
function onVariantValidation(valid, errors, allErrors, culture, segment) {
function onCultureValidation(valid, errors, allErrors, culture) {
var index = scope.vm.variantsWithError.indexOf(culture);
if (valid === true) {
// only want to react to property errors:
if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) {
// we dont have any errors for properties, meaning we will back out.
return;
}
// If error coming back is invariant, we will assign the error to the default variant by picking the defaultVariant language.
if(culture === "invariant") {
culture = scope.vm.defaultVariant.language.culture;
}
var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment)
if(valid === true) {
if (index !== -1) {
scope.vm.variantsWithError.splice(index, 1);
}
} else {
if (index === -1) {
scope.vm.variantsWithError.push(culture);
scope.vm.variantsWithError.push({"culture": culture, "segment": segment});
}
}
checkErrorsOnOtherVariants();
scope.$evalAsync(updateVaraintErrors);
}
function onInit() {
// find default.
angular.forEach(scope.content.variants, function (variant) {
if (variant.language.isDefault) {
// find default + check if we have variants.
scope.content.variants.forEach( function (variant) {
if (variant.language !== null && variant.language.isDefault) {
scope.vm.defaultVariant = variant;
}
});
setCurrentVariant();
angular.forEach(scope.content.apps, (app) => {
if (app.alias === "umbContent") {
app.anchors = scope.content.tabs;
if (variant.language !== null) {
scope.vm.hasCulture = true;
}
if (variant.segment !== null) {
scope.vm.hasSegments = true;
}
});
scope.vm.hasVariants = (scope.vm.hasCulture || scope.vm.hasSegments);
scope.vm.hasSubVariants = (scope.vm.hasCulture && scope.vm.hasSegments);
angular.forEach(scope.content.variants, function (variant) {
unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation));
});
updateVaraintErrors();
unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation));
}
function setCurrentVariant() {
angular.forEach(scope.content.variants, function (variant) {
if (variant.active) {
scope.vm.currentVariant = variant;
checkErrorsOnOtherVariants();
scope.vm.variantMenu = [];
if (scope.vm.hasCulture) {
scope.content.variants.forEach( (v) => {
if (v.language !== null && v.segment === null) {
var variantMenuEntry = {
key: String.CreateGuid(),
open: v.language && v.language.culture === scope.editor.culture,
variant: v,
subVariants: scope.content.variants.filter( (subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null)
};
scope.vm.variantMenu.push(variantMenuEntry);
}
});
} else {
scope.content.variants.forEach( (v) => {
scope.vm.variantMenu.push({
key: String.CreateGuid(),
variant: v
});
});
}
});
}
scope.editor.variantApps.forEach( (app) => {
if (app.alias === "umbContent") {
app.anchors = scope.editor.content.tabs;
}
});
scope.goBack = function () {
if (scope.onBack) {
scope.onBack();
scope.content.variants.forEach( function (variant) {
// if we are looking for the variant with default language then we also want to check for invariant variant.
if (variant.language && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) {
unsubscribe.push(serverValidationManager.subscribe(null, "invariant", null, onVariantValidation, null));
}
unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, null, onVariantValidation, variant.segment));
});
}
};
scope.selectVariant = function (event, variant) {
scope.goBack = function () {
if (scope.onBack) {
scope.onBack();
}
};
if (scope.onSelectVariant) {
scope.vm.dropdownOpen = false;
scope.onSelectVariant({ "variant": variant });
scope.selectVariant = function (event, variant) {
if (scope.onSelectVariant) {
scope.vm.dropdownOpen = false;
scope.onSelectVariant({ "variant": variant });
}
};
scope.selectNavigationItem = function(item) {
if(scope.onSelectNavigationItem) {
scope.onSelectNavigationItem({"item": item});
}
}
};
scope.selectNavigationItem = function (item) {
if (scope.onSelectNavigationItem) {
scope.onSelectNavigationItem({ "item": item });
scope.selectAnchorItem = function(item, anchor) {
if(scope.onSelectAnchorItem) {
scope.onSelectAnchorItem({"item": item, "anchor": anchor});
}
}
}
scope.selectAnchorItem = function (item, anchor) {
if (scope.onSelectAnchorItem) {
scope.onSelectAnchorItem({ "item": item, "anchor": anchor });
}
}
scope.closeSplitView = function () {
if (scope.onCloseSplitView) {
scope.onCloseSplitView();
}
};
scope.closeSplitView = function () {
if (scope.onCloseSplitView) {
scope.onCloseSplitView();
}
};
scope.openInSplitView = function (event, variant) {
if (scope.onOpenInSplitView) {
scope.vm.dropdownOpen = false;
scope.onOpenInSplitView({ "variant": variant });
}
};
/**
* keep track of open variants - this is used to prevent the same variant to be open in more than one split view
* @param {any} culture
*/
scope.variantIsOpen = function (culture) {
return (scope.openVariants.indexOf(culture) !== -1);
}
/**
* Check whether a variant has a error, used to display errors in variant switcher.
* @param {any} culture
*/
scope.variantHasError = function (culture) {
// if we are looking for the default language we also want to check for invariant.
if (culture === scope.vm.defaultVariant.language.culture) {
if (scope.vm.variantsWithError.indexOf("invariant") !== -1) {
scope.openInSplitView = function (event, variant) {
if (scope.onOpenInSplitView) {
scope.vm.dropdownOpen = false;
scope.onOpenInSplitView({ "variant": variant });
}
};
/**
* Check whether a variant has a error, used to display errors in variant switcher.
* @param {any} culture
*/
scope.variantHasError = function(variant) {
if(scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) {
return true;
}
return false;
}
if (scope.vm.variantsWithError.indexOf(culture) !== -1) {
return true;
}
return false;
}
onInit();
//watch for the active culture changing, if it changes, update the current variant
if (scope.content.variants) {
scope.$watch(function () {
for (var i = 0; i < scope.content.variants.length; i++) {
var v = scope.content.variants[i];
if (v.active) {
return v.language.culture;
}
}
return scope.vm.currentVariant.language.culture; //should never get here
}, function (newValue, oldValue) {
if (newValue !== scope.vm.currentVariant.language.culture) {
setCurrentVariant();
onInit();
scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}
scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
var directive = {
transclude: true,
restrict: 'E',
replace: true,
templateUrl: 'views/components/editor/umb-editor-content-header.html',
scope: {
name: "=",
nameDisabled: "<?",
menu: "=",
hideActionsMenu: "<?",
content: "=",
editor: "=",
hideChangeVariant: "<?",
onSelectNavigationItem: "&?",
onSelectAnchorItem: "&?",
showBackButton: "<?",
onBack: "&?",
splitViewOpen: "=?",
onOpenInSplitView: "&?",
onCloseSplitView: "&?",
onSelectVariant: "&?",
serverValidationNameField: "@?",
serverValidationAliasField: "@?"
},
link: link
};
return directive;
}
var directive = {
transclude: true,
restrict: 'E',
replace: true,
templateUrl: 'views/components/editor/umb-editor-content-header.html',
scope: {
name: "=",
nameDisabled: "<?",
menu: "=",
hideActionsMenu: "<?",
content: "=",
openVariants: "<",
hideChangeVariant: "<?",
onSelectNavigationItem: "&?",
onSelectAnchorItem: "&?",
showBackButton: "<?",
onBack: "&?",
splitViewOpen: "=?",
onOpenInSplitView: "&?",
onCloseSplitView: "&?",
onSelectVariant: "&?",
serverValidationNameField: "@?",
serverValidationAliasField: "@?"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbEditorContentHeader', EditorContentHeader);
}) ();
})();
@@ -6,7 +6,7 @@
**/
function EditorSubViewDirective() {
function link(scope, el, attr, ctrl) {
function link(scope) {
//The model can contain: view, viewModel, name, alias, icon
if (!scope.model.view) {
@@ -20,6 +20,7 @@
templateUrl: 'views/components/editor/umb-editor-sub-view.html',
scope: {
model: "=",
variantContent: "=?",
content: "="
},
link: link
@@ -39,7 +39,7 @@ angular.module("umbraco.directives")
function stringToJson(text) {
try {
return JSON.parse(text);
return Utilities.fromJson(text);
} catch (err) {
setInvalid();
return text;
@@ -55,7 +55,7 @@ angular.module("umbraco.directives")
function isValidJson(model) {
var flag = true;
try {
JSON.parse(model)
Utilities.fromJson(model)
} catch (err) {
flag = false;
}
@@ -64,7 +64,7 @@ angular.module("umbraco.directives")
//Support one or more attribute properties to update
var keys = attrs.localize.split(',');
keys.forEach((value, key) => {
Utilities.forEach(keys, (value, key) => {
var attr = element.attr(value);
if (attr) {
@@ -530,6 +530,7 @@
contentType: scope.contentType,
contentTypeName: scope.model.name,
contentTypeAllowCultureVariant: scope.model.allowCultureVariant,
contentTypeAllowSegmentVariant: scope.model.allowSegmentVariant,
view: "views/common/infiniteeditors/propertysettings/propertysettings.html",
size: "small",
submit: function (model) {
@@ -557,6 +558,7 @@
property.isSensitiveData = propertyModel.isSensitiveData;
property.isSensitiveValue = propertyModel.isSensitiveValue;
property.allowCultureVariant = propertyModel.allowCultureVariant;
property.allowSegmentVariant = propertyModel.allowSegmentVariant;
// update existing data types
if (model.updateSameDataTypes) {
@@ -158,9 +158,9 @@ When this combination is hit an overview is opened with shortcuts based on the m
};
function onInit() {
scope.model.forEach(shortcutGroup => {
Utilities.forEach(scope.model, shortcutGroup => {
shortcutGroup.shortcuts.forEach(shortcut => {
Utilities.forEach(shortcutGroup.shortcuts, shortcut => {
shortcut.platformKeys = [];
// get shortcut keys for mac
@@ -38,7 +38,8 @@ function valPropertyMsg(serverValidationManager, localizationService) {
var currentProperty = umbPropCtrl.property;
scope.currentProperty = currentProperty;
var currentCulture = currentProperty.culture;
var currentCulture = currentProperty.culture;
var currentSegment = currentProperty.segment;
// validation object won't exist when editor loads outside the content form (ie in settings section when modifying a content type)
var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined;
@@ -54,7 +55,7 @@ function valPropertyMsg(serverValidationManager, localizationService) {
var currentVariant = umbVariantCtrl.editor.content;
// Lets check if we have variants and we are on the default language then ...
if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) {
if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) {
//This property is locked cause its a invariant property shown on a non-default language.
//Therefor do not validate this field.
return;
@@ -70,7 +71,7 @@ function valPropertyMsg(serverValidationManager, localizationService) {
//this can be null if no property was assigned
if (scope.currentProperty) {
//first try to get the error msg from the server collection
var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "");
var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "", null);
//if there's an error message use it
if (err && err.errorMsg) {
return err.errorMsg;
@@ -221,25 +222,31 @@ function valPropertyMsg(serverValidationManager, localizationService) {
// the correct field validation in their property editors.
if (scope.currentProperty) { //this can be null if no property was assigned
function serverValidationManagerCallback(isValid, propertyErrors, allErrors) {
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false);
startWatch();
}
else {
scope.errorMsg = "";
//flag that the current validator is valid
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
}
unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias,
currentCulture,
"",
function(isValid, propertyErrors, allErrors) {
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false);
startWatch();
}
else {
scope.errorMsg = "";
//flag that the current validator is valid
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
}
}));
serverValidationManagerCallback,
currentSegment
)
);
}
@@ -24,6 +24,7 @@ function valServer(serverValidationManager) {
var currentProperty = umbPropCtrl.property;
var currentCulture = currentProperty.culture;
var currentSegment = currentProperty.segment;
if (umbVariantCtrl) {
//if we are inside of an umbVariantContent directive
@@ -31,7 +32,7 @@ function valServer(serverValidationManager) {
var currentVariant = umbVariantCtrl.editor.content;
// Lets check if we have variants and we are on the default language then ...
if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) {
if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) {
//This property is locked cause its a invariant property shown on a non-default language.
//Therefor do not validate this field.
return;
@@ -75,7 +76,7 @@ function valServer(serverValidationManager) {
if (modelCtrl.$invalid) {
modelCtrl.$setValidity('valServer', true);
//clear the server validation entry
serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName);
serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment);
stopWatch();
}
}, true);
@@ -90,23 +91,26 @@ function valServer(serverValidationManager) {
}
//subscribe to the server validation changes
function serverValidationManagerCallback(isValid, propertyErrors, allErrors) {
if (!isValid) {
modelCtrl.$setValidity('valServer', false);
//assign an error msg property to the current validator
modelCtrl.errorMsg = propertyErrors[0].errorMsg;
startWatch();
}
else {
modelCtrl.$setValidity('valServer', true);
//reset the error message
modelCtrl.errorMsg = "";
stopWatch();
}
}
unsubscribe.push(serverValidationManager.subscribe(currentProperty.alias,
currentCulture,
fieldName,
function(isValid, propertyErrors, allErrors) {
if (!isValid) {
modelCtrl.$setValidity('valServer', false);
//assign an error msg property to the current validator
modelCtrl.errorMsg = propertyErrors[0].errorMsg;
startWatch();
}
else {
modelCtrl.$setValidity('valServer', true);
//reset the error message
modelCtrl.errorMsg = "";
stopWatch();
}
}));
serverValidationManagerCallback,
currentSegment)
);
scope.$on('$destroy', function () {
stopWatch();
@@ -1,6 +1,6 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:CMS_joinArray
* @name umbraco.filters.filter:umbCmsJoinArray
* @namespace umbCmsJoinArray
*
* param {array} array of string or objects, if an object use the third argument to specify which prop to list.
@@ -0,0 +1,19 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:umbCmsTitleCase
* @namespace umbCmsTitleCase
*
* param {string} the text turned into title case.
*
* @description
* Transforms text to title case. Capitalizes the first letter of each word, and transforms the rest of the word to lower case.
*
*/
angular.module("umbraco.filters").filter('umbCmsTitleCase', function() {
return function (str) {
return str.replace(
/\w\S*/g,
txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
}
});
@@ -24,6 +24,7 @@
if ($routeParams) {
// it's an API request, add the current client culture as a header value
config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
config.headers["X-UMB-SEGMENT"] = $routeParams.csegment ? $routeParams.csegment : null;
}
return config;
@@ -90,7 +90,7 @@ angular.module('umbraco.mocks').
name: "1 column layout",
sections: [
{
grid: 12,
grid: 12
}
]
},
@@ -98,7 +98,7 @@ angular.module('umbraco.mocks').
name: "2 column layout",
sections: [
{
grid: 4,
grid: 4
},
{
grid: 8
@@ -139,7 +139,7 @@ angular.module('umbraco.mocks').
}
}
},
}
]
},
{
@@ -698,7 +698,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
// /belle/#/content/edit/9876 (where 9876 is the new id)
//clear the query strings
navigationService.clearSearch(["cculture"]);
navigationService.clearSearch(["cculture", "csegment"]);
if (softRedirect) {
navigationService.setSoftRedirect();
}
@@ -159,9 +159,16 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
//the alias in model state can be in dot notation which indicates
// * the first part is the content property alias
// * the second part is the field to which the valiation msg is associated with
//There will always be at least 3 parts for content properties since all model errors for properties are prefixed with "_Properties"
//There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties"
//If it is not prefixed with "_Properties" that means the error is for a field of the object directly.
// Example: "_Properties.headerImage.en-US.mySegment.myField"
// * it's for a property since it has a _Properties prefix
// * it's for the headerImage property type
// * it's for the en-US culture
// * it's for the mySegment segment
// * it's for the myField html field (optional)
var parts = e.split(".");
//Check if this is for content properties - specific to content/media/member editors because those are special
@@ -179,16 +186,23 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
}
}
//if it contains 3 '.' then we will wire it up to a property's html field
var segment = null;
if (parts.length > 3) {
//add an error with a reference to the field for which the validation belongs too
serverValidationManager.addPropertyError(propertyAlias, culture, parts[3], modelState[e][0]);
segment = parts[3];
//special check in case the string is formatted this way
if (segment === "null") {
segment = null;
}
}
else {
//add a generic error for the property, no reference to a specific html field
serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]);
var htmlFieldReference = "";
if (parts.length > 4) {
htmlFieldReference = parts[4] || "";
}
// add a generic error for the property
serverValidationManager.addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], segment);
} else {
//Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example:
@@ -29,7 +29,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
//A list of query strings defined that when changed will not cause a reload of the route
var nonRoutingQueryStrings = ["mculture", "cculture", "lq", "sr"];
var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"];
var retainedQueryStrings = ["mculture"];
function setMode(mode) {
@@ -13,13 +13,14 @@ function serverValidationManager($timeout) {
var callbacks = [];
/** calls the callback specified with the errors specified, used internally */
function executeCallback(self, errorsForCallback, callback, culture) {
function executeCallback(self, errorsForCallback, callback, culture, segment) {
callback.apply(self, [
false, // pass in a value indicating it is invalid
errorsForCallback, // pass in the errors for this item
self.items, // pass in all errors in total
culture // pass the culture that we are listing for.
culture, // pass the culture that we are listing for.
segment // pass the segment that we are listing for.
]
);
}
@@ -35,7 +36,8 @@ function serverValidationManager($timeout) {
});
}
function getPropertyErrors(self, propertyAlias, culture, fieldName) {
function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) {
if (!Utilities.isString(propertyAlias)) {
throw "propertyAlias must be a string";
}
@@ -46,22 +48,28 @@ function serverValidationManager($timeout) {
if (!culture) {
culture = "invariant";
}
if (!segment) {
segment = null;
}
//find all errors for this property
return _.filter(self.items, function (item) {
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
});
}
function getCultureErrors(self, culture) {
function getVariantErrors(self, culture, segment) {
if (!culture) {
culture = "invariant";
}
if (!segment) {
segment = null;
}
//find all errors for this property
return _.filter(self.items, function (item) {
return (item.culture === culture);
return (item.culture === culture && item.segment === segment);
});
}
@@ -71,21 +79,21 @@ function serverValidationManager($timeout) {
//its a field error callback
var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName);
if (fieldErrors.length > 0) {
executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture);
executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
}
}
else if (callbacks[cb].propertyAlias != null) {
//its a property error
var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName);
var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].segment, callbacks[cb].fieldName);
if (propErrors.length > 0) {
executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture);
executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
}
}
else {
//its a culture error
var cultureErrors = getCultureErrors(self, callbacks[cb].culture);
if (cultureErrors.length > 0) {
executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture);
//its a variant error
var variantErrors = getVariantErrors(self, callbacks[cb].culture, callbacks[cb].segment);
if (variantErrors.length > 0) {
executeCallback(self, variantErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment);
}
}
}
@@ -150,20 +158,27 @@ function serverValidationManager($timeout) {
* field alias to listen for.
* If propertyAlias is null, then this subscription is for a field property (not a user defined property).
*/
subscribe: function (propertyAlias, culture, fieldName, callback) {
subscribe: function (propertyAlias, culture, fieldName, callback, segment) {
if (!callback) {
return;
}
var id = String.CreateGuid();
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
if (propertyAlias === null) {
callbacks.push({
propertyAlias: null,
culture: culture,
segment: segment,
fieldName: fieldName,
callback: callback,
id: id
@@ -175,6 +190,7 @@ function serverValidationManager($timeout) {
callbacks.push({
propertyAlias: propertyAlias,
culture: culture,
segment: segment,
fieldName: fieldName,
callback: callback,
id: id
@@ -199,25 +215,29 @@ function serverValidationManager($timeout) {
* @param {} fieldName
* @returns {}
*/
unsubscribe: function (propertyAlias, culture, fieldName) {
unsubscribe: function (propertyAlias, culture, fieldName, segment) {
//normalize culture to null
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
if (propertyAlias === null) {
//remove all callbacks for the content field
callbacks = _.reject(callbacks, function (item) {
return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName;
return item.propertyAlias === null && item.culture === culture && item.segment === segment && item.fieldName === fieldName;
});
}
else if (propertyAlias !== undefined) {
//remove all callbacks for the content property
callbacks = _.reject(callbacks, function (item) {
return item.propertyAlias === propertyAlias && item.culture === culture &&
return item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment &&
(item.fieldName === fieldName ||
((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === "")));
});
@@ -236,16 +256,20 @@ function serverValidationManager($timeout) {
* This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an
* explicit field name set.
*/
getPropertyCallbacks: function (propertyAlias, culture, fieldName) {
getPropertyCallbacks: function (propertyAlias, culture, fieldName, segment) {
//normalize culture to null
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
var found = _.filter(callbacks, function (item) {
//returns any callback that have been registered directly against the field and for only the property
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
});
return found;
},
@@ -262,7 +286,7 @@ function serverValidationManager($timeout) {
getFieldCallbacks: function (fieldName) {
var found = _.filter(callbacks, function (item) {
//returns any callback that have been registered directly against the field
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
});
return found;
},
@@ -274,12 +298,29 @@ function serverValidationManager($timeout) {
* @function
*
* @description
* Gets all callbacks that has been registered using the subscribe method for the culture.
* Gets all callbacks that has been registered using the subscribe method for the culture. Not including segments.
*/
getCultureCallbacks: function (culture) {
var found = _.filter(callbacks, function (item) {
//returns any callback that have been registered directly/ONLY against the culture
return (item.culture === culture && item.propertyAlias === null && item.fieldName === null);
return (item.culture === culture && item.segment === null && item.propertyAlias === null && item.fieldName === null);
});
return found;
},
/**
* @ngdoc function
* @name getVariantCallbacks
* @methodOf umbraco.services.serverValidationManager
* @function
*
* @description
* Gets all callbacks that has been registered using the subscribe method for the culture and segment.
*/
getVariantCallbacks: function (culture, segment) {
var found = _.filter(callbacks, function (item) {
//returns any callback that have been registered directly against the given culture and given segment.
return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null);
});
return found;
},
@@ -303,6 +344,7 @@ function serverValidationManager($timeout) {
this.items.push({
propertyAlias: null,
culture: "invariant",
segment: null,
fieldName: fieldName,
errorMsg: errorMsg
});
@@ -314,7 +356,7 @@ function serverValidationManager($timeout) {
var cbs = this.getFieldCallbacks(fieldName);
//call each callback for this error
for (var cb in cbs) {
executeCallback(this, errorsForCallback, cbs[cb].callback, null);
executeCallback(this, errorsForCallback, cbs[cb].callback, null, null);
}
},
@@ -327,7 +369,7 @@ function serverValidationManager($timeout) {
* @description
* Adds an error message for the content property
*/
addPropertyError: function (propertyAlias, culture, fieldName, errorMsg) {
addPropertyError: function (propertyAlias, culture, fieldName, errorMsg, segment) {
if (!propertyAlias) {
return;
}
@@ -336,31 +378,36 @@ function serverValidationManager($timeout) {
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
//only add the item if it doesn't exist
if (!this.hasPropertyError(propertyAlias, culture, fieldName)) {
if (!this.hasPropertyError(propertyAlias, culture, fieldName, segment)) {
this.items.push({
propertyAlias: propertyAlias,
culture: culture,
segment: segment,
fieldName: fieldName,
errorMsg: errorMsg
});
}
//find all errors for this item
var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, fieldName);
var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, segment, fieldName);
//we should now call all of the call backs registered for this error
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName);
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName, segment);
//call each callback for this error
for (var cb in cbs) {
executeCallback(this, errorsForCallback, cbs[cb].callback, culture);
executeCallback(this, errorsForCallback, cbs[cb].callback, culture, segment);
}
//execute culture specific callbacks here too when a propery error is added
var cultureCbs = this.getCultureCallbacks(culture);
//execute variant specific callbacks here too when a propery error is added
var variantCbs = this.getVariantCallbacks(culture, segment);
//call each callback for this error
for (var cb in cultureCbs) {
executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture);
for (var cb in variantCbs) {
executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment);
}
},
@@ -373,7 +420,7 @@ function serverValidationManager($timeout) {
* @description
* Removes an error message for the content property
*/
removePropertyError: function (propertyAlias, culture, fieldName) {
removePropertyError: function (propertyAlias, culture, fieldName, segment) {
if (!propertyAlias) {
return;
@@ -383,10 +430,14 @@ function serverValidationManager($timeout) {
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
//remove the item
this.items = _.reject(this.items, function (item) {
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
});
},
@@ -405,7 +456,10 @@ function serverValidationManager($timeout) {
callbacks[cb].callback.apply(this, [
true, //pass in a value indicating it is VALID
[], //pass in empty collection
[]]); //pass in empty collection
[],
null,
null]
);
}
},
@@ -431,16 +485,20 @@ function serverValidationManager($timeout) {
* @description
* Gets the error message for the content property
*/
getPropertyError: function (propertyAlias, culture, fieldName) {
getPropertyError: function (propertyAlias, culture, fieldName, segment) {
//normalize culture to null
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
});
return err;
},
@@ -457,7 +515,7 @@ function serverValidationManager($timeout) {
getFieldError: function (fieldName) {
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
});
return err;
},
@@ -471,16 +529,20 @@ function serverValidationManager($timeout) {
* @description
* Checks if the content property + culture + field name combo has an error
*/
hasPropertyError: function (propertyAlias, culture, fieldName) {
hasPropertyError: function (propertyAlias, culture, fieldName, segment) {
//normalize culture to null
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
});
return err ? true : false;
},
@@ -497,12 +559,11 @@ function serverValidationManager($timeout) {
hasFieldError: function (fieldName) {
var err = _.find(this.items, function (item) {
//return true if the property alias matches and if an empty field name is specified or the field name matches
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName);
});
return err ? true : false;
},
/**
* @ngdoc function
* @name hasCultureError
@@ -513,14 +574,40 @@ function serverValidationManager($timeout) {
* Checks if the given culture has an error
*/
hasCultureError: function (culture) {
//normalize culture to null
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
var err = _.find(this.items, function (item) {
return (item.culture === culture && item.segment === null);
});
return err ? true : false;
},
/**
* @ngdoc function
* @name hasVariantError
* @methodOf umbraco.services.serverValidationManager
* @function
*
* @description
* Checks if the given culture has an error
*/
hasVariantError: function (culture, segment) {
//normalize culture to "invariant"
if (!culture) {
culture = "invariant";
}
//normalize segment to null
if (!segment) {
segment = null;
}
var err = _.find(this.items, function (item) {
return item.culture === culture;
return (item.culture === culture && item.segment === segment);
});
return err ? true : false;
},
@@ -19,9 +19,9 @@
function registerAllTours() {
tours = [];
return tourResource.getTours().then(function (tourFiles) {
tourFiles.forEach(tourFile => {
Utilities.forEach(tourFiles, tourFile => {
tourFile.tours.forEach(newTour => {
Utilities.forEach(tourFile.tours, newTour => {
validateTour(newTour);
validateTourRegistration(newTour);
tours.push(newTour);
@@ -251,7 +251,7 @@
*/
function validateTourRegistration(tour) {
// check for existing tours with the same alias
tours.forEach(existingTour => {
Utilities.forEach(tours, existingTour => {
if (existingTour.alias === tour.alias) {
throw "A tour with the alias " + tour.alias + " is already registered";
}
@@ -267,17 +267,17 @@
var deferred = $q.defer();
currentUserResource.getTours().then(function (storedTours) {
storedTours.forEach(storedTour => {
Utilities.forEach(storedTours, storedTour => {
if (storedTour.completed === true) {
tours.forEach(tour => {
Utilities.forEach(tours, tour => {
if (storedTour.alias === tour.alias) {
tour.completed = true;
}
});
}
if (storedTour.disabled === true) {
tours.forEach(tour => {
Utilities.forEach(tours, tour => {
if (storedTour.alias === tour.alias) {
tour.disabled = true;
}
@@ -64,7 +64,7 @@
var saveModel = _.pick(displayModel,
'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes',
'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed',
'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'isElement');
'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'allowSegmentVariant', 'isElement');
// TODO: Map these
saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; });
@@ -83,7 +83,7 @@
});
var saveProperties = _.map(realProperties, function (p) {
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant');
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant');
return saveProperty;
});
@@ -367,6 +367,7 @@
name: v.name || "", //if its null/empty,we must pass up an empty string else we get json converter errors
properties: getContentProperties(v.tabs),
culture: v.language ? v.language.culture : null,
segment: v.segment,
publish: v.publish,
save: v.save,
releaseDate: v.releaseDate,
@@ -393,38 +394,59 @@
*/
formatContentGetData: function(displayModel) {
//We need to check for invariant properties among the variant variants.
//When we detect this, we want to make sure that the property object instance is the
//same reference object between all variants instead of a copy (which it will be when
//return from the JSON structure).
// We need to check for invariant properties among the variant variants,
// as the value of an invariant property is shared between different variants.
// A property can be culture invariant, segment invariant, or both.
// When we detect this, we want to make sure that the property object instance is the
// same reference object between all variants instead of a copy (which it will be when
// return from the JSON structure).
if (displayModel.variants && displayModel.variants.length > 1) {
// Collect all invariant properties from the variants that are either the
// default language variant or the default segment variant.
var defaultVariants = _.filter(displayModel.variants, function (variant) {
var isDefaultLanguage = variant.language && variant.language.isDefault;
var isDefaultSegment = variant.segment == null;
var invariantProperties = [];
//collect all invariant properties on the first first variant
var firstVariant = displayModel.variants[0];
_.each(firstVariant.tabs, function(tab, tabIndex) {
_.each(tab.properties, function (property, propIndex) {
//in theory if there's more than 1 variant, that means they would all have a language
//but we'll do our safety checks anyways here
if (firstVariant.language && !property.culture) {
invariantProperties.push({
tabIndex: tabIndex,
propIndex: propIndex,
property: property
});
}
});
return isDefaultLanguage || isDefaultSegment;
});
if (defaultVariants.length > 0) {
_.each(defaultVariants, function (defaultVariant) {
var invariantProps = [];
//now assign this same invariant property instance to the same index of the other variants property array
for (var j = 1; j < displayModel.variants.length; j++) {
var variant = displayModel.variants[j];
_.each(defaultVariant.tabs, function (tab, tabIndex) {
_.each(tab.properties, function (property, propIndex) {
// culture == null -> property is culture invariant
// segment == null -> property is *possibly* segment invariant
if (!property.culture || !property.segment) {
invariantProps.push({
tabIndex: tabIndex,
propIndex: propIndex,
property: property
});
}
});
});
_.each(invariantProperties, function (invProp) {
variant.tabs[invProp.tabIndex].properties[invProp.propIndex] = invProp.property;
var otherVariants = _.filter(displayModel.variants, function (variant) {
return variant !== defaultVariant;
});
// now assign this same invariant property instance to the same index of the other variants property array
_.each(otherVariants, function (variant) {
_.each(invariantProps, function (invProp) {
var tab = variant.tabs[invProp.tabIndex];
var prop = tab.properties[invProp.propIndex];
var inheritsCulture = prop.culture === invProp.property.culture && prop.segment == null && invProp.property.segment == null;
var inheritsSegment = prop.segment === invProp.property.segment && !prop.culture;
if (inheritsCulture || inheritsSegment) {
tab.properties[invProp.propIndex] = invProp.property;
}
});
});
});
}
}
@@ -206,7 +206,7 @@ function umbSessionStorage($window) {
return {
get: function (key) {
return JSON.parse(storage["umb_" + key]);
return Utilities.fromJson(storage["umb_" + key]);
},
set: function (key, value) {
@@ -107,6 +107,7 @@
@import "components/overlays.less";
@import "components/card.less";
@import "components/editor/umb-editor.less";
@import "components/editor/umb-variant-switcher.less";
@import "components/umb-sub-views.less";
@import "components/umb-editor-navigation.less";
@import "components/umb-editor-navigation-item.less";
@@ -137,6 +138,7 @@
@import "components/tooltip/umb-tooltip-list.less";
@import "components/overlays/umb-overlay-backdrop.less";
@import "components/overlays/umb-itempicker.less";
@import "components/overlays/umb-variant-selector-overlay";
@import "components/umb-grid.less";
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@@ -2,20 +2,28 @@
Library of card related compoents, like the right-hand icon list on the grid "cards"
*/
.umb-card{
.umb-card {
position: relative;
padding: 5px 10px 5px 10px;
background: @white;
width: 100%;
.title{padding: 12px; color: @gray-3; border-bottom: 1px solid @gray-8; font-weight: 400; font-size: 16px; text-transform: none; margin: 0 -10px 10px -10px;}
.title {
padding: 12px;
color: @gray-3;
border-bottom: 1px solid @gray-8;
font-weight: 400;
font-size: 16px;
text-transform: none;
margin: 0 -10px 10px -10px;
}
}
.umb-card-thumb{
.umb-card-thumb {
text-align: center;
i{
i {
text-align: center;
font-size: 20px;
line-height: 40px;
@@ -25,18 +33,28 @@
}
}
.umb-card-content{
.item-title{color: @blackLight; font-weight: 400; border: none; font-size: 16px; text-transform: none; margin-bottom: 3px;}
p{color: @gray-3; margin-bottom: 1px;}
.umb-card-content {
.item-title {
color: @blackLight;
font-weight: 400;
border: none;
font-size: 16px;
text-transform: none;
margin-bottom: 3px;
}
p {
color: @gray-3;
margin-bottom: 1px;
}
}
.umb-card-actions{
.umb-card-actions {
padding-top: 10px;
border-top: @gray-10 1px solid;
clear: both;
}
.umb-card-icons{
.umb-card-icons {
text-align: center;
vertical-align: middle;
display: block;
@@ -45,7 +63,7 @@
padding: 0;
}
.umb-card-icons.vertical{
.umb-card-icons.vertical {
position: absolute;
top: 7px;
right: 7px;
@@ -53,19 +71,19 @@
width: 1px;
}
.umb-card-icons li{
.umb-card-icons li {
display: inline-block;
margin: 0 2px 0 2px;
}
.umb-card-icons.vertical li{
.umb-card-icons.vertical li {
float: right;
display: block;
margin-bottom: 3px;
}
//card iocn list
.umb-card-list{
.umb-card-list {
display: block;
padding: 0;
margin: 0;
@@ -81,7 +99,7 @@
//Card icon grid for picking items off a card
.umb-card-grid{
.umb-card-grid {
padding: 0;
margin: 0 auto;
list-style: none;
@@ -101,14 +119,24 @@
width: 100px;
}
.umb-card-grid.-six-in-row li {
flex: 0 0 25%;
max-width: 117px;
}
.umb-card-grid.-four-in-row li {
flex: 0 0 25%;
max-width: 25%;
}
.umb-card-grid.-three-in-row li {
flex: 0 0 33.33%;
max-width:33.33%;
flex: 0 0 33.333%;
max-width:33.333%;
i {
font-size: 36px;
line-height: 28px;
}
}
.umb-card-grid .umb-card-grid-item {
@@ -117,7 +145,7 @@
width: 100%;
//height: 100%;
padding-top: 100%;
border-radius: 3px;
border-radius: @baseBorderRadius * 2;
transition: background-color 120ms;
> span {
@@ -141,6 +169,41 @@
color: @ui-option-type-hover;
}
.umb-card-grid .umb-card-grid-item-slot {
position: relative;
display: block;
width: 100%;
padding-top: 100%;
border-radius: @baseBorderRadius * 2;
box-sizing: border-box;
transition: background-color 120ms;
&:hover, &:focus {
background-color: @ui-option-hover;
> span {
color:@ui-action-discreet-type-hover;
border-color:@ui-action-discreet-border-hover;
}
}
> span {
position: absolute;
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: transparent;
border:1.5px dashed @ui-action-discreet-border;
border-radius: @baseBorderRadius * 2;
}
}
.umb-card-grid a {
color: @ui-option-type;
text-decoration: none;
@@ -149,6 +212,7 @@
.umb-card-grid i {
font-size: 30px;
line-height: 20px;
margin-top: 6px;
margin-bottom: 10px;
display: block;
}
@@ -164,7 +228,7 @@
//Round icon-like button - this should be somewhere else
.umb-btn-round{
.umb-btn-round {
padding: 4px 6px 4px 6px;
display: inline-block;
cursor: pointer;
@@ -174,7 +238,8 @@
margin: 2px;
}
.umb-btn-round:hover, .umb-btn-round:hover *{
.umb-btn-round:hover,
.umb-btn-round:hover * {
background: @blueDark !important;
color: @white !important;
border-color: @blueDark !important;
@@ -162,161 +162,6 @@ a.umb-editor-header__close-split-view:hover {
}
}
/* variant switcher */
.umb-variant-switcher__toggle {
position: relative;
display: flex;
align-items: center;
padding: 0 10px;
margin: 1px 1px;
right: 0;
height: 30px;
text-decoration: none !important;
font-size: 13px;
color: @ui-action-discreet-type;
background: transparent;
border: none;
max-width: 50%;
white-space: nowrap;
user-select: none;
span {
text-overflow: ellipsis;
overflow: hidden;
}
}
button.umb-variant-switcher__toggle {
transition: color 0.2s ease-in-out;
&:hover {
//background-color: @gray-10;
color: @ui-action-discreet-type-hover;
.umb-variant-switcher__expand {
color: @ui-action-discreet-type-hover;
}
}
&.--error {
&::before {
content: '!';
position: absolute;
top: -8px;
right: -10px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 10px;
text-align: center;
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
}
}
}
.umb-variant-switcher__expand {
color: @ui-action-discreet-type;
margin-top: 3px;
margin-left: 5px;
margin-right: -5px;
transition: color 0.2s ease-in-out;
}
.umb-variant-switcher__item {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid @gray-9;
position: relative;
}
.umb-variant-switcher__item:last-child {
border-bottom: none;
}
.umb-variant-switcher__item.--current {
color: @ui-light-active-type;
}
.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper {
border-left: 4px solid @ui-active;
}
.umb-variant-switcher__item:hover {
outline: none;
}
.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover {
//background-color: @white !important;
cursor: default;
}
.umb-variant-switcher__item:hover .umb-variant-switcher__split-view {
display: block;
cursor: pointer;
}
.umb-variant-switcher__item.--error {
.umb-variant-switcher__name {
color: @red;
&::after {
content: '!';
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5px;
top: -3px;
width: 14px;
height: 14px;
border-radius: 7px;
font-size: 8px;
text-align: center;
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
}
}
}
.umb-variant-switcher__name-wrapper {
font-size: 14px;
flex: 1;
cursor: pointer;
padding-top: 6px !important;
padding-bottom: 6px !important;
background-color: transparent;
border: none;
border-left: 2px solid transparent;
}
.umb-variant-switcher__name {
display: block;
}
.umb-variant-switcher__state {
font-size: 13px;
color: @gray-4;
}
.umb-variant-switcher__split-view {
font-size: 13px;
display: none;
padding: 16px 20px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
background-color: @white;
&:hover {
background-color: @ui-option-hover;
color: @ui-option-type-hover;
}
}
// container
@@ -0,0 +1,332 @@
/* variant switcher */
.umb-variant-switcher__toggle {
position: relative;
display: flex;
align-items: center;
padding: 0 10px;
margin: 1px 1px;
right: 0;
height: 30px;
text-decoration: none !important;
font-size: 13px;
color: @ui-action-discreet-type;
background: transparent;
border: none;
max-width: 50%;
white-space: nowrap;
user-select: none;
span {
text-overflow: ellipsis;
overflow: hidden;
}
}
button.umb-variant-switcher__toggle {
transition: color 0.2s ease-in-out;
&:hover {
//background-color: @gray-10;
color: @ui-action-discreet-type-hover;
.umb-variant-switcher__expand {
color: @ui-action-discreet-type-hover;
}
}
&.--error {
&::before {
content: '!';
position: absolute;
top: -8px;
right: -10px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 10px;
text-align: center;
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
}
}
}
.umb-variant-switcher__expand {
color: @ui-action-discreet-type;
margin-top: 3px;
margin-left: 5px;
margin-right: -5px;
transition: color 0.2s ease-in-out;
}
.umb-variant-switcher {
min-width: 100%;
max-height: 80vh;
overflow-y: auto;
margin-top: 5px;
user-select: none;
}
.umb-variant-switcher__item {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid @gray-9;
position: relative;
.umb-variant-switcher__name-wrapper:hover {
.umb-variant-switcher__name {
color: @blueMid;
}
.umb-variant-switcher__state {
color: @blueMid;
}
}
}
.umb-variant-switcher__item.--state-notCreated:not(.--active) {
.umb-variant-switcher__name-wrapper::before {
content: "+";
display: block;
float: left;
font-size: 15px;
font-weight: 900;
padding: 8px 16px 8px 6px;
color: @gray-5;
}
.umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper::before {
padding: 8px 16px 8px 20px;
}
.umb-variant-switcher__name {
color: @gray-5;
}
.umb-variant-switcher__state {
color: @gray-6;
}
.umb-variant-switcher__name-wrapper::after {
content: "";
position: absolute;
z-index: 1;
border: 1px dashed @gray-9;
top: 7px;
bottom: 7px;
left: 7px;
right: 7px;
border-radius: 3px;
pointer-events: none;
}
.umb-variant-switcher__name-wrapper:hover {
&::before {
color: @blueMid;
}
.umb-variant-switcher__name {
color: @blueMid;
}
.umb-variant-switcher__state {
color: @blueMid;
}
}
}
/*
.umb-variant-switcher__item.--state-draft {
.umb-variant-switcher__name {
color: @gray-5;
}
&:hover {
.umb-variant-switcher__name {
color: @blueMid;
}
}
}
*/
.umb-variant-switcher.--has-sub-variants {
.umb-variant-switcher__item {
}
}
.umb-variant-switcher__item-expand-button {
text-decoration: none;
display: inline-block;
flex: 0;
align-self: stretch;
padding-left: 22px !important;
padding-right: 14px !important;
font-size: 12px;
* {
pointer-events: none;
}
}
.umb-variant-switcher__item:last-child {
border-bottom: none;
}
.umb-variant-switcher__item.--current {
//color: @ui-light-active-type;
//background-color: @pinkExtraLight;
.umb-variant-switcher__name {
//color: @ui-light-active-type;
font-weight: 700;
}
&::before {
content: '';
position: absolute;
border-radius: 0 4px 4px 0;
background-color: @ui-active-border;
width: 4px;
top:8px;
bottom: 8px;
left:0;
z-index:1;
pointer-events: none;
}
}
.umb-variant-switcher__item:hover {
outline: none;
}
.umb-variant-switcher__item.--active:not(.--current) .umb-variant-switcher__name-wrapper:hover {
//background-color: @white !important;
cursor: default;
}
.umb-variant-switcher__item:focus .umb-variant-switcher__split-view,
.umb-variant-switcher__item:focus-within .umb-variant-switcher__split-view,
.umb-variant-switcher__item:hover .umb-variant-switcher__split-view,
.umb-variant-switcher__split-view:focus {
display: block;
cursor: pointer;
}
.umb-variant-switcher__item.--error {
.umb-variant-switcher__name {
color: @red;
&::after {
content: '!';
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5px;
top: -3px;
width: 14px;
height: 14px;
border-radius: 7px;
font-size: 8px;
text-align: center;
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
}
}
}
.umb-variant-switcher__name-wrapper {
font-size: 14px;
text-align: left;
flex: 1;
cursor: pointer;
background-color: transparent;
border: none;
}
.dropdown-menu>li {
> .umb-variant-switcher__name-wrapper {
padding-top: 10px;
padding-bottom: 10px;
}
> .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper {
padding-left: 5px;
}
}
.umb-variant-switcher__name {
display: block;
font-weight: 600;
margin-bottom: -2px;
}
.umb-variant-switcher__state {
font-size: 12px;
color: @gray-4;
}
.umb-variant-switcher__split-view {
font-size: 12px;
display: none;
padding: 20px 20px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
background-color: @white;
&:hover {
background-color: @ui-option-hover;
color: @ui-option-type-hover;
}
}
.umb-variant-switcher__sub-variants {
position: relative;
border-bottom: 1px solid @gray-9;
background-color: @gray-13;
/*
&::before {
content: "";
position: absolute;
z-index: 1;
top: 0px;
left: 20px;
width: 4px;
bottom: 14px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
background-color: @gray-8;
}
*/
.umb-variant-switcher__item {
border-bottom-color: @gray-10;
}
.umb-variant-switcher__item.--state-notCreated:not(.--active) {
.umb-variant-switcher__name-wrapper::after {
left: 55px;// overwrite left to achieve same indentation on the dashed border as language.
}
}
.umb-variant-switcher__name-wrapper {
margin-left: 48px;
padding-left: 20px;
padding-top: 10px;
padding-bottom: 10px;
&:hover {
color: @ui-option-type-hover;
background-color: @ui-option-hover;
}
.umb-variant-switcher__name {
//margin-right: 20px;
}
.umb-variant-switcher__state {
//flex: 0 0 200px;
}
}
}
@@ -24,16 +24,19 @@
margin-top: 0;
flex-grow: 0;
flex-shrink: 0;
padding: 20px 20px 0;
padding: 30px 30px 0;
}
.umb-overlay__section-header {
width: 100%;
margin-top:30px;
margin-bottom: 10px;
margin-bottom: 20px;
h5 {
display: inline;
font-size: 16px;
line-height: 16px;
font-weight: bold;
}
button {
@@ -66,7 +69,7 @@
flex-shrink: 1;
flex-basis: auto;
position: relative;
padding: 30px;
padding: 20px 30px;
background: @white;
max-height: calc(100vh - 170px);
overflow-y: auto;
@@ -1,6 +1,3 @@
.umb-itempicker .form-search {
margin-top:10px;
}
.umb-card-grid {
margin-top: 10px;
}
@@ -0,0 +1,26 @@
.umb-variant-selector-overlay {
.umb-variant-selector-entry {
.umb-form-check {
.umb-form-check__symbol {
margin-top: 2px;
}
}
}
.umb-variant-selector-entry__title {
font-weight: 600;
font-size: 14px;
.__secondarytitle {
font-weight: normal;
color: @gray-5;
}
}
.umb-variant-selector-entry__description {
display: block;
font-size: 12px;
color: @gray-4;
}
}
@@ -178,11 +178,11 @@
}
// Validation
.umb-sub-views-nav-item__action.-has-error,
.show-validation .umb-sub-views-nav-item__action.-has-error,
.show-validation .umb-sub-views-nav-item > a.-has-error {
color: @red;
&::after {
&::before {
background-color: @red;
}
}
@@ -23,6 +23,7 @@
}
.umb-group-builder__group.-placeholder {
width:100%;
min-height: 86px;
display: flex;
justify-content: center;
@@ -136,9 +137,10 @@ input.umb-group-builder__group-title-input:disabled:hover {
}
.umb-group-builder__group-add-property {
min-height: 46px;
margin-right: 45px;
width: calc(100% - 315px);
margin-left: 270px;
min-height: 46px;
border-radius: 3px;
display: flex;
@@ -344,8 +346,9 @@ input.umb-group-builder__group-title-input:disabled:hover {
.umb-group-builder__property-actions {
flex: 0 0 44px;
text-align: right;
margin-top: 7px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.umb-group-builder__property-action {
@@ -473,7 +476,7 @@ input.umb-group-builder__group-sort-value {
font-weight: bold;
resize: none;
line-height: 1.5em;
padding-left: 0;
padding: 0;
border: none;
&:focus {
@@ -498,45 +501,82 @@ input.umb-group-builder__group-sort-value {
text-decoration: none;
color: @ui-action-type-hover;
border-color: @ui-action-border-hover;
background-color: @ui-action-discreet-hover;
}
}
.editor {
.editor-wrapper {
margin-bottom: 10px;
}
.editor-icon-wrapper {
border: 1px solid @gray-8;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border-radius: 5px;
float: left;
margin-right: 20px;
.editor {
display: flex;
align-items: center;
align-content: stretch;
.icon {
font-size: 26px;
}
min-height: 80px;
border: 1px solid @gray-9;
color: @ui-action-discreet-type;
border-radius: @baseBorderRadius;
}
.editor-info {
flex: 1 0 auto;
text-align: left;
display: flex;
align-items: center;
max-width: calc(100% - 48px);
min-height: 80px;
color: @ui-action-discreet-type;
&:hover {
color: @ui-action-discreet-type-hover;
background-color: @ui-action-discreet-hover;
}
}
.editor-icon-wrapper {
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
flex: 0 0 60px;
padding-left: 10px;
.icon {
font-size: 32px;
}
}
.editor-details {
flex: 1 1 auto;
margin-top: 10px;
margin-bottom: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-details {
float: left;
margin-top: 10px;
.editor-name {
display: block;
font-weight: bold;
}
.editor-editor {
display: block;
font-size: 12px;
}
.editor-editor {
display: block;
font-size: 12px;
}
}
.editor-settings-icon {
font-size: 18px;
margin-top: 8px;
.editor-remove-icon {
flex: 0 0 48px;
width: 48px;
height: 48px;
font-size: 18px;
min-height: 80px;
color: @ui-action-discreet-type;
&:hover {
color: @ui-action-discreet-type-hover;
background-color: @ui-action-discreet-hover;
}
}
@@ -544,6 +584,11 @@ input.umb-group-builder__group-sort-value {
margin-bottom: 20px;
}
.editor-description {
margin-top: 20px;
padding: 0;
}
.editor-description,
.editor-validation-pattern {
min-width: 100%;
@@ -1,15 +1,18 @@
.umb-list--condensed {
.umb-list-item {
padding-top: 5px;
padding-bottom: 5px;
padding-top: 7px;
padding-bottom: 7px;
}
}
.umb-list-item {
border-bottom: 1px solid @gray-9;
border-bottom: 1px solid @gray-11;
padding-top: 15px;
padding-bottom: 15px;
display: flex;
&:last-of-type {
border-bottom: none;
}
}
a.umb-list-item:hover,
@@ -1,13 +1,17 @@
.umb-validation-label {
position: absolute;
top: 27px;
min-width: 100px;
max-width: 200px;
padding: 1px 5px;
z-index: 1;
top: 28px;
min-width: 80px;
max-width: 260px;
padding: 2px 6px;
background: @red;
color: @white;
font-size: 11px;
font-size: 12px;
line-height: 1.5em;
border-radius: @baseBorderRadius;
pointer-events: none;
user-select: none;
}
.umb-validation-label:after {
@@ -49,7 +49,7 @@
}
.date-wrapper-mini--checkbox{
margin: 0 0 0 26px;
margin: 0 0 0 28px;
}
.date-wrapper-mini__date {
@@ -62,6 +62,10 @@
&:first-of-type {
margin-left: 0;
}
.flatpickr-input > button:first-of-type {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.date-wrapper-mini__date .flatpickr-input > a {
@@ -83,6 +83,8 @@
@sand-5: #F3ECE8;// added 2019
@sand-6: #F6F1EF;// added 2019
@sand-7: #F9F7F5;// added 2019
@sand-8: #fbfaf9;// added 2019
@sand-9: #fdfcfc;// added 2019
// Additional Icon Colours
@@ -495,7 +495,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
//execute them sequentially
// set selected language to active
$scope.languages.forEach(language => {
Utilities.forEach($scope.languages, language => {
language.active = false;
});
+24 -2
View File
@@ -94,6 +94,26 @@
return JSON.stringify(obj, toJsonReplacer, pretty);
}
/**
* Equivalent to angular.fromJson
*/
const fromJson = (val) => {
if (!isString(val)) {
return val;
}
return JSON.parse(val);
}
/**
* Not equivalent to angular.forEach. But like the angularJS method this does not fail on null or undefined.
*/
const forEach = (obj, iterator) => {
if (obj) {
return obj.forEach(iterator);
}
return obj;
}
let _utilities = {
noop: noop,
copy: copy,
@@ -106,10 +126,12 @@
isString: isString,
isNumber: isNumber,
isObject: isObject,
toJson: toJson
fromJson: fromJson,
toJson: toJson,
forEach: forEach
};
if (typeof (window.Utilities) === 'undefined') {
window.Utilities = _utilities;
}
})(window);
})(window);
@@ -0,0 +1,106 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DataTypeConfigurationPickerController
* @function
*
* @description
* The controller for the content type editor data type configuration picker dialog
*/
(function() {
"use strict";
function DataTypeConfigurationPicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) {
var vm = this;
vm.configs = [];
vm.loading = true;
vm.newDataType = newDataType;
vm.pickDataType = pickDataType;
vm.close = close;
function activate() {
setTitle();
load();
}
function setTitle() {
if (!$scope.model.title) {
localizationService.localize("defaultdialogs_selectEditorConfiguration")
.then(function(data){
$scope.model.title = data;
});
}
}
function load() {
dataTypeResource.getGroupedDataTypes().then(function(configs) {
var filteredConfigs = [];
_.each(configs, function(configGroup) {
for(var i = 0; i<configGroup.length; i++) {
if (configGroup[i].alias === $scope.model.editor.alias) {
filteredConfigs.push(configGroup[i]);
}
}
}
);
vm.configs = filteredConfigs;
vm.loading = false;
});
}
function newDataType() {
var dataTypeSettings = {
propertyEditor: $scope.model.editor,
property: $scope.model.property,
contentTypeName: $scope.model.contentTypeName,
create: true,
view: "views/common/infiniteeditors/datatypesettings/datatypesettings.html",
submit: function(model) {
contentTypeResource.getPropertyTypeScaffold(model.dataType.id).then(function(propertyType) {
$scope.model.submit(model.dataType, propertyType, true);
editorService.close();
});
},
close: function() {
editorService.close();
}
};
editorService.open(dataTypeSettings);
}
function pickDataType(selectedConfig) {
selectedConfig.loading = true;
dataTypeResource.getById(selectedConfig.id).then(function(dataType) {
contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) {
selectedConfig.loading = false;
$scope.model.submit(dataType, propertyType, false);
});
});
}
function close() {
if($scope.model.close) {
$scope.model.close();
}
}
activate();
}
angular.module("umbraco").controller("Umbraco.Editors.DataTypeConfigurationPickerController", DataTypeConfigurationPicker);
})();
@@ -0,0 +1,70 @@
<div ng-controller="Umbraco.Editors.DataTypeConfigurationPickerController as vm">
<umb-editor-view data-element="editor-data-type-picker">
<form novalidate name="DataTypeConfigurationPickerForm" val-form-manager>
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-box>
<umb-box-content>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div ng-if="!vm.loading">
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="dataTypeConfig in vm.configs | orderBy:'name'"
data-element="datatypeconfig-{{dataTypeConfig.name}}"
ng-click="vm.pickDataType(dataTypeConfig)">
<div ng-if="dataTypeConfig.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataTypeConfig.name }}">
<span>
<i class="{{ dataTypeConfig.icon }}" ng-class="{'icon-autofill': dataTypeConfig.icon == null}"></i>
{{ dataTypeConfig.name }}
</span>
</a>
</li>
</ul>
<ul class="umb-card-grid -three-in-row">
<li ng-click="vm.newDataType()">
<a class="umb-card-grid-item-slot" href="" title="Create a new configuration of {{ model.dataType.name }}">
<span>
<i class="icon icon-add"></i>
Create new
</span>
</a>
</li>
</ul>
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</form>
</umb-editor-view>
</div>
@@ -13,30 +13,23 @@
function DataTypePicker($scope, $filter, dataTypeResource, contentTypeResource, localizationService, editorService) {
var vm = this;
vm.showDataTypes = true;
vm.dataTypes = [];
vm.loading = true;
vm.loadingConfigs = false;
vm.searchTerm = "";
vm.showTabs = false;
vm.tabsLoaded = 0;
vm.typesAndEditors = [];
vm.userConfigured = [];
vm.loading = false;
vm.tabs = [];
vm.labels = {};
vm.onTabChange = onTabChange;
vm.filterItems = filterItems;
vm.showDetailsOverlay = showDetailsOverlay;
vm.hideDetailsOverlay = hideDetailsOverlay;
vm.pickEditor = pickEditor;
vm.searchResult = null;
vm.viewOptionsForEditor = viewOptionsForEditor;
vm.pickDataType = pickDataType;
vm.pickEditor = pickEditor;
vm.close = close;
vm.searchTermChanged = searchTermChanged;
function activate() {
setTitle();
loadTabs();
getGroupedDataTypes();
getGroupedPropertyEditors();
loadTypes();
}
function setTitle() {
@@ -44,128 +37,104 @@
localizationService.localize("defaultdialogs_selectEditor")
.then(function(data){
$scope.model.title = data;
});
}
);
}
}
function loadTabs() {
var labels = ["contentTypeEditor_availableEditors", "contentTypeEditor_reuse"];
localizationService.localizeMany(labels)
.then(function(data){
vm.labels.availableDataTypes = data[0];
vm.labels.reuse = data[1];
vm.tabs = [{
active: true,
id: 1,
label: vm.labels.availableDataTypes,
alias: "Default",
typesAndEditors: []
}, {
active: false,
id: 2,
label: vm.labels.reuse,
alias: "Reuse",
userConfigured: []
}];
});
}
function getGroupedPropertyEditors() {
vm.loading = true;
dataTypeResource.getGroupedPropertyEditors().then(function(data) {
vm.tabs[0].typesAndEditors = data;
vm.typesAndEditors = data;
vm.tabsLoaded = vm.tabsLoaded + 1;
checkIfTabContentIsLoaded();
});
}
function getGroupedDataTypes() {
vm.loading = true;
dataTypeResource.getGroupedDataTypes().then(function(data) {
vm.tabs[1].userConfigured = data;
vm.userConfigured = data;
vm.tabsLoaded = vm.tabsLoaded + 1;
checkIfTabContentIsLoaded();
});
}
function checkIfTabContentIsLoaded() {
if (vm.tabsLoaded === 2) {
function loadTypes() {
dataTypeResource.getGroupedPropertyEditors().then(function(dataTypes) {
vm.dataTypes = dataTypes;
vm.loading = false;
vm.showTabs = true;
}
}
function onTabChange(selectedTab) {
vm.tabs.forEach(function(tab) {
tab.active = false;
});
selectedTab.active = true;
}
function loadConfigurations() {
vm.loading = true;
vm.loadingConfigs = true;
dataTypeResource.getGroupedDataTypes().then(function(configs) {
vm.configs = configs;
vm.loading = false;
performeSearch();
});
}
function filterItems() {
// clear item details
$scope.model.itemDetails = null;
if (vm.searchTerm) {
vm.showTabs = false;
var regex = new RegExp(vm.searchTerm, "i");
var userConfigured = filterCollection(vm.userConfigured, regex),
typesAndEditors = filterCollection(vm.typesAndEditors, regex);
var totalResults = _.reduce(_.pluck(_.union(userConfigured, typesAndEditors), 'count'), (m, n) => m + n, 0);
vm.filterResult = {
userConfigured: userConfigured,
typesAndEditors: typesAndEditors,
totalResults: totalResults
};
function searchTermChanged() {
vm.showDataTypes = (vm.searchTerm === "");
if(vm.loadingConfigs !== true) {
loadConfigurations()
} else {
vm.filterResult = null;
vm.showTabs = true;
performeSearch();
}
}
function performeSearch() {
if (vm.searchTerm) {
if (vm.configs) {
var regex = new RegExp(vm.searchTerm, "i");
vm.searchResult = {
configs: filterCollection(vm.configs, regex),
dataTypes: filterCollection(vm.dataTypes, regex)
};
}
} else {
vm.searchResult = null;
}
}
function filterCollection(collection, regex) {
return _.map(_.keys(collection), function (key) {
var filteredDataTypes = $filter('filter')(collection[key], function (dataType) {
return regex.test(dataType.name) || regex.test(dataType.alias);
});
return {
group: key,
count: filteredDataTypes.length,
dataTypes: filteredDataTypes
entries: $filter('filter')(collection[key], function (dataType) {
return regex.test(dataType.name) || regex.test(dataType.alias);
})
}
});
}
function showDetailsOverlay(property) {
function viewOptionsForEditor(editor) {
var dataTypeConfigurationPicker = {
editor: editor,
property: $scope.model.property,
contentTypeName: $scope.model.contentTypeName,
view: "views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.html",
size: "small",
submit: function(dataType, propertyType, isNew) {
submit(dataType, propertyType, isNew);
editorService.close();
},
close: function() {
editorService.close();
}
};
var propertyDetails = {};
propertyDetails.icon = property.icon;
propertyDetails.title = property.name;
editorService.open(dataTypeConfigurationPicker);
$scope.model.itemDetails = propertyDetails;
}
function hideDetailsOverlay() {
$scope.model.itemDetails = null;
function pickDataType(selectedDataType) {
selectedDataType.loading = true;
dataTypeResource.getById(selectedDataType.id).then(function(dataType) {
contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) {
selectedDataType.loading = false;
submit(dataType, propertyType, false);
});
});
}
function pickEditor(propertyEditor) {
@@ -188,16 +157,7 @@
};
editorService.open(dataTypeSettings);
}
function pickDataType(selectedDataType) {
selectedDataType.loading = true;
dataTypeResource.getById(selectedDataType.id).then(function(dataType) {
contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) {
selectedDataType.loading = false;
submit(dataType, propertyType, false);
});
});
}
function submit(dataType, propertyType, isNew) {
@@ -213,9 +173,9 @@
$scope.model.submit($scope.model);
}
function close() {
if ($scope.model.close) {
if($scope.model.close) {
$scope.model.close();
}
}
@@ -15,127 +15,81 @@
<umb-box>
<umb-box-content>
<!-- FILTER -->
<div class="umb-control-group -no-border">
<div class="form-search">
<i class="icon-search"></i>
<input type="text"
style="width: 100%"
ng-change="vm.filterItems()"
ng-model="vm.searchTerm"
class="umb-search-field search-query search-input input-block-level"
localize="placeholder"
placeholder="@placeholders_filter"
umb-auto-focus
no-dirty-check />
style="width: 100%"
ng-change="vm.searchTermChanged()"
ng-model="vm.searchTerm"
class="umb-search-field search-query search-input input-block-level"
localize="placeholder"
placeholder="@placeholders_search"
umb-auto-focus
no-dirty-check />
</div>
</div>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<umb-load-indicator ng-if="vm.loading === true"></umb-load-indicator>
<!-- TABS -->
<div ng-if="vm.showTabs">
<umb-tabs-nav
ng-if="vm.tabs"
tabs="vm.tabs"
on-tab-change="vm.onTabChange(tab)">
</umb-tabs-nav>
<umb-tab-content ng-repeat="tab in vm.tabs" tab="tab" ng-if="tab.active">
<div ng-if="tab.alias==='Default'">
<div ng-repeat="(key,value) in tab.typesAndEditors">
<h5>{{key}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in value | orderBy:'name'"
data-element="editor-{{systemDataType.name}}"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)"
class="cursor-pointer">
<span class="umb-card-grid-item" title="{{ systemDataType.name }}">
<span>
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
</span>
</span>
</li>
</ul>
</div>
</div>
<div ng-if="tab.alias==='Reuse'">
<div ng-repeat="(key,value) in tab.userConfigured">
<h5>{{key}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="editor-{{dataType.name}}"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)"
class="cursor-pointer">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<span class="umb-card-grid-item" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</span>
</li>
</ul>
</div>
</div>
</umb-tab-content>
<div ng-if="vm.loading === false && vm.showDataTypes === true">
<div ng-repeat="(key,value) in vm.dataTypes">
<h5>{{key | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="datatype-{{dataType.name}}"
ng-click="vm.viewOptionsForEditor(dataType)">
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</a>
</li>
</ul>
</div>
</div>
<!-- FILTER RESULTS -->
<div ng-if="vm.filterResult">
<div ng-if="vm.filterResult.totalResults > 0">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_reuse"></localize></h5>
<div ng-repeat="result in vm.filterResult.userConfigured">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)"
class="cursor-pointer">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<span class="umb-card-grid-item" title="{{dataType.name}}">
<span>
<i class="{{dataType.icon}}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{dataType.name}}
</span>
<!-- SEARCH RESULTS -->
<div ng-if="vm.loading === false && vm.showDataTypes === false && vm.searchResult !== null">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_searchResultSettings"></localize></h5>
<div ng-repeat="result in vm.searchResult.configs">
<div ng-if="result.entries.length > 0">
<h5>{{result.group | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</li>
</ul>
</div>
</a>
</li>
</ul>
</div>
</div>
<div ng-if="vm.filterResult.totalResults > 0">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_availableEditors"></localize></h5>
<div ng-repeat="result in vm.filterResult.typesAndEditors">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)"
class="cursor-pointer">
<span class="umb-card-grid-item" title="{{systemDataType.name}}">
<span>
<i class="{{systemDataType.icon}}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{systemDataType.name}}
</span>
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_searchResultEditors"></localize></h5>
<div ng-repeat="result in vm.searchResult.dataTypes">
<div ng-if="result.entries.length > 0">
<h5>{{result.group | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'"
ng-click="vm.pickEditor(dataType)">
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</li>
</ul>
</div>
</a>
</li>
</ul>
</div>
</div>
<umb-empty-state position="center" ng-if="vm.filterResult.totalResults === 0">
<localize key="general_searchNoResult"></localize>
</umb-empty-state>
</div>
</umb-box-content>
@@ -21,7 +21,7 @@
<umb-box ng-if="!vm.loadingDataType">
<umb-box-content>
<div class="umb-control-group" ng-if="vm.dataType.id !== 0">
<div class="control-group umb-control-group" ng-if="vm.dataType.id !== 0">
<i class="icon-alert red"></i>
<strong class="red"><localize key="contentTypeEditor_allDocumentTypes"></localize></strong> using this editor will get updated with the new settings.
</div>
@@ -70,8 +70,8 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi
//detect if it is a json string
if (val.detectIsJson()) {
try {
//Parse it to json
prop.value = JSON.parse(val);
//Parse it from json
prop.value = Utilities.fromJson(val);
}
catch (e) {
// not json
@@ -30,6 +30,7 @@
vm.close = close;
vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants;
vm.toggleValidation = toggleValidation;
vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile;
vm.toggleMemberCanEdit = toggleMemberCanEdit;
@@ -114,7 +115,7 @@
property: $scope.model.property,
contentTypeName: $scope.model.contentTypeName,
view: "views/common/infiniteeditors/datatypepicker/datatypepicker.html",
size: "small",
size: "medium",
submit: function(model) {
$scope.model.updateSameDataTypes = model.updateSameDataTypes;
@@ -248,6 +249,10 @@
$scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant);
}
function toggleAllowSegmentVariants() {
$scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant);
}
function toggleValidation() {
$scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory);
}
@@ -18,8 +18,8 @@
<div class="content-type-editor-dialog edit-property-settings">
<div class="umb-control-group" ng-if="!model.property.locked">
<div class="control-group">
<div class="umb-control-group -no-border" ng-if="!model.property.locked">
<div class="control-group -no-margin">
<textarea class="editor-label"
data-element="property-name"
name="propertyLabel"
@@ -36,12 +36,12 @@
<span class="umb-validation-label" ng-message="required">Required label</span>
</div>
</div>
<div class="control-group -no-margin">
<div class="control-group -no-margin" style="margin-top:2px;">
<umb-generate-alias enable-lock="true" alias-from="model.property.label" alias="model.property.alias"></umb-generate-alias>
</div>
</div>
<div class="umb-control-group control-group">
<div class="umb-control-group control-group -no-border">
<textarea data-element="property-description"
class="editor-description"
ng-model="model.property.description"
@@ -51,35 +51,42 @@
umb-auto-resize>
</textarea>
</div>
<div class="editor-wrapper umb-control-group control-group" ng-model="model.property.editor" val-require-component ng-if="!model.property.locked">
<a data-element="editor-add" href="" ng-if="!model.property.editor" class="editor-placeholder" hotkey="alt+shift+e" ng-click="vm.openDataTypePicker(model.property)">
<localize key="shortcuts_addEditor"></localize>
</a>
<div class="editor clearfix" ng-if="model.property.editor">
<a href="" class="editor-icon-wrapper" ng-click="vm.openDataTypePicker(model.property)">
<i class="icon {{ model.property.dataTypeIcon }}" ng-class="{'icon-autofill': model.property.dataTypeIcon == null}"></i>
</a>
<div class="editor-details">
<a href="" class="editor-name" ng-click="vm.openDataTypePicker(model.property)">{{ model.property.dataTypeName }}</a>
<a href="" class="editor-editor" ng-click="vm.openDataTypePicker(model.property)">{{ model.property.editor }}</a>
<localize key="defaultdialogs_selectEditor"></localize>
<div ng-messages="model.property.editor" show-validation-on-submit>
<span class="umb-validation-label" ng-message="required">Required editor</span>
</div>
<a href class="editor-settings-icon pull-right"
ng-click="vm.openDataTypeSettings(model.property)"
hotkey="alt+shift+d"
ng-if="model.property.editor">
<i class="icon icon-settings"></i>
</a>
</a>
<div class="editor clearfix" ng-if="model.property.editor">
<button class="btn-reset editor-info" ng-click="vm.openDataTypeSettings(model.property)">
<div class="editor-icon-wrapper">
<i class="icon {{ model.property.dataTypeIcon }}" ng-class="{'icon-autofill': model.property.dataTypeIcon == null}"></i>
</div>
<div class="editor-details">
<span class="editor-name">{{ model.property.dataTypeName }}</span>
<span class="editor-editor">{{ model.property.editor }}</span>
</div>
</button>
<button class="editor-remove-icon btn-reset pull-right"
ng-click="vm.openDataTypePicker(model.property)"
hotkey="alt+shift+d"
localize="title"
title="@actions_changeDataType"
>
<i class="icon icon-wrong"></i>
</button>
</div>
</div>
<div class="umb-control-group clearfix" ng-if="!model.property.locked">
<h5><localize key="validation_validation"></localize></h5>
@@ -132,19 +139,29 @@
ng-keypress="vm.submitOnEnter($event)" />
</div>
<div class="umb-control-group clearfix" ng-if="model.contentType === 'documentType' && model.contentTypeAllowCultureVariant">
<h5><localize key="contentTypeEditor_variantsHeading" /></h5>
<umb-toggle data-element="permissions-allow-culture-variant"
checked="model.property.allowCultureVariant"
on-click="vm.toggleAllowCultureVariants()"
>
</umb-toggle>
<div class="umb-control-group clearfix -no-border" ng-if="model.contentType === 'documentType' && model.contentTypeAllowCultureVariant">
<h5><localize key="contentTypeEditor_cultureVariantHeading" /></h5>
<umb-toggle data-element="permissions-allow-culture-variant"
checked="model.property.allowCultureVariant"
on-click="vm.toggleAllowCultureVariants()"
>
</umb-toggle>
</div>
<div class="umb-control-group clearfix" ng-if="model.contentType === 'memberType'">
<div class="umb-control-group clearfix" ng-if="model.contentType === 'documentType' && model.contentTypeAllowSegmentVariant">
<h5><localize key="contentTypeEditor_segmentVariantHeading" /></h5>
<umb-toggle data-element="permissions-allow-segment-variant"
checked="model.property.allowSegmentVariant"
on-click="vm.toggleAllowSegmentVariants()"
>
</umb-toggle>
</div>
<div class="umb-control-group clearfix -no-border" ng-if="model.contentType === 'memberType'">
<h5><localize key="general_options"></localize></h5>
@@ -177,7 +194,7 @@
checked="model.property.isSensitiveData"
on-click="vm.toggleIsSensitiveData()">
</umb-toggle>
</div>
</div>
@@ -207,7 +224,7 @@
label-key="general_submit"
action="vm.submit(model)">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
@@ -9,9 +9,9 @@
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-load-indicator
ng-if="vm.loading">
</umb-load-indicator>
@@ -21,21 +21,21 @@
<div ng-if="model.node.variants.length > 1">
<h5><localize key="general_language"></localize></h5>
<select
<select
class="input-block-level"
ng-model="vm.selectedLanguage"
ng-options="variant as variant.language.name for variant in model.node.variants track by variant.language.culture"
ng-options="variant as variant.displayName for variant in model.node.variants track by variant.language.culture"
ng-change="vm.changeLanguage(vm.selectedLanguage)">
</select>
</div>
<div>
<h5><localize key="rollback_currentVersion"></localize></h5>
<p>{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})</p>
<h5><localize key="rollback_rollbackTo"></localize></h5>
<select
<select
class="input-block-level"
ng-model="vm.selectedVersion"
ng-options="version.displayValue for version in vm.previousVersions track by version.versionId"
@@ -48,7 +48,7 @@
<h5>Changes</h5>
<small style="margin-bottom: 15px; display: block;"><localize key="rollback_diffHelp"></localize></small>
<table class="table table-condensed table-bordered">
<tbody>
<tr>
@@ -73,7 +73,7 @@
</tr>
</tbody>
</table>
</div>
</umb-box-content>
@@ -189,7 +189,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
if ($scope.model.filter.startsWith("{")) {
$scope.model.filterAdvanced = true;
//convert to object
$scope.model.filter = JSON.parse($scope.model.filter);
$scope.model.filter = Utilities.fromJson($scope.model.filter);
}
}
}
@@ -10,6 +10,7 @@
page="page"
content="content"
culture="culture"
segment="segment"
on-select-app="appChanged(app)"
on-select-app-anchor="appAnchorChanged(app, anchor)"
on-back="onBack()"
@@ -61,9 +62,9 @@
shortcut="ctrl+s"
add-ellipsis="{{page.saveButtonEllipsis}}">
</umb-button>
<umb-button-group
ng-if="defaultButton && !content.trashed && !content.isElement"
<umb-button-group
ng-if="defaultButton && !content.trashed && !content.isElement"
button-style="success"
default-button="defaultButton"
sub-buttons="subButtons"
@@ -44,7 +44,7 @@
<umb-box-header
title="{{historyLabel}}">
<umb-button
ng-hide="node.trashed"
type="button"
@@ -64,7 +64,7 @@
<umb-load-indicator ng-show="loadingAuditTrail"></umb-load-indicator>
<div ng-show="auditTrail.length === 0" style="padding: 10px;">
<umb-empty-state
<umb-empty-state
position="center"
size="small">
<localize key="content_noChanges"></localize>
@@ -79,7 +79,7 @@
<div class="history-item__break">
<div class="history-item__avatar">
<umb-avatar
<umb-avatar
color="secondary"
size="xs"
name="{{item.userName}}"
@@ -105,7 +105,7 @@
<localize key="auditTrails_{{ item.logType | lowercase }}" tokens="[item.parameters]">{{ item.comment }}</localize>
</span>
</div>
</div>
@@ -126,7 +126,7 @@
</div>
<div class="umb-package-details__sidebar">
<umb-box data-element="node-info-general">
<umb-box-header title-key="general_general"></umb-box-header>
<umb-box-content class="block-form">
@@ -11,13 +11,13 @@
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">
show-inherit="propertyEditorDisabled(property)"
inherits-from="defaultVariant.displayName">
<div ng-class="{'o-40 cursor-not-allowed': content.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
<div ng-class="{'o-40 cursor-not-allowed': propertyEditorDisabled(property) }">
<umb-property-editor
model="property"
preview="content.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
preview="propertyEditorDisabled(property)">
</umb-property-editor>
</div>
@@ -1,7 +1,6 @@
<div class="umb-split-views">
<div class="umb-split-view"
ng-repeat="editor in vm.editors track by editor.culture"
ng-class="{'umb-split-view--collapsed': editor.collapsed}">
ng-repeat="editor in vm.editors track by editor.compositeId">
<umb-variant-content
page="vm.page"
@@ -9,7 +8,6 @@
editor="editor"
editor-index="$index"
editor-count="vm.editors.length"
open-variants="vm.openVariants"
on-open-split-view="vm.openSplitView(variant)"
on-close-split-view="vm.closeSplitView($index)"
on-select-variant="vm.selectVariant(variant, $index)"
@@ -5,16 +5,16 @@
<div class="umb-split-view__content" ng-show="!vm.editor.loading">
<ng-form name="contentHeaderForm" ng-if="vm.editor.content.apps.length > 0">
<ng-form name="contentHeaderForm" ng-if="vm.content.apps.length > 0">
<umb-editor-content-header
menu="vm.page.menu"
hide-menu="vm.page.hideActionsMenu"
name="vm.editor.content.name"
name-disabled="vm.nameDisabled"
content="vm.editor.content"
editor="vm.editor"
content="vm.content"
on-select-navigation-item="vm.selectApp(item)"
on-select-anchor-item="vm.selectAppAnchor(item, anchor)"
open-variants="vm.openVariants"
hide-change-variant="vm.page.hideChangeVariant"
show-back-button="vm.showBackButton()"
on-back="vm.onBack()"
@@ -26,7 +26,7 @@
</umb-editor-content-header>
</ng-form>
<umb-editor-container ng-if="vm.editor.content.apps.length > 0">
<umb-editor-container ng-if="vm.content.apps.length > 0">
<!-- Deleted Context Message Bar (Displayed when viewing node in recycle bin) -->
<div ng-show="vm.content.trashed" class="umb-editor--trashed-message">
@@ -34,14 +34,14 @@
</div>
<div class="umb-editor-sub-views">
<div ng-repeat="app in vm.editor.content.apps track by app.alias">
<umb-editor-sub-view model="app" content="vm.content" />
<div ng-repeat="app in vm.editor.variantApps track by app.alias">
<umb-editor-sub-view model="app" content="vm.content" variant-content="vm.editor.content"/>
</div>
</div>
</umb-editor-container>
<umb-empty-state
ng-if="vm.editor.content.apps.length === 0"
ng-if="vm.content.apps.length === 0"
position="center">
<localize key="content_noProperties"></localize>
</umb-empty-state>
@@ -36,26 +36,52 @@
required
aria-required="true"
aria-invalid="{{contentForm.headerNameForm.headerName.$invalid ? true : false}}"
autocomplete="off" maxlength="255" />
autocomplete="off"
maxlength="255" />
</ng-form>
<button type="button" ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle umb-outline" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
<span>{{vm.currentVariant.language.name}}</span>
<button type="button" ng-if="vm.hasVariants === true && hideChangeVariant !== true" class="umb-variant-switcher__toggle umb-outline" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
<span ng-bind="editor.content.displayName"></span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</button>
<span ng-if="hideChangeVariant" class="umb-variant-switcher__toggle">
<span>{{vm.currentVariant.language.name}}</span>
<span ng-if="vm.hasVariants === true && hideChangeVariant" class="umb-variant-switcher__toggle">
<span ng-bind="editor.content.displayName"></span>
</span>
<umb-dropdown ng-if="vm.dropdownOpen" style="min-width: 100%; max-height: 250px; overflow-y: auto; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'--current': variant.active, '--not-allowed': variantIsOpen(variant.language.culture), '--error': variantHasError(variant.language.culture)}" ng-repeat="variant in content.variants">
<button class="umb-variant-switcher__name-wrapper umb-outline umb-outline--thin" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name">{{variant.language.name}}</span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
<umb-dropdown ng-if="vm.dropdownOpen" class="umb-variant-switcher" ng-class="{'--has-sub-variants': vm.hasSubVariants === true}" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item
ng-repeat-start="entry in vm.variantMenu track by entry.key"
class="umb-variant-switcher__item"
ng-class="{'--current': entry.variant === editor.content, '--active': entry.variant.active && vm.dropdownOpen, '--error': entry.variant.active !== true && entry.variant.hasError, '--state-notCreated':entry.variant.state==='NotCreated' && entry.variant.name == null, '--state-draft':entry.variant.state==='Draft' || (entry.variant.state==='NotCreated' && entry.variant.name != null)}"
>
<button type="button" ng-if="entry.subVariants && entry.subVariants.length > 0" class="umb-variant-switcher__item-expand-button umb-outline" ng-click="entry.open = !entry.open">
<i class="icon icon-navigation-down" ng-if="entry.open"></i>
<i class="icon icon-navigation-right" ng-if="!entry.open"></i>
</button>
<div ng-if="splitViewOpen !== true && !variant.active" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>
<button type="button" class="umb-variant-switcher__name-wrapper umb-outline" ng-click="selectVariant($event, entry.variant)" prevent-default>
<span class="umb-variant-switcher__name" ng-bind="entry.variant.displayName"></span>
<umb-variant-state variant="entry.variant" class="umb-variant-switcher__state"></umb-variant-state>
</button>
<div ng-if="splitViewOpen !== true && !entry.variant.active" class="umb-variant-switcher__split-view umb-outline" ng-click="openInSplitView($event, entry.variant)">Open in split view</div>
</umb-dropdown-item>
<div
ng-repeat-end
ng-if="entry.open === true && entry.subVariants && entry.subVariants.length > 0"
class="umb-variant-switcher__sub-variants umb-outline"
>
<umb-dropdown-item
ng-repeat="subVariant in entry.subVariants track by $index"
class="umb-variant-switcher__item"
ng-class="{'--current': subVariant === editor.content, '--active': subVariant.active && vm.dropdownOpen, '--error': subVariant.active !== true && subVariant.hasError, '--state-notCreated':subVariant.state==='NotCreated', '--state-draft':subVariant.state==='Draft'}"
>
<button type="button" class="umb-variant-switcher__name-wrapper umb-outline" ng-click="selectVariant($event, subVariant)" prevent-default>
<span class="umb-variant-switcher__name" ng-bind="subVariant.segment"></span>
<umb-variant-state variant="subVariant" class="umb-variant-switcher__state"></umb-variant-state>
</button>
<div ng-if="splitViewOpen !== true && !subVariant.active" class="umb-variant-switcher__split-view umb-outline" ng-click="openInSplitView($event, subVariant)">Open in split view</div>
</umb-dropdown-item>
</div>
</umb-dropdown>
</div>
@@ -70,10 +96,10 @@
</a>
</div>
<div ng-if="content.apps && splitViewOpen !== true">
<div ng-if="editor.variantApps && splitViewOpen !== true">
<umb-editor-navigation
data-element="editor-sub-views"
navigation="content.apps"
navigation="editor.variantApps"
on-select="selectNavigationItem(item)"
on-anchor-select="selectAnchorItem(item, anchor)">
</umb-editor-navigation>
@@ -1,6 +1,6 @@
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}} umb-overlay--{{size}}" on-outside-click="outSideClick()" role="dialog" aria-labelledby="umb-overlay-title" aria-describedby="umb-overlay-description">
<ng-form class="umb-overlay__form" name="overlayForm" novalidate val-form-manager>
<div data-element="overlay-header" class="umb-overlay-header">
<div data-element="overlay-header" class="umb-overlay-header" ng-show="!model.hideHeader">
<h1 class="umb-overlay__title" id="umb-overlay-title">{{model.title}}</h1>
<p class="umb-overlay__subtitle" id="umb-overlay-description" ng-if="model.subtitle">{{model.subtitle}}</p>
</div>
@@ -9,7 +9,7 @@
alias="compositions"
ng-if="compositions !== false"
type="button"
button-style="action"
button-style="outline"
label-key="contentTypeEditor_compositions"
icon="icon-merge"
action="openCompositionsDialog()"
@@ -22,7 +22,7 @@
alias="reorder"
ng-if="sorting !== false"
type="button"
button-style="action"
button-style="outline"
label-key="{{sortingButtonKey}}"
icon="icon-navigation"
action="toggleSortingMode();"
@@ -37,16 +37,16 @@
<localize key="contentTypeEditor_noGroups"></localize>
</div>
<a ng-if="!sortingMode" hotkey="alt+shift+p" ng-click="addPropertyToActiveGroup()"></a>
<a ng-if="!sortingMode" hotkey="alt+shift+p" ng-click="addPropertyToActiveGroup()"></a>
<ul class="umb-group-builder__groups" ui-sortable="sortableOptionsGroup" ng-model="model.groups">
<li ng-repeat="tab in model.groups" ng-class="{'umb-group-builder__group-sortable': sortingMode}" data-element="group-{{tab.name}}">
<!-- TAB INIT STATE -->
<a href="" class="umb-group-builder__group -placeholder" hotkey="alt+shift+g" ng-click="addGroup(tab)" ng-if="tab.tabState=='init' && !sortingMode" data-element="group-add">
<button href="" class="umb-group-builder__group -placeholder" hotkey="alt+shift+g" ng-click="addGroup(tab)" ng-if="tab.tabState=='init' && !sortingMode" data-element="group-add">
<localize key="contentTypeEditor_addGroup"></localize>
</a>
</button>
<!-- TAB ACTIVE OR INACTIVE STATE -->
<div class="umb-group-builder__group" ng-if="tab.tabState !== 'init'" ng-class="{'-active':tab.tabState=='active', '-inherited': tab.inherited, 'umb-group-builder__group-handle -sortable': sortingMode && !tab.inherited}" tabindex="0" ng-focus="activateGroup(tab)">
@@ -117,7 +117,7 @@
<li data-element="property-{{property.alias}}" ng-class="{'umb-group-builder__property-sortable': sortingMode && !property.inherited}" ng-repeat="property in tab.properties">
<!-- Add new property -->
<a href=""
<button href=""
data-element="property-add"
class="umb-group-builder__group-add-property"
ng-if="property.propertyState=='init' && !sortingMode"
@@ -125,7 +125,7 @@
ng-focus="activateGroup(tab)"
focus-when="{{property.focus}}">
<localize key="contentTypeEditor_addProperty"></localize>
</a>
</button>
<div class="umb-group-builder__property" ng-if="property.propertyState!=='init'" ng-class="{'-active': property.dialogIsOpen, '-active': property.propertyState=='active', '-inherited': property.inherited, '-locked': property.locked, 'umb-group-builder__property-handle -sortable': sortingMode && !property.inherited, '-sortable-locked': sortingMode && property.inherited}">
@@ -215,7 +215,12 @@
<div class="umb-group-builder__property-tag -white" ng-if="property.allowCultureVariant">
<i class="icon-shuffle umb-group-builder__property-tag-icon"></i>
<localize key="contentTypeEditor_variantsHeading"></localize>
<localize key="contentTypeEditor_cultureVariantLabel"></localize>
</div>
<div class="umb-group-builder__property-tag -white" ng-if="property.allowSegmentVariant">
<i class="icon-shuffle umb-group-builder__property-tag-icon"></i>
<localize key="contentTypeEditor_segmentVariantLabel"></localize>
</div>
</div>
@@ -7,35 +7,62 @@
//if we make the viewModel the variant itself, we end up with a circular reference in the models which isn't ideal
// (i.e. variant.apps[contentApp].viewModel = variant)
//so instead since we already have access to the content, we can just get the variant directly by the index.
var unbindLanguageWatcher = function() {};
var unbindSegmentWatcher = function() {};
var timeout = null;
var vm = this;
vm.loading = true;
function onInit() {
//get the variant by index (see notes above)
vm.content = $scope.content.variants[$scope.model.viewModel];
serverValidationManager.notify();
vm.loading = false;
timeout = null;// ensure timeout is set to null, so we know that its not running anymore.
//if this variant has a culture/language assigned, then we need to watch it since it will change
//if the language drop down changes and we need to re-init
if (vm.content.language) {
$scope.$watch(function () {
return vm.content.language.culture;
if ($scope.variantContent) {
if ($scope.variantContent.language) {
unbindLanguageWatcher = $scope.$watch(function () {
return $scope.variantContent.language.culture;
}, function (newVal, oldVal) {
if (newVal !== oldVal) {
requestUpdate();
}
});
}
unbindSegmentWatcher = $scope.$watch(function () {
return $scope.variantContent.segment;
}, function (newVal, oldVal) {
if (newVal !== oldVal) {
vm.loading = true;
// TODO: Can we minimize the flicker?
$timeout(function () {
onInit();
}, 100);
requestUpdate();
}
});
}
}
function requestUpdate() {
if (timeout === null) {
vm.loading = true;
// TODO: Can we minimize the flicker?
timeout = $timeout(function () {
onInit();
}, 100);
}
}
onInit();
$scope.$on("$destroy", function() {
unbindLanguageWatcher();
unbindSegmentWatcher();
$timeout.cancel(timeout);
});
}
angular.module("umbraco").controller("Umbraco.Editors.Content.Apps.ContentController", ContentAppContentController);
@@ -2,7 +2,8 @@
<umb-tabbed-content
ng-if="!vm.loading"
content="vm.content">
content-node-model="content"
content="variantContent">
</umb-tabbed-content>
</div>
@@ -64,6 +64,9 @@ function contentCreateController($scope,
language as what is selected in the tree */
.search("cculture", mainCulture)
/* when we create a new node we must make sure that any previously
opened segments is reset */
.search("csegment", null)
/* when we create a new node we must make sure that any previously
used blueprint is reset */
.search("blueprintId", null);
close();
@@ -28,6 +28,7 @@ function ContentEditController($scope, $routeParams, contentResource) {
$scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create;
//load the default culture selected in the main tree if any
$scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
$scope.segment = $routeParams.csegment ? $routeParams.csegment : null;
//Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered.
//This is so we can listen to changes on the cculture parameter since that will not cause a route change
@@ -36,6 +37,7 @@ function ContentEditController($scope, $routeParams, contentResource) {
//will not cause a route change and so we can update the isNew and contentId flags accordingly.
$scope.$on('$routeUpdate', function (event, next) {
$scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture;
$scope.segment = next.params.csegment ? next.params.csegment : null;
$scope.isNew = next.params.create === "true";
$scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id;
});
@@ -8,6 +8,7 @@
tree-alias="content"
is-new="isNew"
culture="culture"
segment="segment"
infinite-model="model">
</content-editor>
</div>
@@ -5,193 +5,171 @@
var vm = this;
vm.loading = true;
vm.hasPristineVariants = false;
vm.isNew = true;
vm.changeSelection = changeSelection;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
/** Returns true if publish meets the requirements of mandatory languages */
function canPublish() {
var possible = false;
var hasSomethingToPublish = false;
for (var i = 0; i < vm.variants.length; i++) {
var variant = vm.variants[i];
var state = canVariantPublish(variant);
if (state === true) {
possible = true;
}
if (state === false) {
// if varaint is mandatory and not already published:
if (variant.publish === false && notPublishedMandatoryFilter(variant)) {
return false;
}
if (variant.publish === true) {
hasSomethingToPublish = true;
}
}
return possible;
}
/** Returns true if publishing is possible based on if the variant is a un-published mandatory language */
function canVariantPublish(variant) {
//if this variant will show up in the publish-able list
var publishable = dirtyVariantFilter(variant);
var published = !(variant.state === "NotCreated" || variant.state === "Draft");
// is this variant mandatory:
if (variant.language.isMandatory && !published && !variant.publish) {
//if a mandatory variant isn't published or set to be published
//then we cannot continue
return false;
}
// is this variant selected for publish:
if (variant.publish === true) {
return publishable;
}
return null;
return hasSomethingToPublish;
}
function changeSelection(variant) {
// update submit button state:
$scope.model.disableSubmitButton = !canPublish();
//need to set the Save state to true if publish is true
//need to set the Save state to same as publish.
variant.save = variant.publish;
variant.willPublish = canVariantPublish(variant);
}
function dirtyVariantFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * the active one
// * it's editor is in a $dirty state
// * it has pending saves
// * it is unpublished
// * it is in NotCreated state
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated");
}
function hasAnyData(variant) {
function hasAnyDataFilter(variant) {
if (variant.name == null || variant.name.length === 0) {
return false;
}
var result = variant.isDirty != null;
if(result) return true;
if(variant.isDirty === true) {
return true;
}
for (var t=0; t < variant.tabs.length; t++){
for (var p=0; p < variant.tabs[t].properties.length; p++){
var property = variant.tabs[t].properties[p];
if(property.culture == null) continue;
result = result || (property.value != null && property.value.length > 0);
if(result) return true;
if (property.value != null && property.value.length > 0) {
return true;
}
}
}
return result;
return false;
}
function pristineVariantFilter(variant) {
return !(dirtyVariantFilter(variant));
function dirtyVariantFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * it's editor is in a $dirty state
// * it has pending saves
// * it is unpublished
return (variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges");
}
function publishableVariantFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * variant is active
// * it's editor is in a $dirty state
// * it has pending saves
// * it is unpublished
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges");
}
function notPublishedMandatoryFilter(variant) {
return variant.state !== "Published" && isMandatoryFilter(variant);
}
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
function notPublishableButMandatoryFilter(variant) {
//determine a variant is needed, but not already a choice.
// * publishable — aka. displayed as a publish option.
// * published — its already published and everything is then fine.
// * mandatory — this is needed, and thats why we highlight it.
return !publishableVariantFilter(variant) && variant.state !== "Published" && variant.isMandatory === true;
}
function onInit() {
vm.variants = $scope.model.variants;
if (!$scope.model.title) {
localizationService.localize("content_readyToPublish").then(function (value) {
$scope.model.title = value;
});
}
_.each(vm.variants, (variant) => {
// reset to not be published
variant.publish = false;
variant.save = false;
variant.isMandatory = isMandatoryFilter(variant);
vm.hasPristineVariants = false;
_.each(vm.variants,
function (variant) {
if(variant.state === "NotCreated") {
vm.isNew = true;
}
// If we have a variant thats not in the state of NotCreated, then we know we have adata and its not a new content node.
if(variant.state !== "NotCreated") {
vm.isNew = false;
}
);
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
// reset to not be published
variant.publish = false;
variant.save = false;
//check for pristine variants
if (!vm.hasPristineVariants) {
vm.hasPristineVariants = pristineVariantFilter(variant);
}
// If the variant havent been created jet.
if(variant.state === "NotCreated") {
// If the variant is mandatory, then set the variant to be published.
if (variant.language.isMandatory === true) {
variant.publish = true;
variant.save = true;
}
}
variant.canPublish = dirtyVariantFilter(variant);
// if we have data on this variant.
if(variant.canPublish && hasAnyData(variant)) {
// and if some varaints havent been saved before, or they dont have a publishing date set, then we set it for publishing.
if(vm.isNew || variant.publishDate == null){
variant.publish = true;
variant.save = true;
}
}
variant.willPublish = canVariantPublish(variant);
}
);
if (vm.variants.length !== 0) {
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
});
var active = _.find(vm.variants, function (v) {
return v.active;
});
if (active) {
//ensure that the current one is selected
active.publish = true;
active.save = true;
}
$scope.model.disableSubmitButton = !canPublish();
} else {
//disable Publish button if we have nothing to publish, should not happen
$scope.model.disableSubmitButton = true;
}
var labelKey = vm.isNew ? "content_languagesToPublishForFirstTime" : "content_languagesToPublish";
localizationService.localize(labelKey).then(function (value) {
vm.headline = value;
vm.loading = false;
});
_.each(vm.variants, (variant) => {
// if this is a new node and we have data on this variant.
if(vm.isNew === true && hasAnyDataFilter(variant)) {
variant.save = true;
}
});
vm.availableVariants = vm.variants.filter(publishableVariantFilter);
vm.missingMandatoryVariants = vm.variants.filter(notPublishableButMandatoryFilter);
// if any active varaiant that is available for publish, we set it to be published:
_.each(vm.availableVariants, (v) => {
if(v.active) {
v.save = v.publish = true;
}
});
if (vm.availableVariants.length !== 0) {
vm.availableVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
}
$scope.model.disableSubmitButton = !canPublish();
if (vm.missingMandatoryVariants.length > 0) {
localizationService.localize("content_notReadyToPublish").then(function (value) {
$scope.model.title = value;
vm.loading = false;
});
} else {
if (!$scope.model.title) {
localizationService.localize("content_readyToPublish").then(function (value) {
$scope.model.title = value;
vm.loading = false;
});
} else {
vm.loading = false;
}
}
}
@@ -1,67 +1,83 @@
<div ng-controller="Umbraco.Overlays.PublishController as vm">
<div class="mb3">
<p>{{vm.headline}}</p>
</div>
<div ng-controller="Umbraco.Overlays.PublishController as vm" class="umb-variant-selector-overlay">
<div ng-if="vm.loading" style="min-height: 50px; position: relative;">
<umb-load-indicator></umb-load-indicator>
</div>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading">
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && vm.missingMandatoryVariants.length === 0">
<div class="mb3">
<p><localize key="content_variantsToPublish"></localize></p>
</div>
<div class="umb-list-item"
ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
ng-repeat="variant in vm.availableVariants track by variant.compositeId">
<ng-form name="publishVariantSelectorForm">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.publish"
on-change="vm.changeSelection(variant)"
disabled="(variant.canPublish === false)"
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - </span>
<span ng-if="variant.language.isMandatory" ng-class="{'text-error': (variant.language.isMandatory && variant.willPublish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
server-validation-field="{{variant.htmlId}}"
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
</umb-checkbox>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</div>
</ng-form>
</div>
<br />
<br/>
<div class="mb3" ng-if="vm.isNew === true">
<p><localize key="content_variantsWillBeSaved"></localize></p>
</div>
</div>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && vm.hasPristineVariants">
<div class="bold mb3">
<p><localize key="content_publishedLanguages"></localize></p>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && vm.missingMandatoryVariants.length !== 0">
<div class="mb3">
<p><localize key="content_publishRequiresVariants"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.pristineVariantFilter">
<div>
<div style="margin-bottom: 2px;">
<span>{{variant.language.name}}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.missingMandatoryVariants track by variant.compositeId">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<div ng-if="!(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-list-item__description text-success">{{notification.message}}</div>
</div>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</div>
@@ -5,12 +5,16 @@
var vm = this;
vm.includeUnpublished = false;
vm.changeSelection = changeSelection;
vm.toggleIncludeUnpublished = toggleIncludeUnpublished;
function onInit() {
vm.includeUnpublished = false;
vm.variants = $scope.model.variants;
vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications).
vm.labels = {};
if (!$scope.model.title) {
@@ -19,17 +23,30 @@
});
}
_.each(vm.variants,
function (variant) {
variant.compositeId = (variant.language ? variant.language.culture : "inv") + "_" + (variant.segment ? variant.segment : "");
variant.htmlId = "_content_variant_" + variant.compositeId;
});
_.each(vm.variants, function (variant) {
variant.isMandatory = isMandatoryFilter(variant);
});
if (vm.variants.length > 1) {
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
vm.displayVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
var active = _.find(vm.variants, function (v) {
@@ -48,14 +65,17 @@
// localize help text for invariant content
vm.labels.help = {
"key": "content_publishDescendantsHelp",
"tokens": []
"tokens": [vm.variants[0].name]
};
// add the node name as a token so it will show up in the translated text
vm.labels.help.tokens.push(vm.variants[0].name);
}
}
function toggleIncludeUnpublished() {
console.log("toggleIncludeUnpublished")
vm.includeUnpublished = !vm.includeUnpublished;
}
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
function canPublish() {
var selected = [];
@@ -64,7 +84,7 @@
var published = !(variant.state === "NotCreated" || variant.state === "Draft");
if (variant.language.isMandatory && !published && !variant.publish) {
if (variant.segment == null && variant.language && variant.language.isMandatory && !published && !variant.publish) {
//if a mandatory variant isn't published
//and not flagged for saving
//then we cannot continue
@@ -86,6 +106,14 @@
variant.save = variant.publish;
}
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
//when this dialog is closed, reset all 'publish' flags
$scope.$on('$destroy', function () {
for (var i = 0; i < vm.variants.length; i++) {
@@ -1,67 +1,69 @@
<div ng-controller="Umbraco.Overlays.PublishDescendantsController as vm">
<div ng-controller="Umbraco.Overlays.PublishDescendantsController as vm" class="umb-variant-selector-overlay">
<div ng-if="vm.variants.length === 1">
<div ng-if="vm.displayVariants.length === 1">
<div class="mb3">
<p><localize key="{{vm.labels.help.key}}" tokens="vm.labels.help.tokens"></localize></p>
</div>
<div class="flex mb3">
<umb-checkbox
model="model.includeUnpublished"
text="Include drafts: also publish unpublished content items."
label-key="content_includeUnpublished"
<umb-toggle
checked="vm.includeUnpublished"
on-click="vm.toggleIncludeUnpublished()"
class="mr2"
/>
<localize key="content_includeUnpublished"></localize>
</div>
</div>
<div ng-if="vm.variants.length > 1">
<div ng-if="vm.displayVariants.length > 1">
<div class="mb3">
<p><localize key="content_publishDescendantsWithVariantsHelp"></localize></p>
</div>
<div class="flex mb3">
<umb-checkbox
model="model.includeUnpublished"
text="Include drafts: also publish unpublished content items."
label-key="content_includeUnpublished"
<umb-toggle
checked="vm.includeUnpublished"
on-click="vm.toggleIncludeUnpublished()"
class="mr2"
/>
</div>
<div class="bold mb1">
<localize key="treeHeaders_languages"></localize>
<localize key="content_includeUnpublished"></localize>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item umb-list--condensed" ng-repeat="variant in vm.variants">
<div class="umb-list-item umb-list--condensed" ng-repeat="variant in vm.displayVariants track by variant.compositeId">
<ng-form name="publishVariantSelectorForm">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox
<umb-checkbox input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.publish"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"
text="{{variant.language.name}}"/>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<span class="db umb-list-item__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
</div>
</div>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
</umb-checkbox>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</ng-form>
</div>
</div>
@@ -9,8 +9,6 @@
vm.isNew = true;
vm.changeSelection = changeSelection;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
function changeSelection(variant) {
var firstSelected = _.find(vm.variants, function (v) {
@@ -19,16 +17,18 @@
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
}
function dirtyVariantFilter(variant) {
function saveableVariantFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as save-able) if it's
// * the active one
// * it's editor is in a $dirty state
// * it is in NotCreated state
return (variant.active || variant.isDirty);
}
function pristineVariantFilter(variant) {
return !(dirtyVariantFilter(variant));
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
function hasAnyData(variant) {
@@ -57,6 +57,7 @@
function onInit() {
vm.variants = $scope.model.variants;
vm.availableVariants = vm.variants.filter(saveableVariantFilter);
if(!$scope.model.title) {
localizationService.localize("content_readyToSave").then(function(value){
@@ -64,10 +65,15 @@
});
}
vm.hasPristineVariants = false;
_.each(vm.variants,
function (variant) {
//reset state:
variant.save = false;
variant.publish = false;
variant.isMandatory = isMandatoryFilter(variant);
if(variant.state !== "NotCreated"){
vm.isNew = false;
}
@@ -75,34 +81,40 @@
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
//check for pristine variants
if (!vm.hasPristineVariants) {
vm.hasPristineVariants = pristineVariantFilter(variant);
}
if(vm.isNew && hasAnyData(variant)){
variant.save = true;
}
});
if (vm.variants.length !== 0) {
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
_.find(vm.variants, function (v) {
if(v.active) {
//ensure that the current one is selected
v.save = true;
}
});
var active = _.find(vm.variants, function (v) {
return v.active;
vm.availableVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
if (active) {
//ensure that the current one is selected
active.save = true;
}
} else {
//disable save button if we have nothing to save
$scope.model.disableSubmitButton = true;
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Overlays.SaveContentController as vm">
<div ng-controller="Umbraco.Overlays.SaveContentController as vm" class="umb-variant-selector-overlay">
<div ng-if="vm.loading" style="min-height: 50px; position: relative;">
<umb-load-indicator></umb-load-indicator>
@@ -7,17 +7,16 @@
<div ng-if="!vm.loading">
<div class="mb3">
<p>
<localize ng-if="!vm.isNew" key="content_languagesToSave"></localize>
<localize ng-if="vm.isNew" key="content_languagesToSaveForFirstTime"></localize>
<localize key="content_variantsToSave"></localize>
</p>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item"
ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
ng-repeat="variant in vm.availableVariants track by variant.compositeId">
<ng-form name="saveVariantSelectorForm">
<div ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
@@ -25,52 +24,33 @@
model="variant.save"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"
text="{{variant.language.name}}"
/>
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!saveVariantSelectorForm.saveVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span>
</umb-checkbox>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
<span class="db" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</div>
</ng-form>
</div>
<br/>
</div>
<div class="umb-list umb-list--condensed" ng-if="vm.hasPristineVariants">
<div class="bold mb3">
<p>
<localize ng-if="!vm.isNew" key="content_unmodifiedLanguages"></localize>
<localize ng-if="vm.isNew" key="content_untouchedLanguagesForFirstTime"></localize>
</p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.pristineVariantFilter">
<div>
<div style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</div>
<div ng-if="!(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-list-item__description text-success">{{notification.message}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -12,7 +12,6 @@
vm.clearPublishDate = clearPublishDate;
vm.clearUnpublishDate = clearUnpublishDate;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
vm.changeSelection = changeSelection;
vm.firstSelectedDates = {};
@@ -24,7 +23,7 @@
function onInit() {
vm.variants = $scope.model.variants;
vm.hasPristineVariants = false;
vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications).
for (let i = 0; i < vm.variants.length; i++) {
origDates.push({
@@ -38,36 +37,42 @@
$scope.model.title = value;
});
}
_.each(vm.variants, function (variant) {
variant.isMandatory = isMandatoryFilter(variant);
});
// Check for variants: if a node is invariant it will still have the default language in variants
// so we have to check for length > 1
if (vm.variants.length > 1) {
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
//check for pristine variants
if (!vm.hasPristineVariants) {
vm.hasPristineVariants = pristineVariantFilter(variant);
vm.displayVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
});
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
var active = _.find(vm.variants, function (v) {
return v.active;
_.each(vm.variants, function (v) {
if (v.active) {
v.save = true;
}
});
if (active) {
//ensure that the current one is selected
active.save = true;
}
$scope.model.disableSubmitButton = !canSchedule();
}
@@ -127,6 +132,7 @@
* @param {any} type publish or unpublish
*/
function datePickerChange(variant, dateStr, type) {
console.log("datePickerChange", variant, dateStr, type)
if (type === 'publish') {
setPublishDate(variant, dateStr);
} else if (type === 'unpublish') {
@@ -241,6 +247,7 @@
* @param {any} variant
*/
function clearPublishDate(variant) {
console.log("clearPublishDate", variant, variant.releaseDate)
if(variant && variant.releaseDate) {
variant.releaseDate = null;
// we don't have a publish date anymore so we can clear the min date for unpublish
@@ -256,6 +263,7 @@
* @param {any} variant
*/
function clearUnpublishDate(variant) {
console.log("clearUnpublishDate", variant)
if(variant && variant.expireDate) {
variant.expireDate = null;
// we don't have a unpublish date anymore so we can clear the max date for publish
@@ -297,8 +305,11 @@
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated");
}
function pristineVariantFilter(variant) {
return !(dirtyVariantFilter(variant));
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
@@ -321,7 +332,7 @@
return true;
}
var isMandatory = variant.language && variant.language.isMandatory;
var isMandatory = variant.segment == null && variant.language && variant.language.isMandatory;
//if this variant will show up in the publish-able list
var publishable = dirtyVariantFilter(variant);
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Overlays.ScheduleContentController as vm">
<div ng-controller="Umbraco.Overlays.ScheduleContentController as vm" class="umb-variant-selector-overlay">
<!-- invariant nodes -->
<div ng-if="vm.variants.length === 1">
@@ -84,28 +84,39 @@
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="variant in vm.variants">
<div class="umb-list-item" ng-repeat="variant in vm.displayVariants track by variant.compositeId">
<ng-form name="scheduleSelectorForm" style="width:100%;">
<div ng-class="{'umb-list-item--error': scheduleSelectorForm.saveVariantReleaseDate.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': scheduleSelectorForm.saveVariantReleaseDate.$invalid}">
<umb-checkbox
input-id="{{'saveVariantSelector_' + variant.language.culture}}"
input-id="{{variant.htmlId}}"
name="saveVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
text="{{variant.language.name}}"
/>
server-validation-field="{{variant.htmlId}}"
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description"
ng-if="!scheduleSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</umb-checkbox>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox"
ng-if="!scheduleSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<div class="date-wrapper-mini date-wrapper-mini--checkbox">
<div class="date-wrapper-mini__date" ng-if="vm.dirtyVariantFilter(variant) && (variant.releaseDate || variant.save)">
<div class="date-wrapper-mini__date" ng-if="(variant.releaseDate || variant.save)">
<div style="font-size: 13px; margin-right: 5px;">Publish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.releaseDateFormatted}}</em></div>
@@ -117,7 +128,7 @@
on-open="vm.datePickerShow(variant, 'publish')"
on-close="vm.datePickerClose(variant, 'publish')">
<div>
<button ng-show="variant.releaseDate" class="btn umb-button--xxs" style="outline: none;">
<button ng-show="variant.releaseDate" class="btn umb-button--xxs btn-outline umb-outline">
{{variant.releaseDateFormatted}}
</button>
@@ -126,9 +137,9 @@
</a>
</div>
</umb-date-time-picker>
<a ng-show="variant.releaseDate" ng-click="vm.clearPublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
<button ng-show="variant.releaseDate" ng-click="vm.clearPublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle btn-outline umb-outline" style="margin-left: -2px;">
<span class="icon icon-wrong"></span>
</a>
</button>
</div>
</div>
@@ -143,7 +154,7 @@
on-open="vm.datePickerShow(variant, 'unpublish')"
on-close="vm.datePickerClose(variant, 'unpublish')">
<div>
<button ng-show="variant.expireDate" class="btn umb-button--xxs" style="outline: none;">
<button ng-show="variant.expireDate" class="btn umb-button--xxs btn-outline umb-outline">
{{variant.expireDateFormatted}}
</button>
@@ -152,7 +163,7 @@
</a>
</div>
</umb-date-time-picker>
<a ng-show="variant.expireDate" ng-click="vm.clearUnpublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
<a ng-show="variant.expireDate" ng-click="vm.clearUnpublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle btn-outline umb-outline" style="margin-left: -2px;">
<span class="icon icon-wrong"></span>
</a>
</div>
@@ -6,8 +6,6 @@
var vm = this;
vm.loading = true;
vm.modifiedVariantFilter = modifiedVariantFilter;
vm.unmodifiedVariantFilter = unmodifiedVariantFilter;
vm.changeSelection = changeSelection;
function onInit() {
@@ -21,28 +19,40 @@
});
}
_.each(vm.variants, function (variant) {
variant.isMandatory = isMandatoryFilter(variant);
});
vm.availableVariants = vm.variants.filter(publishableVariantFilter);
if (vm.availableVariants.length !== 0) {
if (vm.variants.length !== 0) {
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
});
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
vm.availableVariants = vm.availableVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
var active = _.find(vm.variants, function (v) {
return v.active;
_.each(vm.availableVariants, function (v) {
if(v.active) {
v.save = true;
}
});
if (active) {
//ensure that the current one is selected
active.save = true;
}
} else {
//disable save button if we have nothing to save
$scope.model.disableSubmitButton = true;
@@ -59,20 +69,20 @@
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
}
function modifiedVariantFilter(variant) {
//determine a variant is 'modified' (meaning it will show up as able to send for approval)
// * it's editor is in a $dirty state
// * it is in Draft state
// * it is published with pending changes
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges");
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
function unmodifiedVariantFilter(variant) {
//determine a variant is 'unmodified' (meaning it will NOT show up as able to send for approval)
function publishableVariantFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * variant is active
// * it's editor is in a $dirty state
// * it has been published
// * it is not created for that specific language
return (variant.state === "Published" && !variant.isDirty && !variant.active || variant.state === "NotCreated" && !variant.isDirty && !variant.active);
// * it has pending saves
// * it is unpublished
return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges");
}
//when this dialog is closed, reset all 'save' flags
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Overlays.SendToPublishController as vm">
<div ng-controller="Umbraco.Overlays.SendToPublishController as vm" class="umb-variant-selector-overlay">
<div class="mb3">
<p><localize key="content_languagesToSendForApproval"></localize></p>
@@ -10,31 +10,36 @@
<div class="umb-list umb-list--condensed" ng-if="!vm.loading">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.modifiedVariantFilter">
<div class="umb-list-item" ng-repeat="variant in vm.availableVariants track by variant.compositeId">
<ng-form name="publishVariantSelectorForm">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
<umb-checkbox input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.save"
model="variant.publish"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
</umb-checkbox>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</ng-form>
@@ -42,20 +47,4 @@
<br />
</div>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && (vm.variants | filter:vm.unmodifiedVariantFilter).length > 0">
<div class="bold mb3">
<p><localize key="content_unmodifiedLanguages"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.unmodifiedVariantFilter">
<div>
<div style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</div>
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
</div>
</div>
</div>
</div>
@@ -7,12 +7,11 @@
var autoSelectedVariants = [];
vm.changeSelection = changeSelection;
vm.publishedVariantFilter = publishedVariantFilter;
vm.unpublishedVariantFilter = unpublishedVariantFilter;
function onInit() {
vm.variants = $scope.model.variants;
vm.unpublishableVariants = vm.variants.filter(publishedVariantFilter)
// set dialog title
if (!$scope.model.title) {
@@ -21,24 +20,38 @@
});
}
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
});
_.each(vm.variants, function (variant) {
variant.isMandatory = isMandatoryFilter(variant);
});
// node has variants
if (vm.variants.length !== 1) {
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
vm.unpublishableVariants.sort(function (a, b) {
if (a.language && b.language) {
if (a.language.name > b.language.name) {
return -1;
}
if (a.language.name < b.language.name) {
return 1;
}
}
if (a.segment && b.segment) {
if (a.segment > b.segment) {
return -1;
}
if (a.segment < b.segment) {
return 1;
}
}
return 0;
});
var active = _.find(vm.variants, function (v) {
return v.active;
});
if (active) {
if (active && publishedVariantFilter(active)) {
//ensure that the current one is selected
active.save = true;
}
@@ -51,21 +64,15 @@
function changeSelection(selectedVariant) {
// disable submit button if nothing is selected
var firstSelected = _.find(vm.variants, function (v) {
return v.save;
});
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
// if a mandatory variant is selected we want to select all other variants
// if a mandatory variant is selected we want to select all other variants, we cant have anything published if a mandatory variants gets unpublished.
// and disable selection for the others
if(selectedVariant.save && selectedVariant.language.isMandatory) {
if(selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) {
angular.forEach(vm.variants, function(variant){
if(!variant.save && publishedVariantFilter(variant)) {
vm.variants.forEach(function(variant) {
if(!variant.save) {
// keep track of the variants we automaically select
// so we can remove the selection again
autoSelectedVariants.push(variant.language.culture);
autoSelectedVariants.push(variant);
variant.save = true;
}
variant.disabled = true;
@@ -79,12 +86,12 @@
// if a mandatory variant is deselected we want to deselet all the variants
// that was automatically selected so it goes back to the state before the mandatory language was selected.
// We also want to enable all checkboxes again
if(!selectedVariant.save && selectedVariant.language.isMandatory) {
if(!selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) {
angular.forEach(vm.variants, function(variant){
vm.variants.forEach( function(variant){
// check if variant was auto selected, then deselect
if(_.contains(autoSelectedVariants, variant.language.culture)) {
if(_.contains(autoSelectedVariants, variant)) {
variant.save = false;
};
@@ -93,6 +100,19 @@
autoSelectedVariants = [];
}
// disable submit button if nothing is selected
var firstSelected = _.find(vm.variants, function (v) {
return v.save;
});
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
}
function isMandatoryFilter(variant) {
//determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
// * has a mandatory language
// * without having a segment, segments cant be mandatory at current state of code.
return (variant.language && variant.language.isMandatory === true && variant.segment == null);
}
function publishedVariantFilter(variant) {
@@ -102,13 +122,6 @@
return (variant.state === "Published" || variant.state === "PublishedPendingChanges");
}
function unpublishedVariantFilter(variant) {
//determine a variant is 'modified' (meaning it will NOT show up as able to unpublish)
// * it's editor is in a $dirty state
// * it is published with pending changes
return (variant.state !== "Published" && variant.state !== "PublishedPendingChanges");
}
//when this dialog is closed, remove all unpublish and disabled flags
$scope.$on('$destroy', function () {
for (var i = 0; i < vm.variants.length; i++) {
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Overlays.UnpublishController as vm">
<div ng-controller="Umbraco.Overlays.UnpublishController as vm" class="umb-variant-selector-overlay">
<!-- Single language -->
<div ng-if="vm.variants.length === 1">
@@ -10,12 +10,12 @@
<div style="margin-bottom: 15px;">
<p><localize key="content_languagesToUnpublish"></localize></p>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.publishedVariantFilter">
<div class="umb-list-item" ng-repeat="variant in vm.unpublishableVariants track by variant.compositeId">
<ng-form name="unpublishVariantSelectorForm">
<div>
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': unpublishVariantSelectorForm.unpublishVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
@@ -24,41 +24,35 @@
on-change="vm.changeSelection(variant)"
disabled="variant.disabled"
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
>
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
</div>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
<span class="__secondarytitle"> — {{variant.language.name}}</span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
</umb-checkbox>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</ng-form>
</div>
<br />
</div>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && (vm.variants | filter:vm.unpublishedVariantFilter).length > 0">
<div class="bold mb3">
<p><localize key="content_unpublishedLanguages"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.unpublishedVariantFilter">
<div>
<div style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</div>
<div>
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -44,6 +44,7 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) {
//load the default culture selected in the main tree if any
$scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
$scope.segment = $routeParams.csegment ? $routeParams.csegment : null;
//Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered.
//This is so we can listen to changes on the cculture parameter since that will not cause a route change
@@ -52,6 +53,7 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) {
//will not cause a route change and so we can update the isNew and contentId flags accordingly.
$scope.$on('$routeUpdate', function (event, next) {
$scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture;
$scope.segment = next.params.csegment ? next.params.csegment : null;
$scope.isNew = $routeParams.id === "-1";
$scope.contentId = $routeParams.id;
});
@@ -5,6 +5,7 @@
get-scaffold-method="getScaffoldMethod"
is-new="isNew"
tree-alias="contentblueprints"
culture="culture">
culture="culture"
segment="segment">
</content-editor>
</div>
@@ -33,7 +33,7 @@
var totalInfo = 0;
// count total number of statusses
group.checks.forEach(check => {
Utilities.forEach(group.checks, check => {
if (check.status) {
check.status.forEach(status => {
@@ -95,22 +95,24 @@
group.checkCounter = 0;
group.loading = true;
checks.forEach(check => {
check.loading = true;
if (checks) {
checks.forEach(check => {
check.loading = true;
healthCheckResource.getStatus(check.id)
.then(function (response) {
check.status = response;
group.checkCounter = group.checkCounter + 1;
check.loading = false;
healthCheckResource.getStatus(check.id)
.then(function (response) {
check.status = response;
group.checkCounter = group.checkCounter + 1;
check.loading = false;
// when all checks are done, set global group result
if (group.checkCounter === checks.length) {
setGroupGlobalResultType(group);
group.loading = false;
}
});
});
// when all checks are done, set global group result
if (group.checkCounter === checks.length) {
setGroupGlobalResultType(group);
group.loading = false;
}
});
});
}
}
function openGroup(group) {
@@ -48,7 +48,7 @@
"shortcuts_navigateSections",
"shortcuts_addGroup",
"shortcuts_addProperty",
"shortcuts_addEditor",
"defaultdialogs_selectEditor",
"shortcuts_editDataType",
"shortcuts_toggleListView",
"shortcuts_toggleAllowAsRoot",
@@ -20,12 +20,14 @@
vm.selectedChildren = [];
vm.overlayTitle = "";
vm.showAllowSegmentationOption = Umbraco.Sys.ServerVariables.umbracoSettings.showAllowSegmentationForDocumentTypes || false;
vm.addChild = addChild;
vm.removeChild = removeChild;
vm.sortChildren = sortChildren;
vm.toggleAllowAsRoot = toggleAllowAsRoot;
vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants;
vm.canToggleIsElement = false;
vm.toggleIsElement = toggleIsElement;
@@ -110,6 +112,10 @@
$scope.model.allowCultureVariant = $scope.model.allowCultureVariant ? false : true;
}
function toggleAllowSegmentVariants() {
$scope.model.allowSegmentVariant = $scope.model.allowSegmentVariant ? false : true;
}
function toggleIsElement() {
$scope.model.isElement = $scope.model.isElement ? false : true;
}
@@ -43,8 +43,8 @@
<div class="sub-view-columns">
<div class="sub-view-column-left">
<h5><localize key="contentTypeEditor_variantsHeading" /></h5>
<small><localize key="contentTypeEditor_variantsDescription" /></small>
<h5><localize key="contentTypeEditor_cultureVariantHeading" /></h5>
<small><localize key="contentTypeEditor_cultureVariantDescription" /></small>
</div>
<div class="sub-view-column-right">
@@ -57,6 +57,23 @@
</div>
<div class="sub-view-columns" ng-if="vm.showAllowSegmentationOption">
<div class="sub-view-column-left">
<h5><localize key="contentTypeEditor_segmentVariantHeading" /></h5>
<small><localize key="contentTypeEditor_segmentVariantDescription" /></small>
</div>
<div class="sub-view-column-right">
<umb-toggle data-element="permissions-allow-culture-variant"
checked="model.allowSegmentVariant"
on-click="vm.toggleAllowSegmentVariants()">
</umb-toggle>
</div>
</div>
<div class="sub-view-columns">
<div class="sub-view-column-left">
@@ -16,7 +16,7 @@
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<umb-button button-style="action"
<umb-button button-style="outline"
type="button"
action="vm.addLanguage()"
label-key="languages_addLanguage">
@@ -47,13 +47,18 @@
<div class="editor clearfix" ng-if="model.parameter.editor">
<a href="" class="editor-icon-wrapper" ng-click="vm.openMacroParameterPicker(model.parameter)">
<i class="icon {{model.parameter.dataTypeIcon}}" ng-class="{'icon-autofill': model.parameter.dataTypeIcon == null}"></i>
</a>
<div class="editor-details">
<a href="" class="editor-name" ng-click="vm.openMacroParameterPicker(model.parameter)">{{model.parameter.dataTypeName}}</a>
<a href="" class="editor-editor" ng-click="vm.openMacroParameterPicker(model.parameter)">{{model.parameter.editor}}</a>
<button class="btn-reset editor-info" ng-click="vm.openMacroParameterPicker(model.parameter)">
<div class="editor-icon-wrapper">
<i class="icon {{model.parameter.dataTypeIcon}}" ng-class="{'icon-autofill': model.parameter.dataTypeIcon == null}"></i>
</div>
<div class="editor-details">
<a href="" class="editor-name">{{model.parameter.dataTypeName}}</a>
<a href="" class="editor-editor">{{model.parameter.editor}}</a>
</div>
</button>
</div>
</div>
@@ -208,7 +208,8 @@
});
});
vm.overlayMenu.title = vm.overlayMenu.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty;
vm.overlayMenu.title = labels.grid_addElement;
vm.overlayMenu.hideHeader = vm.overlayMenu.pasteItems.length > 0;
vm.overlayMenu.clickClearPaste = function ($event) {
$event.stopPropagation();
@@ -216,6 +217,7 @@
clipboardService.clearEntriesOfType("elementType", contentTypeAliases);
clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases);
vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
vm.overlayMenu.hideHeader = false;
};
if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) {
@@ -121,13 +121,15 @@ describe('contentEditingHelper tests', function () {
//act
//note the null, that's because culture is null
formHelper.handleServerValidation({ "_Properties.bodyText.null.value": ["Required"] });
formHelper.handleServerValidation({ "_Properties.bodyText.null.null.value": ["Required"] });
//assert
expect(serverValidationManager.items.length).toBe(1);
expect(serverValidationManager.items[0].fieldName).toBe("value");
expect(serverValidationManager.items[0].errorMsg).toBe("Required");
expect(serverValidationManager.items[0].propertyAlias).toBe("bodyText");
expect(serverValidationManager.items[0].culture).toBe("invariant");
expect(serverValidationManager.items[0].segment).toBeNull();
});
it('adds a multiple property and field level server validation errors when they are invalid', function () {
@@ -142,7 +144,7 @@ describe('contentEditingHelper tests', function () {
"Name": ["Required"],
"UpdateDate": ["Invalid date"],
//note the null, that's because culture is null
"_Properties.bodyText.null.value": ["Required field"],
"_Properties.bodyText.en-US.mySegment.value": ["Required field"],
"_Properties.textarea": ["Invalid format"]
});
@@ -157,6 +159,8 @@ describe('contentEditingHelper tests', function () {
expect(serverValidationManager.items[2].fieldName).toBe("value");
expect(serverValidationManager.items[2].errorMsg).toBe("Required field");
expect(serverValidationManager.items[2].propertyAlias).toBe("bodyText");
expect(serverValidationManager.items[2].culture).toBe("en-US");
expect(serverValidationManager.items[2].segment).toBe("mySegment");
expect(serverValidationManager.items[3].fieldName).toBe("");
expect(serverValidationManager.items[3].errorMsg).toBe("Invalid format");
expect(serverValidationManager.items[3].propertyAlias).toBe("textarea");
@@ -61,12 +61,12 @@
it('can retrieve property validation errors for a sub field', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
//act
var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1");
var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2");
var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1", null);
var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2", null);
//assert
expect(err1).not.toBeUndefined();
@@ -85,14 +85,14 @@
it('can retrieve property validation errors for a sub field for culture', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2");
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2", null);
//act
var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1");
var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2");
var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2");
var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2");
var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1", null);
var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value1", null);
var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", null);
var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2", null);
//assert
@@ -111,12 +111,77 @@
expect(err2.errorMsg).toEqual("Another value 2");
expect(err2.culture).toEqual("fr-FR");
});
it('can retrieve property validation errors for a sub field for segments', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", "segment1");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", "segment2");
//act
var err1 = serverValidationManager.getPropertyError("myProperty", null, "value1", "segment1");
var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value1", null);
var err2 = serverValidationManager.getPropertyError("myProperty", null, "value2", "segment2");
var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2", null);
//assert
expect(err1NotFound).toBeUndefined();
expect(err2NotFound).toBeUndefined();
expect(err1).not.toBeUndefined();
expect(err1.propertyAlias).toEqual("myProperty");
expect(err1.fieldName).toEqual("value1");
expect(err1.errorMsg).toEqual("Some value 1");
expect(err1.segment).toEqual("segment1");
expect(err2).not.toBeUndefined();
expect(err2.propertyAlias).toEqual("myProperty");
expect(err2.fieldName).toEqual("value2");
expect(err2.errorMsg).toEqual("Another value 2");
expect(err2.segment).toEqual("segment2");
});
it('can retrieve property validation errors for a sub field for culture with segments', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", "segment1");
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2", "segment2");
//act
var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1", "segment1");
expect(serverValidationManager.getPropertyError("myProperty", null, "value1", null)).toBeUndefined();
expect(serverValidationManager.getPropertyError("myProperty", "en-US", "value1", null)).toBeUndefined();
expect(serverValidationManager.getPropertyError("myProperty", null, "value1", "segment1")).toBeUndefined();
var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", "segment2");
expect(serverValidationManager.getPropertyError("myProperty", null, "value2", null)).toBeUndefined();
expect(serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2", null)).toBeUndefined();
expect(serverValidationManager.getPropertyError("myProperty", null, "value2", "segment2")).toBeUndefined();
//assert
expect(err1).not.toBeUndefined();
expect(err1.propertyAlias).toEqual("myProperty");
expect(err1.fieldName).toEqual("value1");
expect(err1.errorMsg).toEqual("Some value 1");
expect(err1.culture).toEqual("en-US");
expect(err1.segment).toEqual("segment1");
expect(err2).not.toBeUndefined();
expect(err2.propertyAlias).toEqual("myProperty");
expect(err2.fieldName).toEqual("value2");
expect(err2.errorMsg).toEqual("Another value 2");
expect(err2.culture).toEqual("fr-FR");
expect(err2.segment).toEqual("segment2");
});
it('can add a property errors with multiple sub fields and it the first will be retreived with only the property alias', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null);
//act
var err = serverValidationManager.getPropertyError("myProperty");
@@ -132,10 +197,10 @@
it('will return null for a non-existing property error', function () {
//arrage
serverValidationManager.addPropertyError("myProperty", null, "value", "Required");
serverValidationManager.addPropertyError("myProperty", null, "value", "Required", null);
//act
var err = serverValidationManager.getPropertyError("DoesntExist", null, "value");
var err = serverValidationManager.getPropertyError("DoesntExist", null, "value", null);
//assert
expect(err).toBeUndefined();
@@ -145,15 +210,15 @@
it('detects if a property error exists', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null);
//act
var err1 = serverValidationManager.hasPropertyError("myProperty");
var err2 = serverValidationManager.hasPropertyError("myProperty", null, "value1");
var err3 = serverValidationManager.hasPropertyError("myProperty", null, "value2");
var err2 = serverValidationManager.hasPropertyError("myProperty", null, "value1", null);
var err3 = serverValidationManager.hasPropertyError("myProperty", null, "value2", null);
var err4 = serverValidationManager.hasPropertyError("notFound");
var err5 = serverValidationManager.hasPropertyError("myProperty", null, "notFound");
var err5 = serverValidationManager.hasPropertyError("myProperty", null, "notFound", null);
//assert
expect(err1).toBe(true);
@@ -167,15 +232,15 @@
it('can remove a property error with a sub field specified', function () {
//arrage
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null);
//act
serverValidationManager.removePropertyError("myProperty", null, "value1");
serverValidationManager.removePropertyError("myProperty", null, "value1", null);
//assert
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(true);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1", null)).toBe(false);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2", null)).toBe(true);
});
@@ -189,8 +254,8 @@
serverValidationManager.removePropertyError("myProperty");
//assert
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1")).toBe(false);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2")).toBe(false);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value1", null)).toBe(false);
expect(serverValidationManager.hasPropertyError("myProperty", null, "value2", null)).toBe(false);
});
@@ -201,10 +266,10 @@
it('can retrieve culture validation errors', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null);
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", null);
//assert
expect(serverValidationManager.hasCultureError(null)).toBe(true);
@@ -216,6 +281,39 @@
});
describe('managing variant validation errors', function () {
it('can retrieve variant validation errors', function () {
//arrange
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", null);
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", null);
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", "MySegment");
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2", "MySegment");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2", "MySegment");
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3", "MySegment");
//assert
expect(serverValidationManager.hasVariantError(null, null)).toBe(true);
expect(serverValidationManager.hasVariantError("en-US", null)).toBe(true);
expect(serverValidationManager.hasVariantError("fr-FR", null)).toBe(true);
expect(serverValidationManager.hasVariantError(null, "MySegment")).toBe(true);
expect(serverValidationManager.hasVariantError("en-US", "MySegment")).toBe(true);
expect(serverValidationManager.hasVariantError("fr-FR", "MySegment")).toBe(true);
expect(serverValidationManager.hasVariantError("es-ES", null)).toBe(false);
expect(serverValidationManager.hasVariantError("es-ES", "MySegment")).toBe(false);
expect(serverValidationManager.hasVariantError("fr-FR", "MySegmentNotRight")).toBe(false);
expect(serverValidationManager.hasVariantError(null, "MySegmentNotRight")).toBe(false);
});
});
describe('validation error subscriptions', function() {
it('can subscribe to a field error', function() {
@@ -228,11 +326,11 @@
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
}, null);
//act
serverValidationManager.addFieldError("Name", "Required");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
//assert
expect(args).not.toBeUndefined();
@@ -249,8 +347,8 @@
};
var cb2 = function () {
};
serverValidationManager.subscribe(null, null, "Name", cb1);
serverValidationManager.subscribe(null, null, "Title", cb2);
serverValidationManager.subscribe(null, null, "Name", cb1, null);
serverValidationManager.subscribe(null, null, "Title", cb2, null);
//act
serverValidationManager.addFieldError("Name", "Required");
@@ -284,7 +382,7 @@
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
}, null);
serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) {
numCalled++;
@@ -293,12 +391,12 @@
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
}, null);
//act
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2");
serverValidationManager.addPropertyError("myProperty", null, "", "Some value 3");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2", null);
serverValidationManager.addPropertyError("myProperty", null, "", "Some value 3", null);
//assert
expect(args1).not.toBeUndefined();
@@ -335,7 +433,7 @@
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
}, null);
serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) {
numCalled++;
@@ -344,13 +442,13 @@
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
}, null);
//act
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1");
serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2");
serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null);
serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null);
serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null);
//assert
expect(args1).not.toBeUndefined();
@@ -1,5 +1,13 @@
(function () {
describe("Utilities", function () {
describe("fromJson", function () {
it("should deserialize json as object", function () {
expect(Utilities.fromJson('{"a":1,"b":2}')).toEqual({ a: 1, b: 2 });
});
it("should return object as object", function () {
expect(Utilities.fromJson({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
});
}),
describe("toJson", function () {
it("should delegate to JSON.stringify", function () {
var spy = spyOn(JSON, "stringify").and.callThrough();
+16 -4
View File
@@ -8,7 +8,8 @@
<key alias="assignDomain">Tilføj domæne</key>
<key alias="auditTrail">Revisionsspor</key>
<key alias="browse">Gennemse elementer</key>
<key alias="changeDocType">Skift dokumenttype</key>
<key alias="changeDocType">Skift Dokument Type</key>
<key alias="changeDataType">Skift Input Type</key>
<key alias="copy">Kopier</key>
<key alias="create">Opret</key>
<key alias="export">Eksportér</key>
@@ -479,6 +480,7 @@
<key alias="unLinkYour">Fjern link fra dit</key>
<key alias="account">konto</key>
<key alias="selectEditor">Vælg editor</key>
<key alias="selectEditorConfiguration">Vælg konfiguration</key>
<key alias="selectSnippet">Vælg snippet</key>
<key alias="variantdeletewarning">Dette vil slette noden og alle dets sprog. Hvis du kun vil slette et sprog, så afpublicér det i stedet.</key>
</area>
@@ -780,6 +782,7 @@
<key alias="generalHeader">Generelt</key>
<key alias="editorHeader">Editor</key>
<key alias="toggleAllowCultureVariants">Skift tillad sprogvarianter</key>
<key alias="toggleAllowSegmentVariants">Skift tillad segmentering</key>
</area>
<area alias="graphicheadline">
<key alias="backgroundcolor">Baggrundsfarve</key>
@@ -1378,9 +1381,11 @@ Mange hilsner fra Umbraco robotten
<key alias="compositionInUse">Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition</key>
<key alias="noAvailableCompositions">Der er ingen indholdstyper tilgængelige at bruge som komposition</key>
<key alias="compositionRemoveWarning">Når du fjerner en komposition vil alle associerede indholdsdata blive slettet. Når først dokumenttypen er gemt, er der ingen vej tilbage.</key>
<key alias="availableEditors">Tilgængelige editors</key>
<key alias="availableEditors">Opret ny indstilling</key>
<key alias="reuse">Genbrug</key>
<key alias="editorSettings">Editor indstillinger</key>
<key alias="editorSettings">Input indstillinger</key>
<key alias="searchResultSettings">Tilgængelige indstillinger</key>
<key alias="searchResultEditors">Opret ny indstilling</key>
<key alias="configuration">Konfiguration</key>
<key alias="yesDelete">Ja, slet</key>
<key alias="movedUnderneath">blev flyttet til</key>
@@ -1406,9 +1411,16 @@ Mange hilsner fra Umbraco robotten
<key alias="tabHasNoSortOrder">fane har ingen sorteringsrækkefølge</key>
<key alias="compositionUsageHeading">Hvor er denne komposition brugt?</key>
<key alias="compositionUsageSpecification">Denne komposition brugt i kompositionen af de følgende indholdstyper:</key>
<key alias="variantsHeading">Tillad sprogvariation</key>
<key alias="variantsHeading">Tillad variationer</key>
<key alias="cultureVariantHeading">Tillad sprogvariation</key>
<key alias="segmentVariantHeading">Tillad segmentering</key>
<key alias="cultureVariantLabel">Tillader sprogvariationer</key>
<key alias="segmentVariantLabel">Tillader segmentering</key>
<key alias="variantsDescription">Tillad at redaktører kan oprette indhold af denne type på flere sprog.</key>
<key alias="cultureVariantDescription">Tillad at redaktører kan oprette dette indhold på flere sprog.</key>
<key alias="segmentVariantDescription">Tillad at redaktører kan oprette flere udgaver af denne type indhold.</key>
<key alias="allowVaryByCulture">Tillad sprogvariation</key>
<key alias="allowVaryBySegment">Tillad segmentering</key>
<key alias="elementType">Element-type</key>
<key alias="elementHeading">Er en Element-type</key>
<key alias="elementDescription">En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet.</key>
+23 -5
View File
@@ -250,8 +250,8 @@
<key alias="published">Published</key>
<key alias="publishedPendingChanges">Published (pending changes)</key>
<key alias="publishStatus">Publication Status</key>
<key alias="publishDescendantsHelp"><![CDATA[Click <em>Publish with descendants</em> to publish <strong>%0%</strong> and all content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsWithVariantsHelp"><![CDATA[Click <em>Publish with descendants</em> to publish <strong>the selected languages</strong> and the same languages of content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsHelp"><![CDATA[Publish <strong>%0%</strong> and all content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsWithVariantsHelp"><![CDATA[Publish variants and variants of same type underneath and thereby making their content publicly available.]]></key>
<key alias="releaseDate">Publish at</key>
<key alias="unpublishDate">Unpublish at</key>
<key alias="removeDate">Clear Date</key>
@@ -288,7 +288,7 @@
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
<key alias="includeUnpublished">Include drafts: also publish unpublished content items.</key>
<key alias="includeUnpublished">Include drafts and unpublished content items.</key>
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key>
<key alias="isSensitiveValue_short">This value is hidden.</key>
<key alias="languagesToPublishForFirstTime">What languages would you like to publish? All languages with content are saved!</key>
@@ -302,7 +302,17 @@
<key alias="unpublishedLanguages">Unpublished Languages</key>
<key alias="unmodifiedLanguages">Unmodified Languages</key>
<key alias="untouchedLanguagesForFirstTime">These languages haven't been created</key>
<key alias="readyToPublish">Ready to Publish?</key>
<key alias="variantsWillBeSaved">All new variants will be saved.</key>
<key alias="variantsToPublish">Which variants you would like to publish?</key>
<key alias="variantsToSave">Choose which variants to be saved.</key>
<key alias="variantsToSendForApproval">Pick variants to send for approval.</key>
<key alias="variantsToSchedule">Set scheduled publishing...</key>
<key alias="variantsToUnpublish">Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants.</key>
<key alias="publishRequiresVariants">The following variants is required for publishing to take place:</key>
<key alias="notReadyToPublish">We are not ready to Publish</key>
<key alias="readyToPublish">Ready to publish?</key>
<key alias="readyToSave">Ready to Save?</key>
<key alias="sendForApproval">Send for approval</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
@@ -796,6 +806,7 @@
<key alias="generalHeader">General</key>
<key alias="editorHeader">Editor</key>
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
<key alias="toggleAllowSegmentVariants">Toggle allow segmentation</key>
</area>
<area alias="graphicheadline">
<key alias="backgroundcolor">Background colour</key>
@@ -1646,9 +1657,16 @@ 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="variantsHeading">Allow variations</key>
<key alias="cultureVariantHeading">Allow vary by culture</key>
<key alias="segmentVariantHeading">Allow segmentation</key>
<key alias="cultureVariantLabel">Vary by culture</key>
<key alias="segmentVariantLabel">Vary by segments</key>
<key alias="variantsDescription">Allow editors to create content of this type in different languages.</key>
<key alias="cultureVariantDescription">Allow editors to create content of different languages.</key>
<key alias="segmentVariantDescription">Allow editors to create segments of this content.</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
<key alias="allowVaryBySegment">Allow segmentation</key>
<key alias="elementType">Element type</key>
<key alias="elementHeading">Is an Element type</key>
<key alias="elementDescription">An Element type is meant to be used for instance in Nested Content, and not in the tree.</key>
@@ -9,6 +9,7 @@
<key alias="auditTrail">Audit Trail</key>
<key alias="browse">Browse Node</key>
<key alias="changeDocType">Change Document Type</key>
<key alias="changeDataType">Change Data Type</key>
<key alias="copy">Copy</key>
<key alias="create">Create</key>
<key alias="export">Export</key>
@@ -253,8 +254,8 @@
<key alias="published">Published</key>
<key alias="publishedPendingChanges">Published (pending changes)</key>&gt;
<key alias="publishStatus">Publication Status</key>
<key alias="publishDescendantsHelp"><![CDATA[Click <em>Publish with descendants</em> to publish <strong>%0%</strong> and all content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsWithVariantsHelp"><![CDATA[Click <em>Publish with descendants</em> to publish <strong>the selected languages</strong> and the same languages of content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsHelp"><![CDATA[Publish <strong>%0%</strong> and all content items underneath and thereby making their content publicly available.]]></key>
<key alias="publishDescendantsWithVariantsHelp"><![CDATA[Publish variants and variants of same type underneath and thereby making their content publicly available.]]></key>
<key alias="releaseDate">Publish at</key>
<key alias="unpublishDate">Unpublish at</key>
<key alias="removeDate">Clear Date</key>
@@ -292,7 +293,7 @@
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
<key alias="includeUnpublished">Include drafts: also publish unpublished content items.</key>
<key alias="includeUnpublished">Include drafts and unpublished content items.</key>
<key alias="isSensitiveValue">This value is hidden. If you need access to view this value please contact your website administrator.</key>
<key alias="isSensitiveValue_short">This value is hidden.</key>
<key alias="languagesToPublishForFirstTime">What languages would you like to publish? All languages with content are saved!</key>
@@ -306,7 +307,17 @@
<key alias="unpublishedLanguages">Unpublished Languages</key>
<key alias="unmodifiedLanguages">Unmodified Languages</key>
<key alias="untouchedLanguagesForFirstTime">These languages haven't been created</key>
<key alias="readyToPublish">Ready to Publish?</key>
<key alias="variantsWillBeSaved">All new variants will be saved.</key>
<key alias="variantsToPublish">Which variants you would like to publish?</key>
<key alias="variantsToSave">Choose which variants to be saved.</key>
<key alias="variantsToSendForApproval">Pick variants to send for approval.</key>
<key alias="variantsToSchedule">Set scheduled publishing...</key>
<key alias="variantsToUnpublish">Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants.</key>
<key alias="publishRequiresVariants">The following variants is required for publishing to take place:</key>
<key alias="notReadyToPublish">We are not ready to Publish</key>
<key alias="readyToPublish">Ready to publish?</key>
<key alias="readyToSave">Ready to Save?</key>
<key alias="sendForApproval">Send for approval</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
@@ -493,6 +504,7 @@
<key alias="unLinkYour">Un-link your</key>
<key alias="account">account</key>
<key alias="selectEditor">Select editor</key>
<key alias="selectEditorConfiguration">Select configuration</key>
<key alias="selectSnippet">Select snippet</key>
<key alias="variantdeletewarning">This will delete the node and all its languages. If you only want to delete one language, you should unpublish the node in that language instead.</key>
</area>
@@ -797,6 +809,7 @@
<key alias="generalHeader">General</key>
<key alias="editorHeader">Editor</key>
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
<key alias="toggleAllowSegmentVariants">Toggle allow segmentation</key>
</area>
<area alias="graphicheadline">
<key alias="backgroundcolor">Background color</key>
@@ -1631,6 +1644,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="availableEditors">Create new</key>
<key alias="reuse">Use existing</key>
<key alias="editorSettings">Editor settings</key>
<key alias="searchResultSettings">Available configurations</key>
<key alias="searchResultEditors">Create a new configuration</key>
<key alias="configuration">Configuration</key>
<key alias="yesDelete">Yes, delete</key>
<key alias="movedUnderneath">was moved underneath</key>
@@ -1656,9 +1671,16 @@ 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="variantsHeading">Allow variations</key>
<key alias="cultureVariantHeading">Allow vary by culture</key>
<key alias="segmentVariantHeading">Allow segmentation</key>
<key alias="cultureVariantLabel">Vary by culture</key>
<key alias="segmentVariantLabel">Vary by segments</key>
<key alias="variantsDescription">Allow editors to create content of this type in different languages.</key>
<key alias="cultureVariantDescription">Allow editors to create content of different languages.</key>
<key alias="segmentVariantDescription">Allow editors to create segments of this content.</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
<key alias="allowVaryBySegment">Allow segmentation</key>
<key alias="elementType">Element type</key>
<key alias="elementHeading">Is an element type</key>
<key alias="elementDescription">An element type is meant to be used for instance in Nested Content, and not in the tree.</key>
@@ -349,6 +349,7 @@ namespace Umbraco.Web.Editors
{"loginBackgroundImage", Current.Configs.Settings().Content.LoginBackgroundImage},
{"showUserInvite", EmailSender.CanSendRequiredEmail},
{"canSendRequiredEmail", EmailSender.CanSendRequiredEmail},
{"showAllowSegmentationForDocumentTypes", false},
}
},
{
+9 -2
View File
@@ -897,7 +897,13 @@ namespace Umbraco.Web.Editors
if (variantCount > 1)
{
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
var savedWithoutErrors = contentItem.Variants
.Where(x => x.Save && !cultureErrors.Contains(x.Culture) && x.Culture != null)
.Select(x => x.Culture)
.ToArray();
foreach (var c in savedWithoutErrors)
{
AddSuccessNotification(notifications, c,
Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
@@ -1874,7 +1880,8 @@ namespace Umbraco.Web.Editors
? variant.PropertyCollectionDto
: new ContentPropertyCollectionDto
{
Properties = variant.PropertyCollectionDto.Properties.Where(x => !x.Culture.IsNullOrWhiteSpace())
Properties = variant.PropertyCollectionDto.Properties.Where(
x => !x.Culture.IsNullOrWhiteSpace() || !x.Segment.IsNullOrWhiteSpace())
};
//for each variant, map the property values
@@ -198,7 +198,7 @@ namespace Umbraco.Web.Editors.Filters
r.ErrorMessage = property.ValidationRegExpMessage;
}
modelState.AddPropertyError(r, property.Alias, property.Culture);
modelState.AddPropertyError(r, property.Alias, property.Culture, property.Segment);
}
}
}
+4 -2
View File
@@ -49,13 +49,15 @@ namespace Umbraco.Web
/// <param name="propertyAlias"></param>
/// <param name="culture">The culture for the property, if the property is invariant than this is empty</param>
internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
ValidationResult result, string propertyAlias, string culture = "")
ValidationResult result, string propertyAlias, string culture = "", string segment = "")
{
if (culture == null)
culture = "";
modelState.AddValidationError(result, "_Properties", propertyAlias,
//if the culture is null, we'll add the term 'invariant' as part of the key
culture.IsNullOrWhiteSpace() ? "invariant" : culture);
culture.IsNullOrWhiteSpace() ? "invariant" : culture,
// if the segment is null, we'll add the term 'null' as part of the key
segment.IsNullOrWhiteSpace() ? "null" : segment);
}
/// <summary>
@@ -69,6 +69,9 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "allowCultureVariant")]
public bool AllowCultureVariant { get; set; }
[DataMember(Name = "allowSegmentVariant")]
public bool AllowSegmentVariant { get; set; }
//Tabs
[DataMember(Name = "groups")]
public IEnumerable<PropertyGroupBasic<TPropertyType>> Groups { get; set; }
@@ -24,6 +24,9 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "name", IsRequired = true)]
public string Name { get; set; }
[DataMember(Name = "displayName")]
public string DisplayName { get; set; }
/// <summary>
/// Defines the tabs containing display properties
/// </summary>
@@ -20,9 +20,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "defaultTemplate")]
public EntityBasic DefaultTemplate { get; set; }
[DataMember(Name = "allowCultureVariant")]
public bool AllowCultureVariant { get; set; }
[DataMember(Name = "allowSegmentVariant")]
public bool AllowSegmentVariant { get; set; }
}
}
@@ -54,5 +54,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "allowCultureVariant")]
public bool AllowCultureVariant { get; set; }
[DataMember(Name = "allowSegmentVariant")]
public bool AllowSegmentVariant { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More