Umbraco 7.15.3
This commit is contained in:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user