Umbraco 7.15.3

This commit is contained in:
2019-11-23 21:51:02 -05:00
parent 7e9bd9ee5b
commit 866cfa29d7
219 changed files with 66394 additions and 66487 deletions
+1 -2
View File
@@ -11,8 +11,7 @@ LazyLoad.js([
'../js/umbraco.security.js',
'../ServerVariables',
'../lib/spectrum/spectrum.js',
'../js/umbraco.canvasdesigner.js',
'../js/canvasdesigner.panel.js'
'../js/umbraco.canvasdesigner.js'
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['Umbraco.canvasdesigner']);
File diff suppressed because it is too large Load Diff
+411 -96
View File
@@ -1780,6 +1780,19 @@ In the following example you see how to run some custom logic before a step goes
}
angular.module('umbraco.directives').directive('umbTour', TourDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbTourStep
@restrict E
@scope
@description
<b>Added in Umbraco 7.8</b>. The tour step component is a component that can be used in custom views for tour steps.
@param {callback} onClose The callback which should be performened when the close button of the tour step is clicked
@param {boolean=} hideClose A boolean indicating if the close button needs to be shown
**/
(function () {
'use strict';
function TourStepDirective() {
@@ -1806,6 +1819,19 @@ In the following example you see how to run some custom logic before a step goes
}
angular.module('umbraco.directives').directive('umbTourStep', TourStepDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbTourStepContent
@restrict E
@scope
@description
<b>Added in Umbraco 7.8</b>. The tour step content component is a component that can be used in custom views for tour steps.
It's meant to be used in the umb-tour-step directive.
All markup in the body of the directive will be shown after the content attribute
@param {string} content The content that needs to be shown
**/
(function () {
'use strict';
function TourStepContentDirective() {
@@ -1820,6 +1846,20 @@ In the following example you see how to run some custom logic before a step goes
}
angular.module('umbraco.directives').directive('umbTourStepContent', TourStepContentDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbTourStepCounter
@restrict E
@scope
@description
<b>Added in Umbraco 7.8</b>. The tour step counter component is a component that can be used in custom views for tour steps.
It's meant to be used in the umb-tour-step-footer directive. It will show the progress you have made in a tour eg. step 2/12
@param {int} currentStep The current step the tour is on
@param {int} totalSteps The current step the tour is on
**/
(function () {
'use strict';
function TourStepCounterDirective() {
@@ -1836,6 +1876,18 @@ In the following example you see how to run some custom logic before a step goes
}
angular.module('umbraco.directives').directive('umbTourStepCounter', TourStepCounterDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbTourStepFooter
@restrict E
@scope
@description
<b>Added in Umbraco 7.8</b>. The tour step footer component is a component that can be used in custom views for tour steps. It's meant to be used in the umb-tour-step directive.
All markup in the body of the directive will be shown as the footer of the tour step
**/
(function () {
'use strict';
function TourStepFooterDirective() {
@@ -1849,6 +1901,18 @@ In the following example you see how to run some custom logic before a step goes
}
angular.module('umbraco.directives').directive('umbTourStepFooter', TourStepFooterDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbTourStepHeader
@restrict E
@scope
@description
<b>Added in Umbraco 7.8</b>. The tour step header component is a component that can be used in custom views for tour steps. It's meant to be used in the umb-tour-step directive.
@param {string} title The title that needs to be shown
**/
(function () {
'use strict';
function TourStepHeaderDirective() {
@@ -2131,6 +2195,7 @@ Use this directive to render a button with a dropdown of alternative actions.
<umb-toggle
checked="vm.checked"
disabled="vm.disabled"
on-click="vm.toggle()"
show-labels="true"
label-on="Start"
@@ -2151,6 +2216,7 @@ Use this directive to render a button with a dropdown of alternative actions.
var vm = this;
vm.checked = false;
vm.disabled = false;
vm.toggle = toggle;
@@ -2165,6 +2231,7 @@ Use this directive to render a button with a dropdown of alternative actions.
</pre>
@param {boolean} checked Set to <code>true</code> or <code>false</code> to toggle the switch.
@param {boolean} disabled Set to <code>true</code> or <code>false</code> to disable/enable the switch.
@param {callback} onClick The function which should be called when the toggle is clicked.
@param {string=} showLabels Set to <code>true</code> or <code>false</code> to show a "On" or "Off" label next to the switch.
@param {string=} labelOn Set a custom label for when the switched is turned on. It will default to "On".
@@ -2215,6 +2282,7 @@ Use this directive to render a button with a dropdown of alternative actions.
templateUrl: 'views/components/buttons/umb-toggle.html',
scope: {
checked: '=',
disabled: '=',
onClick: '&',
labelOn: '@?',
labelOff: '@?',
@@ -2248,12 +2316,19 @@ Use this directive to render a button with a dropdown of alternative actions.
createButtons(content);
editorState.set($scope.content);
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
if (!$scope.page.isNew) {
if (content.parentId && content.parentId !== -1) {
entityResource.getAncestors(content.id, 'document').then(function (anc) {
$scope.ancestors = anc;
});
if (content.parentId && content.parentId !== -1) {
var ancestorIds = content.path.split(',');
ancestorIds.shift();
// Remove -1
if ($scope.page.isNew) {
ancestorIds.pop(); // Remove 0
}
entityResource.getByIds(ancestorIds, 'document').then(function (anc) {
$scope.ancestors = anc;
if ($scope.page.isNew) {
$scope.ancestors.push({ name: 'Untitled' });
}
});
}
evts.push(eventsService.on('editors.content.changePublishDate', function (event, args) {
createButtons(args.node);
@@ -2370,6 +2445,9 @@ Use this directive to render a button with a dropdown of alternative actions.
//we are creating so get an empty content item
$scope.getScaffoldMethod()().then(function (data) {
$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($scope.content);
resetLastListPageNumber($scope.content);
$scope.page.loading = false;
@@ -2554,7 +2632,7 @@ Use this directive to render a button with a dropdown of alternative actions.
}());
(function () {
'use strict';
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper) {
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, redirectUrlsResource) {
function link(scope, element, attrs, ctrl) {
var evts = [];
var isInfoTab = false;
@@ -2586,10 +2664,19 @@ Use this directive to render a button with a dropdown of alternative actions.
formatDatesToLocal();
// Make sure to set the node status
setNodePublishStatus(scope.node);
//default setting for redirect url management
scope.urlTrackerDisabled = false;
// Declare a fallback URL for the <umb-node-preview/> directive
if (scope.documentType !== null) {
scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id;
}
// only allow configuring scheduled publishing if the user has publish ("U") and unpublish ("Z") permissions on this node
scope.allowScheduledPublishing = _.contains(scope.node.allowedActions, 'U') && _.contains(scope.node.allowedActions, 'Z');
ensureUniqueUrls();
}
// make sure we don't show duplicate URLs in case multiple URL providers assign the same URLs to the content (see issue 3842 for details)
function ensureUniqueUrls() {
scope.node.urls = _.uniq(scope.node.urls);
}
scope.auditTrailPageChange = function (pageNumber) {
scope.auditTrailOptions.pageNumber = pageNumber;
@@ -2638,6 +2725,22 @@ Use this directive to render a button with a dropdown of alternative actions.
scope.loadingAuditTrail = false;
});
}
function loadRedirectUrls() {
scope.loadingRedirectUrls = true;
//check if Redirect Url Management is enabled
redirectUrlsResource.getEnableState().then(function (response) {
scope.urlTrackerDisabled = response.enabled !== true;
if (scope.urlTrackerDisabled === false) {
redirectUrlsResource.getRedirectsForContentItem(scope.node.udi).then(function (data) {
scope.redirectUrls = data.searchResults;
scope.hasRedirects = typeof data.searchResults !== 'undefined' && data.searchResults.length > 0;
scope.loadingRedirectUrls = false;
});
} else {
scope.loadingRedirectUrls = false;
}
});
}
function setAuditTrailLogTypeColor(auditTrail) {
angular.forEach(auditTrail, function (item) {
switch (item.logType) {
@@ -2750,12 +2853,13 @@ Use this directive to render a button with a dropdown of alternative actions.
scope.node.removeDateTime = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'HH:mm')) : null;
});
}
// load audit trail when on the info tab
// load audit trail and redirects when on the info tab
evts.push(eventsService.on('app.tabChange', function (event, args) {
$timeout(function () {
if (args.id === -1) {
isInfoTab = true;
loadAuditTrail();
loadRedirectUrls();
} else {
isInfoTab = false;
}
@@ -2771,8 +2875,10 @@ Use this directive to render a button with a dropdown of alternative actions.
}
if (isInfoTab) {
loadAuditTrail();
loadRedirectUrls();
formatDatesToLocal();
setNodePublishStatus(scope.node);
ensureUniqueUrls();
}
});
//ensure to unregister from all events!
@@ -3583,7 +3689,7 @@ Use this directive to construct a header inside the main editor window.
icon: '=',
hideIcon: '@',
alias: '=',
hideAlias: '@',
hideAlias: '=',
description: '=',
hideDescription: '@',
descriptionLocked: '@',
@@ -4229,29 +4335,32 @@ Use this directive to construct the main editor window.
return {
restrict: 'A',
link: function (scope, element, attr, formCtrl) {
var origColor = null;
if (attr.hexBgOrig) {
//set the orig based on the attribute if there is one
origColor = attr.hexBgOrig;
}
attr.$observe('hexBgColor', function (newVal) {
if (newVal) {
if (!origColor) {
//get the orig color before changing it
origColor = element.css('border-color');
}
//validate it - test with and without the leading hash.
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css('background-color', '#' + newVal);
return;
}
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css('background-color', newVal);
return;
}
// Only add inline hex background color if defined and not "true".
if (attr.hexBgInline === undefined || attr.hexBgInline !== undefined && attr.hexBgInline === 'true') {
var origColor = null;
if (attr.hexBgOrig) {
// Set the orig based on the attribute if there is one.
origColor = attr.hexBgOrig;
}
element.css('background-color', origColor);
});
attr.$observe('hexBgColor', function (newVal) {
if (newVal) {
if (!origColor) {
// Get the orig color before changing it.
origColor = element.css('border-color');
}
// Validate it - test with and without the leading hash.
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css('background-color', '#' + newVal);
return;
}
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css('background-color', newVal);
return;
}
}
element.css('background-color', origColor);
});
}
}
};
}
@@ -4544,6 +4653,103 @@ Use this directive to prevent default action of an element. Effectively implemen
}
};
});
/**
@ngdoc directive
@name umbraco.directives.directive:umbCheckbox
@restrict E
@scope
@description
<b>Added in Umbraco version 7.14.0</b> Use this directive to render an umbraco checkbox.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-checkbox
name="checkboxlist"
value="{{key}}"
model="true"
text="{{text}}">
</umb-checkbox>
</div>
</pre>
@param {boolean} model Set to <code>true</code> or <code>false</code> to set the checkbox to checked or unchecked.
@param {string} value Set the value of the checkbox.
@param {string} name Set the name of the checkbox.
@param {string} text Set the text for the checkbox label.
**/
(function () {
'use strict';
function CheckboxDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/forms/umb-checkbox.html',
scope: {
model: '=',
value: '@',
name: '@',
text: '@',
required: '='
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbCheckbox', CheckboxDirective);
}());
/**
@ngdoc directive
@name umbraco.directives.directive:umbRadiobutton
@restrict E
@scope
@description
<b>Added in Umbraco version 7.14.0</b> Use this directive to render an umbraco radio button.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-radiobutton
name="checkboxlist"
value="{{key}}"
model="true"
text="{{text}}">
</umb-radiobutton>
</div>
</pre>
@param {boolean} model Set to <code>true</code> or <code>false</code> to set the radiobutton to checked or unchecked.
@param {string} value Set the value of the radiobutton.
@param {string} name Set the name of the radiobutton.
@param {string} text Set the text for the radiobutton label.
**/
(function () {
'use strict';
function RadiobuttonDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/forms/umb-radiobutton.html',
scope: {
model: '=',
value: '@',
name: '@',
text: '@'
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbRadiobutton', RadiobuttonDirective);
}());
/*
example usage: <textarea json-edit="myObject" rows="8" class="form-control"></textarea>
@@ -4649,7 +4855,7 @@ will override element type to textarea and add own attribute ngModel tied to jso
}
angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen);
}());
angular.module('umbraco.directives').directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout) {
angular.module('umbraco.directives').directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout, eventsService) {
return {
scope: {
uniqueId: '=',
@@ -4968,8 +5174,18 @@ will override element type to textarea and add own attribute ngModel tied to jso
// // is required for our plugins listening to this event to execute
// tinyMceEditor.fire('LoadContent', null);
//};
var tabShownListener = eventsService.on('app.tabChange', function (e, args) {
var tabId = args.id;
var myTabId = element.closest('.umb-tab-pane').attr('rel');
if (String(tabId) === myTabId) {
//the tab has been shown, trigger the mceAutoResize (as it could have timed out before the tab was shown)
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
tinyMceEditor.execCommand('mceAutoResize', false, null, null);
}
}
});
//listen for formSubmitting event (the result is callback used to remove the event subscription)
var unsubscribe = scope.$on('formSubmitting', function () {
var formSubmittingListener = scope.$on('formSubmitting', function () {
//TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
// we do parse it out on the server side but would be nice to do that on the client side before as well.
scope.value = tinyMceEditor ? tinyMceEditor.getContent() : null;
@@ -4978,7 +5194,8 @@ will override element type to textarea and add own attribute ngModel tied to jso
// NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
scope.$on('$destroy', function () {
unsubscribe();
formSubmittingListener();
eventsService.unsubscribe(tabShownListener);
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
tinyMceEditor.destroy();
}
@@ -5284,23 +5501,6 @@ Use this directive to construct a title. Recommended to use it inside an {@link
// scope.dimensions.viewport.width - 2 * scope.dimensions.margin;
scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin;
};
//when loading an image without any crop info, we center and fit it
var resizeImageToEditor = function () {
//returns size fitting the cropper
var size = cropperHelper.calculateAspectRatioFit(scope.dimensions.image.width, scope.dimensions.image.height, scope.dimensions.cropper.width, scope.dimensions.cropper.height, true);
//sets the image size and updates the scope
scope.dimensions.image.width = size.width;
scope.dimensions.image.height = size.height;
//calculate the best suited ratios
scope.dimensions.scale.min = size.ratio;
scope.dimensions.scale.max = 2;
scope.dimensions.scale.current = size.ratio;
//center the image
var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper);
scope.dimensions.top = position.top;
scope.dimensions.left = position.left;
setConstraints();
};
//resize to a given ratio
var resizeImageToScale = function (ratio) {
//do stuff
@@ -5367,11 +5567,16 @@ Use this directive to construct a title. Recommended to use it inside an {@link
scope.loaded = false;
//set dimensions on image, viewport, cropper etc
setDimensions(image);
//if we have a crop already position the image
if (scope.crop) {
resizeImageToCrop();
} else {
resizeImageToEditor();
//create a default crop if we haven't got one already
var createDefaultCrop = !scope.crop;
if (createDefaultCrop) {
calculateCropBox();
}
resizeImageToCrop();
//if we're creating a new crop, make sure to zoom out fully
if (createDefaultCrop) {
scope.dimensions.scale.current = scope.dimensions.scale.min;
resizeImageToScale(scope.dimensions.scale.min);
}
//sets constaints for the cropper
setConstraints();
@@ -5389,7 +5594,7 @@ Use this directive to construct a title. Recommended to use it inside an {@link
var throttledResizing = _.throttle(function () {
resizeImageToScale(scope.dimensions.scale.current);
calculateCropBox();
}, 100);
}, 16);
//happens when we change the scale
scope.$watch('dimensions.scale.current', function () {
if (scope.loaded) {
@@ -5429,7 +5634,8 @@ Use this directive to construct a title. Recommended to use it inside an {@link
scope: {
src: '=',
center: '=',
onImageLoaded: '='
onImageLoaded: '&',
onGravityChanged: '&'
},
link: function (scope, element, attrs) {
//Internal values for keeping track of the dot and the size of the editor
@@ -5445,7 +5651,7 @@ Use this directive to construct a title. Recommended to use it inside an {@link
var $image = element.find('img');
var $overlay = element.find('.overlay');
scope.style = function () {
if (scope.dimensions.width <= 0) {
if (scope.dimensions.width <= 0 || scope.dimensions.height <= 0) {
setDimensions();
}
return {
@@ -5458,19 +5664,21 @@ Use this directive to construct a title. Recommended to use it inside an {@link
var offsetX = event.offsetX - 10;
var offsetY = event.offsetY - 10;
calculateGravity(offsetX, offsetY);
lazyEndEvent();
gravityChanged();
};
var setDimensions = function () {
scope.dimensions.width = $image.width();
scope.dimensions.height = $image.height();
if (scope.center) {
scope.dimensions.left = scope.center.left * scope.dimensions.width - 10;
scope.dimensions.top = scope.center.top * scope.dimensions.height - 10;
} else {
scope.center = {
left: 0.5,
top: 0.5
};
if (scope.isCroppable) {
scope.dimensions.width = $image.width();
scope.dimensions.height = $image.height();
if (scope.center) {
scope.dimensions.left = scope.center.left * scope.dimensions.width - 10;
scope.dimensions.top = scope.center.top * scope.dimensions.height - 10;
} else {
scope.center = {
left: 0.5,
top: 0.5
};
}
}
};
var calculateGravity = function (offsetX, offsetY) {
@@ -5479,11 +5687,11 @@ Use this directive to construct a title. Recommended to use it inside an {@link
scope.center.left = (scope.dimensions.left + 10) / scope.dimensions.width;
scope.center.top = (scope.dimensions.top + 10) / scope.dimensions.height;
};
var lazyEndEvent = _.debounce(function () {
scope.$apply(function () {
scope.$emit('imageFocalPointStop');
});
}, 2000);
var gravityChanged = function () {
if (angular.isFunction(scope.onGravityChanged)) {
scope.onGravityChanged();
}
};
//Drag and drop positioning, using jquery ui draggable
//TODO ensure that the point doesnt go outside the box
$overlay.draggable({
@@ -5499,16 +5707,34 @@ Use this directive to construct a title. Recommended to use it inside an {@link
var offsetY = $overlay[0].offsetTop;
calculateGravity(offsetX, offsetY);
});
lazyEndEvent();
gravityChanged();
}
});
//// INIT /////
$image.load(function () {
$timeout(function () {
scope.isCroppable = true;
scope.hasDimensions = true;
if (scope.src) {
if (scope.src.endsWith('.svg')) {
scope.isCroppable = false;
scope.hasDimensions = false;
} else {
// From: https://stackoverflow.com/a/51789597/5018
var type = scope.src.substring(scope.src.indexOf('/') + 1, scope.src.indexOf(';base64'));
if (type.startsWith('svg')) {
scope.isCroppable = false;
scope.hasDimensions = false;
}
}
}
setDimensions();
scope.loaded = true;
if (angular.isFunction(scope.onImageLoaded)) {
scope.onImageLoaded();
scope.onImageLoaded({
'isCroppable': scope.isCroppable,
'hasDimensions': scope.hasDimensions
});
}
});
});
@@ -5671,7 +5897,7 @@ Use this directive to construct a title. Recommended to use it inside an {@link
});
(function () {
'use strict';
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper) {
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, mediaHelper) {
function link(scope, element, attrs, ctrl) {
var evts = [];
function onInit() {
@@ -5685,6 +5911,8 @@ Use this directive to construct a title. Recommended to use it inside an {@link
setMediaLink();
// make sure dates are formatted to the user's locale
formatDatesToLocal();
// set media file extension initially
setMediaExtension();
}
function formatDatesToLocal() {
// get current backoffice user and format dates
@@ -5696,11 +5924,21 @@ Use this directive to construct a title. Recommended to use it inside an {@link
function setMediaLink() {
scope.nodeUrl = scope.node.mediaLink;
}
function setMediaExtension() {
scope.node.extension = mediaHelper.getFileExtension(scope.nodeUrl);
}
scope.openMediaType = function (mediaType) {
// remove first "#" from url if it is prefixed else the path won't work
var url = '/settings/mediaTypes/edit/' + mediaType.id;
$location.path(url);
};
scope.openSVG = function () {
var popup = window.open('', '_blank');
var html = '<!DOCTYPE html><body><img src="' + scope.nodeUrl + '"/>' + '<script>history.pushState(null, null,"' + $location.$$absUrl + '");</script></body>';
popup.document.open();
popup.document.write(html);
popup.document.close();
};
// watch for content updates - reload content when node is saved, published etc.
scope.$watch('node.updateDate', function (newValue, oldValue) {
if (!newValue) {
@@ -5713,6 +5951,8 @@ Use this directive to construct a title. Recommended to use it inside an {@link
setMediaLink();
// Update the create and update dates
formatDatesToLocal();
//Update the media file format
setMediaExtension();
});
//ensure to unregister from all events!
scope.$on('$destroy', function () {
@@ -6806,7 +7046,15 @@ Opens an overlay to show a custom YSOD. </br>
treeService.syncTree({
node: treeNode,
path: path,
forceReload: forceReload
forceReload: forceReload,
//when the tree node is expanding during sync tree, handle it and raise appropriate events
treeNodeExpanded: function (args) {
emitEvent('treeNodeExpanded', {
tree: scope.tree,
node: args.node,
children: args.children
});
}
}).then(function (data) {
if (activate === undefined || activate === true) {
scope.currentNode = data;
@@ -6970,7 +7218,7 @@ Opens an overlay to show a custom YSOD. </br>
</file>
</example>
*/
angular.module('umbraco.directives').directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService) {
angular.module('umbraco.directives').directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService, appState) {
return {
restrict: 'E',
replace: true,
@@ -7053,6 +7301,18 @@ Opens an overlay to show a custom YSOD. </br>
if (node.selected) {
css.push('umb-tree-node-checked');
}
//is this the current action node (this is not the same as the current selected node!)
var actionNode = appState.getMenuState('currentNode');
if (actionNode) {
if (actionNode.id === node.id && actionNode.id !== '-1') {
css.push('active');
}
// special handling of root nodes with id -1
// as there can be many nodes with id -1 in a tree we need to check the treeAlias instead
if (actionNode.id === '-1' && actionNode.metaData.treeAlias === node.metaData.treeAlias) {
css.push('active');
}
}
return css.join(' ');
};
//add a method to the node which we can use to call to update the node data if we need to ,
@@ -7205,6 +7465,7 @@ Opens an overlay to show a custom YSOD. </br>
searchFromName: '@',
showSearch: '@',
section: '@',
datatypeId: '@',
hideSearchCallback: '=',
searchCallback: '='
},
@@ -7245,6 +7506,10 @@ Opens an overlay to show a custom YSOD. </br>
if (scope.searchFromId) {
searchArgs['searchFrom'] = scope.searchFromId;
}
//append dataTypeId value if there is one
if (scope.datatypeId) {
searchArgs['dataTypeId'] = scope.datatypeId;
}
searcher(searchArgs).then(function (data) {
scope.searchCallback(data);
//set back to null so it can be re-created
@@ -8089,20 +8354,25 @@ Use this directive to generate color swatches to pick from.
</umb-color-swatches>
</pre>
@param {array} colors (<code>attribute</code>): The array of colors.
@param {string} colors (<code>attribute</code>): The array of colors.
@param {string} selectedColor (<code>attribute</code>): The selected color.
@param {string} size (<code>attribute</code>): The size (s, m).
@param {string} useLabel (<code>attribute</code>): Specify if labels should be used.
@param {string} useColorClass (<code>attribute</code>): Specify if color values are css classes.
@param {function} onSelect (<code>expression</code>): Callback function when the item is selected.
**/
(function () {
'use strict';
function ColorSwatchesDirective() {
function link(scope, el, attr, ctrl) {
// Set default to true if not defined
if (angular.isUndefined(scope.useColorClass)) {
scope.useColorClass = false;
}
scope.setColor = function (color) {
//scope.selectedColor({color: color });
scope.selectedColor = color;
if (scope.onSelect) {
scope.onSelect(color);
scope.onSelect({ color: color });
}
};
}
@@ -8115,7 +8385,9 @@ Use this directive to generate color swatches to pick from.
colors: '=?',
size: '@',
selectedColor: '=',
onSelect: '&'
onSelect: '&',
useLabel: '=',
useColorClass: '=?'
},
link: link
};
@@ -8941,7 +9213,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc
@param {string} aliasFrom (<code>binding</code>): The model to generate the alias from.
@param {boolean=} enableLock (<code>binding</code>): Set to <code>true</code> to add a lock next to the alias from where it can be unlocked and changed.
**/
angular.module('umbraco.directives').directive('umbGenerateAlias', function ($timeout, entityResource) {
angular.module('umbraco.directives').directive('umbGenerateAlias', function ($timeout, entityResource, localizationService) {
return {
restrict: 'E',
templateUrl: 'views/components/umb-generate-alias.html',
@@ -8958,26 +9230,37 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc
var generateAliasTimeout = '';
var updateAlias = false;
scope.locked = true;
scope.placeholderText = 'Enter alias...';
scope.labels = {
idle: 'Enter alias...',
busy: 'Generating alias...'
};
scope.placeholderText = scope.labels.idle;
localizationService.localize('placeholders_enterAlias').then(function (value) {
scope.labels.idle = scope.placeholderText = value;
});
localizationService.localize('placeholders_generatingAlias').then(function (value) {
scope.labels.busy = value;
});
function generateAlias(value) {
if (generateAliasTimeout) {
$timeout.cancel(generateAliasTimeout);
}
if (value !== undefined && value !== '' && value !== null) {
scope.alias = '';
scope.placeholderText = 'Generating Alias...';
scope.placeholderText = scope.labels.busy;
generateAliasTimeout = $timeout(function () {
updateAlias = true;
entityResource.getSafeAlias(encodeURIComponent(value), true).then(function (safeAlias) {
if (updateAlias) {
scope.alias = safeAlias.alias;
}
scope.placeholderText = scope.labels.idle;
});
}, 500);
} else {
updateAlias = true;
scope.alias = '';
scope.placeholderText = 'Enter alias...';
scope.placeholderText = scope.labels.idle;
}
}
// if alias gets unlocked - stop watching alias
@@ -8988,13 +9271,15 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc
}));
// validate custom entered alias
eventBindings.push(scope.$watch('alias', function (newValue, oldValue) {
if (scope.alias === '' && bindWatcher === true || scope.alias === null && bindWatcher === true) {
// add watcher
eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) {
if (bindWatcher) {
generateAlias(newValue);
}
}));
if (scope.alias === '' || scope.alias === null || scope.alias === undefined) {
if (bindWatcher === true) {
// add watcher
eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) {
if (bindWatcher) {
generateAlias(newValue);
}
}));
}
}
}));
// clean up
@@ -10405,6 +10690,18 @@ Use this directive to generate a thumbnail grid of media items.
scope.items.splice(i, 1);
i--;
}
// If subfolder search is not enabled remove the media items that's not needed
// Make sure that includeSubFolder is not undefined since the directive is used
// in contexts where it should not be used. Currently only used when we trigger
// a media picker
if (scope.includeSubFolders !== undefined) {
if (scope.includeSubFolders !== 'true') {
if (item.parentId !== parseInt(scope.currentFolderId)) {
scope.items.splice(i, 1);
i--;
}
}
}
}
if (scope.items.length > 0) {
setFlexValues(scope.items);
@@ -10545,7 +10842,9 @@ Use this directive to generate a thumbnail grid of media items.
itemMaxHeight: '@',
itemMinWidth: '@',
itemMinHeight: '@',
onlyImages: '@'
onlyImages: '@',
includeSubFolders: '@',
currentFolderId: '@'
},
link: link
};
@@ -10600,6 +10899,8 @@ Use this directive to generate a thumbnail grid of media items.
// update children
miniListView.children = data.items;
_.each(miniListView.children, function (c) {
// child allowed by default
c.allowed = true;
// convert legacy icon for node
if (c.icon) {
c.icon = iconHelper.convertFromLegacyIcon(c.icon);
@@ -10611,6 +10912,15 @@ Use this directive to generate a thumbnail grid of media items.
c.published = c.metaData.IsPublished;
}
}
// filter items if there is a filter and it's not advanced
// ** ignores advanced filter at the moment
if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) {
var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(',');
var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0;
if (!scope.entityTypeFilter.filterExclude && !found || scope.entityTypeFilter.filterExclude && found) {
c.allowed = false;
}
}
});
// update pagination
miniListView.pagination.totalItems = data.totalItems;
@@ -10624,7 +10934,7 @@ Use this directive to generate a thumbnail grid of media items.
event.stopPropagation();
};
scope.selectNode = function (node) {
if (scope.onSelect) {
if (scope.onSelect && node.allowed) {
scope.onSelect({ 'node': node });
}
};
@@ -10719,7 +11029,8 @@ Use this directive to generate a thumbnail grid of media items.
entityType: '@',
startNodeId: '=',
onSelect: '&',
onClose: '&'
onClose: '&',
entityTypeFilter: '='
},
link: link
};
@@ -11315,6 +11626,10 @@ Use this directive make an element sticky and follow the page when scrolling.
var clonedBar = null;
var cloneIsMade = false;
function activate() {
if (bar.parents('.umb-property').length > 1) {
bar.addClass('nested');
return;
}
if (attr.scrollableContainer) {
scrollableContainer = $(attr.scrollableContainer);
} else {
+5 -5
View File
@@ -48,16 +48,16 @@
//add to umbraco installer facts here
var facts = [
'Umbraco helped millions of people watch a man jump from the edge of space',
'Over 440 000 websites are currently powered by Umbraco',
'Over 500 000 websites are currently powered by Umbraco',
'At least 2 people have named their cat \'Umbraco\'',
'On an average day, more than 1000 people download Umbraco',
'<a target="_blank" href="https://umbraco.tv">umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
'You can find the world\'s friendliest CMS community at <a target="_blank" href="https://our.umbraco.com">our.umbraco.com</a>',
'On an average day more than 1000 people download Umbraco',
'<a target=\'_blank\' href=\'https://umbraco.tv/\'>umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
'You can find the world\'s friendliest CMS community at <a target=\'_blank\' href=\'https://our.umbraco.com/\'>our.umbraco.com</a>',
'You can become a certified Umbraco developer by attending one of the official courses',
'Umbraco works really well on tablets',
'You have 100% control over your markup and design when crafting a website in Umbraco',
'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company',
'There\'s a pretty big chance, you\'ve visited a website powered by Umbraco today',
'There\'s a pretty big chance you\'ve visited a website powered by Umbraco today',
'\'Umbraco-spotting\' is the game of spotting big brands running Umbraco',
'At least 4 people have the Umbraco logo tattooed on them',
'\'Umbraco\' is the Danish name for an allen key',
File diff suppressed because it is too large Load Diff
+17 -12
View File
@@ -130,19 +130,24 @@
if (filtered) {
return promise;
}
//A 401 means that the user is not logged in
if (originalResponse.status === 401 && !originalResponse.config.url.endsWith('umbraco/backoffice/UmbracoApi/Authentication/GetCurrentUser')) {
var userService = $injector.get('userService');
// see above
//Associate the user name with the retry to ensure we retry for the right user
promise = userService.getCurrentUser().then(function (user) {
var userName = user ? user.name : null;
//The request bounced because it was not authorized - add a new request to the retry queue
return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(originalResponse.config);
if (originalResponse.status === 401) {
//A 401 means that the user is not logged in
//avoid an infinite loop
var umbRequestHelper = $injector.get('umbRequestHelper');
var getCurrentUserPath = umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetCurrentUser');
if (!originalResponse.config.url.endsWith(getCurrentUserPath)) {
var userService = $injector.get('userService');
// see above
//Associate the user name with the retry to ensure we retry for the right user
promise = userService.getCurrentUser().then(function (user) {
var userName = user ? user.name : null;
//The request bounced because it was not authorized - add a new request to the retry queue
return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(originalResponse.config);
});
});
});
}
} else if (originalResponse.status === 404) {
//a 404 indicates that the request was not found - this could be due to a non existing url, or it could
//be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it
+117 -116
View File
@@ -1035,9 +1035,9 @@
}
}
}
// If we have a scheduled publish or unpublish date change the default button to
// If we have a scheduled publish date change the default button to
// "save" and update the label to "save and schedule
if (args.content.releaseDate || args.content.removeDate) {
if (args.content.releaseDate) {
// if save button is alread the default don't change it just update the label
if (buttons.defaultButton && buttons.defaultButton.letter === 'A') {
buttons.defaultButton.labelKey = 'buttons_saveAndSchedule';
@@ -1711,13 +1711,6 @@
}
return crop;
},
centerInsideViewPort: function (img, viewport) {
var left = viewport.width / 2 - img.width / 2, top = viewport.height / 2 - img.height / 2;
return {
left: left,
top: top
};
},
alignToCoordinates: function (image, center, viewport) {
var min_left = image.width - viewport.width;
var min_top = image.height - viewport.height;
@@ -4440,7 +4433,7 @@
* @name umbraco.services.mediaHelper
* @description A helper object used for dealing with media items
**/
function mediaHelper(umbRequestHelper) {
function mediaHelper(umbRequestHelper, $log) {
//container of fileresolvers
var _mediaFileResolvers = {};
return {
@@ -4448,11 +4441,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Returns the file path associated with the media property if there is one
*
*
* @param {object} options Options object
* @param {object} options.mediaModel The media object to retrieve the image path from
* @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image
@@ -4508,11 +4501,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Returns the actual image path associated with the image property if there is one
*
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
@@ -4529,11 +4522,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnail
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* formats the display model used to display the content to the model used to save the content
*
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
@@ -4554,43 +4547,39 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#resolveFileFromEntity
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Gets the media file url for a media entity returned with the entityResource
*
*
* @param {object} mediaEntity A media Entity returned from the entityResource
* @param {boolean} thumbnail Whether to return the thumbnail url or normal url
*/
resolveFileFromEntity: function (mediaEntity, thumbnail) {
if (!angular.isObject(mediaEntity.metaData)) {
throw 'Cannot resolve the file url from the mediaEntity, it does not contain the required metaData';
if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) {
//don't throw since this image legitimately might not contain a media path, but output a warning
$log.warn('Cannot resolve the file url from the mediaEntity, it does not contain the required metaData');
return null;
}
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;
}
} else {
return mediaEntity.metaData.MediaPath;
}
return '';
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#resolveFile
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Gets the media file url for a media object returned with the mediaResource
*
*
* @param {object} mediaEntity A media Entity returned from the entityResource
* @param {boolean} thumbnail Whether to return the thumbnail url or normal url
*/
@@ -4660,11 +4649,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#scaleToMaxSize
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
*
*
* @param {number} maxSize Maximum width & height
* @param {number} width Current width
* @param {number} height Current height
@@ -4704,11 +4693,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnailFromPath
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Returns the path to the thumbnail version of a given media library image path
*
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
getThumbnailFromPath: function (imagePath) {
@@ -4726,11 +4715,11 @@
* @ngdoc function
* @name umbraco.services.mediaHelper#detectIfImageByExtension
* @methodOf umbraco.services.mediaHelper
* @function
* @function
*
* @description
* Returns true/false, indicating if the given path has an allowed image extension
*
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
detectIfImageByExtension: function (imagePath) {
@@ -5015,6 +5004,7 @@
var mainTreeEventHandler = null;
//tracks the user profile dialog
var userDialog = null;
var syncTreePromise;
function setMode(mode) {
switch (mode) {
case 'tree':
@@ -5059,6 +5049,7 @@
appState.setSectionState('showSearchResults', false);
appState.setGlobalState('stickyNavigation', false);
appState.setGlobalState('showTray', false);
appState.setMenuState('currentNode', null);
if (appState.getGlobalState('isTablet') === true) {
appState.setGlobalState('showNavigation', false);
}
@@ -5143,6 +5134,11 @@
//when a tree is loaded into a section, we need to put it into appState
mainTreeEventHandler.bind('treeLoaded', function (ev, args) {
appState.setTreeState('currentRootNode', args.tree);
if (syncTreePromise) {
mainTreeEventHandler.syncTree(syncTreePromise.args).then(function (syncArgs) {
syncTreePromise.resolve(syncArgs);
});
}
});
//when a tree node is synced this event will fire, this allows us to set the currentNode
mainTreeEventHandler.bind('treeSynced', function (ev, args) {
@@ -5248,8 +5244,10 @@
return mainTreeEventHandler.syncTree(args);
}
}
//couldn't sync
return angularHelper.rejectedPromise();
//create a promise and resolve it later
syncTreePromise = $q.defer();
syncTreePromise.args = args;
return syncTreePromise.promise;
},
/**
Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
@@ -5386,7 +5384,7 @@
if (menuAction.length !== 2) {
//if it is not two parts long then this most likely means that it's a legacy action
var js = action.metaData['jsAction'].replace('javascript:', '');
//there's not really a different way to acheive this except for eval
//there's not really a different way to achieve this except for eval
eval(js);
} else {
var menuActionService = $injector.get(menuAction[0]);
@@ -5547,12 +5545,13 @@
* hides the currently open dialog
*/
hideDialog: function (showMenu) {
setMode('default');
if (showMenu) {
this.showMenu(undefined, {
skipDefault: true,
node: appState.getMenuState('currentNode')
});
} else {
setMode('default');
}
},
/**
@@ -5928,7 +5927,7 @@
* @ngdoc service
* @name umbraco.services.searchService
*
*
*
* @description
* Service for handling the main application search, can currently search content, media and members
*
@@ -5941,10 +5940,10 @@
* angular.forEach(results, function(result){
* //returns:
* {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
* })
* var result =
* })
* </pre>
* })
* var result =
* })
* </pre>
*/
angular.module('umbraco.services').factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) {
return {
@@ -5985,7 +5984,7 @@
if (!args.term) {
throw 'args.term is required';
}
return entityResource.search(args.term, 'Document', args.searchFrom, args.canceler).then(function (data) {
return entityResource.search(args.term, 'Document', args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureContentResult(item);
});
@@ -6007,7 +6006,7 @@
if (!args.term) {
throw 'args.term is required';
}
return entityResource.search(args.term, 'Media', args.searchFrom).then(function (data) {
return entityResource.search(args.term, 'Media', args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMediaResult(item);
});
@@ -6032,7 +6031,7 @@
return entityResource.searchAll(args.term, args.canceler).then(function (data) {
_.each(data, function (resultByType) {
//we need to format the search result data to include things like the subtitle, urls, etc...
// this is done with registered angular services as part of the SearchableTreeAttribute, if that
// this is done with registered angular services as part of the SearchableTreeAttribute, if that
// is not found, than we format with the default formatter
var formatterMethod = searchResultFormatter.configureDefaultResult;
//check if a custom formatter is specified...
@@ -6759,7 +6758,7 @@
* @ngdoc service
* @name umbraco.services.tinyMceService
*
*
*
* @description
* A service containing all logic for all of the Umbraco TinyMCE plugins
*/
@@ -6819,7 +6818,7 @@
* @description
* Creates the umbrco insert embedded media tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
* @param {Object} editor the TinyMCE editor instance
* @param {Object} $scope the current controller scope
*/
createInsertEmbeddedMedia: function (editor, scope, callback) {
@@ -6844,7 +6843,7 @@
* @description
* Creates the umbrco insert media tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
* @param {Object} editor the TinyMCE editor instance
* @param {Object} $scope the current controller scope
*/
createMediaPicker: function (editor, scope, callback) {
@@ -6916,7 +6915,7 @@
* @description
* Creates the insert umbrco macro tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
* @param {Object} editor the TinyMCE editor instance
* @param {Object} $scope the current controller scope
*/
createInsertMacro: function (editor, $scope, callback) {
@@ -6935,7 +6934,7 @@
});
});
/**
* Because the macro gets wrapped in a P tag because of the way 'enter' works, this
* Because the macro gets wrapped in a P tag because of the way 'enter' works, this
* method will return the macro element if not wrapped in a p, or the p if the macro
* element is the only one inside of it even if we are deep inside an element inside the macro
*/
@@ -6943,7 +6942,7 @@
var e = $(element).closest('.umb-macro-holder');
if (e.length > 0) {
if (e.get(0).parentNode.nodeName === 'P') {
//now check if we're the only element
//now check if we're the only element
if (element.parentNode.childNodes.length === 1) {
return e.get(0).parentNode;
}
@@ -6972,7 +6971,7 @@
if (endSelection !== startSelection) {
//if the end selection is a macro then move the cursor
//NOTE: we don't have to handle when the selection comes from a previous parent because
// that is automatically taken care of with the normal onNodeChanged logic since the
// that is automatically taken care of with the normal onNodeChanged logic since the
// evt.element will be the macro once it becomes part of the selection.
var $testForMacro = $(endSelection).closest('.umb-macro-holder');
if ($testForMacro.length > 0) {
@@ -7060,7 +7059,7 @@
});
//set onNodeChanged event listener
editor.on('NodeChange', onNodeChanged);
/**
/**
* Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so
* we'll check if the key down is a supported key which requires an action, otherwise we ignore the request
* so the macro cannot be edited.
@@ -7389,32 +7388,6 @@
prependToContext: true
});
},
/**
* @ngdoc method
* @name umbraco.services.tinyMceService#getAnchorNames
* @methodOf umbraco.services.tinyMceService
*
* @description
* From the given string, generates a string array where each item is the id attribute value from a named anchor
* 'some string <a id="anchor"></a>with a named anchor' returns ['anchor']
*
* @param {string} input the string to parse
*/
getAnchorNames: function (input) {
if (!input)
return [];
var anchorPattern = /<a id=\\"(.*?)\\">/gi;
var matches = input.match(anchorPattern);
var anchors = [];
if (matches) {
anchors = matches.map(function (v) {
return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\'));
});
}
return anchors.filter(function (val, i, self) {
return self.indexOf(val) === i;
});
},
insertLinkInEditor: function (editor, target, anchorElm) {
var href = target.url;
// We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null
@@ -7463,7 +7436,7 @@
editor.execCommand('mceInsertLink', false, createElemAttributes());
}
}
if (!href) {
if (!href && !target.anchor) {
editor.execCommand('unlink');
return;
}
@@ -7473,8 +7446,12 @@
insertLink();
return;
}
// Is email and not //user@domain.com
if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) {
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
href = 'mailto:' + href;
insertLink();
return;
@@ -8399,7 +8376,15 @@
self.loadNodeChildren({
node: node,
section: node.section
}).then(function () {
}).then(function (children) {
//we've reloaded a portion of the tree, call the callback if one is specified.
//TODO: In v8, we can just use deferred.notify
if (args.treeNodeExpanded && angular.isFunction(args.treeNodeExpanded)) {
args.treeNodeExpanded({
node: node,
children: children
});
}
//ok, got the children, let's find it
var found = self.getChildNode(node, args.path[currPathIndex]);
if (found) {
@@ -8854,12 +8839,17 @@
}
/** The default error callback used if one is not supplied in the opts */
function defaultError(data, status, headers, config) {
return {
var err = {
//NOTE: the default error message here should never be used based on the above docs!
errorMsg: angular.isString(opts) ? opts : 'An error occurred!',
data: data,
status: status
};
// if "opts" is a promise, we set "err.errorMsg" to be that promise
if (typeof opts == 'object' && typeof opts.then == 'function') {
err.errorMsg = opts;
}
return err;
}
//create the callbacs based on whats been passed in.
var callbacks = {
@@ -9263,6 +9253,25 @@
lastServerTimeoutSet = new Date();
}
}
function getMomentLocales(locales, supportedLocales) {
var localeUrls = [];
var locales = locales.split(',');
for (var i = 0; i < locales.length; i++) {
var locale = locales[i].toString().toLowerCase();
if (locale !== 'en-us') {
if (supportedLocales.indexOf(locale + '.js') > -1) {
localeUrls.push('lib/moment/' + locale + '.js');
}
if (locale.indexOf('-') > -1) {
var majorLocale = locale.split('-')[0] + '.js';
if (supportedLocales.indexOf(majorLocale) > -1) {
localeUrls.push('lib/moment/' + majorLocale);
}
}
}
}
return localeUrls;
}
/** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
function userAuthExpired(isLogout) {
//store the last user id and clear the user
@@ -9284,7 +9293,7 @@
userAuthExpired();
}
});
return {
var services = {
/** Internal method to display the login dialog */
_showLoginDialog: function () {
openLoginDialog();
@@ -9371,41 +9380,33 @@
},
/** Loads the Moment.js Locale for the current user. */
loadMomentLocaleForCurrentUser: function () {
function loadLocales(currentUser, supportedLocales) {
var locale = currentUser.locale.toLowerCase();
if (locale !== 'en-us') {
var localeUrls = [];
if (supportedLocales.indexOf(locale + '.js') > -1) {
localeUrls.push('lib/moment/' + locale + '.js');
}
if (locale.indexOf('-') > -1) {
var majorLocale = locale.split('-')[0] + '.js';
if (supportedLocales.indexOf(majorLocale) > -1) {
localeUrls.push('lib/moment/' + majorLocale);
}
}
return assetsService.load(localeUrls, $rootScope);
} else {
//return a noop promise
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve(true);
return promise;
}
}
var promises = {
currentUser: this.getCurrentUser(),
supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment()
};
return $q.all(promises).then(function (values) {
return loadLocales(values.currentUser, values.supportedLocales);
return services.loadLocales(values.currentUser.locale, values.supportedLocales);
});
},
/** Loads specific Moment.js Locales. */
loadLocales: function (locales, supportedLocales) {
var localeUrls = getMomentLocales(locales, supportedLocales);
if (localeUrls.length >= 1) {
return assetsService.load(localeUrls, $rootScope);
} else {
//return a noop promise
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve(true);
return promise;
}
},
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
}
};
return services;
});
(function () {
'use strict';