Merge branch 'v8/dev' into v8/feature/5170-IPublishedContent
# Conflicts: # src/Umbraco.Web.UI.Client/package-lock.json
This commit is contained in:
@@ -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/"
|
||||
}
|
||||
};
|
||||
|
||||
400
src/Umbraco.Web.UI.Client/package-lock.json
generated
400
src/Umbraco.Web.UI.Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -328,6 +328,8 @@
|
||||
isInfoTab = true;
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
setNodePublishStatus();
|
||||
formatDatesToLocal();
|
||||
} else {
|
||||
isInfoTab = false;
|
||||
}
|
||||
@@ -344,6 +346,7 @@
|
||||
loadAuditTrail(true);
|
||||
loadRedirectUrls();
|
||||
setNodePublishStatus();
|
||||
formatDatesToLocal();
|
||||
}
|
||||
updateCurrentUrls();
|
||||
});
|
||||
|
||||
@@ -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]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -77,4 +77,5 @@
|
||||
|
||||
angular.module("umbraco.services").factory("overlayService", overlayService);
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -72,3 +72,7 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.mce-fullscreen {
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ app.config(function ($routeProvider) {
|
||||
|
||||
},
|
||||
reloadOnSearch: false,
|
||||
reloadOnUrl: false,
|
||||
resolve: canRoute(true)
|
||||
})
|
||||
.otherwise({ redirectTo: '/login' });
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 : [];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"> </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>
|
||||
|
||||
|
||||
@@ -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}"> </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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
function(err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
|
||||
function (err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta
|
||||
vm.page.saveButtonState = "success";
|
||||
}, function (error) {
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: error
|
||||
});
|
||||
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -86,7 +86,6 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe
|
||||
}, function (err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -119,7 +119,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
|
||||
|
||||
}, function (error) {
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: error
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user