Merge branch 'v8/dev' into v8/feature/5170-IPublishedContent

# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
This commit is contained in:
Shannon
2019-06-21 15:06:43 +10:00
137 changed files with 2017 additions and 1814 deletions

View File

@@ -41,12 +41,12 @@ module.exports = {
assets: "./src/assets/**"
}
},
root: "../Umbraco.Web.UI/Umbraco/",
root: "../Umbraco.Web.UI/",
targets: {
js: "js/",
lib: "lib/",
views: "views/",
css: "assets/css/",
assets: "assets/"
js: "Umbraco/js/",
lib: "Umbraco/lib/",
views: "Umbraco/views/",
css: "Umbraco/assets/css/",
assets: "Umbraco/assets/"
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
var watchingCulture = false;
//setup scope vars
$scope.defaultButton = null;
@@ -26,26 +27,58 @@
$scope.allowOpen = true;
$scope.app = null;
//initializes any watches
function startWatches(content) {
//watch for changes to isNew & the content.id, set the page.isNew accordingly and load the breadcrumb if we can
$scope.$watchGroup(['isNew', 'content.id'], function (newVal, oldVal) {
var contentId = newVal[1];
$scope.page.isNew = Object.toBoolean(newVal[0]);
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
if (!$scope.page.isNew && contentId && content.parentId && content.parentId !== -1) {
loadBreadcrumb();
if (!watchingCulture) {
$scope.$watch('culture',
function (value, oldValue) {
if (value !== oldValue) {
loadBreadcrumb();
}
});
}
}
});
}
//this initializes the editor with the data which will be called more than once if the data is re-loaded
function init() {
var content = $scope.content;
if (content.id && content.isChildOfListView && content.trashed === false) {
$scope.page.listViewPath = ($routeParams.page) ?
"/content/content/edit/" + content.parentId + "?page=" + $routeParams.page :
"/content/content/edit/" + content.parentId;
}
// we need to check wether an app is present in the current data, if not we will present the default app.
var isAppPresent = false;
// on first init, we dont have any apps. but if we are re-initializing, we do, but ...
if ($scope.app) {
// 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) {
_.forEach(content.apps, function (app) {
if (app === $scope.app) {
isAppPresent = true;
}
});
// 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) {
_.forEach(content.apps, function (app) {
if (app.alias === $scope.app.alias) {
isAppPresent = true;
app.active = true;
@@ -53,7 +86,7 @@
}
});
}
}
// if we still dont have a app, lets show the first one:
@@ -61,21 +94,8 @@
content.apps[0].active = true;
$scope.appChanged(content.apps[0]);
}
editorState.set(content);
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
if (!$scope.page.isNew) {
if (content.parentId && content.parentId !== -1) {
loadBreadcrumb();
$scope.$watch('culture',
function (value, oldValue) {
if (value !== oldValue) {
loadBreadcrumb();
}
});
}
}
editorState.set(content);
bindEvents();
@@ -118,10 +138,10 @@
function isContentCultureVariant() {
return $scope.content.variants.length > 1;
}
function reload() {
$scope.page.loading = true;
loadContent().then(function() {
loadContent().then(function () {
$scope.page.loading = false;
});
}
@@ -134,7 +154,7 @@
evts.push(eventsService.on("editors.documentType.saved", function (name, args) {
// if this content item uses the updated doc type we need to reload the content item
if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) {
if (args && args.documentType && $scope.content.documentType.id === args.documentType.id) {
reload();
}
}));
@@ -152,12 +172,6 @@
$scope.content = data;
if (data.isChildOfListView && data.trashed === false) {
$scope.page.listViewPath = ($routeParams.page) ?
"/content/content/edit/" + data.parentId + "?page=" + $routeParams.page :
"/content/content/edit/" + data.parentId;
}
init();
syncTreeNode($scope.content, $scope.content.path, true);
@@ -219,7 +233,7 @@
$scope.page.showPreviewButton = true;
}
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
function syncTreeNode(content, path, initialLoad) {
@@ -228,9 +242,13 @@
}
if (!$scope.content.isChildOfListView) {
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
});
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true })
.then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
}, function () {
//handle the rejection
console.log("A problem occurred syncing the tree! A path is probably incorrect.")
});
}
else if (initialLoad === true) {
@@ -328,7 +346,7 @@
$scope.contentForm.$dirty = false;
for (var i = 0; i < $scope.content.variants.length; i++) {
if($scope.content.variants[i].isDirty){
if ($scope.content.variants[i].isDirty) {
$scope.contentForm.$dirty = true;
return;
}
@@ -337,7 +355,7 @@
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
function performSave(args) {
//Used to check validility of nested form - coming from Content Apps mostly
//Set them all to be invalid
var fieldsToRollback = checkValidility();
@@ -348,7 +366,8 @@
scope: $scope,
content: $scope.content,
action: args.action,
showNotifications: args.showNotifications
showNotifications: args.showNotifications,
softRedirect: true
}).then(function (data) {
//success
init();
@@ -364,11 +383,6 @@
function (err) {
syncTreeNode($scope.content, $scope.content.path);
//error
if (err) {
editorState.set($scope.content);
}
resetNestedFieldValiation(fieldsToRollback);
return $q.reject(err);
@@ -421,9 +435,9 @@
//need to show a notification else it's not clear there was an error.
localizationService.localizeMany([
"speechBubbles_validationFailedHeader",
"speechBubbles_validationFailedMessage"
]
"speechBubbles_validationFailedHeader",
"speechBubbles_validationFailedMessage"
]
).then(function (data) {
notificationsService.error(data[0], data[1]);
});
@@ -440,6 +454,7 @@
$scope.content = data;
init();
startWatches($scope.content);
resetLastListPageNumber($scope.content);
@@ -454,13 +469,14 @@
$scope.page.loading = true;
loadContent().then(function () {
startWatches($scope.content);
$scope.page.loading = false;
});
}
$scope.unpublish = function () {
clearNotifications($scope.content);
if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) {
if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/unpublish.html",
@@ -494,6 +510,7 @@
overlayService.close();
}
};
overlayService.open(dialog);
}
};
@@ -510,7 +527,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveToPublish",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -518,14 +535,14 @@
saveMethod: contentResource.sendToPublish,
action: "sendToPublish",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
@@ -534,7 +551,7 @@
return $q.when(err);
});
},
close: function() {
close: function () {
overlayService.close();
}
};
@@ -563,14 +580,13 @@
if (isContentCultureVariant()) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/publish.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveAndPublish",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -578,14 +594,14 @@
saveMethod: contentResource.publish,
action: "publish",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
@@ -594,11 +610,10 @@
return $q.when(err);
});
},
close: function() {
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
@@ -617,7 +632,7 @@
$scope.page.buttonGroupState = "success";
}, function () {
$scope.page.buttonGroupState = "error";
});;
});
}
};
@@ -634,7 +649,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_save",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -642,14 +657,14 @@
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
@@ -658,7 +673,7 @@
return $q.when(err);
});
},
close: function(oldModel) {
close: function (oldModel) {
overlayService.close();
}
};
@@ -685,7 +700,7 @@
};
$scope.schedule = function() {
$scope.schedule = function () {
clearNotifications($scope.content);
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "schedule" })) {
@@ -755,7 +770,7 @@
}
};
$scope.publishDescendants = function() {
$scope.publishDescendants = function () {
clearNotifications($scope.content);
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) {
@@ -883,13 +898,13 @@
* @param {any} app
*/
$scope.appChanged = function (app) {
$scope.app = app;
$scope.$broadcast("editors.apps.appChanged", { app: app });
createButtons($scope.content);
};
/**
@@ -907,11 +922,11 @@
$scope.infiniteModel.close($scope.infiniteModel);
}
};
/**
* Call back when user click the back-icon
*/
$scope.onBack = function() {
$scope.onBack = function () {
if ($scope.infiniteModel && $scope.infiniteModel.close) {
$scope.infiniteModel.close($scope.infiniteModel);
} else {
@@ -927,9 +942,7 @@
}
//since we are not notifying and clearing server validation messages when they are received due to how the variant
//switching works, we need to ensure they are cleared when this editor is destroyed
if (!$scope.page.isNew) {
serverValidationManager.clear();
}
serverValidationManager.clear();
});
}

View File

@@ -328,6 +328,8 @@
isInfoTab = true;
loadAuditTrail();
loadRedirectUrls();
setNodePublishStatus();
formatDatesToLocal();
} else {
isInfoTab = false;
}
@@ -344,6 +346,7 @@
loadAuditTrail(true);
loadRedirectUrls();
setNodePublishStatus();
formatDatesToLocal();
}
updateCurrentUrls();
});

View File

@@ -1,10 +1,12 @@
(function () {
'use strict';
function EditorContentHeader() {
function EditorContentHeader(serverValidationManager) {
function link(scope, el, attr, ctrl) {
var unsubscribe = [];
if (!scope.serverValidationNameField) {
scope.serverValidationNameField = "Name";
}
@@ -15,9 +17,44 @@
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) {
if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) {
check = true;
}
});
scope.vm.errorsOnOtherVariants = check;
}
function onCultureValidation(valid, errors, allErrors, culture) {
var index = scope.vm.variantsWithError.indexOf(culture);
if(valid === true) {
if (index !== -1) {
scope.vm.variantsWithError.splice(index, 1);
}
} else {
if (index === -1) {
scope.vm.variantsWithError.push(culture);
}
}
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) => {
@@ -26,12 +63,22 @@
}
});
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 setCurrentVariant() {
angular.forEach(scope.content.variants, function (variant) {
if (variant.active) {
scope.vm.currentVariant = variant;
checkErrorsOnOtherVariants();
}
});
}
@@ -80,9 +127,24 @@
* @param {any} culture
*/
scope.variantIsOpen = function(culture) {
if(scope.openVariants.indexOf(culture) !== -1) {
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) {
return true;
}
}
if(scope.vm.variantsWithError.indexOf(culture) !== -1) {
return true;
}
return false;
}
onInit();
@@ -103,6 +165,12 @@
}
});
}
scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
}

View File

@@ -3,107 +3,113 @@
**/
angular.module('umbraco.directives')
.directive('onDragEnter', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragEnter);
};
elm.on("dragenter", f);
scope.$on("$destroy", function(){ elm.off("dragenter", f);} );
}
};
})
.directive('onDragLeave', function () {
return function (scope, elm, attrs) {
var f = function (event) {
var rect = this.getBoundingClientRect();
var getXY = function getCursorPosition(event) {
var x, y;
if (typeof event.clientX === 'undefined') {
// try touch screen
x = event.pageX + document.documentElement.scrollLeft;
y = event.pageY + document.documentElement.scrollTop;
} else {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return { x: x, y : y };
};
var e = getXY(event.originalEvent);
// Check the mouseEvent coordinates are outside of the rectangle
if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
scope.$apply(attrs.onDragLeave);
.directive('onDragEnter', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragEnter);
};
elm.on("dragenter", f);
scope.$on("$destroy", function () { elm.off("dragenter", f); });
}
};
})
elm.on("dragleave", f);
scope.$on("$destroy", function(){ elm.off("dragleave", f);} );
};
})
.directive('onDragLeave', function () {
return function (scope, elm, attrs) {
var f = function (event) {
var rect = this.getBoundingClientRect();
var getXY = function getCursorPosition(event) {
var x, y;
.directive('onDragOver', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragOver);
if (typeof event.clientX === 'undefined') {
// try touch screen
x = event.pageX + document.documentElement.scrollLeft;
y = event.pageY + document.documentElement.scrollTop;
} else {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return { x: x, y: y };
};
var e = getXY(event.originalEvent);
// Check the mouseEvent coordinates are outside of the rectangle
if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
scope.$apply(attrs.onDragLeave);
}
};
elm.on("dragover", f);
scope.$on("$destroy", function(){ elm.off("dragover", f);} );
}
};
})
.directive('onDragStart', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragStart);
};
elm.on("dragstart", f);
scope.$on("$destroy", function(){ elm.off("dragstart", f);} );
}
};
})
elm.on("dragleave", f);
scope.$on("$destroy", function () { elm.off("dragleave", f); });
};
})
.directive('onDragEnd', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragEnd);
};
elm.on("dragend", f);
scope.$on("$destroy", function(){ elm.off("dragend", f);} );
}
};
})
.directive('onDragOver', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragOver);
};
elm.on("dragover", f);
scope.$on("$destroy", function () { elm.off("dragover", f); });
}
};
})
.directive('onDrop', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDrop);
};
elm.on("drop", f);
scope.$on("$destroy", function(){ elm.off("drop", f);} );
}
};
})
.directive('onDragStart', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragStart);
};
elm.on("dragstart", f);
scope.$on("$destroy", function () { elm.off("dragstart", f); });
}
};
})
.directive('onOutsideClick', function ($timeout, angularHelper) {
return function (scope, element, attrs) {
.directive('onDragEnd', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDragEnd);
};
elm.on("dragend", f);
scope.$on("$destroy", function () { elm.off("dragend", f); });
}
};
})
var eventBindings = [];
.directive('onDrop', function () {
return {
link: function (scope, elm, attrs) {
var f = function () {
scope.$apply(attrs.onDrop);
};
elm.on("drop", f);
scope.$on("$destroy", function () { elm.off("drop", f); });
}
};
})
.directive('onOutsideClick', function ($timeout, angularHelper) {
return function (scope, element, attrs) {
var eventBindings = [];
function oneTimeClick(event) {
var el = event.target.nodeName;
//ignore link and button clicks
var els = ["INPUT", "A", "BUTTON"];
if (els.indexOf(el) >= 0) { return; }
function oneTimeClick(event) {
// ignore clicks on new overlay
var parents = $(event.target).parents(".umb-overlay,.umb-tour");
if(parents.length > 0){
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
if (parents.length > 0) {
return;
}
@@ -126,70 +132,70 @@ angular.module('umbraco.directives')
}
//ignore clicks inside this element
if( $(element).has( $(event.target) ).length > 0 ){
if ($(element).has($(event.target)).length > 0) {
return;
}
scope.$apply(attrs.onOutsideClick);
}
$timeout(function(){
if ("bindClickOn" in attrs) {
eventBindings.push(scope.$watch(function() {
return attrs.bindClickOn;
}, function(newValue) {
if (newValue === "true") {
$(document).on("click", oneTimeClick);
} else {
$(document).off("click", oneTimeClick);
}
}));
} else {
$(document).on("click", oneTimeClick);
angularHelper.safeApply(scope, attrs.onOutsideClick);
}
scope.$on("$destroy", function() {
$(document).off("click", oneTimeClick);
// unbind watchers
for (var e in eventBindings) {
eventBindings[e]();
$timeout(function () {
if ("bindClickOn" in attrs) {
eventBindings.push(scope.$watch(function () {
return attrs.bindClickOn;
}, function (newValue) {
if (newValue === "true") {
$(document).on("click", oneTimeClick);
} else {
$(document).off("click", oneTimeClick);
}
}));
} else {
$(document).on("click", oneTimeClick);
}
scope.$on("$destroy", function () {
$(document).off("click", oneTimeClick);
// unbind watchers
for (var e in eventBindings) {
eventBindings[e]();
}
});
}); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution.
};
})
.directive('onRightClick', function ($parse) {
document.oncontextmenu = function (e) {
if (e.target.hasAttribute('on-right-click')) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
return function (scope, el, attrs) {
el.on('contextmenu', function (e) {
e.preventDefault();
e.stopPropagation();
var fn = $parse(attrs.onRightClick);
scope.$apply(function () {
fn(scope, { $event: e });
});
return false;
});
}); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution.
};
})
};
})
.directive('onRightClick',function($parse){
document.oncontextmenu = function (e) {
if(e.target.hasAttribute('on-right-click')) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
return function(scope,el,attrs){
el.on('contextmenu',function(e){
e.preventDefault();
e.stopPropagation();
var fn = $parse(attrs.onRightClick);
scope.$apply(function () {
fn(scope, { $event: e });
});
return false;
});
};
})
.directive('onDelayedMouseleave', function ($timeout, $parse) {
.directive('onDelayedMouseleave', function ($timeout, $parse) {
return {
restrict: 'A',
@@ -198,20 +204,20 @@ angular.module('umbraco.directives')
var active = false;
var fn = $parse(attrs.onDelayedMouseleave);
var leave_f = function(event) {
var callback = function() {
fn(scope, {$event:event});
var leave_f = function (event) {
var callback = function () {
fn(scope, { $event: event });
};
active = false;
$timeout(function(){
if(active === false){
$timeout(function () {
if (active === false) {
scope.$apply(callback);
}
}, 650);
};
var enter_f = function(event, args){
var enter_f = function (event, args) {
active = true;
};
@@ -220,7 +226,7 @@ angular.module('umbraco.directives')
element.on("mouseenter", enter_f);
//unsub events
scope.$on("$destroy", function(){
scope.$on("$destroy", function () {
element.off("mouseleave", leave_f);
element.off("mouseenter", enter_f);
});

View File

@@ -12,7 +12,6 @@ function treeSearchBox(localizationService, searchService, $q) {
searchFromName: "@",
showSearch: "@",
section: "@",
ignoreUserStartNodes: "@",
hideSearchCallback: "=",
searchCallback: "="
},
@@ -35,7 +34,6 @@ function treeSearchBox(localizationService, searchService, $q) {
scope.showSearch = "false";
}
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
@@ -62,11 +60,6 @@ function treeSearchBox(localizationService, searchService, $q) {
searchArgs["searchFrom"] = scope.searchFromId;
}
//append ignoreUserStartNodes value if there is one
if (scope.ignoreUserStartNodes) {
searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes;
}
searcher(searchArgs).then(function (data) {
scope.searchCallback(data);
//set back to null so it can be re-created

View File

@@ -96,13 +96,13 @@ Use this directive to generate a pagination.
scope.pageNumber = parseInt(scope.pageNumber);
}
scope.pagination = [];
let tempPagination = [];
var i = 0;
if (scope.totalPages <= 10) {
for (i = 0; i < scope.totalPages; i++) {
scope.pagination.push({
tempPagination.push({
val: (i + 1),
isActive: scope.pageNumber === (i + 1)
});
@@ -119,7 +119,7 @@ Use this directive to generate a pagination.
start = Math.min(maxIndex, start);
for (i = start; i < (10 + start) ; i++) {
scope.pagination.push({
tempPagination.push({
val: (i + 1),
isActive: scope.pageNumber === (i + 1)
});
@@ -129,7 +129,7 @@ Use this directive to generate a pagination.
if (start > 0) {
localizationService.localize("general_first").then(function(value){
var firstLabel = value;
scope.pagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false});
tempPagination.unshift({ name: firstLabel, val: 1, isActive: false }, {val: "...",isActive: false});
});
}
@@ -137,9 +137,11 @@ Use this directive to generate a pagination.
if (start < maxIndex) {
localizationService.localize("general_last").then(function(value){
var lastLabel = value;
scope.pagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false });
tempPagination.push({ val: "...", isActive: false }, { name: lastLabel, val: scope.totalPages, isActive: false });
});
}
scope.pagination = tempPagination;
}
}

View File

@@ -12,27 +12,50 @@
function valPropertyMsg(serverValidationManager) {
return {
require: ['^^form', '^^valFormManager', '^^umbProperty'],
require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'],
replace: true,
restrict: "E",
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
scope: {},
link: function (scope, element, attrs, ctrl) {
var unsubscribe = [];
var watcher = null;
var hasError = false;
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
//the property form controller api
var formCtrl = ctrl[0];
//the valFormManager controller api
var valFormManager = ctrl[1];
//the property controller api
var umbPropCtrl = ctrl[2];
//the variants controller api
var umbVariantCtrl = ctrl[3];
scope.currentProperty = umbPropCtrl.property;
var currentProperty = umbPropCtrl.property;
scope.currentProperty = currentProperty;
var currentCulture = currentProperty.culture;
//if the property is invariant (no culture), then we will explicitly set it to the string 'invariant'
//since this matches the value that the server will return for an invariant property.
var currentCulture = scope.currentProperty.culture || "invariant";
if (umbVariantCtrl) {
//if we are inside of an umbVariantContent directive
var watcher = null;
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) {
//This property is locked cause its a invariant property shown on a non-default language.
//Therefor do not validate this field.
return;
}
}
// if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language.
currentCulture = currentCulture || "invariant";
// Gets the error message to display
function getErrorMsg() {
@@ -138,13 +161,6 @@ function valPropertyMsg(serverValidationManager) {
}
var hasError = false;
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
var unsubscribe = [];
//listen for form validation changes.
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
// subscribing to this single watch.

View File

@@ -7,7 +7,7 @@
**/
function valServer(serverValidationManager) {
return {
require: ['ngModel', '?^^umbProperty'],
require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent'],
restrict: "A",
scope: {},
link: function (scope, element, attr, ctrls) {
@@ -18,9 +18,29 @@ function valServer(serverValidationManager) {
//we cannot proceed, this validator will be disabled
return;
}
// optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages.
var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null;
var currentProperty = umbPropCtrl.property;
var currentCulture = currentProperty.culture;
if (umbVariantCtrl) {
//if we are inside of an umbVariantContent directive
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) {
//This property is locked cause its a invariant property shown on a non-default language.
//Therefor do not validate this field.
return;
}
}
// if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language.
currentCulture = currentCulture || "invariant";
var watcher = null;
var unsubscribe = [];

View File

@@ -365,28 +365,17 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* </pre>
*
* @param {Int} id id of content item to return
* @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to
* @param {Int} culture optional culture to retrieve the item in
* @returns {Promise} resourcePromise object containing the content item.
*
*/
getById: function (id, options) {
var defaults = {
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
getById: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetById",
[{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])),
{ id: id })),
'Failed to retrieve data for content id ' + id)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));

View File

@@ -284,31 +284,15 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity.
*
*/
getAncestors: function (id, type, culture, options) {
var defaults = {
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
getAncestors: function (id, type, culture) {
if (culture === undefined) culture = "";
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetAncestors",
[
{ id: id },
{ type: type },
{ culture: culture },
{ ignoreUserStartNodes: options.ignoreUserStartNodes }
])),
'Failed to retrieve ancestor data for id ' + id);
[{ id: id }, { type: type }, { culture: culture }])),
'Failed to retrieve ancestor data for id ' + id);
},
/**
@@ -440,8 +424,7 @@ function entityResource($q, $http, umbRequestHelper) {
pageNumber: 1,
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder",
ignoreUserStartNodes: false
orderBy: "SortOrder"
};
if (options === undefined) {
options = {};
@@ -470,8 +453,7 @@ function entityResource($q, $http, umbRequestHelper) {
pageSize: options.pageSize,
orderBy: options.orderBy,
orderDirection: options.orderDirection,
filter: encodeURIComponent(options.filter),
ignoreUserStartNodes: options.ignoreUserStartNodes
filter: encodeURIComponent(options.filter)
}
)),
'Failed to retrieve child data for id ' + parentId);
@@ -499,19 +481,12 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity array.
*
*/
search: function (query, type, options, canceler) {
search: function (query, type, searchFrom, canceler) {
var args = [{ query: query }, { type: type }];
if(options !== undefined) {
if (options.searchFrom) {
args.push({ searchFrom: options.searchFrom });
}
if (options.ignoreUserStartNodes) {
args.push({ ignoreUserStartNodes: options.ignoreUserStartNodes });
}
if (searchFrom) {
args.push({ searchFrom: searchFrom });
}
var httpConfig = {};
if (canceler) {

View File

@@ -1,39 +1,39 @@
/**
* @ngdoc service
* @name umbraco.resources.logViewerResource
* @description Retrives Umbraco log items (by default from JSON files on disk)
*
*
**/
function logViewerResource($q, $http, umbRequestHelper) {
* @ngdoc service
* @name umbraco.resources.logViewerResource
* @description Retrives Umbraco log items (by default from JSON files on disk)
*
*
**/
function logViewerResource($q, $http, umbRequestHelper) {
//the factory object returned
return {
getNumberOfErrors: function () {
getNumberOfErrors: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetNumberOfErrors")),
"GetNumberOfErrors")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve number of errors in logs');
},
getLogLevelCounts: function () {
getLogLevelCounts: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetLogLevelCounts")),
"GetLogLevelCounts")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve log level counts');
},
getMessageTemplates: function () {
getMessageTemplates: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetMessageTemplates")),
"GetMessageTemplates")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve log templates');
},
@@ -93,12 +93,12 @@
'Failed to retrieve common log messages');
},
canViewLogs: function () {
canViewLogs: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetCanViewLogs")),
"GetCanViewLogs") + '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve state if logs can be viewed');
}

View File

@@ -329,8 +329,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder",
orderBySystemField: true,
ignoreUserStartNodes: false
orderBySystemField: true
};
if (options === undefined) {
options = {};
@@ -368,7 +367,6 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"GetChildren",
[
{ id: parentId },
{ ignoreUserStartNodes: options.ignoreUserStartNodes },
{ pageNumber: options.pageNumber },
{ pageSize: options.pageSize },
{ orderBy: options.orderBy },

View File

@@ -5,7 +5,7 @@
* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by
* all editors to share logic and reduce the amount of replicated code among editors.
**/
function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) {
function contentEditingHelper(fileManager, $q, $location, $routeParams, editorState, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) {
function isValidIdentifier(id) {
@@ -34,9 +34,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
return {
//TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire
//service should only be used for content/media/members
/** Used by the content editor and mini content editor to perform saving operations */
// TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms
// = this is already done in the formhelper service
contentEditorPerformSave: function (args) {
if (!angular.isObject(args)) {
throw "args must be an object";
@@ -53,18 +54,19 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
if (args.showNotifications === undefined) {
args.showNotifications = true;
}
var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true;
var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true;
if (args.softRedirect === undefined) {
//when true, the url will change but it won't actually re-route
//this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors
//use this service unfortunately and probably packages too.
args.softRedirect = false;
}
var self = this;
//we will use the default one for content if not specified
var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) {
args.scope.busy = true;
if (formHelper.submitForm({ scope: args.scope, action: args.action })) {
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications)
.then(function (data) {
@@ -74,26 +76,30 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
self.handleSuccessfulSave({
scope: args.scope,
savedContent: data,
redirectOnSuccess: redirectOnSuccess,
softRedirect: args.softRedirect,
rebindCallback: function () {
rebindCallback.apply(self, [args.content, data]);
}
});
args.scope.busy = false;
//update editor state to what is current
editorState.set(args.content);
return $q.resolve(data);
}, function (err) {
self.handleSaveError({
showNotifications: args.showNotifications,
redirectOnFailure: redirectOnFailure,
softRedirect: args.softRedirect,
err: err,
rebindCallback: function () {
rebindCallback.apply(self, [args.content, err.data]);
}
});
args.scope.busy = false;
//update editor state to what is current
editorState.set(args.content);
return $q.reject(err);
});
}
@@ -265,7 +271,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// if publishing is allowed also allow schedule publish
// we add this manually becuase it doesn't have a permission so it wont
// get picked up by the loop through permissions
if( _.contains(args.content.allowedActions, "U")) {
if (_.contains(args.content.allowedActions, "U")) {
buttons.subButtons.push(createButtonDefinition("SCHEDULE"));
buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS"));
}
@@ -274,7 +280,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// so long as it's already published and if the user has access to publish
// and the user has access to unpublish (may have been removed via Event)
if (!args.create) {
var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0;
var hasPublishedVariant = args.content.variants.filter(function (variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0;
if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) {
buttons.subButtons.push(createButtonDefinition("Z"));
}
@@ -444,7 +450,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var shouldIgnore = function (propName) {
return _.some([
"variants",
"tabs",
"properties",
"apps",
@@ -574,15 +580,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* A function to handle what happens when we have validation issues from the server side
*
*/
//TODO: Too many editors use this method for saving when this entire service should only be used for content/media/members,
// there is formHelper.handleError for other editors which should be used!
handleSaveError: function (args) {
if (!args.err) {
throw "args.err cannot be null";
}
if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) {
throw "args.redirectOnFailure must be set to true or false";
}
//When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors.
//Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something).
//Or, some strange server error
@@ -600,16 +607,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
}
if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) {
//we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data. Then we need
// to re-bind any server validation errors after the digest takes place.
if (!this.redirectToCreatedContent(args.err.data.id) || args.softRedirect) {
// If we are not redirecting it's because this is not newly created content, else in some cases we are
// soft-redirecting which means the URL will change but the route wont (i.e. creating content).
// In this case we need to detect what properties have changed and re-bind them with the server data.
if (args.rebindCallback && angular.isFunction(args.rebindCallback)) {
args.rebindCallback();
}
//notify all validators (don't clear the server validations though since we need to maintain their state because of
// In this case notify all validators (don't clear the server validations though since we need to maintain their state because of
// how the variant switcher works in content). server validation state is always cleared when an editor first loads
// and in theory when an editor is destroyed.
serverValidationManager.notify();
@@ -633,6 +640,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect
* when we're creating new content.
*/
//TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire
//service should only be used for content/media/members
handleSuccessfulSave: function (args) {
if (!args) {
@@ -642,14 +653,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
throw "args.savedContent cannot be null";
}
// the default behaviour is to redirect on success. This adds option to prevent when false
args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true;
if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id) || args.softRedirect) {
if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) {
// If we are not redirecting it's because this is not newly created content, else in some cases we are
// soft-redirecting which means the URL will change but the route wont (i.e. creating content).
//we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data.
//call the callback
// In this case we need to detect what properties have changed and re-bind them with the server data.
if (args.rebindCallback && angular.isFunction(args.rebindCallback)) {
args.rebindCallback();
}
@@ -667,7 +676,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* We need to decide if we need to redirect to edito mode or if we will remain in create mode.
* We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID
*/
redirectToCreatedContent: function (id, modelState) {
redirectToCreatedContent: function (id) {
//only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid
if ($routeParams.create && (isValidIdentifier(id))) {
@@ -679,7 +688,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//clear the query strings
navigationService.clearSearch(["cculture"]);
//change to new path
$location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id);
//don't add a browser history for this
@@ -699,6 +708,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* For some editors like scripts or entites that have names as ids, these names can change and we need to redirect
* to their new paths, this is helper method to do that.
*/
//TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire
//service should only be used for content/media/members
redirectToRenamedContent: function (id) {
//clear the query strings
navigationService.clearSearch();

View File

@@ -5,13 +5,15 @@
*
* @description
* Tracks the parent object for complex editors by exposing it as
* an object reference via editorState.current.entity
* an object reference via editorState.current.getCurrent().
* The state is cleared on each successful route.
*
* it is possible to modify this object, so should be used with care
*/
angular.module('umbraco.services').factory("editorState", function() {
angular.module('umbraco.services').factory("editorState", function ($rootScope) {
var current = null;
var state = {
/**
@@ -40,7 +42,7 @@ angular.module('umbraco.services').factory("editorState", function() {
* Since the editorstate entity is read-only, you cannot set it to null
* only through the reset() method
*/
reset: function() {
reset: function () {
current = null;
},
@@ -59,9 +61,10 @@ angular.module('umbraco.services').factory("editorState", function() {
* editorState.current can not be overwritten, you should only read values from it
* since modifying individual properties should be handled by the property editors
*/
getCurrent: function() {
getCurrent: function () {
return current;
}
};
// TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing.
@@ -76,5 +79,13 @@ angular.module('umbraco.services').factory("editorState", function() {
}
});
//execute on each successful route (this is only bound once per application since a service is a singleton)
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
//reset the editorState on each successful route chage
state.reset();
});
return state;
});

View File

@@ -8,11 +8,12 @@
* that need to attach files.
* When a route changes successfully, we ensure that the collection is cleared.
*/
function fileManager() {
function fileManager($rootScope) {
var fileCollection = [];
return {
var mgr = {
/**
* @ngdoc function
* @name umbraco.services.fileManager#addFiles
@@ -24,7 +25,7 @@ function fileManager() {
* for the files collection that effectively clears the files for the specified editor.
*/
setFiles: function (args) {
//propertyAlias, files
if (!angular.isString(args.propertyAlias)) {
throw "args.propertyAlias must be a non empty string";
@@ -52,7 +53,7 @@ function fileManager() {
fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData });
}
},
/**
* @ngdoc function
* @name umbraco.services.fileManager#getFiles
@@ -62,10 +63,10 @@ function fileManager() {
* @description
* Returns all of the files attached to the file manager
*/
getFiles: function() {
getFiles: function () {
return fileCollection;
},
/**
* @ngdoc function
* @name umbraco.services.fileManager#clearFiles
@@ -78,7 +79,17 @@ function fileManager() {
clearFiles: function () {
fileCollection = [];
}
};
};
//execute on each successful route (this is only bound once per application since a service is a singleton)
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
//reset the file manager on each route change, the file collection is only relavent
// when working in an editor and submitting data to the server.
//This ensures that memory remains clear of any files and that the editors don't have to manually clear the files.
mgr.clearFiles();
});
return mgr;
}
angular.module('umbraco.services').factory('fileManager', fileManager);

View File

@@ -40,7 +40,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
else {
currentForm = args.formCtrl;
}
//the first thing any form must do is broadcast the formSubmitting event
args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action });
@@ -53,10 +53,10 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
//reset the server validations
serverValidationManager.reset();
return true;
},
/**
* @ngdoc function
* @name umbraco.services.formHelper#submitForm
@@ -75,21 +75,21 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
if (!args.scope) {
throw "args.scope cannot be null";
}
args.scope.$broadcast("formSubmitted", { scope: args.scope });
},
showNotifications: function (args) {
if (!args || !args.notifications) {
return false;
}
if (angular.isArray(args.notifications)) {
for (var i = 0; i < args.notifications.length; i++) {
notificationsService.showNotification(args.notifications[i]);
if (!args || !args.notifications) {
return false;
}
return true;
}
return false;
if (angular.isArray(args.notifications)) {
for (var i = 0; i < args.notifications.length; i++) {
notificationsService.showNotification(args.notifications[i]);
}
return true;
}
return false;
},
/**
@@ -104,7 +104,12 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
*
* @param {object} err The error object returned from the http promise
*/
handleError: function (err) {
handleError: function (err) {
//TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that
// non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use
// and they won't need to manually do it.
//When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors.
//Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something).
//Or, some strange server error
@@ -116,7 +121,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
this.handleServerValidation(err.data.ModelState);
//execute all server validation events and subscribers
serverValidationManager.notifyAndClearAllSubscriptions();
serverValidationManager.notifyAndClearAllSubscriptions();
}
}
else {
@@ -124,7 +129,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
// TODO: All YSOD handling should be done with an interceptor
overlayService.ysod(err);
}
},
/**
@@ -184,8 +189,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]);
}
}
else {
} else {
//Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example:
// Groups[0].Properties[2].Alias

View File

@@ -143,27 +143,21 @@ function mediaHelper(umbRequestHelper) {
*/
resolveFileFromEntity: function (mediaEntity, thumbnail) {
if (!angular.isObject(mediaEntity.metaData)) {
if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) {
throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData";
}
var values = _.values(mediaEntity.metaData);
for (var i = 0; i < values.length; i++) {
var val = values[i];
if (angular.isObject(val) && val.PropertyEditorAlias) {
for (var resolver in _mediaFileResolvers) {
if (val.PropertyEditorAlias === resolver) {
//we need to format a property variable that coincides with how the property would be structured
// if it came from the mediaResource just to keep things slightly easier for the file resolvers.
var property = { value: val.Value };
return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail);
}
}
if (thumbnail) {
if (this.detectIfImageByExtension(mediaEntity.metaData.MediaPath)) {
return this.getThumbnailFromPath(mediaEntity.metaData.MediaPath);
}
else {
return null;
}
}
return "";
else {
return mediaEntity.metaData.MediaPath;
}
},
/**

View File

@@ -13,7 +13,7 @@
* Section navigation and search, and maintain their state for the entire application lifetime
*
*/
function navigationService($routeParams, $location, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) {
function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) {
//the promise that will be resolved when the navigation is ready
var navReadyPromise = $q.defer();
@@ -31,7 +31,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve
//A list of query strings defined that when changed will not cause a reload of the route
var nonRoutingQueryStrings = ["mculture", "cculture", "lq"];
var retainedQueryStrings = ["mculture"];
//A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!)
var nonRoutingTreesOnCreate = ["content", "contentblueprints"];
function setMode(mode) {
switch (mode) {
@@ -115,16 +116,17 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve
}
var service = {
/**
* @ngdoc method
* @name umbraco.services.navigationService#isRouteChangingNavigation
* @methodOf umbraco.services.navigationService
*
* @description
* Detects if the route param differences will cause a navigation change or if the route param differences are
* Detects if the route param differences will cause a navigation/route change or if the route param differences are
* only tracking state changes.
* This is used for routing operations where reloadOnSearch is false and when detecting form dirty changes when navigating to a different page.
* This is used for routing operations where "reloadOnSearch: false" or "reloadOnUrl: false", when detecting form dirty changes when navigating to a different page,
* and when we are creating new entities and moving from a route with the ?create=true parameter to an ID based parameter once it's created.
* @param {object} currUrlParams Either a string path or a dictionary of route parameters
* @param {object} nextUrlParams Either a string path or a dictionary of route parameters
*/
@@ -138,6 +140,14 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve
nextUrlParams = pathToRouteParts(nextUrlParams);
}
//first check if this is a ?create=true url being redirected to it's true url
if (currUrlParams.create === "true" && currUrlParams.id && currUrlParams.section && currUrlParams.tree && currUrlParams.method === "edit" &&
!nextUrlParams.create && nextUrlParams.id && nextUrlParams.section === currUrlParams.section && nextUrlParams.tree === currUrlParams.tree && nextUrlParams.method === currUrlParams.method &&
nonRoutingTreesOnCreate.indexOf(nextUrlParams.tree.toLowerCase()) >= 0) {
//this means we're coming from a path like /content/content/edit/1234?create=true to the created path like /content/content/edit/9999
return false;
}
var allowRoute = true;
//The only time that we want to not route is if only any of the nonRoutingQueryStrings have changed/added.

View File

@@ -77,4 +77,5 @@
angular.module("umbraco.services").factory("overlayService", overlayService);
})();

View File

@@ -42,11 +42,7 @@ angular.module('umbraco.services')
throw "args.term is required";
}
var options = {
searchFrom: args.searchFrom
}
return entityResource.search(args.term, "Member", options).then(function (data) {
return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMemberResult(item);
});
@@ -71,12 +67,7 @@ angular.module('umbraco.services')
throw "args.term is required";
}
var options = {
searchFrom: args.searchFrom,
ignoreUserStartNodes: args.ignoreUserStartNodes
}
return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) {
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureContentResult(item);
});
@@ -101,12 +92,7 @@ angular.module('umbraco.services')
throw "args.term is required";
}
var options = {
searchFrom: args.searchFrom,
ignoreUserStartNodes: args.ignoreUserStartNodes
}
return entityResource.search(args.term, "Media", options).then(function (data) {
return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMediaResult(item);
});

View File

@@ -13,12 +13,15 @@ function serverValidationManager($timeout) {
var callbacks = [];
/** calls the callback specified with the errors specified, used internally */
function executeCallback(self, errorsForCallback, callback) {
function executeCallback(self, errorsForCallback, callback, culture) {
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
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.
]
);
}
function getFieldErrors(self, fieldName) {
@@ -28,7 +31,7 @@ function serverValidationManager($timeout) {
//find errors for this field name
return _.filter(self.items, function (item) {
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
});
}
@@ -39,27 +42,50 @@ function serverValidationManager($timeout) {
if (fieldName && !angular.isString(fieldName)) {
throw "fieldName must be a string";
}
if (!culture) {
culture = "invariant";
}
//find all errors for this property
return _.filter(self.items, function (item) {
return _.filter(self.items, function (item) {
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
});
}
function getCultureErrors(self, culture) {
if (!culture) {
culture = "invariant";
}
//find all errors for this property
return _.filter(self.items, function (item) {
return (item.culture === culture);
});
}
function notifyCallbacks(self) {
for (var cb in callbacks) {
if (callbacks[cb].propertyAlias === null) {
if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) {
//its a field error callback
var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName);
if (fieldErrors.length > 0) {
executeCallback(self, fieldErrors, callbacks[cb].callback);
executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture);
}
}
else {
else if (callbacks[cb].propertyAlias != null) {
//its a property error
var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName);
if (propErrors.length > 0) {
executeCallback(self, propErrors, callbacks[cb].callback);
executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture);
}
}
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);
}
}
}
@@ -130,11 +156,14 @@ function serverValidationManager($timeout) {
}
var id = String.CreateGuid();
if (!culture) {
culture = "invariant";
}
if (propertyAlias === null) {
callbacks.push({
propertyAlias: null,
culture: null,
culture: culture,
fieldName: fieldName,
callback: callback,
id: id
@@ -142,12 +171,11 @@ function serverValidationManager($timeout) {
}
else if (propertyAlias !== undefined) {
//normalize culture to null
if (!culture) {
culture = null;
}
callbacks.push({
propertyAlias: propertyAlias,
culture: culture, fieldName: fieldName,
culture: culture,
fieldName: fieldName,
callback: callback,
id: id
});
@@ -173,21 +201,20 @@ function serverValidationManager($timeout) {
*/
unsubscribe: function (propertyAlias, culture, fieldName) {
//normalize culture to null
if (!culture) {
culture = "invariant";
}
if (propertyAlias === null) {
//remove all callbacks for the content field
callbacks = _.reject(callbacks, function (item) {
return item.propertyAlias === null && item.culture === null && item.fieldName === fieldName;
return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName;
});
}
else if (propertyAlias !== undefined) {
//normalize culture to null
if (!culture) {
culture = null;
}
//remove all callbacks for the content property
callbacks = _.reject(callbacks, function (item) {
return item.propertyAlias === propertyAlias && item.culture === culture &&
@@ -213,7 +240,7 @@ function serverValidationManager($timeout) {
//normalize culture to null
if (!culture) {
culture = null;
culture = "invariant";
}
var found = _.filter(callbacks, function (item) {
@@ -235,7 +262,24 @@ 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 === null && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
});
return found;
},
/**
* @ngdoc function
* @name getCultureCallbacks
* @methodOf umbraco.services.serverValidationManager
* @function
*
* @description
* Gets all callbacks that has been registered using the subscribe method for the culture.
*/
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 found;
},
@@ -258,7 +302,7 @@ function serverValidationManager($timeout) {
if (!this.hasFieldError(fieldName)) {
this.items.push({
propertyAlias: null,
culture: null,
culture: "invariant",
fieldName: fieldName,
errorMsg: errorMsg
});
@@ -270,7 +314,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);
executeCallback(this, errorsForCallback, cbs[cb].callback, null);
}
},
@@ -288,9 +332,9 @@ function serverValidationManager($timeout) {
return;
}
//normalize culture to null
//normalize culture to "invariant"
if (!culture) {
culture = null;
culture = "invariant";
}
//only add the item if it doesn't exist
@@ -309,9 +353,16 @@ function serverValidationManager($timeout) {
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName);
//call each callback for this error
for (var cb in cbs) {
executeCallback(this, errorsForCallback, cbs[cb].callback);
executeCallback(this, errorsForCallback, cbs[cb].callback, culture);
}
},
//execute culture specific callbacks here too when a propery error is added
var cultureCbs = this.getCultureCallbacks(culture);
//call each callback for this error
for (var cb in cultureCbs) {
executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture);
}
},
/**
* @ngdoc function
@@ -330,7 +381,7 @@ function serverValidationManager($timeout) {
//normalize culture to null
if (!culture) {
culture = null;
culture = "invariant";
}
//remove the item
@@ -384,7 +435,7 @@ function serverValidationManager($timeout) {
//normalize culture to null
if (!culture) {
culture = null;
culture = "invariant";
}
var err = _.find(this.items, function (item) {
@@ -406,7 +457,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 === null && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
});
return err;
},
@@ -424,7 +475,7 @@ function serverValidationManager($timeout) {
//normalize culture to null
if (!culture) {
culture = null;
culture = "invariant";
}
var err = _.find(this.items, function (item) {
@@ -446,11 +497,33 @@ 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 === null && item.fieldName === fieldName);
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
});
return err ? true : false;
},
/**
* @ngdoc function
* @name hasCultureError
* @methodOf umbraco.services.serverValidationManager
* @function
*
* @description
* Checks if the given culture has an error
*/
hasCultureError: function (culture) {
//normalize culture to null
if (!culture) {
culture = "invariant";
}
var err = _.find(this.items, function (item) {
return item.culture === culture;
});
return err ? true : false;
},
/** The array of error messages */
items: []
};

View File

@@ -422,23 +422,12 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
insertMediaInEditor: function (editor, img) {
if (img) {
var hasUdi = img.udi ? true : false;
var data = {
alt: img.altText || "",
src: (img.url) ? img.url : "nothing.jpg",
id: '__mcenew'
id: '__mcenew',
'data-udi': img.udi
};
if (hasUdi) {
data["data-udi"] = img.udi;
} else {
//Considering these fixed because UDI will now be used and thus
// we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595
//TODO: Kill rel attribute
data["rel"] = img.id;
data["data-id"] = img.id;
}
editor.selection.setContent(editor.dom.createHTML('img', data));
@@ -990,7 +979,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
}
if (!href) {
if (!href && !target.anchor) {
editor.execCommand('unlink');
return;
}
@@ -1004,6 +993,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
return;
}
if (!href) {
href = "";
}
// Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified
if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) {
// assume it's a mailto link
@@ -1153,25 +1146,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
let self = this;
function getIgnoreUserStartNodes(args) {
var ignoreUserStartNodes = false;
// Most property editors have a "config" property with ignoreUserStartNodes on then
if (args.model.config) {
ignoreUserStartNodes = Object.toBoolean(args.model.config.ignoreUserStartNodes);
}
// EXCEPT for the grid's TinyMCE editor, that one wants to be special and the config is called "configuration" instead
else if (args.model.configuration) {
ignoreUserStartNodes = Object.toBoolean(args.model.configuration.ignoreUserStartNodes);
}
return ignoreUserStartNodes;
}
//create link picker
self.createLinkPicker(args.editor, function (currentTarget, anchorElement) {
var linkPicker = {
currentTarget: currentTarget,
anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [],
ignoreUserStartNodes: getIgnoreUserStartNodes(args),
submit: function (model) {
self.insertLinkInEditor(args.editor, model.target, anchorElement);
editorService.close();
@@ -1185,25 +1164,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
//Create the insert media plugin
self.createMediaPicker(args.editor, function (currentTarget, userData) {
var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
var startNodeIsVirtual = userData.startMediaIds.length !== 1;
var ignoreUserStartNodes = getIgnoreUserStartNodes(args);
if (ignoreUserStartNodes) {
ignoreUserStartNodes = true;
startNodeId = -1;
startNodeIsVirtual = true;
}
var mediaPicker = {
currentTarget: currentTarget,
onlyImages: true,
showDetails: true,
disableFolderSelect: true,
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
ignoreUserStartNodes: ignoreUserStartNodes,
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
submit: function (model) {
self.insertMediaInEditor(args.editor, model.selection[0]);
editorService.close();

View File

@@ -1,6 +1,6 @@
/** Executed when the application starts, binds to events and set global state */
app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource',
function (userService, $q, $log, $rootScope, $route, $location, urlHelper, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) {
app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService',
function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) {
//This sets the default jquery ajax headers to include our csrf token, we
// need to user the beforeSend method because our token changes per user/login so
@@ -91,13 +91,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH
$rootScope.locationTitle = "Umbraco - " + $location.$$host;
}
//reset the editorState on each successful route chage
editorState.reset();
//reset the file manager on each route change, the file collection is only relavent
// when working in an editor and submitting data to the server.
//This ensures that memory remains clear of any files and that the editors don't have to manually clear the files.
fileManager.clearFiles();
});
/** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including
@@ -122,7 +115,7 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH
});
//Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered.
//This is the case when a route uses reloadOnSearch: false which is the case for many or our routes so that we are able to maintain
//This is the case when a route uses "reloadOnSearch: false" or "reloadOnUrl: false" which is the case for many or our routes so that we are able to maintain
//global state query strings without force re-loading views.
//We can then detect if it's a location change that should force a route or not programatically.
$rootScope.$on('$routeUpdate', function (event, next) {

View File

@@ -174,6 +174,8 @@ a.umb-editor-header__close-split-view:hover {
max-width: 50%;
white-space: nowrap;
user-select: none;
span {
text-overflow: ellipsis;
overflow: hidden;
@@ -189,6 +191,25 @@ a.umb-variant-switcher__toggle {
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 {
@@ -205,20 +226,16 @@ a.umb-variant-switcher__toggle {
align-items: center;
border-bottom: 1px solid @gray-9;
position: relative;
&:hover .umb-variant-switcher__name-wrapper {
}
}
.umb-variant-switcher__item:last-child {
border-bottom: none;
}
.umb-variant-switcher_item--current {
.umb-variant-switcher__item.--current {
color: @ui-light-active-type;
}
.umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper {
//background-color: @gray-10;
.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper {
border-left: 4px solid @ui-active;
}
@@ -227,7 +244,7 @@ a.umb-variant-switcher__toggle {
outline: none;
}
.umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover {
.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover {
//background-color: @white !important;
cursor: default;
}
@@ -237,6 +254,29 @@ a.umb-variant-switcher__toggle {
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: -6px;
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;

View File

@@ -20,6 +20,15 @@
.umb-logviewer__sidebar {
flex: 0 0 @sidebarwidth;
.flatpickr-input {
background-color: @white;
border: 0;
width: 100%;
text-align: center;
font-size: larger;
padding-top: 20px;
}
}
@media (max-width: 768px) {

View File

@@ -72,3 +72,7 @@
line-height: 20px;
}
}
.mce-fullscreen {
position:absolute;
}

View File

@@ -228,6 +228,7 @@ app.config(function ($routeProvider) {
},
reloadOnSearch: false,
reloadOnUrl: false,
resolve: canRoute(true)
})
.otherwise({ redirectTo: '/login' });

View File

@@ -28,11 +28,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: [],
ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes
selectedSearchResults: []
};
$scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : "";
$scope.showTarget = $scope.model.hideTarget !== true;
// this ensures that we only sync the tree once and only when it's ready
@@ -89,11 +87,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
});
// get the content properties to build the anchor name list
var options = {};
options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes;
contentResource.getById(id, options).then(function (resp) {
contentResource.getById(id).then(function (resp) {
handleContentTarget(resp);
});
}
@@ -143,11 +137,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
if (args.node.id < 0) {
$scope.model.target.url = "/";
} else {
var options = {};
options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes;
contentResource.getById(args.node.id, options).then(function (resp) {
contentResource.getById(args.node.id).then(function (resp) {
handleContentTarget(resp);
});
}
@@ -170,17 +162,9 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
var startNodeIsVirtual = userData.startMediaIds.length !== 1;
if (dialogOptions.ignoreUserStartNodes) {
startNodeId = -1;
startNodeIsVirtual = true;
}
var mediaPicker = {
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes,
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
submit: function (model) {
var media = model.selection[0];

View File

@@ -66,7 +66,6 @@
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
ignore-user-start-nodes="{{searchInfo.ignoreUserStartNodes}}"
section="{{section}}">
</umb-tree-search-box>
@@ -83,7 +82,6 @@
section="content"
hideheader="true"
hideoptions="true"
customtreeparams="{{customTreeParams}}"
api="dialogTreeApi"
on-init="onTreeInit()"
enablelistviewexpand="true"

View File

@@ -1,7 +1,7 @@
//used for the media picker dialog
angular.module("umbraco")
.controller("Umbraco.Editors.MediaPickerController",
function ($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, userService, treeService, localStorageService, localizationService, editorService) {
function($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) {
if (!$scope.model.title) {
localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"])
@@ -20,13 +20,11 @@ angular.module("umbraco")
$scope.showDetails = dialogOptions.showDetails;
$scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
$scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
$scope.ignoreUserStartNodes = Object.toBoolean(dialogOptions.ignoreUserStartNodes);
$scope.cropSize = dialogOptions.cropSize;
$scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId");
$scope.lockedFolder = true;
$scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false;
var userStartNodes = [];
var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
if ($scope.onlyImages) {
@@ -56,8 +54,7 @@ angular.module("umbraco")
pageSize: 100,
totalItems: 0,
totalPages: 0,
filter: "",
ignoreUserStartNodes: $scope.model.ignoreUserStartNodes
filter: ''
};
//preload selected item
@@ -67,19 +64,15 @@ angular.module("umbraco")
}
function onInit() {
userService.getCurrentUser().then(function(userData) {
userStartNodes = userData.startMediaIds;
if ($scope.startNodeId !== -1) {
entityResource.getById($scope.startNodeId, "media")
.then(function(ent) {
$scope.startNodeId = ent.id;
run();
});
} else {
run();
}
});
if ($scope.startNodeId !== -1) {
entityResource.getById($scope.startNodeId, "media")
.then(function (ent) {
$scope.startNodeId = ent.id;
run();
});
} else {
run();
}
}
function run() {
@@ -150,7 +143,7 @@ angular.module("umbraco")
}
};
$scope.gotoFolder = function (folder) {
$scope.gotoFolder = function(folder) {
if (!$scope.multiPicker) {
deselectAllImages($scope.model.selection);
}
@@ -159,10 +152,8 @@ angular.module("umbraco")
folder = { id: -1, name: "Media", icon: "icon-folder" };
}
var options = {};
if (folder.id > 0) {
options.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes;
entityResource.getAncestors(folder.id, "media", options)
entityResource.getAncestors(folder.id, "media")
.then(function(anc) {
$scope.path = _.filter(anc,
function(f) {
@@ -178,26 +169,13 @@ angular.module("umbraco")
$scope.path = [];
}
$scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false;
$scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual;
$scope.currentFolder = folder;
localStorageService.set("umbLastOpenedMediaNodeId", folder.id);
options.ignoreUserStartNodes = $scope.ignoreUserStartNodes;
return getChildren(folder.id, options);
return getChildren(folder.id);
};
function hasFolderAccess(node) {
var nodePath = node.path ? node.path.split(',') : [node.id];
for (var i = 0; i < nodePath.length; i++) {
if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1)
return true;
}
return false;
}
$scope.clickHandler = function(image, event, index) {
if (image.isFolder) {
if ($scope.disableFolderSelect) {
@@ -321,8 +299,7 @@ angular.module("umbraco")
pageSize: 100,
totalItems: 0,
totalPages: 0,
filter: "",
ignoreUserStartNodes: $scope.model.ignoreUserStartNodes
filter: ''
};
getChildren($scope.currentFolder.id);
}
@@ -390,9 +367,9 @@ angular.module("umbraco")
}
}
function getChildren(id, options) {
function getChildren(id) {
$scope.loading = true;
return mediaResource.getChildren(id, options)
return mediaResource.getChildren(id)
.then(function(data) {
$scope.searchOptions.filter = "";
$scope.images = data.items ? data.items : [];

View File

@@ -36,7 +36,6 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
selectedSearchResults: []
}
vm.startNodeId = $scope.model.startNodeId;
vm.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes;
//Used for toggling an empty-state message
//Some trees can have no items (dictionary & forms email templates)
vm.hasItems = true;
@@ -172,9 +171,6 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
if (vm.startNodeId) {
queryParams["startNodeId"] = $scope.model.startNodeId;
}
if (vm.ignoreUserStartNodes) {
queryParams["ignoreUserStartNodes"] = $scope.model.ignoreUserStartNodes;
}
if (vm.selectedLanguage && vm.selectedLanguage.id) {
queryParams["culture"] = vm.selectedLanguage.culture;
}

View File

@@ -27,7 +27,7 @@
<a ng-click="vm.selectLanguage(language)" ng-repeat="language in vm.languages" href="">{{language.name}}</a>
</div>
</div>
<div class="umb-control-group">
<umb-tree-search-box
ng-if="vm.enableSearh"
@@ -36,7 +36,6 @@
search-from-id="{{vm.searchInfo.searchFromId}}"
search-from-name="{{vm.searchInfo.searchFromName}}"
show-search="{{vm.searchInfo.showSearch}}"
ignore-user-start-nodes="{{vm.ignoreUserStartNodes}}"
section="{{vm.section}}">
</umb-tree-search-box>
</div>

View File

@@ -12,7 +12,7 @@
<ins class="umb-language-picker__expand" ng-class="{'icon-navigation-down': !page.languageSelectorIsOpen, 'icon-navigation-up': page.languageSelectorIsOpen}" class="icon-navigation-right">&nbsp;</ins>
</div>
<div class="umb-language-picker__dropdown" ng-if="page.languageSelectorIsOpen">
<a class="umb-language-picker__dropdown-item" ng-class="{'umb-language-picker__dropdown-item--current': language.active, 'bold': language.isDefault}" ng-click="selectLanguage(language)" ng-repeat="language in languages" href="">{{language.name}}</a>
<a class="umb-language-picker__dropdown-item" ng-class="{'umb-language-picker__dropdown-item--current': language.active}" ng-click="selectLanguage(language)" ng-repeat="language in languages" href="">{{language.name}}</a>
</div>
</div>

View File

@@ -29,7 +29,7 @@
autocomplete="off" maxlength="255" />
</ng-form>
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
<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>{{vm.currentVariant.language.name}}</span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</a>
@@ -39,9 +39,9 @@
</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="{'umb-variant-switcher_item--current': variant.active, 'umb-variant-switcher_item--not-allowed': variantIsOpen(variant.language.culture)}" ng-repeat="variant in content.variants">
<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">
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name" ng-class="{'bold': variant.language.isDefault}">{{variant.language.name}}</span>
<span class="umb-variant-switcher__name">{{variant.language.name}}</span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
</a>
<div ng-if="splitViewOpen !== true && !variant.active" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>

View File

@@ -7,7 +7,7 @@
</a>
</li>
<li ng-repeat="pgn in pagination track by pgn.val"
<li ng-repeat="pgn in pagination track by $index"
ng-class="{active:pgn.isActive}">
<a href="#" ng-click="goToPage(pgn.val - 1)" prevent-default

View File

@@ -36,7 +36,6 @@
function(err) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err
});

View File

@@ -6,7 +6,7 @@
* @description
* The controller for the content editor
*/
function ContentEditController($scope, $rootScope, $routeParams, contentResource) {
function ContentEditController($scope, $routeParams, contentResource) {
var infiniteMode = $scope.model && $scope.model.infiniteMode;
@@ -24,13 +24,17 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource
$scope.page = $routeParams.page;
$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.culture = $routeParams.cculture ? $routeParams.cculture : ($routeParams.mculture === "true");
//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
// and then we can pass in the updated culture to the editor
//and then we can pass in the updated culture to the editor.
//This will also execute when we are redirecting from creating an item to a newly created item since that
//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.isNew = next.params.create === "true";
$scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id;
});
}

View File

@@ -47,9 +47,13 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) {
//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
// and then we can pass in the updated culture to the editor
//and then we can pass in the updated culture to the editor.
//This will also execute when we are redirecting from creating an item to a newly created item since that
//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.isNew = $routeParams.id === "-1";
$scope.contentId = $routeParams.id;
});
}

View File

@@ -174,7 +174,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
//NOTE: in the case of data type values we are setting the orig/new props
// to be the same thing since that only really matters for content/media.
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err
});

View File

@@ -91,7 +91,6 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
function (err) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err
});

View File

@@ -317,10 +317,6 @@
saveMethod: contentTypeResource.save,
scope: $scope,
content: vm.contentType,
//We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
// we need to rebind... the IDs that have been created!
rebindCallback: function (origContentType, savedContentType) {
vm.contentType.id = savedContentType.id;

View File

@@ -39,7 +39,7 @@
<tr ng-repeat="language in vm.languages track by language.id" ng-click="vm.editLanguage(language)" style="cursor: pointer;">
<td>
<i class="icon-globe" style="color: #BBBABF; margin-right: 5px;"></i>
<span ng-class="{'bold': language.isDefault}">{{ language.name }}</span>
<span>{{ language.name }}</span>
</td>
<td>
<span>{{ language.culture }}</span>

View File

@@ -22,16 +22,32 @@
}
};
let querystring = $location.search();
if(querystring.startDate){
vm.startDate = querystring.startDate;
}else{
vm.startDate = new Date(Date.now());
vm.startDate.setDate(vm.startDate.getDate()-1);
vm.startDate = vm.startDate.toIsoDateString();
}
if(querystring.endDate){
vm.endDate = querystring.endDate;
}else{
vm.endDate = new Date(Date.now()).toIsoDateString();
}
vm.period = [vm.startDate, vm.endDate];
//functions
vm.searchLogQuery = searchLogQuery;
vm.findMessageTemplate = findMessageTemplate;
function preFlightCheck(){
vm.loading = true;
//Do our pre-flight check (to see if we can view logs)
//IE the log file is NOT too big such as 1GB & crash the site
logViewerResource.canViewLogs().then(function(result){
logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function(result){
vm.loading = false;
vm.canLoadLogs = result;
@@ -46,7 +62,7 @@
function init() {
vm.loading = true;
var savedSearches = logViewerResource.getSavedSearches().then(function (data) {
vm.searches = data;
},
@@ -80,11 +96,11 @@
]
});
var numOfErrors = logViewerResource.getNumberOfErrors().then(function (data) {
var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) {
vm.numberOfErrors = data;
});
var logCounts = logViewerResource.getLogLevelCounts().then(function (data) {
var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) {
vm.logTypeData = [];
vm.logTypeData.push(data.Information);
vm.logTypeData.push(data.Debug);
@@ -93,7 +109,7 @@
vm.logTypeData.push(data.Fatal);
});
var commonMsgs = logViewerResource.getMessageTemplates().then(function(data){
var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function(data){
vm.commonLogMessages = data;
});
@@ -108,7 +124,7 @@
}
function searchLogQuery(logQuery){
$location.path("/settings/logViewer/search").search({lq: logQuery});
$location.path("/settings/logViewer/search").search({lq: logQuery, startDate: vm.startDate, endDate: vm.endDate});
}
function findMessageTemplate(template){
@@ -116,8 +132,38 @@
searchLogQuery(logQuery);
}
preFlightCheck();
preFlightCheck();
/////////////////////
vm.config = {
enableTime: false,
dateFormat: "Y-m-d",
time_24hr: false,
mode: "range",
maxDate: "today",
conjunction: " to "
};
vm.dateRangeChange = function(selectedDates, dateStr, instance) {
if(selectedDates.length > 0){
vm.startDate = selectedDates[0].toIsoDateString();
vm.endDate = selectedDates[selectedDates.length-1].toIsoDateString(); // Take the last date as end
if(vm.startDate === vm.endDate){
vm.period = [vm.startDate];
}else{
vm.period = [vm.startDate, vm.endDate];
}
preFlightCheck();
}
}
}
angular.module("umbraco").controller("Umbraco.Editors.LogViewer.OverviewController", LogViewerOverviewController);

View File

@@ -14,45 +14,45 @@
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<!-- Warning message (if unable to view log files) -->
<div ng-show="!vm.loading && !vm.canLoadLogs">
<umb-box>
<umb-box-header title="Unable to view logs"/>
<umb-box-content>
<p>Today's log file is too large to be viewed and would cause performance problems.</p>
<p>If you need to view the log files, try opening them manually</p>
</umb-box-content>
</umb-box>
</div>
<div class="umb-logviewer" ng-show="!vm.loading && vm.canLoadLogs">
<div class="umb-logviewer" ng-show="!vm.loading">
<div class="umb-logviewer__main-content">
<!-- Saved Searches -->
<umb-box>
<umb-box-header title="Saved Searches"></umb-box-header>
<umb-box-content>
<table>
<tr>
<td>
<a ng-click="vm.searchLogQuery()" title="View all Logs" class="btn btn-link">All Logs <i class="icon-search"></i></a>
</td>
</tr>
<!-- Fetch saved searches -->
<tr ng-repeat="search in vm.searches">
<td>
<a ng-click="vm.searchLogQuery(search.query)" title="{{search.name}}" class="btn btn-link">{{search.name}} <i class="icon-search"></i></a>
</td>
</tr>
</table>
</umb-box-content>
</umb-box>
<div ng-show="!vm.canLoadLogs">
<umb-box>
<umb-box-header title="Unable to view logs"/>
<umb-box-content>
<p>Today's log file is too large to be viewed and would cause performance problems.</p>
<p>If you need to view the log files, try opening them manually</p>
</umb-box-content>
</umb-box>
</div>
<div ng-show="vm.canLoadLogs">
<!-- Saved Searches -->
<umb-box>
<umb-box-header title="Saved Searches"></umb-box-header>
<umb-box-content>
<table>
<tr>
<td>
<a ng-click="vm.searchLogQuery()" title="View all Logs" class="btn btn-link">All Logs <i class="icon-search"></i></a>
</td>
</tr>
<!-- Fetch saved searches -->
<tr ng-repeat="search in vm.searches">
<td>
<a ng-click="vm.searchLogQuery(search.query)" title="{{search.name}}" class="btn btn-link">{{search.name}} <i class="icon-search"></i></a>
</td>
</tr>
</table>
</umb-box-content>
</umb-box>
<!-- List of top X common log messages -->
<umb-box>
<umb-box-header title="Common Log Messages"></umb-box-header>
<umb-box-content class="block-form">
<em>Total Unique Message types</em>: {{ vm.commonLogMessages.length }}
<table class="table table-hover">
<tbody>
<!-- List of top X common log messages -->
<umb-box>
<umb-box-header title="Common Log Messages"></umb-box-header>
<umb-box-content class="block-form">
<em>Total Unique Message types</em>: {{ vm.commonLogMessages.length }}
<table class="table table-hover">
<tbody>
<tr ng-repeat="template in vm.commonLogMessages | limitTo:vm.commonLogMessagesCount" ng-click="vm.findMessageTemplate(template)" style="cursor: pointer;">
<td>
{{ template.MessageTemplate }}
@@ -61,38 +61,54 @@
{{ template.Count }}
</td>
</tr>
</tbody>
</table>
<a class="btn btn-primary" ng-if="vm.commonLogMessagesCount < vm.commonLogMessages.length" ng-click="vm.commonLogMessagesCount = vm.commonLogMessagesCount +10">Show More</a>
</umb-box-content>
</umb-box>
</tbody>
</table>
<a class="btn btn-primary" ng-if="vm.commonLogMessagesCount < vm.commonLogMessages.length" ng-click="vm.commonLogMessagesCount = vm.commonLogMessagesCount +10">Show More</a>
</umb-box-content>
</umb-box>
</div>
</div>
<div class="umb-logviewer__sidebar">
<!-- No of Errors -->
<umb-box ng-click="vm.searchLogQuery('Has(@Exception)')" style="cursor:pointer;">
<umb-box-header title="Number of Errors"></umb-box-header>
<umb-box-content class="block-form" style="font-size: 40px; font-weight:900; text-align:center; color:#fe6561;">
{{ vm.numberOfErrors }}
</umb-box-content>
</umb-box>
<!-- Chart of diff log types -->
<!-- Time period -->
<umb-box>
<umb-box-header title="Log Types"></umb-box-header>
<umb-box-content class="block-form">
<umb-box-header title="Time Period"></umb-box-header>
<canvas chart-doughnut
chart-data="vm.logTypeData"
chart-labels="vm.logTypeLabels"
chart-colors="vm.logTypeColors"
chart-options="vm.chartOptions">
</canvas>
</umb-box-content>
<umb-flatpickr
class="datepicker"
ng-model="vm.period"
options="vm.config"
on-close="vm.dateRangeChange(selectedDates, dateStr, instance)">
</umb-flatpickr>
</umb-box>
<div ng-show=" vm.canLoadLogs">
<!-- No of Errors -->
<umb-box ng-click="vm.searchLogQuery('Has(@Exception)')" style="cursor:pointer;">
<umb-box-header title="Number of Errors"></umb-box-header>
<umb-box-content class="block-form" style="font-size: 40px; font-weight:900; text-align:center; color:#fe6561;">
{{ vm.numberOfErrors }}
</umb-box-content>
</umb-box>
<!-- Chart of diff log types -->
<umb-box>
<umb-box-header title="Log Types"></umb-box-header>
<umb-box-content class="block-form">
<canvas chart-doughnut
chart-data="vm.logTypeData"
chart-labels="vm.logTypeLabels"
chart-colors="vm.logTypeColors"
chart-options="vm.chartOptions">
</canvas>
</umb-box-content>
</umb-box>
</div>
</div>
</div>
</umb-editor-container>
</umb-editor-view>
</div>

View File

@@ -96,6 +96,14 @@
vm.logOptions.filterExpression = querystring.lq;
}
if(querystring.startDate){
vm.logOptions.startDate = querystring.startDate;
}
if(querystring.endDate){
vm.logOptions.endDate = querystring.endDate;
}
vm.loading = true;
logViewerResource.getSavedSearches().then(function (data) {
@@ -270,7 +278,11 @@
submitButtonLabel: "Save Search",
disableSubmitButton: true,
view: "logviewersearch",
query: vm.logOptions.filterExpression,
query: {
filterExpression: vm.logOptions.filterExpression,
startDate: vm.logOptions.startDate,
endDate: vm.logOptions.endDate
},
submit: function (model) {
//Resource call with two params (name & query)
//API that opens the JSON and adds it to the bottom

View File

@@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta
vm.page.saveButtonState = "success";
}, function (error) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: error
});

View File

@@ -184,7 +184,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
savedContent: data,
redirectOnSuccess: !infiniteMode,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
@@ -206,7 +205,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
contentEditingHelper.handleSaveError({
err: err,
redirectOnFailure: !infiniteMode,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});

View File

@@ -269,10 +269,6 @@
saveMethod: mediaTypeResource.save,
scope: $scope,
content: vm.contentType,
//We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
// we need to rebind... the IDs that have been created!
rebindCallback: function (origContentType, savedContentType) {
vm.contentType.id = savedContentType.id;

View File

@@ -137,6 +137,13 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
$scope.page.saveButtonState = "busy";
//anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' });
if (!passwordProp.value.reset) {
//so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
}
memberResource.save($scope.content, $routeParams.create, fileManager.getFiles())
.then(function(data) {
@@ -161,7 +168,6 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
}, function (err) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});

View File

@@ -86,7 +86,6 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe
}, function (err) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err
});

View File

@@ -180,10 +180,6 @@
saveMethod: memberTypeResource.save,
scope: $scope,
content: vm.contentType,
//We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
// we need to rebind... the IDs that have been created!
rebindCallback: function (origContentType, savedContentType) {
vm.contentType.id = savedContentType.id;

View File

@@ -65,10 +65,6 @@
saveMethod: codefileResource.save,
scope: $scope,
content: vm.partialViewMacro,
// We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
// create macro if needed

View File

@@ -83,10 +83,6 @@
saveMethod: codefileResource.save,
scope: $scope,
content: vm.partialView,
//We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {

View File

@@ -80,7 +80,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
showOpenButton: false,
showEditButton: false,
showPathOnHover: false,
ignoreUserStartNodes: false,
maxNumber: 1,
minNumber: 0,
startNode: {
@@ -118,8 +117,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
$scope.model.config.showOpenButton = Object.toBoolean($scope.model.config.showOpenButton);
$scope.model.config.showEditButton = Object.toBoolean($scope.model.config.showEditButton);
$scope.model.config.showPathOnHover = Object.toBoolean($scope.model.config.showPathOnHover);
$scope.model.config.ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes);
var entityType = $scope.model.config.startNode.type === "member"
? "Member"
: $scope.model.config.startNode.type === "media"
@@ -135,7 +133,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
entityType: entityType,
filterCssClass: "not-allowed not-published",
startNodeId: null,
ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes,
currentNode: editorState ? editorState.current : null,
callback: function (data) {
if (angular.isArray(data)) {

View File

@@ -14,7 +14,6 @@
sortable="!sortableOptions.disabled"
allow-remove="allowRemoveButton"
allow-open="model.config.showOpenButton && allowOpenButton && !dialogEditor"
ignore-user-startnodes="model.config.ignoreUserStartNodes"
on-remove="remove($index)"
on-open="openContentEditor(node)">
</umb-node-preview>

View File

@@ -1,32 +1,25 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.Grid.MediaController",
function ($scope, $timeout, userService, editorService) {
var ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes);
$scope.thumbnailUrl = getThumbnailUrl();
if (!$scope.model.config.startNodeId) {
if (ignoreUserStartNodes === true) {
$scope.model.config.startNodeId = -1;
$scope.model.config.startNodeIsVirtual = true;
} else {
userService.getCurrentUser().then(function (userData) {
$scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
$scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
});
}
userService.getCurrentUser().then(function (userData) {
$scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
$scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
});
}
$scope.setImage = function(){
var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
var mediaPicker = {
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
ignoreUserStartNodes: ignoreUserStartNodes,
cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined,
showDetails: true,
disableFolderSelect: true,

View File

@@ -34,7 +34,7 @@ angular.module("umbraco")
// Sortable options
// *********************************************
var draggedRteSettings;
var draggedRteSettings;// holds a dictionary of RTE settings to remember when dragging things around.
$scope.sortableOptionsRow = {
distance: 10,
@@ -92,7 +92,7 @@ angular.module("umbraco")
}
};
var notIncludedRte = [];
var notIncludedRte = [];// used for RTEs that has been affected by the sorting
var cancelMove = false;
var startingArea;
$scope.sortableOptionsCell = {
@@ -134,17 +134,17 @@ angular.module("umbraco")
(startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1)) {
$scope.$apply(function () {
event.target.getScope_HackForSortable().area.dropNotAllowed = true;
area.dropNotAllowed = true;
});
ui.placeholder.hide();
cancelMove = true;
}
else {
if (event.target.getScope_HackForSortable().area.controls.length == 0) {
if (area.controls.length == 0) {
$scope.$apply(function () {
event.target.getScope_HackForSortable().area.dropOnEmpty = true;
area.dropOnEmpty = true;
});
ui.placeholder.hide();
} else {
@@ -169,16 +169,32 @@ angular.module("umbraco")
ui.item.sortable.cancel();
}
ui.item.parents(".umb-cell-content").find(".umb-rte").each(function (key, value) {
var v1 = value.id;
var rteId = value.id;
if ($.inArray(v1, notIncludedRte) < 0) {
notIncludedRte.splice(0, 0, v1);
if ($.inArray(rteId, notIncludedRte) < 0) {
// remember this RTEs settings, cause we need to update it later.
var editor = _.findWhere(tinyMCE.editors, { id: rteId })
if (editor) {
draggedRteSettings[rteId] = editor.settings;
}
notIncludedRte.splice(0, 0, rteId);
}
});
}
else {
$(event.target).find(".umb-rte").each(function () {
if ($.inArray($(this).attr("id"), notIncludedRte) < 0) {
var rteId = $(this).attr("id");
if ($.inArray(rteId, notIncludedRte) < 0) {
// remember this RTEs settings, cause we need to update it later.
var editor = _.findWhere(tinyMCE.editors, { id: rteId })
if (editor) {
draggedRteSettings[rteId] = editor.settings;
}
notIncludedRte.splice(0, 0, $(this).attr("id"));
}
});
@@ -196,17 +212,20 @@ angular.module("umbraco")
ui.item[0].style.opacity = "0.5";
// reset dragged RTE settings in case a RTE isn't dragged
draggedRteSettings = undefined;
draggedRteSettings = {};
notIncludedRte = [];
ui.item[0].style.display = "block";
ui.item.find(".umb-rte").each(function (key, value) {
notIncludedRte = [];
var rteId = value.id;
var editors = _.findWhere(tinyMCE.editors, { id: rteId });
// remember this RTEs settings, cause we need to update it later.
var editor = _.findWhere(tinyMCE.editors, { id: rteId });
// save the dragged RTE settings
if (editors) {
draggedRteSettings = editors.settings;
if (editor) {
draggedRteSettings[rteId] = editor.settings;
// remove the dragged RTE
tinyMCE.execCommand("mceRemoveEditor", false, rteId);
@@ -223,22 +242,29 @@ angular.module("umbraco")
ui.item.offsetParent().find(".umb-rte").each(function (key, value) {
var rteId = value.id;
if ($.inArray(rteId, notIncludedRte) < 0) {
var editor = _.findWhere(tinyMCE.editors, { id: rteId });
if (editor) {
draggedRteSettings[rteId] = editor.settings;
}
// add all dragged's neighbouring RTEs in the new cell
notIncludedRte.splice(0, 0, rteId);
}
});
// reconstruct the dragged RTE (could be undefined when dragging something else than RTE)
if (draggedRteSettings !== undefined) {
tinyMCE.init(draggedRteSettings);
}
_.forEach(notIncludedRte, function (id) {
_.forEach(notIncludedRte, function (rteId) {
// reset all the other RTEs
if (draggedRteSettings === undefined || id !== draggedRteSettings.id) {
var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
tinyMCE.execCommand("mceRemoveEditor", false, id);
tinyMCE.init(rteSettings);
if (draggedRteSettings === undefined || rteId !== draggedRteSettings.id) {
tinyMCE.execCommand("mceRemoveEditor", false, rteId);
if (draggedRteSettings[rteId]) {
tinyMCE.init(draggedRteSettings[rteId]);
}
}
});

View File

@@ -3,19 +3,19 @@
<div ng-if="!vm.languages">
<p><localize key="prompt_confirmListViewPublish"></localize></p>
</div>
<div ng-if="vm.languages.length > 1">
<div class="mb3">
<p><localize key="content_languagesToPublish"></localize></p>
</div>
<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-item" ng-repeat="language in vm.languages track by language.id">
<ng-form name="publishLanguageSelectorForm">
<div class="flex">
@@ -28,20 +28,20 @@
style="margin-right: 8px;" />
<div>
<label for="{{language.culture}}" style="margin-bottom: 2px;">
<span ng-class="{'bold': language.isDefault}">{{ language.name }}</span>
<span>{{ language.name }}</span>
</label>
<div class="umb-list-item__description">
<span ng-if="language.isMandatory"><localize key="languages_mandatoryLanguage"></localize></span>
</div>
</div>
</div>
</ng-form>
</div>
</div>
</div>
</div>

View File

@@ -4,16 +4,16 @@
<div ng-if="!vm.languages">
<p><localize key="prompt_confirmListViewUnpublish"></localize></p>
</div>
<!-- Multiple languages -->
<div ng-if="vm.languages.length > 0">
<div class="mb3">
<p><localize key="content_languagesToUnpublish"></localize></p>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="language in vm.languages track by language.id">
<ng-form name="unpublishLanguageSelectorForm">
<div class="flex">
@@ -26,21 +26,21 @@
style="margin-right: 8px;" />
<div>
<label for="{{language.culture}}" style="margin-bottom: 2px;">
<span ng-class="{'bold': language.isDefault}">{{ language.name }}</span>
<span>{{ language.name }}</span>
</label>
<div class="umb-list-item__description">
<span ng-if="language.isMandatory"><localize key="languages_mandatoryLanguage"></localize></span>
</div>
</div>
</div>
</ng-form>
</div>
</div>
</div>
</div>

View File

@@ -7,12 +7,9 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
var ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes);
$scope.allowEditMedia = false;
$scope.allowAddMedia = false;
function setupViewModel() {
$scope.mediaItems = [];
$scope.ids = [];
@@ -108,16 +105,12 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
}
function init() {
userService.getCurrentUser().then(function (userData) {
if (!$scope.model.config.startNodeId) {
$scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
$scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
}
if (ignoreUserStartNodes === true) {
$scope.model.config.startNodeId = -1;
$scope.model.config.startNodeIsVirtual = true;
}
// only allow users to add and edit media if they have access to the media section
var hasAccessToMedia = userData.allowedSections.indexOf("media") !== -1;
$scope.allowEditMedia = hasAccessToMedia;
@@ -175,7 +168,6 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
var mediaPicker = {
startNodeId: $scope.model.config.startNodeId,
startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
ignoreUserStartNodes: ignoreUserStartNodes,
multiPicker: multiPicker,
onlyImages: onlyImages,
disableFolderSelect: disableFolderSelect,

View File

@@ -68,10 +68,9 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
url: link.url,
target: link.target
} : null;
var linkPicker = {
currentTarget: target,
ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes),
submit: function (model) {
if (model.target.url || model.target.anchor) {
// if an anchor exists, check that it is appropriately prefixed

View File

@@ -25,7 +25,6 @@
section: "content",
treeAlias: "content",
multiPicker: false,
ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes),
idType: $scope.model.config.idType ? $scope.model.config.idType : "int",
submit: function (model) {
select(model.selection[0]);
@@ -48,7 +47,6 @@
section: "content",
treeAlias: "content",
multiPicker: false,
ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes),
idType: $scope.model.config.idType ? $scope.model.config.idType : "udi",
submit: function (model) {
select(model.selection[0]);

View File

@@ -24,13 +24,6 @@
text="{{css.name}}"/>
</div>
</umb-control-group>
<umb-control-group label="Ignore user start nodes" description="Selecting this option allows a user to choose nodes that they normally don't have access to. Note: this applies only to rich text editors in this grid editor.">
<div>
<label>
<umb-checkbox model="model.value.ignoreUserStartNodes"/>
</label>
</div>
</umb-control-group>
<umb-control-group label="Dimensions" description="Set the editor dimensions">

View File

@@ -119,7 +119,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
}, function (error) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: error
});

View File

@@ -45,10 +45,6 @@
saveMethod: codefileResource.save,
scope: $scope,
content: vm.script,
// We do not redirect on failure for scripts - this is because it is not possible to actually save the script
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {

View File

@@ -68,10 +68,6 @@
saveMethod: codefileResource.save,
scope: $scope,
content: vm.stylesheet,
// We do not redirect on failure for style sheets - this is because it is not possible to actually save the style sheet
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {

View File

@@ -83,10 +83,6 @@
saveMethod: templateResource.save,
scope: $scope,
content: vm.template,
// We do not redirect on failure for templates - this is because it is not possible to actually save the template
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {

View File

@@ -81,10 +81,6 @@
saveMethod: userGroupsResource.saveUserGroup,
scope: $scope,
content: vm.userGroup,
// We do not redirect on failure for users - this is because it is not possible to actually save a user
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) { }
}).then(function (saved) {

View File

@@ -136,7 +136,10 @@
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
if (vm.user.changePassword) {
vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
//NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user
//can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat).
//if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password.
vm.user.changePassword.reset = (!vm.user.changePassword.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword;
}
vm.page.saveButtonState = "busy";
@@ -169,7 +172,6 @@
}, function (err) {
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err,
showNotifications: true
});

View File

@@ -261,7 +261,8 @@
</umb-button>
</div>
<div>
<umb-button type="button" ng-if="model.changePasswordModel.isChanging === false && model.user.userDisplayState.key !== 'Invited'"
<umb-button type="button" ng-if="model.user.userDisplayState.key !== 'Invited' && model.changePasswordModel.isChanging === false"
button-style="[info,block]"
action="model.toggleChangePassword()"
label="Change password"

View File

@@ -30,7 +30,6 @@ describe('contentEditingHelper tests', function () {
//act
var handled = contentEditingHelper.handleSaveError({
redirectOnFailure: true,
err: err,
allNewProps: contentEditingHelper.getAllProps(content),
allOrigProps: contentEditingHelper.getAllProps(content)
@@ -49,7 +48,6 @@ describe('contentEditingHelper tests', function () {
//act
var handled = contentEditingHelper.handleSaveError({
redirectOnFailure: true,
err: err,
allNewProps: [],
allOrigProps: []
@@ -70,7 +68,6 @@ describe('contentEditingHelper tests', function () {
//act
var handled = contentEditingHelper.handleSaveError({
redirectOnFailure: true,
err: err,
allNewProps: contentEditingHelper.getAllProps(content),
allOrigProps: contentEditingHelper.getAllProps(content)

View File

@@ -22,6 +22,7 @@
expect(err.propertyAlias).toBeNull();
expect(err.fieldName).toEqual("Name");
expect(err.errorMsg).toEqual("Required");
expect(err.culture).toEqual("invariant");
});
it('will return null for a non-existing field error', function () {
@@ -72,11 +73,43 @@
expect(err1.propertyAlias).toEqual("myProperty");
expect(err1.fieldName).toEqual("value1");
expect(err1.errorMsg).toEqual("Some value 1");
expect(err1.culture).toEqual("invariant");
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("invariant");
});
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");
//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");
//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.culture).toEqual("en-US");
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");
});
it('can add a property errors with multiple sub fields and it the first will be retreived with only the property alias', function () {
@@ -93,6 +126,7 @@
expect(err.propertyAlias).toEqual("myProperty");
expect(err.fieldName).toEqual("value1");
expect(err.errorMsg).toEqual("Some value 1");
expect(err.culture).toEqual("invariant");
});
it('will return null for a non-existing property error', function () {
@@ -162,6 +196,26 @@
});
describe('managing culture validation errors', function () {
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");
//assert
expect(serverValidationManager.hasCultureError(null)).toBe(true);
expect(serverValidationManager.hasCultureError("en-US")).toBe(true);
expect(serverValidationManager.hasCultureError("fr-FR")).toBe(true);
expect(serverValidationManager.hasCultureError("es-ES")).toBe(false);
});
});
describe('validation error subscriptions', function() {
it('can subscribe to a field error', function() {
@@ -267,6 +321,45 @@
// if the property has errors existing.
expect(numCalled).toEqual(3);
});
it('can subscribe to a culture error for both a property and its sub field', function () {
var args1;
var args2;
var numCalled = 0;
//arrange
serverValidationManager.subscribe(null, "en-US", null, function (isValid, propertyErrors, allErrors) {
numCalled++;
args1 = {
isValid: isValid,
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) {
numCalled++;
args2 = {
isValid: isValid,
propertyErrors: propertyErrors,
allErrors: allErrors
};
});
//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");
//assert
expect(args1).not.toBeUndefined();
expect(args1.isValid).toBe(false);
expect(args2).toBeUndefined();
expect(numCalled).toEqual(2);
});
// TODO: Finish testing the rest!