implement segment support

This commit is contained in:
Niels Lyngsø
2020-01-23 11:26:55 +01:00
parent 4a028ed22c
commit 109d9e1f7b
20 changed files with 145 additions and 90 deletions
@@ -1026,6 +1026,7 @@
getMethod: "&",
getScaffoldMethod: "&?",
culture: "=?",
segment: "=?",
infiniteModel: "=?"
}
};
@@ -12,7 +12,6 @@
editor: "<",
editorIndex: "<",
editorCount: "<",
openVariants: "<",
onCloseSplitView: "&",
onSelectVariant: "&",
onOpenSplitView: "&",
@@ -9,7 +9,8 @@
bindings: {
page: "<",
content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant
variantId: "<",
culture: "<",
segment: "<",
onSelectApp: "&?",
onSelectAppAnchor: "&?",
onBack: "&?",
@@ -42,7 +43,6 @@
//Used to track the open variants across the split views
// The values are the variant ids of the currently open variants.
// See variantHelper.getId() for the current format.
vm.openVariants = [];
/** Called when the component initializes */
function onInit() {
@@ -61,7 +61,10 @@
*/
function onChanges(changes) {
if (changes.variantId && !changes.variantId.isFirstChange() && changes.variantId.currentValue !== changes.variantId.previousValue) {
if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) {
setActiveVariant();
}
if (changes.segment && !changes.segment.isFirstChange() && changes.segment.currentValue !== changes.segment.previousValue) {
setActiveVariant();
}
}
@@ -81,13 +84,17 @@
}
/**
* Set the active variant based on the current culture + segment (query string)
* Set the active variant based on the current culture or segment (query string)
*/
function setActiveVariant() {
// set the active variant
var activeVariant = null;
_.each(vm.content.variants, function (v) {
if (variantHelper.getId(v) === vm.variantId) {
if (
(!v.language || v.language.culture === vm.culture)
&&
(v.segment === vm.segment)
) {
v.active = true;
activeVariant = v;
}
@@ -107,10 +114,9 @@
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 = variantHelper.getId(vm.editors[s].content);
//get the variant from the scope model
var variant = _.find(vm.content.variants, function (v) {
return variantHelper.getId(v) === editorVariantId;
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);
}
@@ -125,19 +131,25 @@
*/
function insertVariantEditor(index, variant) {
var variantId = variantHelper.getId(variant);
//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;
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;
//check if the segment at the index is the same, if it's null an editor will be added
var currentSegment = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].segment;
if (currentCulture !== variantCulture || currentSegment !== variantSegment) {
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
variantId: variantId
culture: variantCulture,
segment: variantSegment
});
}
else {
@@ -147,6 +159,7 @@
}
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
@@ -158,8 +171,8 @@
variant.apps = angular.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 this is a variant it has a culture/language or segment than we need to assign the variant drop down
if (variant.language || variant.segment !== null) {
//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,
@@ -178,8 +191,11 @@
//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].segment === variant.segment) {
if (
(!variant.variants[i].language || variant.variants[i].language.culture === variant.language.culture)
&&
variant.variants[i].segment === variant.segment
) {
variant.variants[i].active = true;
}
else {
@@ -187,6 +203,8 @@
}
}
/*
//SEGMENTS_TODO: Remove this part if not used.
var variantId = variantHelper.getId(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
@@ -195,7 +213,7 @@
} else {
vm.openVariants[editorIndex] = variantId;
}
*/
}
//then assign the variant to a view model to the content app
@@ -221,16 +239,19 @@
return variant;
}
function getCultureFromVariant(variant) {
return variant.language ? variant.language.culture : null;
}
/**
* Adds a new editor to the editors array to show content in a split view
* @param {any} selectedVariant
*/
function openSplitView(selectedVariant) {
var variant = variantHelper.getId(selectedVariant);
//Find the whole variant model based on the culture that was chosen
var variant = _.find(vm.content.variants, function (v) {
return variantHelper.getId(v) === variant;
return getCultureFromVariant(v) === getCultureFromVariant(selectedVariant) && v.segment === selectedVariant.segment;
});
insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length));
@@ -273,9 +294,12 @@
$timeout(function () {
vm.editors.splice(editorIndex, 1);
//remove variant from open variants
vm.openVariants.splice(editorIndex, 1);
// SEGMENTS_TODO: Test this scenario.
//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]);
//$location.search({"cculture": vm.openVariants[0].language, "csegment": vm.openVariants[0]});
$location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment});
splitViewChanged();
}, 400);
}
@@ -287,10 +311,10 @@
*/
function selectVariant(variant, editorIndex) {
var variantId = variantHelper.getId(variant);
// prevent variants already open in a split view to be opened
if (vm.openVariants.indexOf(variantId) !== -1) {
var variantCulture = variant.language ? variant.language.culture : "invariant";
var variantSegment = variant.segment || null;
if (vm.editors.find((editor) => (!editor.content.language || editor.content.language.culture === variantCulture) && editor.content.segment === variantSegment)) {
return;
}
@@ -299,7 +323,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", variantId);
$location.search({"cculture": variantCulture, "csegment": variant.segment});
}
else {
@@ -311,12 +335,14 @@
}
variant.active = true;
//get the variant content model and initialize the editor with that
var contentVariant = _.find(vm.content.variants,
function (v) {
return variantHelper.getId(v) === variantId;
return (!v.language || v.language.culture === variantCulture) && v.segment === variantSegment;
});
editor.content = initVariant(contentVariant, editorIndex);
//update the editors collection
insertVariantEditor(editorIndex, contentVariant);
@@ -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) {
@@ -3,7 +3,7 @@
function EditorContentHeader(serverValidationManager, localizationService, editorState, variantHelper) {
function link(scope, el, attr, ctrl) {
function link(scope) {
var unsubscribe = [];
@@ -42,22 +42,24 @@
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)) {
// SEGMENTS_TODO: Check that this correction is okay, can we even see the active var here?
if (variant.active !== true && scope.variantHasError(variant)) {
check = true;
}
});
scope.vm.errorsOnOtherVariants = check;
}
function onCultureValidation(valid, errors, allErrors, culture) {
var index = scope.vm.variantsWithError.indexOf(culture);
function onVariantValidation(valid, errors, allErrors, culture, segment) {
// SEGMENTS_TODO: See wether we can use errors, allErrors?
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();
@@ -82,10 +84,10 @@
angular.forEach(scope.content.variants, function (variant) {
unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, variant.segment, null, onCultureValidation));
unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, variant.segment, null, onVariantValidation));
});
unsubscribe.push(serverValidationManager.subscribe(null, null, null, null, onCultureValidation));
unsubscribe.push(serverValidationManager.subscribe(null, null, null, null, onVariantValidation));
@@ -100,6 +102,10 @@
});
}
function getCultureFromVariant(variant) {
return variant.language ? variant.language.culture : null;
}
scope.getVariantDisplayName = variantHelper.getDisplayName;
scope.goBack = function () {
@@ -143,25 +149,30 @@
/**
* 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
* @param {any} variant
*/
scope.variantIsOpen = function (variant) {
var variantId = variantHelper.getId(variant);
return (scope.openVariants.indexOf(variantId) !== -1);
// SEGMENTS_TODO: ... does this work?
if (scope.content.variants.find((v) => variant.active === true && (getCultureFromVariant(v) === getCultureFromVariant(variant)) && variant.segment === variant.segment)) {
console.log("VARIANT IS OPEN")
return;
}
console.log("VARIANT IS closed", scope.content.variants)
}
/**
* Check whether a variant has a error, used to display errors in variant switcher.
* @param {any} culture
*/
scope.variantHasError = function(culture) {
scope.variantHasError = function(variant) {
// 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) {
if (variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) {
if(scope.vm.variantsWithError.find((item) => item.culture === "invariant" && item.segment === null) !== undefined) {
return true;
}
}
if(scope.vm.variantsWithError.indexOf(culture) !== -1) {
if(scope.vm.variantsWithError.find((item) => item.culture === variant.language.culture && item.segment === variant.segment) !== undefined) {
return true;
}
return false;
@@ -205,7 +216,6 @@
menu: "=",
hideActionsMenu: "<?",
content: "=",
openVariants: "<",
hideChangeVariant: "<?",
onSelectNavigationItem: "&?",
onSelectAnchorItem: "&?",
@@ -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;
@@ -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();
}
@@ -179,7 +179,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
}
}
htmlFieldReference = "";
var htmlFieldReference = "";
if (parts.length > 3) {
htmlFieldReference = parts[3] || "";
}
@@ -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) {
@@ -8,27 +8,6 @@ function variantHelper() {
* Returns the id for this variant
* @param {any} variant
*/
function getId(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 getDisplayName(variant) {
if (variant == null) {
return "";
@@ -54,7 +33,6 @@ function variantHelper() {
}
return {
getId,
getDisplayName
}
}
@@ -9,7 +9,8 @@
<umb-variant-content-editors
page="page"
content="content"
variant-id="culture"
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"
@@ -1,6 +1,6 @@
<div class="umb-split-views">
<div class="umb-split-view"
ng-repeat="editor in vm.editors track by editor.variantId"
ng-repeat="editor in vm.editors track by editor.culture+'.'+editor.segment"
ng-class="{'umb-split-view--collapsed': editor.collapsed}">
<umb-variant-content
@@ -9,7 +9,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)"
@@ -14,7 +14,6 @@
content="vm.editor.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()"
@@ -40,18 +40,22 @@
</ng-form>
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
<span ng-bind="getVariantDisplayName(vm.currentVariant)"></span>
<span ng-bind="::getVariantDisplayName(vm.currentVariant)"></span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</a>
<span ng-if="hideChangeVariant" class="umb-variant-switcher__toggle">
<span ng-bind="getVariantDisplayName(variant)"></span>
<span ng-bind="::getVariantDisplayName(variant)"></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), '--error': variantHasError(variant.language.culture)}" ng-repeat="variant in content.variants">
<umb-dropdown-item
ng-repeat="variant in content.variants"
class="umb-variant-switcher__item"
ng-class="{'--current': variant.active, '--not-allowed': variantIsOpen(variant), '--error': variantHasError(variant)}"
>
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name" ng-bind="getVariantDisplayName(variant)"></span>
<span class="umb-variant-switcher__name" ng-bind="::getVariantDisplayName(variant)"></span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
</a>
<div ng-if="splitViewOpen !== true && !variant.active" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>
@@ -7,6 +7,10 @@
//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;
var unbindSegmentWatcher;
var timeout = null;
var vm = this;
vm.loading = true;
@@ -16,26 +20,50 @@
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 () {
unbindLanguageWatcher = $scope.$watch(function () {
return vm.content.language.culture;
}, function (newVal, oldVal) {
if (newVal !== oldVal) {
vm.loading = true;
// TODO: Can we minimize the flicker?
$timeout(function () {
onInit();
}, 100);
requestUpdate();
}
});
} else {
unbindLanguageWatcher = function() {}
}
unbindSegmentWatcher = $scope.$watch(function () {
return vm.content.segment;
}, function (newVal, oldVal) {
if (newVal !== oldVal) {
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);
@@ -60,6 +60,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>
@@ -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>