Skip to content

Commit

Permalink
Merge pull request #7973 from umbraco/v8/feature/AB6057-segment-feature
Browse files Browse the repository at this point in the history
Segment feature
  • Loading branch information
Warren Buckley authored May 5, 2020
2 parents d7469e6 + 1320191 commit 0f4681f
Show file tree
Hide file tree
Showing 74 changed files with 1,994 additions and 1,287 deletions.
9 changes: 5 additions & 4 deletions src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,16 @@ public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVar
// 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
Expand Down
54 changes: 51 additions & 3 deletions src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void Add_Invariant_Property_Error()

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]
Expand All @@ -73,9 +73,57 @@ public void Add_Variant_Property_Error()
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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
*/
Expand All @@ -226,6 +225,7 @@

$scope.content = data;

appendRuntimeData();
init();

syncTreeNode($scope.content, $scope.content.path, true);
Expand All @@ -251,6 +251,7 @@

$scope.content = data;

appendRuntimeData();
init();
startWatches($scope.content);

Expand All @@ -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;
Expand Down Expand Up @@ -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" })) {

Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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" })) {

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -963,11 +964,18 @@
* Call back when a content app changes
* @param {any} app
*/
$scope.appChanged = function (app) {

$scope.app = app;
$scope.appChanged = function (activeApp) {

$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);

Expand Down Expand Up @@ -1029,6 +1037,7 @@
getMethod: "&",
getScaffoldMethod: "&?",
culture: "=?",
segment: "=?",
infiniteModel: "=?"
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -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;
};
Expand All @@ -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 = {
Expand All @@ -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,
}
};

Expand Down
Loading

0 comments on commit 0f4681f

Please sign in to comment.