From 10bf55dbaadae4fd8515caa016169d8990cab807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Thu, 29 Aug 2019 13:23:11 +0200 Subject: [PATCH 01/97] Backoffice support for viewing segments --- .../umbvariantcontenteditors.directive.js | 80 +++++++++++++------ .../src/views/components/content/edit.html | 2 +- .../content/umb-variant-content-editors.html | 2 +- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index a4dac046e5c0..2118b888802f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -9,7 +9,7 @@ bindings: { page: "<", content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant - culture: "<", + variantId: "<", onSelectApp: "&?", onSelectAppAnchor: "&?", onBack: "&?", @@ -40,12 +40,14 @@ //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 + // The values are the variant ids of the currently open variants. + // See getVariantId() for the current format. vm.openVariants = []; /** Called when the component initializes */ function onInit() { prevContentDateUpdated = angular.copy(vm.content.updateDate); - setActiveCulture(); + setActiveVariant(); } /** Called when the component has linked all elements, this is when the form controller is available */ @@ -59,15 +61,15 @@ */ function onChanges(changes) { - if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) { - setActiveCulture(); + if (changes.variantId && !changes.variantId.isFirstChange() && changes.variantId.currentValue !== changes.variantId.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 = angular.copy(vm.content.updateDate); } } @@ -79,13 +81,13 @@ } /** - * Set the active variant based on the current culture (query string) + * Set the active variant based on the current culture + segment (query string) */ - function setActiveCulture() { + function setActiveVariant() { // set the active variant - var activeVariant = null; + var activeVariant = null; _.each(vm.content.variants, function (v) { - if (v.language && v.language.culture === vm.culture) { + if (getVariantId(v) === vm.variantId) { v.active = true; activeVariant = v; } @@ -105,9 +107,10 @@ 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++) { + var editorVariantId = getVariantId(vm.editors[s].content); //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 getVariantId(v) === editorVariantId; }); vm.editors[s].content = initVariant(variant, s); } @@ -122,19 +125,19 @@ */ function insertVariantEditor(index, variant) { - var variantCulture = variant.language ? variant.language.culture : "invariant"; + var variantId = getVariantId(variant); - //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; + //check if the variant at the index is the same, if it's null an editor will be added + var currentVariantId = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].variantId; - if (currentCulture !== variantCulture) { + if (currentVariantId !== variantId) { //Not the current culture 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, { content: variant, //used for "track-by" ng-repeat - culture: variantCulture + variantId: variantId }); } else { @@ -143,6 +146,27 @@ } } + function getVariantId(variant) { + var hasLanguage = variant.language && !!variant.language.culture; + var hasSegment = !!variant.segment; + + var sep = ";"; + + if (!hasLanguage && !hasSegment) { + // Invariant + return ""; + } else if (hasLanguage && !hasSegment) { + // Culture only + return variant.language.culture; + } else if (!hasLanguage && hasSegment) { + // Segment only + return sep + variant.segment; + } else { + // Culture and Segment + return variant.language.culture + sep + variant.segment; + } + } + 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 @@ -161,7 +185,7 @@ if (!variant.variants) { variant.variants = _.map(vm.content.variants, function (v) { - return _.pick(v, "active", "language", "state"); + return _.pick(v, "active", "language", "segment", "state"); }); } else { @@ -169,13 +193,14 @@ angular.extend(variant.variants, _.map(vm.content.variants, function (v) { - return _.pick(v, "active", "language", "state"); + return _.pick(v, "active", "language", "segment", "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) { + if (variant.variants[i].language.culture === variant.language.culture && + variant.variants[i].segment === variant.segment) { variant.variants[i].active = true; } else { @@ -183,12 +208,13 @@ } } + var variantId = getVariantId(variant); // 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); + if (vm.openVariants && vm.openVariants.length === 0) { + vm.openVariants.push(variantId); } else { - vm.openVariants[editorIndex] = variant.language.culture; + vm.openVariants[editorIndex] = variantId; } } @@ -221,11 +247,11 @@ * @param {any} selectedVariant */ function openSplitView(selectedVariant) { - var selectedCulture = selectedVariant.language.culture; + var variant = getVariantId(selectedVariant); //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 getVariantId(v) === variant; }); insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); @@ -282,8 +308,10 @@ */ function selectVariant(variant, editorIndex) { + var variantId = getVariantId(variant); + // prevent variants already open in a split view to be opened - if(vm.openVariants.indexOf(variant.language.culture) !== -1) { + if (vm.openVariants.indexOf(variantId) !== -1) { return; } @@ -292,7 +320,7 @@ 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", variantId); } else { @@ -307,7 +335,7 @@ //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; + return getVariantId(v) === variantId; }); editor.content = initVariant(contentVariant, editorIndex); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html index 8dd78883c247..da91e0bee502 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html @@ -9,7 +9,7 @@
Date: Fri, 11 Oct 2019 14:18:46 +0200 Subject: [PATCH 02/97] Support for saving edited segment values --- .../src/common/services/umbdataformatter.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index a03a71febe04..c2d0c9b7f0c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -368,6 +368,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, From 318a511aec5b43da86737793098fcf4a2363e2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Fri, 30 Aug 2019 11:38:40 +0200 Subject: [PATCH 03/97] Improve support for invariant properties in combination with segments --- .../src/common/services/umbdataformatter.service.js | 12 ++++++++++-- .../views/components/content/umb-tabbed-content.html | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index c2d0c9b7f0c7..f7a7a52ee880 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -410,7 +410,7 @@ _.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) { + if (firstVariant.language && !property.culture && !property.segment) { invariantProperties.push({ tabIndex: tabIndex, propIndex: propIndex, @@ -426,7 +426,15 @@ var variant = displayModel.variants[j]; _.each(invariantProperties, function (invProp) { - variant.tabs[invProp.tabIndex].properties[invProp.propIndex] = invProp.property; + var tab = variant.tabs[invProp.tabIndex]; + var prop = tab.properties[invProp.propIndex]; + + if (prop.segment) { + // Do not touch segmented properties + return; + } + + tab.properties[invProp.propIndex] = invProp.property; }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html index 9cdafd82b1a7..8d7bdad873d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html @@ -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" + show-inherit="content.variants.length > 1 && ((!activeVariant.language.isDefault && !property.culture) || (activeVariant.segment && !property.segment)) && !property.unlockInvariantValue" inherits-from="defaultVariant.language.name"> -
+
+ preview="content.variants.length > 1 && ((!activeVariant.language.isDefault && !property.culture) || (activeVariant.segment && !property.segment)) && !property.unlockInvariantValue">
From 73ddba5158ed0f64ecf7d7e4f0e589f3cad62747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Fri, 30 Aug 2019 13:32:44 +0200 Subject: [PATCH 04/97] Show segment name in switcher. --- .../umbeditorcontentheader.directive.js | 303 ++++++++++-------- .../editor/umb-editor-content-header.html | 10 +- 2 files changed, 177 insertions(+), 136 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index b3948bd7c42e..09d44e0578e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -3,10 +3,10 @@ function EditorContentHeader(serverValidationManager, localizationService, editorState) { - function link(scope, el, attr, ctrl) { + var unsubscribe = []; - + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -15,36 +15,30 @@ } scope.isNew = scope.content.state == "NotCreated"; - + localizationService.localizeMany([ - scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", - "placeholders_a11yName", - scope.isNew ? "general_new" : "general_edit"] + scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", + "placeholders_a11yName"] ).then(function (data) { - scope.a11yMessage = data[0]; scope.a11yName = data[1]; - var title = data[2] + ": "; if (!scope.isNew) { scope.a11yMessage += " " + scope.content.name; - title += scope.content.name; + } else { var name = editorState.current.contentTypeName; scope.a11yMessage += " " + name; scope.a11yName = name + " " + scope.a11yName; - title += name; } - - scope.$emit("$changeTitle", title); }); scope.vm = {}; 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 checkErrorsOnOtherVariants() { var check = false; angular.forEach(scope.content.variants, function (variant) { @@ -54,10 +48,10 @@ }); scope.vm.errorsOnOtherVariants = check; } - + function onCultureValidation(valid, errors, allErrors, culture) { var index = scope.vm.variantsWithError.indexOf(culture); - if (valid === true) { + if(valid === true) { if (index !== -1) { scope.vm.variantsWithError.splice(index, 1); } @@ -68,165 +62,212 @@ } checkErrorsOnOtherVariants(); } - + function onInit() { - + // find default. angular.forEach(scope.content.variants, function (variant) { if (variant.language.isDefault) { scope.vm.defaultVariant = variant; } }); - + setCurrentVariant(); - + angular.forEach(scope.content.apps, (app) => { if (app.alias === "umbContent") { app.anchors = scope.content.tabs; } }); + + + angular.forEach(scope.content.variants, function (variant) { + unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); + }); + + unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); + + + + } + function getVariantDisplayName(variant) { + if (variant == null) { + return ""; + } - angular.forEach(scope.content.variants, function (variant) { - unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); - }); + var parts = []; - unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); + if (variant.language && variant.language.name) { + parts.push(variant.language.name); + } + if (variant.segment) { + parts.push(variant.segment); + } + if (parts.length === 0) { + // Invariant + parts.push("Default"); + } - } + return parts.join(" - "); + } - function setCurrentVariant() { - angular.forEach(scope.content.variants, function (variant) { - if (variant.active) { - scope.vm.currentVariant = variant; - checkErrorsOnOtherVariants(); + function setCurrentVariant() { + angular.forEach(scope.content.variants, function (variant) { + if (variant.active) { + scope.vm.currentVariant = variant; + checkErrorsOnOtherVariants(); + } + }); + } + + scope.getVariantDisplayName = getVariantDisplayName; + + scope.goBack = function () { + if (scope.onBack) { + scope.onBack(); } - }); - } + }; - scope.goBack = function () { - if (scope.onBack) { - scope.onBack(); - } - }; + scope.selectVariant = function (event, variant) { - scope.selectVariant = function (event, variant) { + if (scope.onSelectVariant) { + scope.vm.dropdownOpen = false; + scope.onSelectVariant({ "variant": 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 }); + } + }; + + function getVariantId(variant) { + var hasLanguage = variant.language && !!variant.language.culture; + var hasSegment = !!variant.segment; + + var sep = ";"; + + if (!hasLanguage && !hasSegment) { + // Invariant + return ""; + } else if (hasLanguage && !hasSegment) { + // Culture only + return variant.language.culture; + } else if (!hasLanguage && hasSegment) { + // Segment only + return sep + variant.segment; + } else { + // Culture and Segment + return variant.language.culture + sep + variant.segment; + } } - }; - 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 (variant) { + var variantId = getVariantId(variant); + return (scope.openVariants.indexOf(variantId) !== -1); } - }; - - /** - * 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) { + + /** + * 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) { + return true; + } + } + if(scope.vm.variantsWithError.indexOf(culture) !== -1) { return true; } + return false; } - if (scope.vm.variantsWithError.indexOf(culture) !== -1) { - return true; - } - return false; - } - onInit(); + 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; + //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(); + return scope.vm.currentVariant.language.culture; //should never get here + }, function (newValue, oldValue) { + if (newValue !== scope.vm.currentVariant.language.culture) { + setCurrentVariant(); + } + }); + } + + 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: " - {{vm.currentVariant.language.name}} +   - + - {{vm.currentVariant.language.name}} + - + - {{variant.language.name}} +
Open in split view
From f11f4c0a4efe67c75e8f3952a9d917812ad5a6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Fri, 30 Aug 2019 14:41:32 +0200 Subject: [PATCH 05/97] Show segment name in Publish dialog (cherry picked from commit 43679c0b8a591ee9459b397ceb4ef0f9abcbc604) --- .../content/overlays/publish.controller.js | 26 +++++++++++++++++++ .../src/views/content/overlays/publish.html | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js index 8caa38ea17b6..fe667f31b9a4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js @@ -12,6 +12,32 @@ vm.dirtyVariantFilter = dirtyVariantFilter; vm.pristineVariantFilter = pristineVariantFilter; + $scope.getVariantDisplayName = getVariantDisplayName; + + // TODO: Move to some variantService / helper + function getVariantDisplayName(variant) { + if (variant == null) { + return ""; + } + + var parts = []; + + if (variant.language && variant.language.name) { + parts.push(variant.language.name); + } + + if (variant.segment) { + parts.push(variant.segment); + } + + if (parts.length === 0) { + // Invariant + parts.push("Default"); + } + + return parts.join(" - "); + } + /** Returns true if publishing is possible based on if there are un-published mandatory languages */ function canPublish() { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index a9afdffda651..7152c701702d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -23,7 +23,7 @@ server-validation-field="{{variant.htmlId}}" />