diff --git a/samples/src/Dev/Dev.Sandbox/Cofoundry/PageTemplates/General.cshtml b/samples/src/Dev/Dev.Sandbox/Cofoundry/PageTemplates/General.cshtml
index fc1406524..40b88a658 100644
--- a/samples/src/Dev/Dev.Sandbox/Cofoundry/PageTemplates/General.cshtml
+++ b/samples/src/Dev/Dev.Sandbox/Cofoundry/PageTemplates/General.cshtml
@@ -18,9 +18,7 @@
*@
@(await Cofoundry.Template.Region("Body")
.AllowMultipleBlocks()
- .AllowBlockTypes("ContentSection", "ContentSplitSection")
.EmptyContentMinHeight(500)
.InvokeAsync())
-
\ No newline at end of file
diff --git a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
index fc7dc730e..82d579e05 100644
--- a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
+++ b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
@@ -433,7 +433,7 @@ tinymce.PluginManager.add("textpattern",function(a){function b(){return j&&(i.so
tinymce.PluginManager.add("visualblocks",function(a,b){function c(){var b=this;b.active(f),a.on("VisualBlocks",function(){b.active(a.dom.hasClass(a.getBody(),"mce-visualblocks"))})}var d,e,f;window.NodeList&&(a.addCommand("mceVisualBlocks",function(){var c,g=a.dom;d||(d=g.uniqueId(),c=g.create("link",{id:d,rel:"stylesheet",href:b+"/css/visualblocks.css"}),a.getDoc().getElementsByTagName("head")[0].appendChild(c)),a.on("PreviewFormats AfterPreviewFormats",function(b){f&&g.toggleClass(a.getBody(),"mce-visualblocks","afterpreviewformats"==b.type)}),g.toggleClass(a.getBody(),"mce-visualblocks"),f=a.dom.hasClass(a.getBody(),"mce-visualblocks"),e&&e.active(g.hasClass(a.getBody(),"mce-visualblocks")),a.fire("VisualBlocks")}),a.addButton("visualblocks",{title:"Show blocks",cmd:"mceVisualBlocks",onPostRender:c}),a.addMenuItem("visualblocks",{text:"Show blocks",cmd:"mceVisualBlocks",onPostRender:c,selectable:!0,context:"view",prependToContext:!0}),a.on("init",function(){a.settings.visualblocks_default_state&&a.execCommand("mceVisualBlocks",!1,null,{skip_focus:!0})}),a.on("remove",function(){a.dom.removeClass(a.getBody(),"mce-visualblocks")}))});
tinymce.PluginManager.add("visualchars",function(a){function b(b){function c(a){return''+a+""}function f(){var a,b="";for(a in n)b+=a;return new RegExp("["+b+"]","g")}function g(){var a,b="";for(a in n)b&&(b+=","),b+="span.mce-"+n[a];return b}var h,i,j,k,l,m,n,o,p=a.getBody(),q=a.selection;if(n={"\xa0":"nbsp","\xad":"shy"},d=!d,e.state=d,a.fire("VisualChars",{state:d}),o=f(),b&&(m=q.getBookmark()),d)for(i=[],tinymce.walk(p,function(a){3==a.nodeType&&a.nodeValue&&o.test(a.nodeValue)&&i.push(a)},"childNodes"),j=0;j=0;j--)a.dom.remove(i[j],1);q.moveToBookmark(m)}function c(){var b=this;a.on("VisualChars",function(a){b.active(a.state)})}var d,e=this;a.addCommand("mceVisualChars",b),a.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c}),a.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c,selectable:!0,context:"view",prependToContext:!0}),a.on("beforegetcontent",function(a){d&&"raw"!=a.format&&!a.draft&&(d=!0,b(!1))})});
tinymce.PluginManager.add("wordcount",function(a){function b(){a.theme.panel.find("#wordcount").text(["Words: {0}",e.getCount()])}var c,d,e=this;c=a.getParam("wordcount_countregex",/[\w\u2019\x27\-\u00C0-\u1FFF]+/g),d=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),a.on("init",function(){var c=a.theme.panel&&a.theme.panel.find("#statusbar")[0];c&&tinymce.util.Delay.setEditorTimeout(a,function(){c.insert({type:"label",name:"wordcount",text:["Words: {0}",e.getCount()],classes:"wordcount",disabled:a.settings.readonly},0),a.on("setcontent beforeaddundo",b),a.on("keyup",function(a){32==a.keyCode&&b()})},0)}),e.getCount=function(){var b=a.getContent({format:"raw"}),e=0;if(b){b=b.replace(/\.\.\./g," "),b=b.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),b=b.replace(/(\w+)(?[a-z0-9]+;)+(\w+)/i,"$1$3").replace(/&.+?;/g," "),b=b.replace(d,"");var f=b.match(c);f&&(e=f.length)}return e}});
-angular.module("ui.tinymce", []).value("uiTinymceConfig", {}).directive("uiTinymce", ["$rootScope", "$compile", "$timeout", "$window", "$sce", "uiTinymceConfig", function (a, b, c, d, e, f) { f = f || {}; var g = "ui-tinymce"; return f.baseUrl && (tinymce.baseURL = f.baseUrl), { require: ["ngModel", "^?form"], priority: 599, link: function (h, i, j, k) { function l(a) { a ? (m(), o && o.getBody().setAttribute("contenteditable", !1)) : (m(), o && !o.settings.readonly && o.getDoc() && o.getBody().setAttribute("contenteditable", !0)) } function m() { o || (o = tinymce.get(j.id)) } if (d.tinymce) { var n, o, p = k[0], q = k[1] || null, r = { debounce: !0 }, s = function (b) { var c = b.getContent({ format: r.format }).trim(); c = e.trustAsHtml(c), p.$setViewValue(c), a.$$phase || h.$digest() }; j.$set("id", g + "-" + (new Date).valueOf()), n = {}, angular.extend(n, h.$eval(j.uiTinymce)); var t = function (a) { var b; return function (d) { c.cancel(b), b = c(function () { return function (a) { a.isDirty() && (a.save(), s(a)) }(d) }, a) } }(400), u = { setup: function (b) { b.on("init", function () { p.$render(), p.$setPristine(), p.$setUntouched(), q && q.$setPristine() }), b.on("ExecCommand change NodeChange ObjectResized", function () { return r.debounce ? void t(b) : (b.save(), void s(b)) }), b.on("blur", function () { i[0].blur(), p.$setTouched(), a.$$phase || h.$digest() }), b.on("remove", function () { i.remove() }), f.setup && f.setup(b, { updateView: s }), n.setup && n.setup(b, { updateView: s }) }, format: n.format || "html", selector: "#" + j.id }; angular.extend(r, f, n, u), c(function () { r.baseURL && (tinymce.baseURL = r.baseURL); var a = tinymce.init(r); a && "function" == typeof a.then ? a.then(function () { l(h.$eval(j.ngDisabled)) }) : l(h.$eval(j.ngDisabled)) }), p.$formatters.unshift(function (a) { return a ? e.trustAsHtml(a) : "" }), p.$parsers.unshift(function (a) { return a ? e.getTrustedHtml(a) : "" }), p.$render = function () { m(); var a = p.$viewValue ? e.getTrustedHtml(p.$viewValue) : ""; o && o.getDoc() && (o.setContent(a), o.fire("change")) }, j.$observe("disabled", l), h.$on("$tinymce:refresh", function (a, c) { var d = j.id; if (angular.isUndefined(c) || c === d) { var e = i.parent(), f = i.clone(); f.removeAttr("id"), f.removeAttr("style"), f.removeAttr("aria-hidden"), tinymce.execCommand("mceRemoveEditor", !1, d), e.append(b(f)(h)) } }), h.$on("$destroy", function () { m(), o && (o.remove(), o = null) }) } } } }]);
+angular.module("ui.tinymce",[]).value("uiTinymceConfig",{}).directive("uiTinymce",["$rootScope","$compile","$timeout","$window","$sce","uiTinymceConfig","uiTinymceService",function(a,b,c,d,e,f,g){return f=f||{},f.baseUrl&&(tinymce.baseURL=f.baseUrl),{require:["ngModel","^?form"],priority:599,link:function(h,i,j,k){function l(a){a?(m(),o&&o.getBody().setAttribute("contenteditable",!1)):(m(),o&&!o.settings.readonly&&o.getDoc()&&o.getBody().setAttribute("contenteditable",!0))}function m(){o||(o=tinymce.get(j.id))}if(d.tinymce){var n,o,p=k[0],q=k[1]||null,r={debounce:!0},s=function(b){var c=b.getContent({format:r.format}).trim();c=e.trustAsHtml(c),p.$setViewValue(c),a.$$phase||h.$digest()},t=g.getUniqueId();j.$set("id",t),n={},angular.extend(n,h.$eval(j.uiTinymce));var u=function(a){var b;return function(d){c.cancel(b),b=c(function(){return function(a){a.isDirty()&&(a.save(),s(a))}(d)},a)}}(400),v={setup:function(b){b.on("init",function(){p.$render(),p.$setPristine(),p.$setUntouched(),q&&q.$setPristine()}),b.on("ExecCommand change NodeChange ObjectResized",function(){return r.debounce?void u(b):(b.save(),void s(b))}),b.on("blur",function(){i[0].blur(),p.$setTouched(),a.$$phase||h.$digest()}),b.on("remove",function(){i.remove()}),f.setup&&f.setup(b,{updateView:s}),n.setup&&n.setup(b,{updateView:s})},format:n.format||"html",selector:"#"+j.id};angular.extend(r,f,n,v),c(function(){r.baseURL&&(tinymce.baseURL=r.baseURL);var a=tinymce.init(r);a&&"function"==typeof a.then?a.then(function(){l(h.$eval(j.ngDisabled))}):l(h.$eval(j.ngDisabled))}),p.$formatters.unshift(function(a){return a?e.trustAsHtml(a):""}),p.$parsers.unshift(function(a){return a?e.getTrustedHtml(a):""}),p.$render=function(){m();var a=p.$viewValue?e.getTrustedHtml(p.$viewValue):"";o&&o.getDoc()&&(o.setContent(a),o.fire("change"))},j.$observe("disabled",l),h.$on("$tinymce:refresh",function(a,c){var d=j.id;if(angular.isUndefined(c)||c===d){var e=i.parent(),f=i.clone();f.removeAttr("id"),f.removeAttr("style"),f.removeAttr("aria-hidden"),tinymce.execCommand("mceRemoveEditor",!1,d),e.append(b(f)(h))}}),h.$on("$destroy",function(){m(),o&&(o.remove(),o=null)})}}}}]).service("uiTinymceService",[function(){var a=function(){var a="ui-tinymce",b=0,c=function(){return b++,a+"-"+b};return{getUniqueId:c}};return new a}]);
/*
AngularJS v1.8.2
(c) 2010-2020 Google LLC. http://angularjs.org
@@ -8270,6 +8270,127 @@ function (
return service;
}]);
+angular.module('cms.shared').directive('cmsFormFieldDirectorySelector', [
+ '_',
+ 'shared.directiveUtilities',
+ 'shared.internalModulePath',
+ 'shared.directoryService',
+function (
+ _,
+ directiveUtilities,
+ modulePath,
+ directoryService
+ ) {
+
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Directories/FormFieldDirectorySelector.html',
+ scope: {
+ model: '=cmsModel',
+ title: '@cmsTitle',
+ onLoaded: '&cmsOnLoaded',
+ readonly: '=cmsReadonly'
+ },
+ link: {
+ pre: preLink
+ },
+ controller: Controller,
+ controllerAs: 'vm',
+ bindToController: true
+ };
+
+ /* COMPILE */
+
+ function preLink(scope, el, attrs) {
+ var vm = scope.vm;
+
+ if (angular.isDefined(attrs.required)) {
+ vm.isRequired = true;
+ } else {
+ vm.isRequired = false;
+ vm.defaultItemText = attrs.cmsDefaultItemText || 'None';
+ }
+ vm.title = attrs.cmsTitle || 'Directory';
+ vm.description = attrs.cmsDescription;
+ directiveUtilities.setModelName(vm, attrs);
+ }
+
+ /* CONTROLLER */
+
+ function Controller() {
+ var vm = this;
+
+ directoryService.getAll().then(function (pageDirectories) {
+ vm.pageDirectories = pageDirectories;
+
+ if (vm.onLoaded) vm.onLoaded();
+ });
+ }
+}]);
+angular.module('cms.shared').directive('cmsButton', [
+ 'shared.internalModulePath',
+function (
+ modulePath) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ templateUrl: modulePath + 'UIComponents/Buttons/Button.html',
+ scope: {
+ text: '@cmsText'
+ }
+ };
+}]);
+angular.module('cms.shared').directive('cmsButtonIcon', [
+ 'shared.internalModulePath',
+ function (modulePath) {
+
+ return {
+ restrict: 'E',
+ replace: false,
+ templateUrl: modulePath + 'UIComponents/Buttons/ButtonIcon.html',
+ scope: {
+ title: '@cmsTitle',
+ icon: '@cmsIcon',
+ href: '@cmsHref',
+ external: '@cmsExternal'
+ },
+ link: function (scope, el) {
+ if (scope.icon) {
+ scope.iconCls = 'fa-' + scope.icon;
+ }
+ }
+ };
+}]);
+angular.module('cms.shared').directive('cmsButtonLink', [
+ 'shared.internalModulePath',
+ function (modulePath) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ templateUrl: modulePath + 'UIComponents/Buttons/ButtonLink.html',
+ scope: {
+ text: '@cmsText',
+ href: '@cmsHref'
+ }
+ };
+}]);
+angular.module('cms.shared').directive('cmsButtonSubmit', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ templateUrl: modulePath + 'UIComponents/Buttons/ButtonSubmit.html',
+ scope: {
+ text: '@cmsText'
+ }
+ };
+}]);
angular.module('cms.shared').controller('AddCustomEntityDialogController', [
'$scope',
'$location',
@@ -9171,147 +9292,50 @@ function (
function Controller() {
}
}]);
-angular.module('cms.shared').directive('cmsButton', [
- 'shared.internalModulePath',
-function (
- modulePath) {
-
- return {
- restrict: 'E',
- replace: true,
- templateUrl: modulePath + 'UIComponents/Buttons/Button.html',
- scope: {
- text: '@cmsText'
- }
- };
-}]);
-angular.module('cms.shared').directive('cmsButtonIcon', [
- 'shared.internalModulePath',
- function (modulePath) {
-
- return {
- restrict: 'E',
- replace: false,
- templateUrl: modulePath + 'UIComponents/Buttons/ButtonIcon.html',
- scope: {
- title: '@cmsTitle',
- icon: '@cmsIcon',
- href: '@cmsHref',
- external: '@cmsExternal'
- },
- link: function (scope, el) {
- if (scope.icon) {
- scope.iconCls = 'fa-' + scope.icon;
- }
- }
- };
-}]);
-angular.module('cms.shared').directive('cmsButtonLink', [
- 'shared.internalModulePath',
- function (modulePath) {
-
- return {
- restrict: 'E',
- replace: true,
- templateUrl: modulePath + 'UIComponents/Buttons/ButtonLink.html',
- scope: {
- text: '@cmsText',
- href: '@cmsHref'
- }
- };
-}]);
-angular.module('cms.shared').directive('cmsButtonSubmit', [
- 'shared.internalModulePath',
-function (
- modulePath
-) {
-
- return {
- restrict: 'E',
- replace: true,
- templateUrl: modulePath + 'UIComponents/Buttons/ButtonSubmit.html',
- scope: {
- text: '@cmsText'
- }
- };
-}]);
-angular.module('cms.shared').directive('cmsFormFieldDirectorySelector', [
- '_',
- 'shared.directiveUtilities',
+angular.module('cms.shared').directive('cmsDocumentAsset', [
'shared.internalModulePath',
- 'shared.directoryService',
+ 'shared.urlLibrary',
function (
- _,
- directiveUtilities,
modulePath,
- directoryService
+ urlLibrary
) {
return {
restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Directories/FormFieldDirectorySelector.html',
scope: {
- model: '=cmsModel',
- title: '@cmsTitle',
- onLoaded: '&cmsOnLoaded',
- readonly: '=cmsReadonly'
- },
- link: {
- pre: preLink
+ document: '=cmsDocument'
},
- controller: Controller,
- controllerAs: 'vm',
- bindToController: true
- };
-
- /* COMPILE */
-
- function preLink(scope, el, attrs) {
- var vm = scope.vm;
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAsset.html',
+ link: function (scope, el, attributes) {
- if (angular.isDefined(attrs.required)) {
- vm.isRequired = true;
- } else {
- vm.isRequired = false;
- vm.defaultItemText = attrs.cmsDefaultItemText || 'None';
+ scope.getDocumentUrl = urlLibrary.getDocumentUrl;
}
- vm.title = attrs.cmsTitle || 'Directory';
- vm.description = attrs.cmsDescription;
- directiveUtilities.setModelName(vm, attrs);
- }
-
- /* CONTROLLER */
-
- function Controller() {
- var vm = this;
-
- directoryService.getAll().then(function (pageDirectories) {
- vm.pageDirectories = pageDirectories;
-
- if (vm.onLoaded) vm.onLoaded();
- });
- }
+ };
}]);
-angular.module('cms.shared').controller('ImageAssetEditorDialogController', [
+angular.module('cms.shared').controller('DocumentAssetPickerDialogController', [
'$scope',
'shared.LoadState',
- 'shared.imageService',
+ 'shared.documentService',
'shared.SearchQuery',
+ 'shared.modalDialogService',
+ 'shared.internalModulePath',
+ 'shared.permissionValidationService',
'shared.urlLibrary',
'options',
'close',
function (
$scope,
LoadState,
- imageService,
+ documentService,
SearchQuery,
+ modalDialogService,
+ modulePath,
+ permissionValidationService,
urlLibrary,
options,
close) {
- var vm = $scope,
- isAssetInitialized;
-
+ var vm = $scope;
init();
/* INIT */
@@ -9319,607 +9343,623 @@ function (
function init() {
angular.extend($scope, options);
- vm.formLoadState = new LoadState();
- vm.saveLoadState = new LoadState();
-
- vm.onInsert = onInsert;
+ vm.onOk = onOk;
vm.onCancel = onCancel;
+ vm.onSelect = onSelect;
+ vm.onUpload = onUpload;
+ vm.selectedAsset = vm.currentAsset; // currentAsset is null in single mode
+ vm.onSelectAndClose = onSelectAndClose;
+ vm.close = onCancel;
- vm.onImageChanged = onImageChanged;
- vm.command = {};
+ vm.gridLoadState = new LoadState();
+ vm.query = new SearchQuery({
+ onChanged: onQueryChanged,
+ useHistory: false,
+ defaultParams: options.filter
+ });
+ vm.presetFilter = options.filter;
- setCurrentImage();
+ vm.filter = vm.query.getFilters();
+ vm.toggleFilter = toggleFilter;
+
+ vm.isSelected = isSelected;
+ vm.multiMode = vm.selectedIds ? true : false;
+ vm.okText = vm.multiMode ? 'Ok' : 'Select';
+
+ vm.canCreate = permissionValidationService.canCreate('COFDOC');
+
+ vm.getDocumentUrl = urlLibrary.getDocumentUrl;
+
+ toggleFilter(false);
+ loadGrid();
}
/* ACTIONS */
- function setCurrentImage() {
- // If we have an existing image, we need to find the asset id to set the command image
- if (vm.imageAssetHtml && vm.imageAssetHtml.length) {
- vm.command.imageAssetId = vm.imageAssetHtml.attr('data-image-asset-id');
- vm.command.altTag = vm.imageAssetHtml.attr('alt');
- vm.command.style = vm.imageAssetHtml.attr('style');
+ function toggleFilter(show) {
+ vm.isFilterVisible = _.isUndefined(show) ? !vm.isFilterVisible : show;
+ }
- // If the image had any styles (mainly dimensions), pass them to the command so they are retained
- if (vm.command.style) {
- var styles = parseStyles(vm.command.style);
- vm.command.width = styles['width'];
- vm.command.height = styles['height'];
+ function onQueryChanged() {
+ toggleFilter(false);
+ loadGrid();
+ }
- // Else, look to see if the dimensions are stored as attibutes of the image
- } else {
- vm.command.width = vm.imageAssetHtml.attr('width');
- vm.command.height = vm.imageAssetHtml.attr('height');
- }
+ function loadGrid() {
+ vm.gridLoadState.on();
- // If we cannot find the asset id (could have removed the data attribute that this relies on),
- // we try to work this out based on the image path (this might change in future versions of cofoundry so less reliable)
- if (!vm.command.imageAssetId) {
- var src = vm.imageAssetHtml.attr('src');
- var lastIndex = src.lastIndexOf('/');
- var extractId = src.substr(lastIndex + 1, ((src.indexOf('_') - lastIndex) - 1));
- vm.command.imageAssetId = extractId;
- }
- }
+ return documentService.getAll(vm.query.getParameters()).then(function (result) {
+ vm.result = result;
+ vm.gridLoadState.off();
+ });
}
/* EVENTS */
function onCancel() {
+ if (!vm.multiMode) {
+ // in single-mode reset the currentAsset
+ vm.onSelected(vm.currentAsset);
+ }
close();
}
- function onImageChanged() {
- vm.command.altTag = vm.command.imageAsset.title || vm.command.imageAsset.fileName;
- }
-
- function onInsert() {
-
- // Parse and hold dimensions
- var dimensions = {
- width: parseUnits(vm.command.width),
- height: parseUnits(vm.command.height)
- };
-
- // If we have no sizes set, default to percentage respecting ratio
- if (!dimensions.width && !dimensions.height) {
- dimensions.width = '100%';
- dimensions.height = 'auto';
+ function onSelect(document) {
+ if (!vm.multiMode) {
+ vm.selectedAsset = document;
+ return;
}
- // Get the image path, including specific size options if nessessary
- var path = urlLibrary.getImageUrl(vm.command.imageAsset, parseImageRequestSize(dimensions));
-
- // Default the alt tag to an empty string if not specified
- var alt = vm.command.altTag || '';
+ addOrRemove(document);
+ }
- // Define an object thay holds formatted outputs, plus the model itself
- var output = {
- markdown: "",
- html: "
",
- model: vm.command
- };
+ function onSelectAndClose(document) {
+ if (!vm.multiMode) {
+ vm.selectedAsset = document;
+ onOk();
+ return;
+ }
- // Add css styles to output html
- output.html = insertCssStyles(output.html, dimensions);
+ addOrRemove(document);
+ onOk();
+ }
- // Call callback with output
- vm.onSelected(output);
+ function onOk() {
+ if (!vm.multiMode) {
+ vm.onSelected(vm.selectedAsset);
+ } else {
+ vm.onSelected(vm.selectedIds);
+ }
- // Close dialog
close();
}
- /* PUBLIC HELPERS */
+ function onUpload() {
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/UploadDocumentAssetDialog.html',
+ controller: 'UploadDocumentAssetDialogController',
+ options: {
+ filter: options.filter,
+ onUploadComplete: onUploadComplete
+ }
+ });
- function insertCssStyles(html, styles) {
- return angular.element(html).css(styles)[0].outerHTML;
+ function onUploadComplete(documentAssetId) {
+ onSelectAndClose({ documentAssetId: documentAssetId });
+ }
}
- function parseImageRequestSize(dimensions) {
- // If unit type is percent, use original image size
- if ((dimensions.width || '').indexOf('%') > -1 || (dimensions.height || '').indexOf('%') > -1) return {};
-
- // Else, return raw pixel sizes
- return {
- width: dimensions.width.replace('px', ''),
- height: dimensions.height.replace('px', '')
- };
- }
+ /* PUBLIC HELPERS */
- function parseUnits(value) {
- if (!value) return '';
+ function isSelected(document) {
+ if (vm.selectedIds && document && vm.selectedIds.indexOf(document.documentAssetId) > -1) return true;
- // Default to pixels if not unit type specified
- if (value.indexOf('px') == -1 && value.indexOf('%') == -1 && value.indexOf('auto') == -1) return value + 'px';
+ if (!document || !vm.selectedAsset) return false;
- // Return original value if we get here
- return value;
+ return document.documentAssetId === vm.selectedAsset.documentAssetId;
}
- function parseStyles(cssText) {
- var regex = /([\w-]*)\s*:\s*([^;]*)/g;
- var match, properties = {};
- while (match = regex.exec(cssText)) properties[match[1]] = match[2];
- return properties;
+ function addOrRemove(document) {
+ if (!isSelected(document)) {
+ vm.selectedIds.push(document.documentAssetId);
+ } else {
+ var index = vm.selectedIds.indexOf(document.documentAssetId);
+ vm.selectedIds.splice(index, 1);
+ }
}
-
}]);
-angular.module('cms.shared').controller('AddEntityAccessRuleController', [
- '$scope',
- '$q',
- 'shared.LoadState',
- 'shared.roleService',
- 'shared.userAreaService',
- 'options',
- 'close',
-function (
- $scope,
- $q,
- LoadState,
- roleService,
- userAreaService,
- options,
- close) {
-
- var vm = $scope;
+/**
+ * File upload control for documents/files. Uses https://github.com/danialfarid/angular-file-upload
+ */
+angular.module('cms.shared').directive('cmsDocumentUpload', [
+ '_',
+ 'shared.internalModulePath',
+ 'shared.urlLibrary',
+ function (
+ _,
+ modulePath,
+ urlLibrary
+ ) {
- init();
+ /* CONFIG */
- /* INIT */
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentUpload.html',
+ scope: {
+ asset: '=cmsAsset',
+ loadState: '=cmsLoadState',
+ isEditMode: '=cmsIsEditMode',
+ modelName: '=cmsModelName',
+ ngModel: '=ngModel',
+ onChange: '&cmsOnChange'
+ },
+ require: 'ngModel',
+ controller: function () { },
+ controllerAs: 'vm',
+ bindToController: true,
+ link: link
+ };
- function init() {
+ /* LINK */
- vm.globalLoadState = new LoadState();
- vm.saveLoadState = new LoadState();
- vm.formLoadState = new LoadState();
- setLoadingOn(vm.formLoadState);
+ function link(scope, el, attributes, ngModelController) {
+ var vm = scope.vm,
+ isRequired = _.has(attributes, 'required');
- vm.onAdd = onAdd;
- vm.onCancel = onCancel;
- vm.onUserAreaChanged = onUserAreaChanged;
- vm.searchRoles = searchRoles;
+ init();
- initData();
- }
-
- /* EVENTS */
+ /* INIT */
- function onUserAreaChanged() {
- vm.command.roleId = null;
- }
+ function init() {
+ vm.remove = remove;
+ vm.fileChanged = onFileChanged;
+ vm.isRemovable = _.isObject(vm.ngModel) && !isRequired;
+ scope.$watch("vm.asset", setAsset);
+ }
- function searchRoles(query) {
- if (!vm.command.userAreaCode) {
- var def = $q.defer();
- def.resolve();
- return def.promise;
- }
+ /* EVENTS */
- query.userAreaCode = vm.command.userAreaCode;
- query.excludeAnonymous = true;
+ function remove() {
+ onFileChanged();
+ }
- return roleService.search(query);
- }
+ /**
+ * Initialise the state when the asset is changed
+ */
+ function setAsset() {
+ var asset = vm.asset;
+ if (asset) {
+ vm.previewUrl = urlLibrary.getDocumentUrl(asset);
+ vm.isRemovable = !isRequired;
- function onAdd() {
- options.onSave(vm.command);
+ ngModelController.$setViewValue({
+ name: asset.fileName + '.' + asset.fileExtension,
+ size: asset.fileSizeInBytes,
+ isCurrentFile: true
+ });
- close();
- }
+ } else {
+ vm.isRemovable = false;
- function onCancel() {
- close();
- }
+ if (ngModelController.$modelValue) {
+ ngModelController.$setViewValue(undefined);
+ }
+ }
- /* PRIVATE FUNCS */
+ setButtonText();
+ }
- function initData() {
-
- vm.command = {};
- onUserAreaChanged();
+ function onFileChanged($files) {
+ if ($files && $files[0]) {
+ // set the file is one is selected
+ ngModelController.$setViewValue($files[0]);
+ vm.isRemovable = !isRequired;
- userAreaService
- .getAll()
- .then(loadUserAreas)
- .finally(setLoadingOff.bind(null, vm.formLoadState));
+ } else if (!vm.ngModel || _.isUndefined($files)) {
+ // if we don't have a file loaded already, remove the file.
+ ngModelController.$setViewValue(undefined);
+ vm.previewUrl = null;
+ vm.isRemovable = false;
+ vm.asset = undefined;
+ }
- function loadUserAreas(userAreas) {
- vm.userAreas = _.filter(userAreas, function(userArea) { return userArea.userAreaCode !== 'COF' });
+ setButtonText();
- if (vm.userAreas.length == 1) {
- vm.command.userAreaCode = vm.userAreas[0].userAreaCode;
- }
+ // base onChange event
+ if (vm.onChange) vm.onChange(vm.ngModel);
}
- }
- function setLoadingOn(loadState) {
- vm.globalLoadState.on();
- if (loadState && _.isFunction(loadState.on)) loadState.on();
- }
+ /* Helpers */
- function setLoadingOff(loadState) {
- vm.globalLoadState.off();
- if (loadState && _.isFunction(loadState.off)) loadState.off();
+ function setButtonText() {
+ vm.buttonText = ngModelController.$modelValue ? 'Change' : 'Upload';
+ }
}
+
}]);
-angular.module('cms.shared').controller('EntityAccessEditorController', [
- '$scope',
- '$q',
- 'shared.LoadState',
- 'shared.userAreaService',
- 'shared.roleService',
- 'shared.modalDialogService',
- 'shared.arrayUtilities',
+/**
+ * A form field control for an image asset that uses a search and pick dialog
+ * to allow the user to change the selected file.
+ */
+angular.module('cms.shared').directive('cmsFormFieldDocumentAsset', [
+ '_',
'shared.internalModulePath',
- 'shared.permissionValidationService',
+ 'shared.internalContentPath',
+ 'shared.modalDialogService',
+ 'shared.stringUtilities',
+ 'shared.documentService',
'shared.urlLibrary',
- 'options',
- 'close',
+ 'baseFormFieldFactory',
function (
- $scope,
- $q,
- LoadState,
- userAreaService,
- roleService,
- modalDialogService,
- arrayUtilities,
+ _,
modulePath,
- permissionValidationService,
+ contentPath,
+ modalDialogService,
+ stringUtilities,
+ documentService,
urlLibrary,
- options,
- close) {
+ baseFormFieldFactory) {
- var vm = $scope;
+ /* CONFIG */
- init();
-
- /* INIT */
+ var config = {
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentAsset.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ asset: '=cmsAsset',
+ loadState: '=cmsLoadState',
+ updateAsset: '@cmsUpdateAsset' // update the asset property if it changes
+ }),
+ passThroughAttributes: ['required'],
+ link: link
+ };
- function init() {
-
- // UI actions
- vm.save = save;
- vm.close = close;
- vm.add = add;
- vm.deleteRule = deleteRule;
+ return baseFormFieldFactory.create(config);
- // Properties
- vm.globalLoadState = new LoadState();
- vm.saveLoadState = new LoadState();
- vm.formLoadState = new LoadState(true);
- vm.urlLibrary = urlLibrary;
+ /* LINK */
- // permissions
- vm.canManage = permissionValidationService.hasPermission(options.entityDefinitionCode + 'ACCRUL');
- vm.editMode = vm.canManage;
+ function link(scope, el, attributes, controllers) {
+ var vm = scope.vm,
+ isRequired = _.has(attributes, 'required'),
+ isAssetInitialized;
- // Init
- initData(vm.formLoadState);
- }
+ init();
+ return baseFormFieldFactory.defaultConfig.link(scope, el, attributes, controllers);
- /* UI ACTIONS */
+ /* INIT */
- function save() {
- vm.command.accessRules = _.map(vm.accessRuleSet.accessRules, function(rule) {
- var idProp = options.entityIdPrefix + 'AccessRuleId';
- var command = {
- userAreaCode: rule.userArea.userAreaCode,
- roleId: rule.role ? rule.role.roleId : null
- };
+ function init() {
- command[idProp] = rule[idProp];
+ vm.urlLibrary = urlLibrary;
+ vm.showPicker = showPicker;
+ vm.remove = remove;
+ vm.isRemovable = _.isObject(vm.model) && !isRequired;
- return command;
- });
-
- if (!vm.command.redirectToSignIn) {
- vm.command.userAreaCodeForSignInRedirect = null;
- }
+ vm.filter = parseFilters(attributes);
- setLoadingOn(vm.saveLoadState);
+ scope.$watch("vm.asset", setAsset);
+ scope.$watch("vm.model", setAssetById);
+ }
- options.saveAccess(vm.command)
- .then(onSuccess.bind(null, 'Access rules updated successfully'))
- .then(close)
- .finally(setLoadingOff.bind(null, vm.saveLoadState));
- }
+ /* EVENTS */
- function add() {
+ function remove() {
+ setAsset(null);
+ }
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/EntityAccess/AddEntityAccessRule.html',
- controller: 'AddEntityAccessRuleController',
- options: {
- onSave: onAddRule
- }
- });
- }
-
- function deleteRule(rule, $index) {
+ function showPicker() {
- arrayUtilities.removeObject(vm.accessRuleSet.accessRules, rule);
- setUserAreasInRules();
- }
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAssetPickerDialog.html',
+ controller: 'DocumentAssetPickerDialogController',
+ options: {
+ currentAsset: vm.previewAsset,
+ filter: vm.filter,
+ onSelected: onSelected
+ }
+ });
- /* EVENTS */
+ function onSelected(newAsset) {
+ if (!newAsset && vm.asset) {
+ setAsset(null);
+ } else if (!vm.asset || (newAsset && vm.asset.documentAssetId !== newAsset.documentAssetId)) {
+ setAsset(newAsset);
+ }
+ }
+ }
- function onAddRule(command) {
-
- var rule = {};
+ /**
+ * When the model is set without a preview asset, we need to go get the full
+ * asset details. This query can be bypassed by setting the cms-asset attribute
+ */
+ function setAssetById(assetId) {
- var duplicateRule = _.find(vm.accessRuleSet.accessRules, function(accessRule) {
- var roleId = accessRule.role ? accessRule.role.roleId : null;
- return accessRule.userArea.userAreaCode === command.userAreaCode && roleId == command.roleId;
- });
+ // Remove the id if it is 0 or invalid to make sure required validation works
+ if (!assetId) {
+ vm.model = assetId = undefined;
+ }
- if (duplicateRule) {
- return;
+ if (assetId && (!vm.previewAsset || vm.previewAsset.documentAssetId != assetId)) {
+ documentService.getById(assetId).then(function (asset) {
+ setAsset(asset);
+ });
+ }
}
- setLoadingOn();
+ /**
+ * Initialise the state when the asset is changed
+ */
+ function setAsset(asset) {
- $q.all([getRole(), getUserArea()])
- .then(function() {
- vm.accessRuleSet.accessRules.push(rule);
- sortRules();
- setUserAreasInRules();
- })
- .finally(setLoadingOff);
+ if (asset) {
+ vm.previewAsset = asset;
+ vm.isRemovable = !isRequired;
+ vm.model = asset.documentAssetId;
- function getUserArea() {
- return userAreaService
- .getByCode(command.userAreaCode)
- .then(function(userArea) {
- rule.userArea = userArea;
- });
- }
+ if (vm.updateAsset) {
+ vm.asset = asset;
+ }
- function getRole() {
- if (!command.roleId) {
- return $q(function(resolve) { resolve() });
+ } else if (isAssetInitialized) {
+ // Ignore if we are running this first time to avoid overwriting the model with a null vlaue
+ vm.previewAsset = null;
+ vm.isRemovable = false;
+
+ if (vm.model) {
+ vm.model = null;
+ }
+ if (vm.updateAsset) {
+ vm.asset = null;
+ }
}
- return roleService
- .getById(command.roleId)
- .then(function(role) {
- rule.role = role;
- });
+ setButtonText();
+
+ isAssetInitialized = true;
}
- }
- function onSuccess(message, loadStateToTurnOff) {
+ /* Helpers */
- return initData(loadStateToTurnOff)
- .then(vm.mainForm.formStatus.success.bind(null, message));
- }
+ function parseFilters(attributes) {
+ var filter = {},
+ attributePrefix = 'cms';
- /* PRIVATE FUNCS */
+ setAttribute('Tags');
+ setAttribute('FileExtension');
+ setAttribute('FileExtensions');
- function initData(loadStateToTurnOff) {
+ return filter;
- vm.entityDefinitionName = options.entityDefinitionName;
- vm.entityDefinitionNameLower = options.entityDefinitionName.toLowerCase();
- vm.entityDescription = options.entityDescription;
- vm.violationActions = [{
- id: 'Error',
- name: 'Error',
- description: 'Error (403: Forbidden)'
- }, {
- id: 'NotFound',
- name: 'Not Found',
- description: 'Not Found (404: Not Found)'
- }];
+ function setAttribute(attributeName) {
+ var filterName = stringUtilities.lowerCaseFirstWord(attributeName);
+ filter[filterName] = attributes[attributePrefix + attributeName];
+ }
+ }
- return options.entityAccessLoader()
- .then(function (accessRuleSet) {
- vm.accessRuleSet = accessRuleSet;
- vm.command = mapUpdateCommand(accessRuleSet);
- vm.inheritedRules = [];
-
- _.each(vm.accessRuleSet.inheritedAccessRules, function (inheritedAccessRuleSet) {
- inheritedAccessRuleSet.violationAction = _.findWhere(vm.violationActions, { id: inheritedAccessRuleSet.violationAction });
- if (inheritedAccessRuleSet.userAreaForSignInRedirect) {
- inheritedAccessRuleSet.signInRedirect = 'Yes';
- inheritedAccessRuleSet.signInRedirectDescription = 'If the user is not signed in, then they will be redirected to the sign in page associated with the ' + inheritedAccessRuleSet.userAreaForSignInRedirect.name + ' user area.';
- } else {
- inheritedAccessRuleSet.signInRedirect = 'No';
- inheritedAccessRuleSet.signInRedirectDescription = 'No sign in redirection, the default action will trigger instead.';
- }
+ function setButtonText() {
+ vm.buttonText = vm.model ? 'Change' : 'Select';
+ }
+ }
- _.each(inheritedAccessRuleSet.accessRules, function(rule) {
- rule.accessRuleSet = inheritedAccessRuleSet;
- vm.inheritedRules.push(rule);
- });
- });
+}]);
+angular.module('cms.shared').directive('cmsFormFieldDocumentAssetCollection', [
+ '_',
+ 'shared.internalModulePath',
+ 'shared.LoadState',
+ 'shared.documentService',
+ 'shared.modalDialogService',
+ 'shared.arrayUtilities',
+ 'shared.stringUtilities',
+ 'shared.urlLibrary',
+ 'baseFormFieldFactory',
+function (
+ _,
+ modulePath,
+ LoadState,
+ documentService,
+ modalDialogService,
+ arrayUtilities,
+ stringUtilities,
+ urlLibrary,
+ baseFormFieldFactory) {
- setUserAreasInRules();
- })
- .then(setLoadingOff.bind(null, loadStateToTurnOff));
- }
+ /* VARS */
- function mapUpdateCommand(accessRuleSet) {
+ var DOCUMENT_ASSET_ID_PROP = 'documentAssetId',
+ baseConfig = baseFormFieldFactory.defaultConfig;
- var command = _.pick(accessRuleSet,
- options.entityIdPrefix + 'Id',
- 'userAreaCodeForSignInRedirect',
- 'violationAction'
- );
-
- if (accessRuleSet.userAreaForSignInRedirect) {
- command.userAreaCodeForSignInRedirect = accessRuleSet.userAreaForSignInRedirect.userAreaCode;
- command.redirectToSignIn = true;
- }
+ /* CONFIG */
- return command;
- }
+ var config = {
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentAssetCollection.html',
+ passThroughAttributes: [
+ 'required'
+ ],
+ link: link
+ };
- function setUserAreasInRules() {
- vm.userAreasInRules = _(vm.accessRuleSet.accessRules)
- .chain()
- .map(function(rule) { return rule.userArea; })
- .uniq(function(userArea) { return userArea.userAreaCode; })
- .sortBy('userAreaCode')
- .value();
+ return baseFormFieldFactory.create(config);
- if (!vm.userAreasInRules.length) {
- // all user areas have been removed from the list
- vm.command.redirectToSignIn = false;
- vm.command.userAreaCodeForSignInRedirect = null;
- } else if (!_.find(vm.userAreasInRules, function(userArea) { return userArea.userAreaCode === vm.command.userAreaCodeForSignInRedirect; })) {
- // the selected user area has been removed from the list
- vm.command.redirectToSignIn = false;
- vm.command.userAreaCodeForSignInRedirect = null;
- }
-
- if (!vm.command.userAreaCodeForSignInRedirect && vm.userAreasInRules.length) {
- // set a default selection in-case the list is hidden
- vm.command.userAreaCodeForSignInRedirect = vm.userAreasInRules[0].userAreaCode;
- console.log('setting vm.command.userAreaCodeForSignInRedirect', vm.command.userAreaCodeForSignInRedirect);
- }
- }
+ /* LINK */
- function sortRules() {
- vm.accessRuleSet.accessRules = _(vm.accessRuleSet.accessRules)
- .chain()
- .sortBy(function (rule) {
- return rule.role ? rule.role.roleId : -1;
- })
- .sortBy(function (rule) {
- return rule.userArea.userAreaCode;
- })
- .value();
- }
+ function link(scope, el, attributes, controllers) {
+ var vm = scope.vm,
+ isRequired = _.has(attributes, 'required');
- function setLoadingOn(loadState) {
- vm.globalLoadState.on();
- if (loadState && _.isFunction(loadState.on)) loadState.on();
- }
+ init();
+ return baseConfig.link(scope, el, attributes, controllers);
- function setLoadingOff(loadState) {
+ /* INIT */
- vm.globalLoadState.off();
- if (loadState && _.isFunction(loadState.off)) loadState.off();
- }
-}]);
-angular.module('cms.shared').factory('shared.entityVersionModalDialogService', [
- 'shared.entityVersionService',
- 'shared.modalDialogService',
-function (
- entityVersionService,
- modalDialogService) {
+ function init() {
- var service = {},
- pageEntityConfig = {
- entityNameSingular: 'Page'
- };
+ vm.urlLibrary = urlLibrary;
+ vm.gridLoadState = new LoadState();
- /* PUBLIC */
+ vm.showPicker = showPicker;
+ vm.remove = remove;
+ vm.onDrop = onDrop;
- service.publish = function (entityId, onLoadingStart, customEntityConfig) {
- var config = customEntityConfig || pageEntityConfig;
+ scope.$watch("vm.model", setGridItems);
+ }
- var options = {
- title: 'Publish ' + config.entityNameSingular,
- message: 'Are you sure you want to publish this ' + config.entityNameSingular.toLowerCase() + '?',
- okButtonTitle: 'Yes, publish it',
- onOk: onOk
- };
+ /* EVENTS */
- return modalDialogService.confirm(options);
+ function remove(document) {
- function onOk() {
- onLoadingStart();
- return entityVersionService.publish(config.isCustomEntity, entityId);
- }
- }
+ removeItemFromArray(vm.gridData, document);
+ removeItemFromArray(vm.model, document[DOCUMENT_ASSET_ID_PROP]);
- service.unpublish = function (entityId, onLoadingStart, customEntityConfig) {
- var config = customEntityConfig || pageEntityConfig;
+ function removeItemFromArray(arr, item) {
+ var index = arr.indexOf(item);
- var options = {
- title: 'Unpublish ' + config.entityNameSingular,
- message: 'Unpublishing this ' + config.entityNameSingular.toLowerCase() + ' will remove it from the live site and put it into draft status. Are you sure you want to continue?',
- okButtonTitle: 'Yes, unpublish it',
- onOk: onOk
- };
+ if (index >= 0) {
+ return arr.splice(index, 1);
+ }
+ }
+ }
- return modalDialogService.confirm(options);
+ function showPicker() {
- function onOk() {
- onLoadingStart();
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAssetPickerDialog.html',
+ controller: 'DocumentAssetPickerDialogController',
+ options: {
+ selectedIds: vm.model || [],
+ filter: getFilter(),
+ onSelected: onSelected
+ }
+ });
- return entityVersionService.unpublish(config.isCustomEntity, entityId);
+ function onSelected(newArr) {
+ vm.model = newArr;
+ setGridItems(newArr);
+ }
}
- }
- service.copyToDraft = function (entityId, entityVersionId, hasDraft, onLoadingStart, customEntityConfig) {
- var config = customEntityConfig || pageEntityConfig;
+ function onDrop($index, droppedEntity) {
- var options = {
- title: 'Copy ' + config.entityNameSingular + ' Version',
- message: 'A draft version of this ' + config.entityNameSingular.toLowerCase() + ' already exists. Copying this version will delete the current draft. Do you wish to continue?',
- okButtonTitle: 'Yes, replace it',
- onOk: onOk
- };
+ arrayUtilities.moveObject(vm.gridData, droppedEntity, $index, DOCUMENT_ASSET_ID_PROP);
- if (hasDraft) {
- // If there's a draft already, warn the user
- return modalDialogService
- .confirm(options);
- } else {
- // Run the command directly
- onLoadingStart();
- return runCommand();
+ // Update model with new orering
+ setModelFromGridData();
}
- /* helpers */
+ function setModelFromGridData() {
+ vm.model = _.pluck(vm.gridData, DOCUMENT_ASSET_ID_PROP);
+ }
- function onOk() {
- onLoadingStart();
+ /* HELPERS */
- return entityVersionService
- .removeDraft(config.isCustomEntity, entityId)
- .then(runCommand);
+ function getFilter() {
+ var filter = {},
+ attributePrefix = 'cms';
+
+ setAttribute('Tags');
+ setAttribute('FileExtension');
+ setAttribute('FileExtensions');
+
+ return filter;
+
+ function setAttribute(attributeName) {
+ var filterName = stringUtilities.lowerCaseFirstWord(attributeName);
+ filter[filterName] = attributes[attributePrefix + attributeName];
+ }
}
- function runCommand() {
- return entityVersionService.duplicateDraft(config.isCustomEntity, entityId, entityVersionId);
+ /**
+ * Load the grid data if it is inconsistent with the Ids collection.
+ */
+ function setGridItems(ids) {
+
+ if (!ids || !ids.length) {
+ vm.gridData = [];
+ }
+ else if (!vm.gridData || _.pluck(vm.gridData, DOCUMENT_ASSET_ID_PROP).join() != ids.join()) {
+
+ vm.gridLoadState.on();
+ documentService.getByIdRange(ids).then(function (items) {
+ vm.gridData = items;
+ vm.gridLoadState.off();
+ });
+ }
}
}
-
- return service;
}]);
-angular.module('cms.shared').directive('cmsDocumentAsset', [
+angular.module('cms.shared').directive('cmsFormFieldDocumentTypeSelector', [
+ '_',
'shared.internalModulePath',
- 'shared.urlLibrary',
+ 'shared.documentService',
function (
+ _,
modulePath,
- urlLibrary
- ) {
+ documentService) {
return {
restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentTypeSelector.html',
scope: {
- document: '=cmsDocument'
+ model: '=cmsModel',
+ disabled: '=cmsDisabled',
+ readonly: '=cmsReadonly',
+ onLoaded: '&cmsOnLoaded'
},
- templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAsset.html',
- link: function (scope, el, attributes) {
+ controller: Controller,
+ controllerAs: 'vm',
+ bindToController: true
+ };
- scope.getDocumentUrl = urlLibrary.getDocumentUrl;
- }
+ /* CONTROLLER */
+
+ function Controller() {
+ var vm = this;
+
+ documentService.getAllDocumentFileTypes().then(function (fileTypes) {
+
+ vm.fileTypes = fileTypes;
+
+ if (vm.onLoaded) vm.onLoaded();
+ });
+ }
+}]);
+/**
+ * File upload control for images. Uses https://github.com/danialfarid/angular-file-upload
+ */
+angular.module('cms.shared').directive('cmsFormFieldDocumentUpload', [
+ '_',
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ _,
+ modulePath,
+ baseFormFieldFactory) {
+
+ /* CONFIG */
+
+ var config = {
+ templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentUpload.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ asset: '=cmsAsset',
+ loadState: '=cmsLoadState'
+ }),
+ passThroughAttributes: ['required', 'ngRequired'],
+ getInputEl: getInputEl
};
+
+ return baseFormFieldFactory.create(config);
+
+ function getInputEl(rootEl) {
+ return rootEl.find('cms-document-upload');
+ }
}]);
-angular.module('cms.shared').controller('DocumentAssetPickerDialogController', [
+angular.module('cms.shared').controller('UploadDocumentAssetDialogController', [
'$scope',
'shared.LoadState',
'shared.documentService',
'shared.SearchQuery',
- 'shared.modalDialogService',
- 'shared.internalModulePath',
- 'shared.permissionValidationService',
- 'shared.urlLibrary',
+ 'shared.focusService',
+ 'shared.stringUtilities',
'options',
'close',
function (
@@ -9927,14 +9967,95 @@ function (
LoadState,
documentService,
SearchQuery,
- modalDialogService,
- modulePath,
- permissionValidationService,
+ focusService,
+ stringUtilities,
+ options,
+ close) {
+
+ var vm = $scope;
+ init();
+
+ /* INIT */
+ function init() {
+ angular.extend($scope, options);
+
+ initData();
+
+ vm.onUpload = onUpload;
+ vm.onCancel = onCancel;
+ vm.close = onCancel;
+ vm.filter = options.filter;
+ vm.onFileChanged = onFileChanged;
+ vm.hasFilterRestrictions = hasFilterRestrictions;
+
+ vm.saveLoadState = new LoadState();
+ }
+
+ /* EVENTS */
+ function onUpload() {
+ vm.saveLoadState.on();
+
+ documentService
+ .add(vm.command)
+ .progress(vm.saveLoadState.setProgress)
+ .then(uploadComplete);
+ }
+
+ function onFileChanged() {
+ var command = vm.command;
+
+ if (command.file && command.file.name) {
+ command.title = stringUtilities.capitaliseFirstLetter(stringUtilities.getFileNameWithoutExtension(command.file.name));
+ command.fileName = stringUtilities.slugify(command.title);
+ focusService.focusById('title');
+ }
+ }
+
+ function onCancel() {
+ close();
+ }
+
+ /* PUBLIC HELPERS */
+ function initData() {
+ vm.command = {};
+ }
+
+ function hasFilterRestrictions() {
+ return options.filter.fileExtension ||
+ options.filter.fileExtensions;
+ }
+
+ function cancel() {
+ close();
+ }
+
+ function uploadComplete(documentAssetId) {
+ options.onUploadComplete(documentAssetId);
+ close();
+ }
+
+}]);
+
+angular.module('cms.shared').controller('ImageAssetEditorDialogController', [
+ '$scope',
+ 'shared.LoadState',
+ 'shared.imageService',
+ 'shared.SearchQuery',
+ 'shared.urlLibrary',
+ 'options',
+ 'close',
+function (
+ $scope,
+ LoadState,
+ imageService,
+ SearchQuery,
urlLibrary,
options,
close) {
- var vm = $scope;
+ var vm = $scope,
+ isAssetInitialized;
+
init();
/* INIT */
@@ -9942,697 +10063,767 @@ function (
function init() {
angular.extend($scope, options);
- vm.onOk = onOk;
- vm.onCancel = onCancel;
- vm.onSelect = onSelect;
- vm.onUpload = onUpload;
- vm.selectedAsset = vm.currentAsset; // currentAsset is null in single mode
- vm.onSelectAndClose = onSelectAndClose;
- vm.close = onCancel;
-
- vm.gridLoadState = new LoadState();
- vm.query = new SearchQuery({
- onChanged: onQueryChanged,
- useHistory: false,
- defaultParams: options.filter
- });
- vm.presetFilter = options.filter;
-
- vm.filter = vm.query.getFilters();
- vm.toggleFilter = toggleFilter;
-
- vm.isSelected = isSelected;
- vm.multiMode = vm.selectedIds ? true : false;
- vm.okText = vm.multiMode ? 'Ok' : 'Select';
+ vm.formLoadState = new LoadState();
+ vm.saveLoadState = new LoadState();
- vm.canCreate = permissionValidationService.canCreate('COFDOC');
+ vm.onInsert = onInsert;
+ vm.onCancel = onCancel;
- vm.getDocumentUrl = urlLibrary.getDocumentUrl;
+ vm.onImageChanged = onImageChanged;
+ vm.command = {};
- toggleFilter(false);
- loadGrid();
+ setCurrentImage();
}
/* ACTIONS */
- function toggleFilter(show) {
- vm.isFilterVisible = _.isUndefined(show) ? !vm.isFilterVisible : show;
- }
+ function setCurrentImage() {
+ // If we have an existing image, we need to find the asset id to set the command image
+ if (vm.imageAssetHtml && vm.imageAssetHtml.length) {
+ vm.command.imageAssetId = vm.imageAssetHtml.attr('data-image-asset-id');
+ vm.command.altTag = vm.imageAssetHtml.attr('alt');
+ vm.command.style = vm.imageAssetHtml.attr('style');
- function onQueryChanged() {
- toggleFilter(false);
- loadGrid();
- }
+ // If the image had any styles (mainly dimensions), pass them to the command so they are retained
+ if (vm.command.style) {
+ var styles = parseStyles(vm.command.style);
+ vm.command.width = styles['width'];
+ vm.command.height = styles['height'];
- function loadGrid() {
- vm.gridLoadState.on();
+ // Else, look to see if the dimensions are stored as attibutes of the image
+ } else {
+ vm.command.width = vm.imageAssetHtml.attr('width');
+ vm.command.height = vm.imageAssetHtml.attr('height');
+ }
- return documentService.getAll(vm.query.getParameters()).then(function (result) {
- vm.result = result;
- vm.gridLoadState.off();
- });
+ // If we cannot find the asset id (could have removed the data attribute that this relies on),
+ // we try to work this out based on the image path (this might change in future versions of cofoundry so less reliable)
+ if (!vm.command.imageAssetId) {
+ var src = vm.imageAssetHtml.attr('src');
+ var lastIndex = src.lastIndexOf('/');
+ var extractId = src.substr(lastIndex + 1, ((src.indexOf('_') - lastIndex) - 1));
+ vm.command.imageAssetId = extractId;
+ }
+ }
}
/* EVENTS */
function onCancel() {
- if (!vm.multiMode) {
- // in single-mode reset the currentAsset
- vm.onSelected(vm.currentAsset);
- }
close();
}
- function onSelect(document) {
- if (!vm.multiMode) {
- vm.selectedAsset = document;
- return;
- }
-
- addOrRemove(document);
+ function onImageChanged() {
+ vm.command.altTag = vm.command.imageAsset.title || vm.command.imageAsset.fileName;
}
- function onSelectAndClose(document) {
- if (!vm.multiMode) {
- vm.selectedAsset = document;
- onOk();
- return;
- }
+ function onInsert() {
- addOrRemove(document);
- onOk();
- }
+ // Parse and hold dimensions
+ var dimensions = {
+ width: parseUnits(vm.command.width),
+ height: parseUnits(vm.command.height)
+ };
- function onOk() {
- if (!vm.multiMode) {
- vm.onSelected(vm.selectedAsset);
- } else {
- vm.onSelected(vm.selectedIds);
+ // If we have no sizes set, default to percentage respecting ratio
+ if (!dimensions.width && !dimensions.height) {
+ dimensions.width = '100%';
+ dimensions.height = 'auto';
}
+ // Get the image path, including specific size options if nessessary
+ var path = urlLibrary.getImageUrl(vm.command.imageAsset, parseImageRequestSize(dimensions));
+
+ // Default the alt tag to an empty string if not specified
+ var alt = vm.command.altTag || '';
+
+ // Define an object thay holds formatted outputs, plus the model itself
+ var output = {
+ markdown: "",
+ html: "
",
+ model: vm.command
+ };
+
+ // Add css styles to output html
+ output.html = insertCssStyles(output.html, dimensions);
+
+ // Call callback with output
+ vm.onSelected(output);
+
+ // Close dialog
close();
}
- function onUpload() {
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/DocumentAssets/UploadDocumentAssetDialog.html',
- controller: 'UploadDocumentAssetDialogController',
- options: {
- filter: options.filter,
- onUploadComplete: onUploadComplete
- }
- });
+ /* PUBLIC HELPERS */
- function onUploadComplete(documentAssetId) {
- onSelectAndClose({ documentAssetId: documentAssetId });
- }
+ function insertCssStyles(html, styles) {
+ return angular.element(html).css(styles)[0].outerHTML;
}
- /* PUBLIC HELPERS */
+ function parseImageRequestSize(dimensions) {
+ // If unit type is percent, use original image size
+ if ((dimensions.width || '').indexOf('%') > -1 || (dimensions.height || '').indexOf('%') > -1) return {};
- function isSelected(document) {
- if (vm.selectedIds && document && vm.selectedIds.indexOf(document.documentAssetId) > -1) return true;
+ // Else, return raw pixel sizes
+ return {
+ width: dimensions.width.replace('px', ''),
+ height: dimensions.height.replace('px', '')
+ };
+ }
- if (!document || !vm.selectedAsset) return false;
+ function parseUnits(value) {
+ if (!value) return '';
- return document.documentAssetId === vm.selectedAsset.documentAssetId;
+ // Default to pixels if not unit type specified
+ if (value.indexOf('px') == -1 && value.indexOf('%') == -1 && value.indexOf('auto') == -1) return value + 'px';
+
+ // Return original value if we get here
+ return value;
}
- function addOrRemove(document) {
- if (!isSelected(document)) {
- vm.selectedIds.push(document.documentAssetId);
- } else {
- var index = vm.selectedIds.indexOf(document.documentAssetId);
- vm.selectedIds.splice(index, 1);
- }
+ function parseStyles(cssText) {
+ var regex = /([\w-]*)\s*:\s*([^;]*)/g;
+ var match, properties = {};
+ while (match = regex.exec(cssText)) properties[match[1]] = match[2];
+ return properties;
}
-}]);
-/**
- * File upload control for documents/files. Uses https://github.com/danialfarid/angular-file-upload
- */
-angular.module('cms.shared').directive('cmsDocumentUpload', [
- '_',
- 'shared.internalModulePath',
- 'shared.urlLibrary',
- function (
- _,
- modulePath,
- urlLibrary
- ) {
+}]);
- /* CONFIG */
-
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentUpload.html',
- scope: {
- asset: '=cmsAsset',
- loadState: '=cmsLoadState',
- isEditMode: '=cmsIsEditMode',
- modelName: '=cmsModelName',
- ngModel: '=ngModel',
- onChange: '&cmsOnChange'
- },
- require: 'ngModel',
- controller: function () { },
- controllerAs: 'vm',
- bindToController: true,
- link: link
- };
+angular.module('cms.shared').controller('AddEntityAccessRuleController', [
+ '$scope',
+ '$q',
+ 'shared.LoadState',
+ 'shared.roleService',
+ 'shared.userAreaService',
+ 'options',
+ 'close',
+function (
+ $scope,
+ $q,
+ LoadState,
+ roleService,
+ userAreaService,
+ options,
+ close) {
- /* LINK */
+ var vm = $scope;
- function link(scope, el, attributes, ngModelController) {
- var vm = scope.vm,
- isRequired = _.has(attributes, 'required');
+ init();
+
+ /* INIT */
- init();
+ function init() {
- /* INIT */
+ vm.globalLoadState = new LoadState();
+ vm.saveLoadState = new LoadState();
+ vm.formLoadState = new LoadState();
+ setLoadingOn(vm.formLoadState);
- function init() {
- vm.remove = remove;
- vm.fileChanged = onFileChanged;
- vm.isRemovable = _.isObject(vm.ngModel) && !isRequired;
- scope.$watch("vm.asset", setAsset);
- }
+ vm.onAdd = onAdd;
+ vm.onCancel = onCancel;
+ vm.onUserAreaChanged = onUserAreaChanged;
+ vm.searchRoles = searchRoles;
- /* EVENTS */
+ initData();
+ }
+
+ /* EVENTS */
- function remove() {
- onFileChanged();
- }
+ function onUserAreaChanged() {
+ vm.command.roleId = null;
+ }
- /**
- * Initialise the state when the asset is changed
- */
- function setAsset() {
- var asset = vm.asset;
- if (asset) {
- vm.previewUrl = urlLibrary.getDocumentUrl(asset);
- vm.isRemovable = !isRequired;
+ function searchRoles(query) {
+ if (!vm.command.userAreaCode) {
+ var def = $q.defer();
+ def.resolve();
+ return def.promise;
+ }
- ngModelController.$setViewValue({
- name: asset.fileName + '.' + asset.fileExtension,
- size: asset.fileSizeInBytes,
- isCurrentFile: true
- });
+ query.userAreaCode = vm.command.userAreaCode;
+ query.excludeAnonymous = true;
- } else {
- vm.isRemovable = false;
+ return roleService.search(query);
+ }
- if (ngModelController.$modelValue) {
- ngModelController.$setViewValue(undefined);
- }
- }
+ function onAdd() {
+ options.onSave(vm.command);
- setButtonText();
- }
+ close();
+ }
- function onFileChanged($files) {
- if ($files && $files[0]) {
- // set the file is one is selected
- ngModelController.$setViewValue($files[0]);
- vm.isRemovable = !isRequired;
+ function onCancel() {
+ close();
+ }
- } else if (!vm.ngModel || _.isUndefined($files)) {
- // if we don't have a file loaded already, remove the file.
- ngModelController.$setViewValue(undefined);
- vm.previewUrl = null;
- vm.isRemovable = false;
- vm.asset = undefined;
- }
+ /* PRIVATE FUNCS */
- setButtonText();
+ function initData() {
+
+ vm.command = {};
+ onUserAreaChanged();
- // base onChange event
- if (vm.onChange) vm.onChange(vm.ngModel);
- }
+ userAreaService
+ .getAll()
+ .then(loadUserAreas)
+ .finally(setLoadingOff.bind(null, vm.formLoadState));
- /* Helpers */
+ function loadUserAreas(userAreas) {
+ vm.userAreas = _.filter(userAreas, function(userArea) { return userArea.userAreaCode !== 'COF' });
- function setButtonText() {
- vm.buttonText = ngModelController.$modelValue ? 'Change' : 'Upload';
+ if (vm.userAreas.length == 1) {
+ vm.command.userAreaCode = vm.userAreas[0].userAreaCode;
+ }
}
}
+ function setLoadingOn(loadState) {
+ vm.globalLoadState.on();
+ if (loadState && _.isFunction(loadState.on)) loadState.on();
+ }
+
+ function setLoadingOff(loadState) {
+ vm.globalLoadState.off();
+ if (loadState && _.isFunction(loadState.off)) loadState.off();
+ }
}]);
-/**
- * A form field control for an image asset that uses a search and pick dialog
- * to allow the user to change the selected file.
- */
-angular.module('cms.shared').directive('cmsFormFieldDocumentAsset', [
- '_',
- 'shared.internalModulePath',
- 'shared.internalContentPath',
+angular.module('cms.shared').controller('EntityAccessEditorController', [
+ '$scope',
+ '$q',
+ 'shared.LoadState',
+ 'shared.userAreaService',
+ 'shared.roleService',
'shared.modalDialogService',
- 'shared.stringUtilities',
- 'shared.documentService',
+ 'shared.arrayUtilities',
+ 'shared.internalModulePath',
+ 'shared.permissionValidationService',
'shared.urlLibrary',
- 'baseFormFieldFactory',
+ 'options',
+ 'close',
function (
- _,
- modulePath,
- contentPath,
+ $scope,
+ $q,
+ LoadState,
+ userAreaService,
+ roleService,
modalDialogService,
- stringUtilities,
- documentService,
+ arrayUtilities,
+ modulePath,
+ permissionValidationService,
urlLibrary,
- baseFormFieldFactory) {
-
- /* CONFIG */
+ options,
+ close) {
- var config = {
- templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentAsset.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- asset: '=cmsAsset',
- loadState: '=cmsLoadState',
- updateAsset: '@cmsUpdateAsset' // update the asset property if it changes
- }),
- passThroughAttributes: ['required'],
- link: link
- };
+ var vm = $scope;
- return baseFormFieldFactory.create(config);
+ init();
+
+ /* INIT */
- /* LINK */
+ function init() {
+
+ // UI actions
+ vm.save = save;
+ vm.close = close;
+ vm.add = add;
+ vm.deleteRule = deleteRule;
- function link(scope, el, attributes, controllers) {
- var vm = scope.vm,
- isRequired = _.has(attributes, 'required'),
- isAssetInitialized;
+ // Properties
+ vm.globalLoadState = new LoadState();
+ vm.saveLoadState = new LoadState();
+ vm.formLoadState = new LoadState(true);
+ vm.urlLibrary = urlLibrary;
- init();
- return baseFormFieldFactory.defaultConfig.link(scope, el, attributes, controllers);
+ // permissions
+ vm.canManage = permissionValidationService.hasPermission(options.entityDefinitionCode + 'ACCRUL');
+ vm.editMode = vm.canManage;
- /* INIT */
+ // Init
+ initData(vm.formLoadState);
+ }
- function init() {
+ /* UI ACTIONS */
- vm.urlLibrary = urlLibrary;
- vm.showPicker = showPicker;
- vm.remove = remove;
- vm.isRemovable = _.isObject(vm.model) && !isRequired;
+ function save() {
+ vm.command.accessRules = _.map(vm.accessRuleSet.accessRules, function(rule) {
+ var idProp = options.entityIdPrefix + 'AccessRuleId';
+ var command = {
+ userAreaCode: rule.userArea.userAreaCode,
+ roleId: rule.role ? rule.role.roleId : null
+ };
- vm.filter = parseFilters(attributes);
+ command[idProp] = rule[idProp];
- scope.$watch("vm.asset", setAsset);
- scope.$watch("vm.model", setAssetById);
+ return command;
+ });
+
+ if (!vm.command.redirectToSignIn) {
+ vm.command.userAreaCodeForSignInRedirect = null;
}
- /* EVENTS */
-
- function remove() {
- setAsset(null);
- }
+ setLoadingOn(vm.saveLoadState);
- function showPicker() {
+ options.saveAccess(vm.command)
+ .then(onSuccess.bind(null, 'Access rules updated successfully'))
+ .then(close)
+ .finally(setLoadingOff.bind(null, vm.saveLoadState));
+ }
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAssetPickerDialog.html',
- controller: 'DocumentAssetPickerDialogController',
- options: {
- currentAsset: vm.previewAsset,
- filter: vm.filter,
- onSelected: onSelected
- }
- });
+ function add() {
- function onSelected(newAsset) {
- if (!newAsset && vm.asset) {
- setAsset(null);
- } else if (!vm.asset || (newAsset && vm.asset.documentAssetId !== newAsset.documentAssetId)) {
- setAsset(newAsset);
- }
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/EntityAccess/AddEntityAccessRule.html',
+ controller: 'AddEntityAccessRuleController',
+ options: {
+ onSave: onAddRule
}
+ });
+ }
+
+ function deleteRule(rule, $index) {
+
+ arrayUtilities.removeObject(vm.accessRuleSet.accessRules, rule);
+ setUserAreasInRules();
+ }
+
+ /* EVENTS */
+
+ function onAddRule(command) {
+
+ var rule = {};
+
+ var duplicateRule = _.find(vm.accessRuleSet.accessRules, function(accessRule) {
+ var roleId = accessRule.role ? accessRule.role.roleId : null;
+ return accessRule.userArea.userAreaCode === command.userAreaCode && roleId == command.roleId;
+ });
+
+ if (duplicateRule) {
+ return;
}
- /**
- * When the model is set without a preview asset, we need to go get the full
- * asset details. This query can be bypassed by setting the cms-asset attribute
- */
- function setAssetById(assetId) {
+ setLoadingOn();
+
+ $q.all([getRole(), getUserArea()])
+ .then(function() {
+ vm.accessRuleSet.accessRules.push(rule);
+ sortRules();
+ setUserAreasInRules();
+ })
+ .finally(setLoadingOff);
+
+ function getUserArea() {
+ return userAreaService
+ .getByCode(command.userAreaCode)
+ .then(function(userArea) {
+ rule.userArea = userArea;
+ });
+ }
- // Remove the id if it is 0 or invalid to make sure required validation works
- if (!assetId) {
- vm.model = assetId = undefined;
+ function getRole() {
+ if (!command.roleId) {
+ return $q(function(resolve) { resolve() });
}
- if (assetId && (!vm.previewAsset || vm.previewAsset.documentAssetId != assetId)) {
- documentService.getById(assetId).then(function (asset) {
- setAsset(asset);
+ return roleService
+ .getById(command.roleId)
+ .then(function(role) {
+ rule.role = role;
});
- }
}
+ }
- /**
- * Initialise the state when the asset is changed
- */
- function setAsset(asset) {
+ function onSuccess(message, loadStateToTurnOff) {
- if (asset) {
- vm.previewAsset = asset;
- vm.isRemovable = !isRequired;
- vm.model = asset.documentAssetId;
+ return initData(loadStateToTurnOff)
+ .then(vm.mainForm.formStatus.success.bind(null, message));
+ }
- if (vm.updateAsset) {
- vm.asset = asset;
- }
+ /* PRIVATE FUNCS */
- } else if (isAssetInitialized) {
- // Ignore if we are running this first time to avoid overwriting the model with a null vlaue
- vm.previewAsset = null;
- vm.isRemovable = false;
+ function initData(loadStateToTurnOff) {
- if (vm.model) {
- vm.model = null;
- }
- if (vm.updateAsset) {
- vm.asset = null;
- }
- }
+ vm.entityDefinitionName = options.entityDefinitionName;
+ vm.entityDefinitionNameLower = options.entityDefinitionName.toLowerCase();
+ vm.entityDescription = options.entityDescription;
+ vm.violationActions = [{
+ id: 'Error',
+ name: 'Error',
+ description: 'Error (403: Forbidden)'
+ }, {
+ id: 'NotFound',
+ name: 'Not Found',
+ description: 'Not Found (404: Not Found)'
+ }];
- setButtonText();
+ return options.entityAccessLoader()
+ .then(function (accessRuleSet) {
+ vm.accessRuleSet = accessRuleSet;
+ vm.command = mapUpdateCommand(accessRuleSet);
+ vm.inheritedRules = [];
+
+ _.each(vm.accessRuleSet.inheritedAccessRules, function (inheritedAccessRuleSet) {
+ inheritedAccessRuleSet.violationAction = _.findWhere(vm.violationActions, { id: inheritedAccessRuleSet.violationAction });
+ if (inheritedAccessRuleSet.userAreaForSignInRedirect) {
+ inheritedAccessRuleSet.signInRedirect = 'Yes';
+ inheritedAccessRuleSet.signInRedirectDescription = 'If the user is not signed in, then they will be redirected to the sign in page associated with the ' + inheritedAccessRuleSet.userAreaForSignInRedirect.name + ' user area.';
+ } else {
+ inheritedAccessRuleSet.signInRedirect = 'No';
+ inheritedAccessRuleSet.signInRedirectDescription = 'No sign in redirection, the default action will trigger instead.';
+ }
- isAssetInitialized = true;
- }
+ _.each(inheritedAccessRuleSet.accessRules, function(rule) {
+ rule.accessRuleSet = inheritedAccessRuleSet;
+ vm.inheritedRules.push(rule);
+ });
+ });
- /* Helpers */
+ setUserAreasInRules();
+ })
+ .then(setLoadingOff.bind(null, loadStateToTurnOff));
+ }
- function parseFilters(attributes) {
- var filter = {},
- attributePrefix = 'cms';
+ function mapUpdateCommand(accessRuleSet) {
- setAttribute('Tags');
- setAttribute('FileExtension');
- setAttribute('FileExtensions');
+ var command = _.pick(accessRuleSet,
+ options.entityIdPrefix + 'Id',
+ 'userAreaCodeForSignInRedirect',
+ 'violationAction'
+ );
+
+ if (accessRuleSet.userAreaForSignInRedirect) {
+ command.userAreaCodeForSignInRedirect = accessRuleSet.userAreaForSignInRedirect.userAreaCode;
+ command.redirectToSignIn = true;
+ }
- return filter;
+ return command;
+ }
- function setAttribute(attributeName) {
- var filterName = stringUtilities.lowerCaseFirstWord(attributeName);
- filter[filterName] = attributes[attributePrefix + attributeName];
- }
- }
+ function setUserAreasInRules() {
+ vm.userAreasInRules = _(vm.accessRuleSet.accessRules)
+ .chain()
+ .map(function(rule) { return rule.userArea; })
+ .uniq(function(userArea) { return userArea.userAreaCode; })
+ .sortBy('userAreaCode')
+ .value();
- function setButtonText() {
- vm.buttonText = vm.model ? 'Change' : 'Select';
+ if (!vm.userAreasInRules.length) {
+ // all user areas have been removed from the list
+ vm.command.redirectToSignIn = false;
+ vm.command.userAreaCodeForSignInRedirect = null;
+ } else if (!_.find(vm.userAreasInRules, function(userArea) { return userArea.userAreaCode === vm.command.userAreaCodeForSignInRedirect; })) {
+ // the selected user area has been removed from the list
+ vm.command.redirectToSignIn = false;
+ vm.command.userAreaCodeForSignInRedirect = null;
}
+
+ if (!vm.command.userAreaCodeForSignInRedirect && vm.userAreasInRules.length) {
+ // set a default selection in-case the list is hidden
+ vm.command.userAreaCodeForSignInRedirect = vm.userAreasInRules[0].userAreaCode;
+ console.log('setting vm.command.userAreaCodeForSignInRedirect', vm.command.userAreaCodeForSignInRedirect);
+ }
+ }
+
+ function sortRules() {
+ vm.accessRuleSet.accessRules = _(vm.accessRuleSet.accessRules)
+ .chain()
+ .sortBy(function (rule) {
+ return rule.role ? rule.role.roleId : -1;
+ })
+ .sortBy(function (rule) {
+ return rule.userArea.userAreaCode;
+ })
+ .value();
+ }
+
+ function setLoadingOn(loadState) {
+ vm.globalLoadState.on();
+ if (loadState && _.isFunction(loadState.on)) loadState.on();
}
+ function setLoadingOff(loadState) {
+
+ vm.globalLoadState.off();
+ if (loadState && _.isFunction(loadState.off)) loadState.off();
+ }
}]);
-angular.module('cms.shared').directive('cmsFormFieldDocumentAssetCollection', [
- '_',
- 'shared.internalModulePath',
- 'shared.LoadState',
- 'shared.documentService',
+angular.module('cms.shared').factory('shared.entityVersionModalDialogService', [
+ 'shared.entityVersionService',
'shared.modalDialogService',
- 'shared.arrayUtilities',
- 'shared.stringUtilities',
- 'shared.urlLibrary',
- 'baseFormFieldFactory',
function (
- _,
- modulePath,
- LoadState,
- documentService,
- modalDialogService,
- arrayUtilities,
- stringUtilities,
- urlLibrary,
- baseFormFieldFactory) {
+ entityVersionService,
+ modalDialogService) {
- /* VARS */
+ var service = {},
+ pageEntityConfig = {
+ entityNameSingular: 'Page'
+ };
- var DOCUMENT_ASSET_ID_PROP = 'documentAssetId',
- baseConfig = baseFormFieldFactory.defaultConfig;
+ /* PUBLIC */
- /* CONFIG */
+ service.publish = function (entityId, onLoadingStart, customEntityConfig) {
+ var config = customEntityConfig || pageEntityConfig;
- var config = {
- templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentAssetCollection.html',
- passThroughAttributes: [
- 'required'
- ],
- link: link
- };
+ var options = {
+ title: 'Publish ' + config.entityNameSingular,
+ message: 'Are you sure you want to publish this ' + config.entityNameSingular.toLowerCase() + '?',
+ okButtonTitle: 'Yes, publish it',
+ onOk: onOk
+ };
- return baseFormFieldFactory.create(config);
+ return modalDialogService.confirm(options);
- /* LINK */
+ function onOk() {
+ onLoadingStart();
+ return entityVersionService.publish(config.isCustomEntity, entityId);
+ }
+ }
- function link(scope, el, attributes, controllers) {
- var vm = scope.vm,
- isRequired = _.has(attributes, 'required');
+ service.unpublish = function (entityId, onLoadingStart, customEntityConfig) {
+ var config = customEntityConfig || pageEntityConfig;
- init();
- return baseConfig.link(scope, el, attributes, controllers);
+ var options = {
+ title: 'Unpublish ' + config.entityNameSingular,
+ message: 'Unpublishing this ' + config.entityNameSingular.toLowerCase() + ' will remove it from the live site and put it into draft status. Are you sure you want to continue?',
+ okButtonTitle: 'Yes, unpublish it',
+ onOk: onOk
+ };
- /* INIT */
+ return modalDialogService.confirm(options);
- function init() {
+ function onOk() {
+ onLoadingStart();
- vm.urlLibrary = urlLibrary;
- vm.gridLoadState = new LoadState();
+ return entityVersionService.unpublish(config.isCustomEntity, entityId);
+ }
+ }
- vm.showPicker = showPicker;
- vm.remove = remove;
- vm.onDrop = onDrop;
+ service.copyToDraft = function (entityId, entityVersionId, hasDraft, onLoadingStart, customEntityConfig) {
+ var config = customEntityConfig || pageEntityConfig;
+
+ var options = {
+ title: 'Copy ' + config.entityNameSingular + ' Version',
+ message: 'A draft version of this ' + config.entityNameSingular.toLowerCase() + ' already exists. Copying this version will delete the current draft. Do you wish to continue?',
+ okButtonTitle: 'Yes, replace it',
+ onOk: onOk
+ };
- scope.$watch("vm.model", setGridItems);
+ if (hasDraft) {
+ // If there's a draft already, warn the user
+ return modalDialogService
+ .confirm(options);
+ } else {
+ // Run the command directly
+ onLoadingStart();
+ return runCommand();
}
- /* EVENTS */
-
- function remove(document) {
+ /* helpers */
- removeItemFromArray(vm.gridData, document);
- removeItemFromArray(vm.model, document[DOCUMENT_ASSET_ID_PROP]);
+ function onOk() {
+ onLoadingStart();
- function removeItemFromArray(arr, item) {
- var index = arr.indexOf(item);
+ return entityVersionService
+ .removeDraft(config.isCustomEntity, entityId)
+ .then(runCommand);
+ }
- if (index >= 0) {
- return arr.splice(index, 1);
- }
- }
+ function runCommand() {
+ return entityVersionService.duplicateDraft(config.isCustomEntity, entityId, entityVersionId);
}
+ }
+
+ return service;
+}]);
+angular.module('cms.shared').directive('cmsForm', [
+ 'shared.internalModulePath',
+function (
+ modulePath) {
- function showPicker() {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Form/Form.html',
+ replace: true,
+ transclude: true,
+ scope: {
+ editMode: '=cmsEditMode',
+ name: '@cmsName'
+ },
+ compile: compile,
+ controller: ['$scope', FormController]
+ };
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/DocumentAssets/DocumentAssetPickerDialog.html',
- controller: 'DocumentAssetPickerDialogController',
- options: {
- selectedIds: vm.model || [],
- filter: getFilter(),
- onSelected: onSelected
- }
- });
+ /* CONTROLLER/COMPILE */
- function onSelected(newArr) {
- vm.model = newArr;
- setGridItems(newArr);
- }
+ function FormController($scope) {
+ $scope.getForm = function () {
+ return $scope[$scope.name];
}
- function onDrop($index, droppedEntity) {
-
- arrayUtilities.moveObject(vm.gridData, droppedEntity, $index, DOCUMENT_ASSET_ID_PROP);
+ this.getFormScope = function () {
- // Update model with new orering
- setModelFromGridData();
+ return $scope;
}
+ };
- function setModelFromGridData() {
- vm.model = _.pluck(vm.gridData, DOCUMENT_ASSET_ID_PROP);
+ function compile(element, attrs) {
+ // Default edit mode to true if not specified
+ if (!angular.isDefined(attrs.cmsEditMode)) {
+ attrs.cmsEditMode = 'true';
}
- /* HELPERS */
+ return link;
+ }
- function getFilter() {
- var filter = {},
- attributePrefix = 'cms';
+ function link (scope, el, attrs, controllers) {
+ // Do somethng similar to the behavior of NgForm and bind the form property a
+ // parent scope except in our case the root scope.
+ var parentScope = findRootScopeModel(scope);
+ parentScope[scope.name] = scope.getForm();
+ }
- setAttribute('Tags');
- setAttribute('FileExtension');
- setAttribute('FileExtensions');
+ /* HELPERS */
- return filter;
+ function findRootScopeModel(scope, vmScope) {
+ var parent = scope.$parent;
- function setAttribute(attributeName) {
- var filterName = stringUtilities.lowerCaseFirstWord(attributeName);
- filter[filterName] = attributes[attributePrefix + attributeName];
- }
+ // We've reached the root, return a vm scope or the root scope
+ if (!parent) return vmScope || scope;
+
+ if (angular.isDefined(parent.vm)) {
+ // we've found a parent with a controller as 'vm' scope
+ vmScope = parent.vm;
}
- /**
- * Load the grid data if it is inconsistent with the Ids collection.
- */
- function setGridItems(ids) {
+ // Keep searching up the tree recursively and return the last one found
+ return findRootScopeModel(parent, vmScope);
+ }
+}]);
+angular.module('cms.shared').directive('cmsFormSection', ['shared.internalModulePath', '$timeout', function (modulePath, $timeout) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Form/FormSection.html',
+ scope: {
+ title: '@cmsTitle'
+ },
+ replace: true,
+ transclude: true,
+ link: link
+ };
- if (!ids || !ids.length) {
- vm.gridData = [];
- }
- else if (!vm.gridData || _.pluck(vm.gridData, DOCUMENT_ASSET_ID_PROP).join() != ids.join()) {
+ function link(scope, elem, attrs) {
+ // Wait a moment until child components are rendered before searching the dom
+ $timeout(function () {
+ var helpers = angular.element(elem[0].querySelector('.help-inline'));
+ var btn = angular.element(elem[0].querySelector('.toggle-helpers'));
- vm.gridLoadState.on();
- documentService.getByIdRange(ids).then(function (items) {
- vm.gridData = items;
- vm.gridLoadState.off();
- });
+ if (helpers.length) {
+ btn
+ .addClass('show')
+ .on('click', function () {
+ btn.toggleClass('active');
+ elem.toggleClass('show-helpers');
+ });
}
- }
+ }, 100);
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldDocumentTypeSelector', [
- '_',
- 'shared.internalModulePath',
- 'shared.documentService',
-function (
- _,
- modulePath,
- documentService) {
-
+angular.module('cms.shared').directive('cmsFormSectionActions', ['shared.internalModulePath', '$timeout', function (modulePath, $timeout) {
return {
restrict: 'E',
- templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentTypeSelector.html',
+ templateUrl: modulePath + 'UIComponents/Form/FormSectionActions.html',
scope: {
- model: '=cmsModel',
- disabled: '=cmsDisabled',
- readonly: '=cmsReadonly',
- onLoaded: '&cmsOnLoaded'
},
- controller: Controller,
- controllerAs: 'vm',
- bindToController: true
+ replace: true,
+ transclude: true,
+ link: link
};
- /* CONTROLLER */
-
- function Controller() {
- var vm = this;
-
- documentService.getAllDocumentFileTypes().then(function (fileTypes) {
-
- vm.fileTypes = fileTypes;
-
- if (vm.onLoaded) vm.onLoaded();
- });
+ function link(scope, elem, attrs) {
}
}]);
+angular.module('cms.shared').directive('cmsFormSectionAuditData', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Form/FormSectionAuditData.html',
+ scope: {
+ auditData: '=cmsAuditData'
+ }
+ };
+}]);
/**
- * File upload control for images. Uses https://github.com/danialfarid/angular-file-upload
+ * A status message that can appear in a form to notify the user of any errors or other messages. A scope is
+ * attached to the parent form and can be accessed via [myFormName].formStatus
*/
-angular.module('cms.shared').directive('cmsFormFieldDocumentUpload', [
+angular.module('cms.shared').directive('cmsFormStatus', [
'_',
+ 'shared.validationErrorService',
'shared.internalModulePath',
- 'baseFormFieldFactory',
function (
_,
- modulePath,
- baseFormFieldFactory) {
-
- /* CONFIG */
-
- var config = {
- templateUrl: modulePath + 'UIComponents/DocumentAssets/FormFieldDocumentUpload.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- asset: '=cmsAsset',
- loadState: '=cmsLoadState'
- }),
- passThroughAttributes: ['required', 'ngRequired'],
- getInputEl: getInputEl
- };
-
- return baseFormFieldFactory.create(config);
-
- function getInputEl(rootEl) {
- return rootEl.find('cms-document-upload');
- }
-}]);
-angular.module('cms.shared').controller('UploadDocumentAssetDialogController', [
- '$scope',
- 'shared.LoadState',
- 'shared.documentService',
- 'shared.SearchQuery',
- 'shared.focusService',
- 'shared.stringUtilities',
- 'options',
- 'close',
-function (
- $scope,
- LoadState,
- documentService,
- SearchQuery,
- focusService,
- stringUtilities,
- options,
- close) {
-
- var vm = $scope;
- init();
-
- /* INIT */
- function init() {
- angular.extend($scope, options);
+ validationErrorService,
+ modulePath) {
- initData();
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Form/FormStatus.html',
+ require: ['^^cmsForm'],
+ replace: true,
+ scope: true,
+ link: { post: link }
+ };
- vm.onUpload = onUpload;
- vm.onCancel = onCancel;
- vm.close = onCancel;
- vm.filter = options.filter;
- vm.onFileChanged = onFileChanged;
- vm.hasFilterRestrictions = hasFilterRestrictions;
+ function link(scope, el, attr, controllers) {
- vm.saveLoadState = new LoadState();
+ initScope(scope, controllers[0]);
+ bindValidationHandler(scope, el);
}
- /* EVENTS */
- function onUpload() {
- vm.saveLoadState.on();
+ function initScope(scope, formController) {
+ var formScope = formController.getFormScope(),
+ form = formScope.getForm();
- documentService
- .add(vm.command)
- .progress(vm.saveLoadState.setProgress)
- .then(uploadComplete);
+ scope.success = success.bind(scope);
+ scope.error = error.bind(scope);
+ scope.errors = errors.bind(scope);
+ scope.clear = clear.bind(scope);
+ form.formStatus = scope;
}
- function onFileChanged() {
- var command = vm.command;
+ function bindValidationHandler(scope, el) {
- if (command.file && command.file.name) {
- command.title = stringUtilities.capitaliseFirstLetter(stringUtilities.getFileNameWithoutExtension(command.file.name));
- command.fileName = stringUtilities.slugify(command.title);
- focusService.focusById('title');
- }
+ validationErrorService.addHandler('', scope.errors);
+ scope.$on('$destroy', function () {
+ validationErrorService.removeHandler(scope.errors);
+ });
}
- function onCancel() {
- close();
- }
+ function errors(errors, message) {
- /* PUBLIC HELPERS */
- function initData() {
- vm.command = {};
+ var processedErrors = _.uniq(errors, function (error) {
+ return error.message;
+ });
+
+ setScope(this, message, 'error', processedErrors);
}
- function hasFilterRestrictions() {
- return options.filter.fileExtension ||
- options.filter.fileExtensions;
+ function error(message) {
+ setScope(this, message, 'error');
}
- function cancel() {
- close();
+ function success(message) {
+ setScope(this, message, 'success');
}
- function uploadComplete(documentAssetId) {
- options.onUploadComplete(documentAssetId);
- close();
+ function clear() {
+ setScope(this);
}
+ function setScope(scope, message, cls, errors) {
+ scope.message = message;
+ scope.errors = errors;
+ scope.cls = cls;
+ }
}]);
/**
@@ -10831,207 +11022,16 @@ function (
function mapCmsAttribute(key, value) {
key = ATTR_PREFIX + stringUtilities.toSnakeCase(key);
return formatAttributeText(key, value);
- }
-
- function formatAttributeText(key, value) {
- if (!value) return ' ' + key;
-
- return ' ' + key + '="' + value + '"'
- }
- }
-
-}]);
-angular.module('cms.shared').directive('cmsForm', [
- 'shared.internalModulePath',
-function (
- modulePath) {
-
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Form/Form.html',
- replace: true,
- transclude: true,
- scope: {
- editMode: '=cmsEditMode',
- name: '@cmsName'
- },
- compile: compile,
- controller: ['$scope', FormController]
- };
-
- /* CONTROLLER/COMPILE */
-
- function FormController($scope) {
- $scope.getForm = function () {
- return $scope[$scope.name];
- }
-
- this.getFormScope = function () {
-
- return $scope;
- }
- };
-
- function compile(element, attrs) {
- // Default edit mode to true if not specified
- if (!angular.isDefined(attrs.cmsEditMode)) {
- attrs.cmsEditMode = 'true';
- }
-
- return link;
- }
-
- function link (scope, el, attrs, controllers) {
- // Do somethng similar to the behavior of NgForm and bind the form property a
- // parent scope except in our case the root scope.
- var parentScope = findRootScopeModel(scope);
- parentScope[scope.name] = scope.getForm();
- }
-
- /* HELPERS */
-
- function findRootScopeModel(scope, vmScope) {
- var parent = scope.$parent;
-
- // We've reached the root, return a vm scope or the root scope
- if (!parent) return vmScope || scope;
-
- if (angular.isDefined(parent.vm)) {
- // we've found a parent with a controller as 'vm' scope
- vmScope = parent.vm;
- }
-
- // Keep searching up the tree recursively and return the last one found
- return findRootScopeModel(parent, vmScope);
- }
-}]);
-angular.module('cms.shared').directive('cmsFormSection', ['shared.internalModulePath', '$timeout', function (modulePath, $timeout) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Form/FormSection.html',
- scope: {
- title: '@cmsTitle'
- },
- replace: true,
- transclude: true,
- link: link
- };
-
- function link(scope, elem, attrs) {
- // Wait a moment until child components are rendered before searching the dom
- $timeout(function () {
- var helpers = angular.element(elem[0].querySelector('.help-inline'));
- var btn = angular.element(elem[0].querySelector('.toggle-helpers'));
-
- if (helpers.length) {
- btn
- .addClass('show')
- .on('click', function () {
- btn.toggleClass('active');
- elem.toggleClass('show-helpers');
- });
- }
- }, 100);
- }
-}]);
-angular.module('cms.shared').directive('cmsFormSectionActions', ['shared.internalModulePath', '$timeout', function (modulePath, $timeout) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Form/FormSectionActions.html',
- scope: {
- },
- replace: true,
- transclude: true,
- link: link
- };
-
- function link(scope, elem, attrs) {
- }
-}]);
-angular.module('cms.shared').directive('cmsFormSectionAuditData', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Form/FormSectionAuditData.html',
- scope: {
- auditData: '=cmsAuditData'
- }
- };
-}]);
-/**
- * A status message that can appear in a form to notify the user of any errors or other messages. A scope is
- * attached to the parent form and can be accessed via [myFormName].formStatus
- */
-angular.module('cms.shared').directive('cmsFormStatus', [
- '_',
- 'shared.validationErrorService',
- 'shared.internalModulePath',
-function (
- _,
- validationErrorService,
- modulePath) {
-
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Form/FormStatus.html',
- require: ['^^cmsForm'],
- replace: true,
- scope: true,
- link: { post: link }
- };
-
- function link(scope, el, attr, controllers) {
-
- initScope(scope, controllers[0]);
- bindValidationHandler(scope, el);
- }
-
- function initScope(scope, formController) {
- var formScope = formController.getFormScope(),
- form = formScope.getForm();
-
- scope.success = success.bind(scope);
- scope.error = error.bind(scope);
- scope.errors = errors.bind(scope);
- scope.clear = clear.bind(scope);
- form.formStatus = scope;
- }
-
- function bindValidationHandler(scope, el) {
-
- validationErrorService.addHandler('', scope.errors);
- scope.$on('$destroy', function () {
- validationErrorService.removeHandler(scope.errors);
- });
- }
-
- function errors(errors, message) {
-
- var processedErrors = _.uniq(errors, function (error) {
- return error.message;
- });
-
- setScope(this, message, 'error', processedErrors);
- }
-
- function error(message) {
- setScope(this, message, 'error');
- }
+ }
- function success(message) {
- setScope(this, message, 'success');
- }
+ function formatAttributeText(key, value) {
+ if (!value) return ' ' + key;
- function clear() {
- setScope(this);
+ return ' ' + key + '="' + value + '"'
+ }
}
- function setScope(scope, message, cls, errors) {
- scope.message = message;
- scope.errors = errors;
- scope.cls = cls;
- }
}]);
-
/**
* Base class for form fields that uses default conventions and includes integration with
* server validation.
@@ -12689,6 +12689,121 @@ function (
ngModelController.$validators[DIRECTIVE_ID] = validator;
}
}]);
+angular.module('cms.shared').directive('cmsField', [
+ '$timeout',
+ 'shared.internalModulePath',
+ function (
+ $timeout,
+ modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Layout/Field.html',
+ transclude: true,
+ replace: true,
+ require: '^^cmsForm'
+ }
+}]);
+(function ($) {
+
+ // Context
+ var $el = $(document.getElementsByClassName('main-nav'));
+
+ // Temp vars
+ var categories, currentCategory;
+
+ function init() {
+
+ categories = $(document.getElementsByClassName('category'));
+ currentCategory = $(document.getElementsByClassName('category selected'));
+
+ //Events
+ categories.on('mouseenter', function (e) {
+ var $src = $(e.srcElement);
+
+ currentCategory.removeClass('selected');
+ //$src.addClass('selected');
+ });
+
+ categories.on('mouseleave', function (e) {
+ var $src = $(e.srcElement);
+
+ currentCategory.addClass('selected');
+ //$src.removeClass('selected');
+ });
+ }
+
+ init();
+
+})(angular.element);
+angular.module('cms.shared').directive('cmsPageActions', function () {
+ return {
+ restrict: 'E',
+ template: '',
+ replace: true,
+ transclude: true,
+ }
+});
+angular.module('cms.shared').directive('cmsPageBody', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Layout/PageBody.html',
+ scope: {
+ contentType: '@cmsContentType',
+ hasActions: '=cmsHasActions'
+ },
+ replace: true,
+ transclude: true,
+ controllerAs: 'vm',
+ bindToController: true,
+ controller: ['$scope', Controller]
+ };
+
+ /* CONTROLLER */
+
+ function Controller(scope) {
+ };
+}]);
+angular.module('cms.shared').directive('cmsPageFilter', function () {
+ return {
+ restrict: 'E',
+ template: '',
+ replace: true,
+ transclude: true
+ }
+});
+angular.module('cms.shared').directive('cmsPageHeader', function () {
+ return {
+ restrict: 'E',
+ template: '',
+ replace: true,
+ transclude: true,
+ scope: {
+ title: '@cmsTitle',
+ parentTitle: '@cmsParentTitle',
+ parentHref: '@cmsParentHref'
+ },
+ }
+});
+angular.module('cms.shared').directive('cmsPageHeaderButtons', function () {
+ return {
+ restrict: 'E',
+ template: '',
+ replace: true,
+ transclude: true
+ }
+});
+angular.module('cms.shared').directive('cmsPageSubHeader', function () {
+ return {
+ restrict: 'E',
+ template: '',
+ replace: true,
+ transclude: true
+ }
+});
angular.module('cms.shared').directive('cmsFormFieldImageAnchorLocationSelector', [
'_',
'shared.internalModulePath',
@@ -13575,133 +13690,18 @@ function (
}
}
}
-
- function cancel() {
- close();
- }
-
- function uploadComplete(imageAssetId) {
- options.onUploadComplete(imageAssetId);
- close();
- }
-
-}]);
-
-angular.module('cms.shared').directive('cmsField', [
- '$timeout',
- 'shared.internalModulePath',
- function (
- $timeout,
- modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Layout/Field.html',
- transclude: true,
- replace: true,
- require: '^^cmsForm'
- }
-}]);
-(function ($) {
-
- // Context
- var $el = $(document.getElementsByClassName('main-nav'));
-
- // Temp vars
- var categories, currentCategory;
-
- function init() {
-
- categories = $(document.getElementsByClassName('category'));
- currentCategory = $(document.getElementsByClassName('category selected'));
-
- //Events
- categories.on('mouseenter', function (e) {
- var $src = $(e.srcElement);
-
- currentCategory.removeClass('selected');
- //$src.addClass('selected');
- });
-
- categories.on('mouseleave', function (e) {
- var $src = $(e.srcElement);
-
- currentCategory.addClass('selected');
- //$src.removeClass('selected');
- });
- }
-
- init();
-
-})(angular.element);
-angular.module('cms.shared').directive('cmsPageActions', function () {
- return {
- restrict: 'E',
- template: '',
- replace: true,
- transclude: true,
- }
-});
-angular.module('cms.shared').directive('cmsPageBody', [
- 'shared.internalModulePath',
-function (
- modulePath
-) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Layout/PageBody.html',
- scope: {
- contentType: '@cmsContentType',
- hasActions: '=cmsHasActions'
- },
- replace: true,
- transclude: true,
- controllerAs: 'vm',
- bindToController: true,
- controller: ['$scope', Controller]
- };
-
- /* CONTROLLER */
-
- function Controller(scope) {
- };
-}]);
-angular.module('cms.shared').directive('cmsPageFilter', function () {
- return {
- restrict: 'E',
- template: '',
- replace: true,
- transclude: true
- }
-});
-angular.module('cms.shared').directive('cmsPageHeader', function () {
- return {
- restrict: 'E',
- template: '',
- replace: true,
- transclude: true,
- scope: {
- title: '@cmsTitle',
- parentTitle: '@cmsParentTitle',
- parentHref: '@cmsParentHref'
- },
- }
-});
-angular.module('cms.shared').directive('cmsPageHeaderButtons', function () {
- return {
- restrict: 'E',
- template: '',
- replace: true,
- transclude: true
- }
-});
-angular.module('cms.shared').directive('cmsPageSubHeader', function () {
- return {
- restrict: 'E',
- template: '',
- replace: true,
- transclude: true
+
+ function cancel() {
+ close();
}
-});
+
+ function uploadComplete(imageAssetId) {
+ options.onUploadComplete(imageAssetId);
+ close();
+ }
+
+}]);
+
angular.module('cms.shared').directive('cmsLoading', function () {
return {
restrict: 'A',
@@ -13770,29 +13770,272 @@ angular.module('cms.shared').factory('shared.LoadState', ['$q', '$rootScope', '_
if (_.isUndefined(total)) total = 100;
progress = parseInt(100.0 * loaded / total);
- if (progress <= 0) progress = 0;
+ if (progress <= 0) progress = 0;
+
+ if (progress >= 100) {
+ progress = 100;
+ } else {
+ me.on();
+ }
+
+ me.progress = progress;
+ }
+ }
+}]);
+angular.module('cms.shared').directive('cmsProgressBar', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+ ) {
+
+ return {
+ restrict: 'E',
+ scope: { loadState: '=' },
+ templateUrl: modulePath + 'UIComponents/Loader/ProgressBar.html'
+ };
+}]);
+angular.module('cms.shared').directive('cmsMenu', [
+ 'shared.internalModulePath',
+function (
+ modulePath) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ transclude: true,
+ templateUrl: modulePath + 'UIComponents/Menus/Menu.html',
+ scope: {
+ icon: '@cmsIcon'
+ }
+ };
+}]);
+angular.module('cms.shared').controller('AlertController', [
+ '$scope',
+ 'options',
+ 'close', function (
+ $scope,
+ options,
+ close) {
+
+ angular.extend($scope, options);
+ $scope.close = close;
+}]);
+angular.module('cms.shared').controller('ConfirmDialogController', ['$scope', 'options', 'close', function ($scope, options, close) {
+ angular.extend($scope, options);
+ $scope.close = resolve;
+
+ /* helpers */
+
+ function resolve(result) {
+ var resolver = result ? options.ok : options.cancel;
+
+ if (resolver) {
+ resolver()
+ .then(closeIfRequired)
+ .finally(options.onCancel);
+ }
+ }
+
+ function closeIfRequired() {
+ if (options.autoClose) {
+ close();
+ }
+ }
+
+}]);
+angular.module('cms.shared').controller('DeveloperExceptionController', [
+ '$scope',
+ '$sce',
+ 'shared.internalContentPath',
+ 'options',
+ 'close',
+function (
+ $scope,
+ $sce,
+ internalContentPath,
+ options,
+ close) {
+
+ var html = options.response.data;
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('srcdoc', html);
+ iframe.setAttribute('src', internalContentPath + 'developer-exception-not-supported.html');
+ iframe.setAttribute('sandbox', 'allow-scripts');
+ $scope.messageHtml = $sce.trustAsHtml(iframe.outerHTML);
+
+ angular.extend($scope, options);
+ $scope.close = close;
+
+}]);
+angular.module('cms.shared').directive('cmsModalDialogActions', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogActions.html',
+ transclude: true
+ };
+}]);
+angular.module('cms.shared').directive('cmsModalDialogBody', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogBody.html',
+ transclude: true,
+ };
+}]);
+angular.module('cms.shared').directive('cmsModalDialogContainer', [
+ 'shared.internalModulePath',
+ '$timeout',
+function (
+ modulePath,
+ $timeout) {
+
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogContainer.html',
+ transclude: true,
+ link: link,
+ controller: angular.noop
+ };
+
+ function link(scope, el, attributes) {
+ var cls = attributes.cmsModalSize === 'large' ? 'modal-lg' : '';
+ cls += (scope.isRootModal ? ' is-root-modal' : ' is-child-modal');
+ if (attributes.cmsModalSize === 'large') {
+ scope.sizeCls = cls;
+ }
+ $timeout(function () {
+ scope.sizeCls = cls + ' modal--show';
+ }, 1);
+ }
+}]);
+angular.module('cms.shared').directive('cmsModalDialogHeader', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogHeader.html',
+ transclude: true,
+ };
+}]);
+angular.module('cms.shared').factory('shared.modalDialogService', [
+ '$q',
+ '_',
+ 'ModalService',
+ 'shared.internalModulePath',
+ 'shared.LoadState',
+function (
+ $q,
+ _,
+ ModalService,
+ modulePath,
+ LoadState) {
+
+ var service = {};
+
+ /* PUBLIC */
+
+ /**
+ * Displays a modal message with a button to dismiss the message.
+ */
+ service.alert = function (optionsOrMessage) {
+ var deferred = $q.defer(),
+ options = optionsOrMessage || {};
+
+ if (_.isString(optionsOrMessage)) {
+ options = {
+ message: optionsOrMessage
+ }
+ }
+
+ ModalService.showModal({
+ templateUrl: modulePath + "UIComponents/Modals/Alert.html",
+ controller: "AlertController",
+ inputs: {
+ options: options
+ }
+ }).then(function (modal) {
+
+ // Apres-creation stuff
+ modal.close.then(deferred.resolve);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Displays a custom modal popup using a template at the specified url.
+ */
+ service.show = function (modalOptions) {
+ return ModalService.showModal({
+ templateUrl: modalOptions.templateUrl,
+ controller: modalOptions.controller,
+ inputs: {
+ options: modalOptions.options
+ }
+ });
+ }
+
+ /**
+ * Displays a modal message with a button options to ok/cancel an action.
+ */
+ service.confirm = function (optionsOrMessage) {
+ var returnDeferred = $q.defer(),
+ onOkLoadState = new LoadState(),
+ options = initOptions(optionsOrMessage);
+
+ ModalService.showModal({
+ templateUrl: modulePath + "UIComponents/Modals/ConfirmDialog.html",
+ controller: "ConfirmDialogController",
+ inputs: {
+ options: options
+ }
+ });
+
+ return returnDeferred.promise;
+
+ /* helpers */
+
+ function initOptions(optionsOrMessage) {
+ var options = optionsOrMessage || {},
+ defaults = {
+ okButtonTitle: 'OK',
+ cancelButtonTitle: 'Cancel',
+ autoClose: true,
+ // onCancel: fn or promise
+ // onOk: fn or promise
+ },
+ internalScope = {
+ ok: resolve.bind(null, true),
+ cancel: resolve.bind(null, false),
+ onOkLoadState: onOkLoadState
+ };
+
+ if (_.isString(optionsOrMessage)) {
+ options = {
+ message: optionsOrMessage
+ }
+ }
+
+ return _.defaults(internalScope, options, defaults);
+ }
- if (progress >= 100) {
- progress = 100;
- } else {
- me.on();
+ function resolve(isSuccess) {
+ var optionToExec = isSuccess ? options.onOk : options.onOk.onCancel,
+ deferredAction = isSuccess ? returnDeferred.resolve : returnDeferred.reject,
+ optionResult;
+
+ // run the action
+ if (_.isFunction(optionToExec)) {
+ onOkLoadState.on();
+ optionResult = optionToExec();
}
- me.progress = progress;
+ // Wait for the result to resolve if its a promise
+ // Then resolve/reject promise we returned to the callee
+ return $q.when(optionResult)
+ .then(deferredAction);
}
}
-}]);
-angular.module('cms.shared').directive('cmsProgressBar', [
- 'shared.internalModulePath',
-function (
- modulePath
- ) {
- return {
- restrict: 'E',
- scope: { loadState: '=' },
- templateUrl: modulePath + 'UIComponents/Loader/ProgressBar.html'
- };
+ return service;
}]);
angular.module('cms.shared').directive('cmsFormFieldLocaleSelector', [
'_',
@@ -14007,351 +14250,123 @@ angular.module('cms.shared').factory('shared.ImagePreviewFieldCollection', [
return item[propertyName];
}
- }
-}]);
-angular.module('cms.shared').factory('shared.ModelPreviewFieldset', [
- '$q',
- '_',
- 'shared.stringUtilities',
- 'shared.imageService',
-function (
- $q,
- _,
- stringUtilities,
- imageService
-) {
- return ModelPreviewFieldset;
-
- function ModelPreviewFieldset(modelMetaData) {
- var me = this,
- PREVIEW_TITLE_FIELD_NAME = 'previewTitle',
- PREVIEW_DESCRIPTION_FIELD_NAME = 'previewDescription',
- PREVIEW_IMAGE_FIELD_NAME = 'previewImage';
-
- /* Public Properties */
-
- me.modelMetaData = modelMetaData;
- me.fields = parseFields(modelMetaData);
- me.showTitle = canShowTitleColumn(me.fields);
- me.titleTerm = getTitleTerm(me.fields);
-
- /* Public Funcs */
-
- me.on = function () {
- me.isLoading = true;
- if (me.progress === 100) {
- me.progress = 0;
- }
- };
-
- /* Private */
-
- function parseFields(modelMetaData) {
- var fields = {};
-
- setGridField(PREVIEW_TITLE_FIELD_NAME);
- setGridField(PREVIEW_DESCRIPTION_FIELD_NAME);
- setGridField(PREVIEW_IMAGE_FIELD_NAME);
-
- return fields;
-
- function setGridField(fieldName) {
-
- var field = _.find(modelMetaData.dataModelProperties, function (property) {
-
- return property.additionalAttributes[fieldName];
- });
-
- if (field) {
- field.lowerName = stringUtilities.lowerCaseFirstWord(field.name);
- fields[fieldName] = field;
- fields.hasFields = true;
- }
- }
- }
-
- /**
- * Title is shown when a [PreviewTitle] attribute is present
- * or if no other preview attributes are present.
- */
- function canShowTitleColumn(gridFields) {
- return gridFields[PREVIEW_TITLE_FIELD_NAME] || !gridFields.hasFields;
- }
-
- /**
- * The title field will default to "Title" but optionally
- * a different field can be specified as the title using
- * the [PreviewTitle] attribute
- */
- function getTitleTerm(gridFields) {
-
- if (gridFields[PREVIEW_TITLE_FIELD_NAME]) {
- return gridFields[PREVIEW_TITLE_FIELD_NAME].displayName;
- }
-
- return "Title";
- }
-
- function loadImageFields() {
- if (!vm.result || !vm.gridFields || !vm.gridFields[PREVIEW_IMAGE_FIELD_NAME]) return;
-
- var field = vm.gridFields[PREVIEW_IMAGE_FIELD_NAME];
-
- var allImageIds = _.chain(vm.result.items)
- .map(function (item) {
- return item.model[field.lowerName];
- })
- .filter(function (id) {
- return id;
- })
- .uniq()
- .value();
-
- return imageService.getByIdRange(allImageIds).then(function (images) {
- vm.modelImages = [];
-
- _.each(vm.result.items, function (item) {
- var id = item.model[field.lowerName],
- image;
-
- if (id) {
- image = _.find(images, { imageAssetId: id });
- }
-
- vm.modelImages.push(image);
- });
- });
- }
-
- }
-}]);
-angular.module('cms.shared').controller('AlertController', [
- '$scope',
- 'options',
- 'close', function (
- $scope,
- options,
- close) {
-
- angular.extend($scope, options);
- $scope.close = close;
-}]);
-angular.module('cms.shared').controller('ConfirmDialogController', ['$scope', 'options', 'close', function ($scope, options, close) {
- angular.extend($scope, options);
- $scope.close = resolve;
-
- /* helpers */
-
- function resolve(result) {
- var resolver = result ? options.ok : options.cancel;
-
- if (resolver) {
- resolver()
- .then(closeIfRequired)
- .finally(options.onCancel);
- }
- }
-
- function closeIfRequired() {
- if (options.autoClose) {
- close();
- }
- }
-
-}]);
-angular.module('cms.shared').controller('DeveloperExceptionController', [
- '$scope',
- '$sce',
- 'shared.internalContentPath',
- 'options',
- 'close',
-function (
- $scope,
- $sce,
- internalContentPath,
- options,
- close) {
-
- var html = options.response.data;
-
- var iframe = document.createElement('iframe');
- iframe.setAttribute('srcdoc', html);
- iframe.setAttribute('src', internalContentPath + 'developer-exception-not-supported.html');
- iframe.setAttribute('sandbox', 'allow-scripts');
- $scope.messageHtml = $sce.trustAsHtml(iframe.outerHTML);
-
- angular.extend($scope, options);
- $scope.close = close;
-
-}]);
-angular.module('cms.shared').directive('cmsModalDialogActions', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogActions.html',
- transclude: true
- };
-}]);
-angular.module('cms.shared').directive('cmsModalDialogBody', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogBody.html',
- transclude: true,
- };
-}]);
-angular.module('cms.shared').directive('cmsModalDialogContainer', [
- 'shared.internalModulePath',
- '$timeout',
-function (
- modulePath,
- $timeout) {
-
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogContainer.html',
- transclude: true,
- link: link,
- controller: angular.noop
- };
-
- function link(scope, el, attributes) {
- var cls = attributes.cmsModalSize === 'large' ? 'modal-lg' : '';
- cls += (scope.isRootModal ? ' is-root-modal' : ' is-child-modal');
- if (attributes.cmsModalSize === 'large') {
- scope.sizeCls = cls;
- }
- $timeout(function () {
- scope.sizeCls = cls + ' modal--show';
- }, 1);
- }
-}]);
-angular.module('cms.shared').directive('cmsModalDialogHeader', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogHeader.html',
- transclude: true,
- };
+ }
}]);
-angular.module('cms.shared').factory('shared.modalDialogService', [
+angular.module('cms.shared').factory('shared.ModelPreviewFieldset', [
'$q',
'_',
- 'ModalService',
- 'shared.internalModulePath',
- 'shared.LoadState',
+ 'shared.stringUtilities',
+ 'shared.imageService',
function (
$q,
_,
- ModalService,
- modulePath,
- LoadState) {
+ stringUtilities,
+ imageService
+) {
+ return ModelPreviewFieldset;
- var service = {};
+ function ModelPreviewFieldset(modelMetaData) {
+ var me = this,
+ PREVIEW_TITLE_FIELD_NAME = 'previewTitle',
+ PREVIEW_DESCRIPTION_FIELD_NAME = 'previewDescription',
+ PREVIEW_IMAGE_FIELD_NAME = 'previewImage';
- /* PUBLIC */
+ /* Public Properties */
- /**
- * Displays a modal message with a button to dismiss the message.
- */
- service.alert = function (optionsOrMessage) {
- var deferred = $q.defer(),
- options = optionsOrMessage || {};
+ me.modelMetaData = modelMetaData;
+ me.fields = parseFields(modelMetaData);
+ me.showTitle = canShowTitleColumn(me.fields);
+ me.titleTerm = getTitleTerm(me.fields);
- if (_.isString(optionsOrMessage)) {
- options = {
- message: optionsOrMessage
- }
- }
+ /* Public Funcs */
- ModalService.showModal({
- templateUrl: modulePath + "UIComponents/Modals/Alert.html",
- controller: "AlertController",
- inputs: {
- options: options
+ me.on = function () {
+ me.isLoading = true;
+ if (me.progress === 100) {
+ me.progress = 0;
}
- }).then(function (modal) {
-
- // Apres-creation stuff
- modal.close.then(deferred.resolve);
- });
+ };
- return deferred.promise;
- }
+ /* Private */
- /**
- * Displays a custom modal popup using a template at the specified url.
- */
- service.show = function (modalOptions) {
- return ModalService.showModal({
- templateUrl: modalOptions.templateUrl,
- controller: modalOptions.controller,
- inputs: {
- options: modalOptions.options
- }
- });
- }
+ function parseFields(modelMetaData) {
+ var fields = {};
- /**
- * Displays a modal message with a button options to ok/cancel an action.
- */
- service.confirm = function (optionsOrMessage) {
- var returnDeferred = $q.defer(),
- onOkLoadState = new LoadState(),
- options = initOptions(optionsOrMessage);
+ setGridField(PREVIEW_TITLE_FIELD_NAME);
+ setGridField(PREVIEW_DESCRIPTION_FIELD_NAME);
+ setGridField(PREVIEW_IMAGE_FIELD_NAME);
- ModalService.showModal({
- templateUrl: modulePath + "UIComponents/Modals/ConfirmDialog.html",
- controller: "ConfirmDialogController",
- inputs: {
- options: options
- }
- });
+ return fields;
- return returnDeferred.promise;
+ function setGridField(fieldName) {
- /* helpers */
+ var field = _.find(modelMetaData.dataModelProperties, function (property) {
- function initOptions(optionsOrMessage) {
- var options = optionsOrMessage || {},
- defaults = {
- okButtonTitle: 'OK',
- cancelButtonTitle: 'Cancel',
- autoClose: true,
- // onCancel: fn or promise
- // onOk: fn or promise
- },
- internalScope = {
- ok: resolve.bind(null, true),
- cancel: resolve.bind(null, false),
- onOkLoadState: onOkLoadState
- };
+ return property.additionalAttributes[fieldName];
+ });
- if (_.isString(optionsOrMessage)) {
- options = {
- message: optionsOrMessage
+ if (field) {
+ field.lowerName = stringUtilities.lowerCaseFirstWord(field.name);
+ fields[fieldName] = field;
+ fields.hasFields = true;
}
}
+ }
- return _.defaults(internalScope, options, defaults);
+ /**
+ * Title is shown when a [PreviewTitle] attribute is present
+ * or if no other preview attributes are present.
+ */
+ function canShowTitleColumn(gridFields) {
+ return gridFields[PREVIEW_TITLE_FIELD_NAME] || !gridFields.hasFields;
}
- function resolve(isSuccess) {
- var optionToExec = isSuccess ? options.onOk : options.onOk.onCancel,
- deferredAction = isSuccess ? returnDeferred.resolve : returnDeferred.reject,
- optionResult;
+ /**
+ * The title field will default to "Title" but optionally
+ * a different field can be specified as the title using
+ * the [PreviewTitle] attribute
+ */
+ function getTitleTerm(gridFields) {
- // run the action
- if (_.isFunction(optionToExec)) {
- onOkLoadState.on();
- optionResult = optionToExec();
+ if (gridFields[PREVIEW_TITLE_FIELD_NAME]) {
+ return gridFields[PREVIEW_TITLE_FIELD_NAME].displayName;
}
- // Wait for the result to resolve if its a promise
- // Then resolve/reject promise we returned to the callee
- return $q.when(optionResult)
- .then(deferredAction);
+ return "Title";
}
- }
- return service;
+ function loadImageFields() {
+ if (!vm.result || !vm.gridFields || !vm.gridFields[PREVIEW_IMAGE_FIELD_NAME]) return;
+
+ var field = vm.gridFields[PREVIEW_IMAGE_FIELD_NAME];
+
+ var allImageIds = _.chain(vm.result.items)
+ .map(function (item) {
+ return item.model[field.lowerName];
+ })
+ .filter(function (id) {
+ return id;
+ })
+ .uniq()
+ .value();
+
+ return imageService.getByIdRange(allImageIds).then(function (images) {
+ vm.modelImages = [];
+
+ _.each(vm.result.items, function (item) {
+ var id = item.model[field.lowerName],
+ image;
+
+ if (id) {
+ image = _.find(images, { imageAssetId: id });
+ }
+
+ vm.modelImages.push(image);
+ });
+ });
+ }
+
+ }
}]);
angular.module('cms.shared').controller('EditNestedDataModelDialogController', [
'$scope',
@@ -14820,21 +14835,6 @@ angular.module('cms.shared').directive('cmsFormFieldNestedDataModelMultiTypeColl
}
}]);
-angular.module('cms.shared').directive('cmsMenu', [
- 'shared.internalModulePath',
-function (
- modulePath) {
-
- return {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: modulePath + 'UIComponents/Menus/Menu.html',
- scope: {
- icon: '@cmsIcon'
- }
- };
-}]);
angular.module('cms.shared').directive('cmsFormFieldPageCollection', [
'_',
'shared.internalModulePath',
@@ -15763,6 +15763,37 @@ function (
}
}
}]);
+angular.module('cms.shared').directive('cmsUserLink', [
+ 'shared.internalModulePath',
+ 'shared.urlLibrary',
+ 'shared.permissionValidationService',
+function (
+ modulePath,
+ urlLibrary,
+ permissionValidationService
+ ) {
+
+ return {
+ restrict: 'E',
+ scope: { user: '=cmsUser' },
+ templateUrl: modulePath + 'UIComponents/User/UserLink.html',
+ controller: controller,
+ controllerAs: 'vm',
+ bindToController: true
+ };
+
+ function controller() {
+ var vm = this;
+
+ vm.urlLibrary = urlLibrary;
+ vm.canRead = permissionValidationService.canRead('COFUSR');
+ vm.formatName = formatName;
+ }
+
+ function formatName(user) {
+ return user.displayName || user.username || 'User ' + user.userId;
+ }
+}]);
angular.module('cms.shared').directive('cmsTimeAgo', ['shared.internalModulePath', function (modulePath) {
return {
@@ -15837,37 +15868,6 @@ angular.module('cms.shared').directive('cmsTimeAgo', ['shared.internalModulePath
return time;
}
}]);
-angular.module('cms.shared').directive('cmsUserLink', [
- 'shared.internalModulePath',
- 'shared.urlLibrary',
- 'shared.permissionValidationService',
-function (
- modulePath,
- urlLibrary,
- permissionValidationService
- ) {
-
- return {
- restrict: 'E',
- scope: { user: '=cmsUser' },
- templateUrl: modulePath + 'UIComponents/User/UserLink.html',
- controller: controller,
- controllerAs: 'vm',
- bindToController: true
- };
-
- function controller() {
- var vm = this;
-
- vm.urlLibrary = urlLibrary;
- vm.canRead = permissionValidationService.canRead('COFUSR');
- vm.formatName = formatName;
- }
-
- function formatName(user) {
- return user.displayName || user.username || 'User ' + user.userId;
- }
-}]);
/**
* If this element is in a modal popup or in a form in edit mode, then add a target="_blank" attribute
* so that links open in a new tab
diff --git a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
index d6d079441..e8b4d87ec 100644
--- a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
+++ b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
@@ -433,7 +433,7 @@ tinymce.PluginManager.add("textpattern",function(a){function b(){return j&&(i.so
tinymce.PluginManager.add("visualblocks",function(a,b){function c(){var b=this;b.active(f),a.on("VisualBlocks",function(){b.active(a.dom.hasClass(a.getBody(),"mce-visualblocks"))})}var d,e,f;window.NodeList&&(a.addCommand("mceVisualBlocks",function(){var c,g=a.dom;d||(d=g.uniqueId(),c=g.create("link",{id:d,rel:"stylesheet",href:b+"/css/visualblocks.css"}),a.getDoc().getElementsByTagName("head")[0].appendChild(c)),a.on("PreviewFormats AfterPreviewFormats",function(b){f&&g.toggleClass(a.getBody(),"mce-visualblocks","afterpreviewformats"==b.type)}),g.toggleClass(a.getBody(),"mce-visualblocks"),f=a.dom.hasClass(a.getBody(),"mce-visualblocks"),e&&e.active(g.hasClass(a.getBody(),"mce-visualblocks")),a.fire("VisualBlocks")}),a.addButton("visualblocks",{title:"Show blocks",cmd:"mceVisualBlocks",onPostRender:c}),a.addMenuItem("visualblocks",{text:"Show blocks",cmd:"mceVisualBlocks",onPostRender:c,selectable:!0,context:"view",prependToContext:!0}),a.on("init",function(){a.settings.visualblocks_default_state&&a.execCommand("mceVisualBlocks",!1,null,{skip_focus:!0})}),a.on("remove",function(){a.dom.removeClass(a.getBody(),"mce-visualblocks")}))});
tinymce.PluginManager.add("visualchars",function(a){function b(b){function c(a){return''+a+""}function f(){var a,b="";for(a in n)b+=a;return new RegExp("["+b+"]","g")}function g(){var a,b="";for(a in n)b&&(b+=","),b+="span.mce-"+n[a];return b}var h,i,j,k,l,m,n,o,p=a.getBody(),q=a.selection;if(n={"\xa0":"nbsp","\xad":"shy"},d=!d,e.state=d,a.fire("VisualChars",{state:d}),o=f(),b&&(m=q.getBookmark()),d)for(i=[],tinymce.walk(p,function(a){3==a.nodeType&&a.nodeValue&&o.test(a.nodeValue)&&i.push(a)},"childNodes"),j=0;j=0;j--)a.dom.remove(i[j],1);q.moveToBookmark(m)}function c(){var b=this;a.on("VisualChars",function(a){b.active(a.state)})}var d,e=this;a.addCommand("mceVisualChars",b),a.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c}),a.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c,selectable:!0,context:"view",prependToContext:!0}),a.on("beforegetcontent",function(a){d&&"raw"!=a.format&&!a.draft&&(d=!0,b(!1))})});
tinymce.PluginManager.add("wordcount",function(a){function b(){a.theme.panel.find("#wordcount").text(["Words: {0}",e.getCount()])}var c,d,e=this;c=a.getParam("wordcount_countregex",/[\w\u2019\x27\-\u00C0-\u1FFF]+/g),d=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),a.on("init",function(){var c=a.theme.panel&&a.theme.panel.find("#statusbar")[0];c&&tinymce.util.Delay.setEditorTimeout(a,function(){c.insert({type:"label",name:"wordcount",text:["Words: {0}",e.getCount()],classes:"wordcount",disabled:a.settings.readonly},0),a.on("setcontent beforeaddundo",b),a.on("keyup",function(a){32==a.keyCode&&b()})},0)}),e.getCount=function(){var b=a.getContent({format:"raw"}),e=0;if(b){b=b.replace(/\.\.\./g," "),b=b.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),b=b.replace(/(\w+)(?[a-z0-9]+;)+(\w+)/i,"$1$3").replace(/&.+?;/g," "),b=b.replace(d,"");var f=b.match(c);f&&(e=f.length)}return e}});
-angular.module("ui.tinymce", []).value("uiTinymceConfig", {}).directive("uiTinymce", ["$rootScope", "$compile", "$timeout", "$window", "$sce", "uiTinymceConfig", function (a, b, c, d, e, f) { f = f || {}; var g = "ui-tinymce"; return f.baseUrl && (tinymce.baseURL = f.baseUrl), { require: ["ngModel", "^?form"], priority: 599, link: function (h, i, j, k) { function l(a) { a ? (m(), o && o.getBody().setAttribute("contenteditable", !1)) : (m(), o && !o.settings.readonly && o.getDoc() && o.getBody().setAttribute("contenteditable", !0)) } function m() { o || (o = tinymce.get(j.id)) } if (d.tinymce) { var n, o, p = k[0], q = k[1] || null, r = { debounce: !0 }, s = function (b) { var c = b.getContent({ format: r.format }).trim(); c = e.trustAsHtml(c), p.$setViewValue(c), a.$$phase || h.$digest() }; j.$set("id", g + "-" + (new Date).valueOf()), n = {}, angular.extend(n, h.$eval(j.uiTinymce)); var t = function (a) { var b; return function (d) { c.cancel(b), b = c(function () { return function (a) { a.isDirty() && (a.save(), s(a)) }(d) }, a) } }(400), u = { setup: function (b) { b.on("init", function () { p.$render(), p.$setPristine(), p.$setUntouched(), q && q.$setPristine() }), b.on("ExecCommand change NodeChange ObjectResized", function () { return r.debounce ? void t(b) : (b.save(), void s(b)) }), b.on("blur", function () { i[0].blur(), p.$setTouched(), a.$$phase || h.$digest() }), b.on("remove", function () { i.remove() }), f.setup && f.setup(b, { updateView: s }), n.setup && n.setup(b, { updateView: s }) }, format: n.format || "html", selector: "#" + j.id }; angular.extend(r, f, n, u), c(function () { r.baseURL && (tinymce.baseURL = r.baseURL); var a = tinymce.init(r); a && "function" == typeof a.then ? a.then(function () { l(h.$eval(j.ngDisabled)) }) : l(h.$eval(j.ngDisabled)) }), p.$formatters.unshift(function (a) { return a ? e.trustAsHtml(a) : "" }), p.$parsers.unshift(function (a) { return a ? e.getTrustedHtml(a) : "" }), p.$render = function () { m(); var a = p.$viewValue ? e.getTrustedHtml(p.$viewValue) : ""; o && o.getDoc() && (o.setContent(a), o.fire("change")) }, j.$observe("disabled", l), h.$on("$tinymce:refresh", function (a, c) { var d = j.id; if (angular.isUndefined(c) || c === d) { var e = i.parent(), f = i.clone(); f.removeAttr("id"), f.removeAttr("style"), f.removeAttr("aria-hidden"), tinymce.execCommand("mceRemoveEditor", !1, d), e.append(b(f)(h)) } }), h.$on("$destroy", function () { m(), o && (o.remove(), o = null) }) } } } }]);
+angular.module("ui.tinymce",[]).value("uiTinymceConfig",{}).directive("uiTinymce",["$rootScope","$compile","$timeout","$window","$sce","uiTinymceConfig","uiTinymceService",function(a,b,c,d,e,f,g){return f=f||{},f.baseUrl&&(tinymce.baseURL=f.baseUrl),{require:["ngModel","^?form"],priority:599,link:function(h,i,j,k){function l(a){a?(m(),o&&o.getBody().setAttribute("contenteditable",!1)):(m(),o&&!o.settings.readonly&&o.getDoc()&&o.getBody().setAttribute("contenteditable",!0))}function m(){o||(o=tinymce.get(j.id))}if(d.tinymce){var n,o,p=k[0],q=k[1]||null,r={debounce:!0},s=function(b){var c=b.getContent({format:r.format}).trim();c=e.trustAsHtml(c),p.$setViewValue(c),a.$$phase||h.$digest()},t=g.getUniqueId();j.$set("id",t),n={},angular.extend(n,h.$eval(j.uiTinymce));var u=function(a){var b;return function(d){c.cancel(b),b=c(function(){return function(a){a.isDirty()&&(a.save(),s(a))}(d)},a)}}(400),v={setup:function(b){b.on("init",function(){p.$render(),p.$setPristine(),p.$setUntouched(),q&&q.$setPristine()}),b.on("ExecCommand change NodeChange ObjectResized",function(){return r.debounce?void u(b):(b.save(),void s(b))}),b.on("blur",function(){i[0].blur(),p.$setTouched(),a.$$phase||h.$digest()}),b.on("remove",function(){i.remove()}),f.setup&&f.setup(b,{updateView:s}),n.setup&&n.setup(b,{updateView:s})},format:n.format||"html",selector:"#"+j.id};angular.extend(r,f,n,v),c(function(){r.baseURL&&(tinymce.baseURL=r.baseURL);var a=tinymce.init(r);a&&"function"==typeof a.then?a.then(function(){l(h.$eval(j.ngDisabled))}):l(h.$eval(j.ngDisabled))}),p.$formatters.unshift(function(a){return a?e.trustAsHtml(a):""}),p.$parsers.unshift(function(a){return a?e.getTrustedHtml(a):""}),p.$render=function(){m();var a=p.$viewValue?e.getTrustedHtml(p.$viewValue):"";o&&o.getDoc()&&(o.setContent(a),o.fire("change"))},j.$observe("disabled",l),h.$on("$tinymce:refresh",function(a,c){var d=j.id;if(angular.isUndefined(c)||c===d){var e=i.parent(),f=i.clone();f.removeAttr("id"),f.removeAttr("style"),f.removeAttr("aria-hidden"),tinymce.execCommand("mceRemoveEditor",!1,d),e.append(b(f)(h))}}),h.$on("$destroy",function(){m(),o&&(o.remove(),o=null)})}}}}]).service("uiTinymceService",[function(){var a=function(){var a="ui-tinymce",b=0,c=function(){return b++,a+"-"+b};return{getUniqueId:c}};return new a}]);
!function(C){"use strict";function i(e,r){var a=[],e=e.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(e,r,t,n){return e="?"===n||"*?"===n,n="*"===n||"*?"===n,a.push({name:t,optional:e}),r=r||"",(e?"(?:"+r:r+"(?:")+(n?"(.+?)":"([^/]+)")+(e?"?)?":")")}).replace(/([/$*])/g,"\\$1");return r.ignoreTrailingSlashes&&(e=e.replace(/\/+$/,"")+"/*"),{keys:a,regexp:new RegExp("^"+e+"(?:[?#]|$)",r.caseInsensitiveMatch?"i":"")}}function e(e){a&&e.get("$route")}function r(d,f,$){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(t,n,e,r,a){function i(){l&&($.cancel(l),l=null),c&&(c.$destroy(),c=null),u&&((l=$.leave(u)).done(function(e){!1!==e&&(l=null)}),u=null)}function o(){var e,r=d.current&&d.current.locals;C.isDefined(r&&r.$template)?(r=t.$new(),e=d.current,u=a(r,function(e){$.enter(e,null,u||n).done(function(e){!1===e||!C.isDefined(s)||s&&!t.$eval(s)||f()}),i()}),(c=e.scope=r).$emit("$viewContentLoaded"),c.$eval(h)):i()}var c,u,l,s=e.autoscroll,h=e.onload||"";t.$on("$routeChangeSuccess",o),o()}}}function t(o,c,u){return{restrict:"ECA",priority:-400,link:function(e,r){var t=u.current,n=t.locals;r.html(n.$template);var a,i=o(r.contents());t.controller&&(n.$scope=e,a=c(t.controller,n),t.controllerAs&&(e[t.controllerAs]=a),r.data("$ngControllerController",a),r.children().data("$ngControllerController",a)),e[t.resolveAs||"$resolve"]=n,i(e)}}}var o,c,n,R,a,u=C.module("ngRoute",[]).info({angularVersion:"1.8.2"}).provider("$route",function(){function w(e,r){return C.extend(Object.create(e),r)}o=C.isArray,c=C.isObject,n=C.isDefined,R=C.noop;var P={};this.when=function(e,r){var t=void 0;if(o(r)){t=t||[];for(var n=0,a=r.length;nn.width;)a=a.substring(0,a.length-1),t=e.measureText(a+"…").width;return a+"…"}(a,e,n);a.fillText(e,4,n.padding+4);e=new Image;return e.src=t.toDataURL(),{image:e,xOffset:n.xOffset,yOffset:n.yOffset}}}])):b.module("ang-drag-drop",[])}(angular);
@@ -476,21 +476,17 @@ angular.module("cms.shared").config(["$httpProvider","csrfToken","csrfHeaderName
angular.module("cms.shared").config(["$locationProvider",function(o){o.hashPrefix("")}]);
angular.module("cms.shared").factory("shared.permissionValidationService",["_","shared.currentUser",function(e,r){var s={};return s.hasPermission=function(n){return e.contains(r.permissionCodes,n)},s.canRead=function(n){return s.hasPermission(n+"COMRED")},s.canViewModule=function(n){return s.hasPermission(n+"COMMOD")},s.canCreate=function(n){return s.hasPermission(n+"COMCRT")},s.canUpdate=function(n){return s.hasPermission(n+"COMUPD")},s.canDelete=function(n){return s.hasPermission(n+"COMDEL")},s}]);
angular.module("cms.shared").factory("shared.validationErrorService",["_",function(t){var n={},i=[];return n.raise=function(n){var r,o=[];n.forEach(function(e){var n=t.filter(i,function(r){return t.find(e.properties,function(n){return!(!n||!r.prop)&&r.prop.toLowerCase()===n.toLowerCase()})});n.length?n.forEach(function(n){n.fn([e])}):o.push(e)}),o.length&&((n=t.filter(i,function(n){return!n.prop})).length?(r=o,n.forEach(function(n){n.fn(r)})):function(){throw new Error("An unhandled validation exception has occurred")}())},n.addHandler=function(n,r){r={prop:n,fn:r};return i.push(r),r},n.removeHandler=function(n){n=t.isFunction(n)?t.where(i,{fn:n}):t.where(i,{prop:n});i=t.difference(i,n)},n}]);
+angular.module("cms.shared").directive("cmsFormFieldDirectorySelector",["_","shared.directiveUtilities","shared.internalModulePath","shared.directoryService",function(e,i,r,t){return{restrict:"E",templateUrl:r+"UIComponents/Directories/FormFieldDirectorySelector.html",scope:{model:"=cmsModel",title:"@cmsTitle",onLoaded:"&cmsOnLoaded",readonly:"=cmsReadonly"},link:{pre:function(e,r,t){e=e.vm;angular.isDefined(t.required)?e.isRequired=!0:(e.isRequired=!1,e.defaultItemText=t.cmsDefaultItemText||"None");e.title=t.cmsTitle||"Directory",e.description=t.cmsDescription,i.setModelName(e,t)}},controller:function(){var r=this;t.getAll().then(function(e){r.pageDirectories=e,r.onLoaded&&r.onLoaded()})},controllerAs:"vm",bindToController:!0}}]);
+angular.module("cms.shared").directive("cmsButton",["shared.internalModulePath",function(t){return{restrict:"E",replace:!0,templateUrl:t+"UIComponents/Buttons/Button.html",scope:{text:"@cmsText"}}}]);
+angular.module("cms.shared").directive("cmsButtonIcon",["shared.internalModulePath",function(n){return{restrict:"E",replace:!1,templateUrl:n+"UIComponents/Buttons/ButtonIcon.html",scope:{title:"@cmsTitle",icon:"@cmsIcon",href:"@cmsHref",external:"@cmsExternal"},link:function(n,t){n.icon&&(n.iconCls="fa-"+n.icon)}}}]);
+angular.module("cms.shared").directive("cmsButtonLink",["shared.internalModulePath",function(t){return{restrict:"E",replace:!0,templateUrl:t+"UIComponents/Buttons/ButtonLink.html",scope:{text:"@cmsText",href:"@cmsHref"}}}]);
+angular.module("cms.shared").directive("cmsButtonSubmit",["shared.internalModulePath",function(t){return{restrict:"E",replace:!0,templateUrl:t+"UIComponents/Buttons/ButtonSubmit.html",scope:{text:"@cmsText"}}}]);
angular.module("cms.shared").controller("AddCustomEntityDialogController",["$scope","$location","shared.stringUtilities","shared.LoadState","shared.customEntityService","options","close",function(t,o,n,a,e,i,l){var d=t;function u(t){var o=t?(d.command.publish=!0,d.saveAndPublishLoadState):d.saveLoadState;t=o,d.globalLoadState.on(),t&&_.isFunction(t.on)&&t.on(),e.add(d.command,i.customEntityDefinition.customEntityDefinitionCode).then(s).finally(function(t){d.globalLoadState.off(),t&&_.isFunction(t.off)&&t.off()}.bind(null,o))}function c(){d.command.urlSlug=n.slugify(d.command.title)}function m(){l()}function s(t){i.onComplete(t),l()}angular.extend(t,i.customEntityDefinition),d.globalLoadState=new a,d.saveLoadState=new a,d.saveAndPublishLoadState=new a,d.formLoadState=new a(!0),d.editMode=!1,d.options=i.customEntityDefinition,d.saveButtonText=i.customEntityDefinition.autoPublish?"Save":"Save & Publish",d.save=u.bind(null,!1),d.saveAndPublish=u.bind(null,!0),d.cancel=m,d.close=m,d.onNameChanged=c,e.getDataModelSchema(i.customEntityDefinition.customEntityDefinitionCode).then(function(t){t.defaultValue&&t.defaultValue.value?d.command.model=angular.copy(t.defaultValue.value):d.command.model={},d.formDataSource={model:d.command.model,modelMetaData:t},d.formLoadState.off()}),d.command={},t.$watch("vm.command.localeId",function(t){d.additionalParameters=t?{localeId:t}:{}})}]);
angular.module("cms.shared").directive("cmsCustomEntityLink",["shared.internalModulePath","shared.urlLibrary",function(t,n){return{restrict:"E",scope:{customEntityDefinition:"=cmsCustomEntityDefinition",customEntity:"=cmsCustomEntity"},templateUrl:t+"UIComponents/CustomEntities/CustomEntityLink.html",controller:function(){this.urlLibrary=n},controllerAs:"vm",bindToController:!0}}]);
angular.module("cms.shared").controller("CustomEntityPickerDialogController",["$scope","$q","shared.LoadState","shared.customEntityService","shared.SearchQuery","shared.modalDialogService","shared.internalModulePath","shared.permissionValidationService","shared.ModelPreviewFieldset","shared.ImagePreviewFieldCollection","options","close",function(e,n,t,o,i,s,d,l,r,c,u,a){var m=e;function f(e){m.isFilterVisible=_.isUndefined(e)?!m.isFilterVisible:e}function y(){f(!1),E()}function E(){var e,t=u.customEntityDefinition.customEntityDefinitionCode,i=(m.gridLoadState.on(),o.getAll(m.query.getParameters(),t).then(function(e){m.result=e,m.gridLoadState.off()}));return m.previewFields?(e=n.defer()).resolve():e=o.getDataModelSchema(t).then(function(e){m.previewFields=new r(e)}),n.all([e,i]).then(function(){return m.gridImages=new c,m.gridImages.load(m.result.items,m.previewFields)})}function g(){m.multiMode||m.onSelected(m.currentEntity),a()}function h(e){m.multiMode?v(e):m.selectedEntity=e}function C(e){if(!m.multiMode)return m.selectedEntity=e,void I();v(e),I()}function I(){m.multiMode?m.onSelected(m.selectedIds):m.onSelected(m.selectedEntity),a()}function S(){s.show({templateUrl:d+"UIComponents/CustomEntities/AddCustomEntityDialog.html",controller:"AddCustomEntityDialogController",options:{customEntityDefinition:u.customEntityDefinition,onComplete:function(e){m.multiMode?(h({customEntityId:e}),E()):C({customEntityId:e})}}})}function p(e){return!!(m.selectedIds&&e&&-1",model:s.command};t.html=(a=t.html,e=e,angular.element(a).css(e)[0].outerHTML),s.onSelected(t),d()}function h(e){return e?-1==e.indexOf("px")&&-1==e.indexOf("%")&&-1==e.indexOf("auto")?e+"px":e:""}angular.extend(e,n),s.formLoadState=new t,s.saveLoadState=new t,s.onInsert=g,s.onCancel=o,s.onImageChanged=r,s.command={},function(){var e,t;s.imageAssetHtml&&s.imageAssetHtml.length&&(s.command.imageAssetId=s.imageAssetHtml.attr("data-image-asset-id"),s.command.altTag=s.imageAssetHtml.attr("alt"),s.command.style=s.imageAssetHtml.attr("style"),s.command.style?(t=function(e){var t,a=/([\w-]*)\s*:\s*([^;]*)/g,m={};for(;t=a.exec(e);)m[t[1]]=t[2];return m}(s.command.style),s.command.width=t.width,s.command.height=t.height):(s.command.width=s.imageAssetHtml.attr("width"),s.command.height=s.imageAssetHtml.attr("height")),s.command.imageAssetId||(e=s.imageAssetHtml.attr("src"),t=e.lastIndexOf("/"),t=e.substr(t+1,e.indexOf("_")-t-1),s.command.imageAssetId=t))}()}]);
-angular.module("cms.shared").controller("AddEntityAccessRuleController",["$scope","$q","shared.LoadState","shared.roleService","shared.userAreaService","options","close",function(e,o,n,r,a,t,d){var s=e;function u(){s.command.roleId=null}function l(e){if(s.command.userAreaCode)return e.userAreaCode=s.command.userAreaCode,e.excludeAnonymous=!0,r.search(e);e=o.defer();return e.resolve(),e.promise}function c(){t.onSave(s.command),d()}function f(){d()}s.globalLoadState=new n,s.saveLoadState=new n,s.formLoadState=new n,function(e){s.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}(s.formLoadState),s.onAdd=c,s.onCancel=f,s.onUserAreaChanged=u,s.searchRoles=l,s.command={},u(),a.getAll().then(function(e){s.userAreas=_.filter(e,function(e){return"COF"!==e.userAreaCode}),1==s.userAreas.length&&(s.command.userAreaCode=s.userAreas[0].userAreaCode)}).finally(function(e){s.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}.bind(null,s.formLoadState))}]);
-angular.module("cms.shared").controller("EntityAccessEditorController",["$scope","$q","shared.LoadState","shared.userAreaService","shared.roleService","shared.modalDialogService","shared.arrayUtilities","shared.internalModulePath","shared.permissionValidationService","shared.urlLibrary","options","close",function(e,o,n,t,i,r,s,a,c,u,d,l){var A=e;function f(){A.command.accessRules=_.map(A.accessRuleSet.accessRules,function(e){var n=d.entityIdPrefix+"AccessRuleId",r={userAreaCode:e.userArea.userAreaCode,roleId:e.role?e.role.roleId:null};return r[n]=e[n],r}),A.command.redirectToSignIn||(A.command.userAreaCodeForSignInRedirect=null),I(A.saveLoadState),d.saveAccess(A.command).then(function(e,n){return h(n).then(A.mainForm.formStatus.success.bind(null,e))}.bind(null,"Access rules updated successfully")).then(l).finally(C.bind(null,A.saveLoadState))}function R(){r.show({templateUrl:a+"UIComponents/EntityAccess/AddEntityAccessRule.html",controller:"AddEntityAccessRuleController",options:{onSave:S}})}function m(e,n){s.removeObject(A.accessRuleSet.accessRules,e),g()}function S(r){var n={};_.find(A.accessRuleSet.accessRules,function(e){var n=e.role?e.role.roleId:null;return e.userArea.userAreaCode===r.userAreaCode&&n==r.roleId})||(I(),o.all([r.roleId?i.getById(r.roleId).then(function(e){n.role=e}):o(function(e){e()}),t.getByCode(r.userAreaCode).then(function(e){n.userArea=e})]).then(function(){A.accessRuleSet.accessRules.push(n),A.accessRuleSet.accessRules=_(A.accessRuleSet.accessRules).chain().sortBy(function(e){return e.role?e.role.roleId:-1}).sortBy(function(e){return e.userArea.userAreaCode}).value(),g()}).finally(C))}function h(e){return A.entityDefinitionName=d.entityDefinitionName,A.entityDefinitionNameLower=d.entityDefinitionName.toLowerCase(),A.entityDescription=d.entityDescription,A.violationActions=[{id:"Error",name:"Error",description:"Error (403: Forbidden)"},{id:"NotFound",name:"Not Found",description:"Not Found (404: Not Found)"}],d.entityAccessLoader().then(function(e){A.accessRuleSet=e,A.command=function(e){var n=_.pick(e,d.entityIdPrefix+"Id","userAreaCodeForSignInRedirect","violationAction");e.userAreaForSignInRedirect&&(n.userAreaCodeForSignInRedirect=e.userAreaForSignInRedirect.userAreaCode,n.redirectToSignIn=!0);return n}(e),A.inheritedRules=[],_.each(A.accessRuleSet.inheritedAccessRules,function(n){n.violationAction=_.findWhere(A.violationActions,{id:n.violationAction}),n.userAreaForSignInRedirect?(n.signInRedirect="Yes",n.signInRedirectDescription="If the user is not signed in, then they will be redirected to the sign in page associated with the "+n.userAreaForSignInRedirect.name+" user area."):(n.signInRedirect="No",n.signInRedirectDescription="No sign in redirection, the default action will trigger instead."),_.each(n.accessRules,function(e){e.accessRuleSet=n,A.inheritedRules.push(e)})}),g()}).then(C.bind(null,e))}function g(){A.userAreasInRules=_(A.accessRuleSet.accessRules).chain().map(function(e){return e.userArea}).uniq(function(e){return e.userAreaCode}).sortBy("userAreaCode").value(),A.userAreasInRules.length&&_.find(A.userAreasInRules,function(e){return e.userAreaCode===A.command.userAreaCodeForSignInRedirect})||(A.command.redirectToSignIn=!1,A.command.userAreaCodeForSignInRedirect=null),!A.command.userAreaCodeForSignInRedirect&&A.userAreasInRules.length&&(A.command.userAreaCodeForSignInRedirect=A.userAreasInRules[0].userAreaCode,console.log("setting vm.command.userAreaCodeForSignInRedirect",A.command.userAreaCodeForSignInRedirect))}function I(e){A.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}function C(e){A.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}A.save=f,A.close=l,A.add=R,A.deleteRule=m,A.globalLoadState=new n,A.saveLoadState=new n,A.formLoadState=new n(!0),A.urlLibrary=u,A.canManage=c.hasPermission(d.entityDefinitionCode+"ACCRUL"),A.editMode=A.canManage,h(A.formLoadState)}]);
-angular.module("cms.shared").factory("shared.entityVersionModalDialogService",["shared.entityVersionService","shared.modalDialogService",function(s,a){var t={},l={entityNameSingular:"Page"};return t.publish=function(t,e,i){var n=i||l,i={title:"Publish "+n.entityNameSingular,message:"Are you sure you want to publish this "+n.entityNameSingular.toLowerCase()+"?",okButtonTitle:"Yes, publish it",onOk:function(){return e(),s.publish(n.isCustomEntity,t)}};return a.confirm(i)},t.unpublish=function(t,e,i){var n=i||l,i={title:"Unpublish "+n.entityNameSingular,message:"Unpublishing this "+n.entityNameSingular.toLowerCase()+" will remove it from the live site and put it into draft status. Are you sure you want to continue?",okButtonTitle:"Yes, unpublish it",onOk:function(){return e(),s.unpublish(n.isCustomEntity,t)}};return a.confirm(i)},t.copyToDraft=function(t,e,i,n,r){var o=r||l,r={title:"Copy "+o.entityNameSingular+" Version",message:"A draft version of this "+o.entityNameSingular.toLowerCase()+" already exists. Copying this version will delete the current draft. Do you wish to continue?",okButtonTitle:"Yes, replace it",onOk:function(){return n(),s.removeDraft(o.isCustomEntity,t).then(u)}};return i?a.confirm(r):(n(),u());function u(){return s.duplicateDraft(o.isCustomEntity,t,e)}},t}]);
angular.module("cms.shared").directive("cmsDocumentAsset",["shared.internalModulePath","shared.urlLibrary",function(e,r){return{restrict:"E",scope:{document:"=cmsDocument"},templateUrl:e+"UIComponents/DocumentAssets/DocumentAsset.html",link:function(e,t,n){e.getDocumentUrl=r.getDocumentUrl}}}]);
angular.module("cms.shared").controller("DocumentAssetPickerDialogController",["$scope","shared.LoadState","shared.documentService","shared.SearchQuery","shared.modalDialogService","shared.internalModulePath","shared.permissionValidationService","shared.urlLibrary","options","close",function(e,t,s,o,n,l,d,r,c,i){var u=e;function a(e){u.isFilterVisible=_.isUndefined(e)?!u.isFilterVisible:e}function m(){a(!1),f()}function f(){return u.gridLoadState.on(),s.getAll(u.query.getParameters()).then(function(e){u.result=e,u.gridLoadState.off()})}function A(){u.multiMode||u.onSelected(u.currentAsset),i()}function g(e){u.multiMode?C(e):u.selectedAsset=e}function h(e){if(!u.multiMode)return u.selectedAsset=e,void S();C(e),S()}function S(){u.multiMode?u.onSelected(u.selectedIds):u.onSelected(u.selectedAsset),i()}function p(){n.show({templateUrl:l+"UIComponents/DocumentAssets/UploadDocumentAssetDialog.html",controller:"UploadDocumentAssetDialogController",options:{filter:c.filter,onUploadComplete:function(e){h({documentAssetId:e})}}})}function I(e){return!!(u.selectedIds&&e&&-1"+t+">"}),a.append(i(r)(t)))}t.$watch("vm.dataSource",function(e){o(e)})},controller:["$scope",function(e){}],bindToController:!0,controllerAs:"vm"};function l(){var a="cms-",o={maxlength:i,minlength:i,min:i,max:i,pattern:i,step:i,placeholder:i,match:e,model:e,options:function(e,t,a){return c(e,"vm.dataSource.modelMetaData.dataModelProperties["+a+"].additionalAttributes['"+e+"']")},required:function(e,t){if(t)return r(e.toLowerCase());return""},rows:i,cols:i};function e(e,t){return c(e,t="vm.dataSource.model['"+t+"']")}function i(e,t){return r(d.toSnakeCase(e),t)}function c(e,t){return r(e=a+d.toSnakeCase(e),t)}function r(e,t){return t?" "+e+'="'+t+'"':" "+e}this.map=function(e,t,a){var r="ValMsg",n=o[e];return!n&&d.endsWith(e,r)?(r=e.substring(0,e.length-r.length),t&&o[r]===i&&(n=i)):n=n||c,n?n(e,t,a):""}}}]);
+angular.module("cms.shared").controller("ImageAssetEditorDialogController",["$scope","shared.LoadState","shared.imageService","shared.SearchQuery","shared.urlLibrary","options","close",function(e,t,a,m,i,n,d){var s=e;function o(){d()}function r(){s.command.altTag=s.command.imageAsset.title||s.command.imageAsset.fileName}function g(){var e={width:h(s.command.width),height:h(s.command.height)};e.width||e.height||(e.width="100%",e.height="auto");var t=i.getImageUrl(s.command.imageAsset,-1<((a=e).width||"").indexOf("%")||-1<(a.height||"").indexOf("%")?{}:{width:a.width.replace("px",""),height:a.height.replace("px","")}),a=s.command.altTag||"",t={markdown:"",html:"
",model:s.command};t.html=(a=t.html,e=e,angular.element(a).css(e)[0].outerHTML),s.onSelected(t),d()}function h(e){return e?-1==e.indexOf("px")&&-1==e.indexOf("%")&&-1==e.indexOf("auto")?e+"px":e:""}angular.extend(e,n),s.formLoadState=new t,s.saveLoadState=new t,s.onInsert=g,s.onCancel=o,s.onImageChanged=r,s.command={},function(){var e,t;s.imageAssetHtml&&s.imageAssetHtml.length&&(s.command.imageAssetId=s.imageAssetHtml.attr("data-image-asset-id"),s.command.altTag=s.imageAssetHtml.attr("alt"),s.command.style=s.imageAssetHtml.attr("style"),s.command.style?(t=function(e){var t,a=/([\w-]*)\s*:\s*([^;]*)/g,m={};for(;t=a.exec(e);)m[t[1]]=t[2];return m}(s.command.style),s.command.width=t.width,s.command.height=t.height):(s.command.width=s.imageAssetHtml.attr("width"),s.command.height=s.imageAssetHtml.attr("height")),s.command.imageAssetId||(e=s.imageAssetHtml.attr("src"),t=e.lastIndexOf("/"),t=e.substr(t+1,e.indexOf("_")-t-1),s.command.imageAssetId=t))}()}]);
+angular.module("cms.shared").controller("AddEntityAccessRuleController",["$scope","$q","shared.LoadState","shared.roleService","shared.userAreaService","options","close",function(e,o,n,r,a,t,d){var s=e;function u(){s.command.roleId=null}function l(e){if(s.command.userAreaCode)return e.userAreaCode=s.command.userAreaCode,e.excludeAnonymous=!0,r.search(e);e=o.defer();return e.resolve(),e.promise}function c(){t.onSave(s.command),d()}function f(){d()}s.globalLoadState=new n,s.saveLoadState=new n,s.formLoadState=new n,function(e){s.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}(s.formLoadState),s.onAdd=c,s.onCancel=f,s.onUserAreaChanged=u,s.searchRoles=l,s.command={},u(),a.getAll().then(function(e){s.userAreas=_.filter(e,function(e){return"COF"!==e.userAreaCode}),1==s.userAreas.length&&(s.command.userAreaCode=s.userAreas[0].userAreaCode)}).finally(function(e){s.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}.bind(null,s.formLoadState))}]);
+angular.module("cms.shared").controller("EntityAccessEditorController",["$scope","$q","shared.LoadState","shared.userAreaService","shared.roleService","shared.modalDialogService","shared.arrayUtilities","shared.internalModulePath","shared.permissionValidationService","shared.urlLibrary","options","close",function(e,o,n,t,i,r,s,a,c,u,d,l){var A=e;function f(){A.command.accessRules=_.map(A.accessRuleSet.accessRules,function(e){var n=d.entityIdPrefix+"AccessRuleId",r={userAreaCode:e.userArea.userAreaCode,roleId:e.role?e.role.roleId:null};return r[n]=e[n],r}),A.command.redirectToSignIn||(A.command.userAreaCodeForSignInRedirect=null),I(A.saveLoadState),d.saveAccess(A.command).then(function(e,n){return h(n).then(A.mainForm.formStatus.success.bind(null,e))}.bind(null,"Access rules updated successfully")).then(l).finally(C.bind(null,A.saveLoadState))}function R(){r.show({templateUrl:a+"UIComponents/EntityAccess/AddEntityAccessRule.html",controller:"AddEntityAccessRuleController",options:{onSave:S}})}function m(e,n){s.removeObject(A.accessRuleSet.accessRules,e),g()}function S(r){var n={};_.find(A.accessRuleSet.accessRules,function(e){var n=e.role?e.role.roleId:null;return e.userArea.userAreaCode===r.userAreaCode&&n==r.roleId})||(I(),o.all([r.roleId?i.getById(r.roleId).then(function(e){n.role=e}):o(function(e){e()}),t.getByCode(r.userAreaCode).then(function(e){n.userArea=e})]).then(function(){A.accessRuleSet.accessRules.push(n),A.accessRuleSet.accessRules=_(A.accessRuleSet.accessRules).chain().sortBy(function(e){return e.role?e.role.roleId:-1}).sortBy(function(e){return e.userArea.userAreaCode}).value(),g()}).finally(C))}function h(e){return A.entityDefinitionName=d.entityDefinitionName,A.entityDefinitionNameLower=d.entityDefinitionName.toLowerCase(),A.entityDescription=d.entityDescription,A.violationActions=[{id:"Error",name:"Error",description:"Error (403: Forbidden)"},{id:"NotFound",name:"Not Found",description:"Not Found (404: Not Found)"}],d.entityAccessLoader().then(function(e){A.accessRuleSet=e,A.command=function(e){var n=_.pick(e,d.entityIdPrefix+"Id","userAreaCodeForSignInRedirect","violationAction");e.userAreaForSignInRedirect&&(n.userAreaCodeForSignInRedirect=e.userAreaForSignInRedirect.userAreaCode,n.redirectToSignIn=!0);return n}(e),A.inheritedRules=[],_.each(A.accessRuleSet.inheritedAccessRules,function(n){n.violationAction=_.findWhere(A.violationActions,{id:n.violationAction}),n.userAreaForSignInRedirect?(n.signInRedirect="Yes",n.signInRedirectDescription="If the user is not signed in, then they will be redirected to the sign in page associated with the "+n.userAreaForSignInRedirect.name+" user area."):(n.signInRedirect="No",n.signInRedirectDescription="No sign in redirection, the default action will trigger instead."),_.each(n.accessRules,function(e){e.accessRuleSet=n,A.inheritedRules.push(e)})}),g()}).then(C.bind(null,e))}function g(){A.userAreasInRules=_(A.accessRuleSet.accessRules).chain().map(function(e){return e.userArea}).uniq(function(e){return e.userAreaCode}).sortBy("userAreaCode").value(),A.userAreasInRules.length&&_.find(A.userAreasInRules,function(e){return e.userAreaCode===A.command.userAreaCodeForSignInRedirect})||(A.command.redirectToSignIn=!1,A.command.userAreaCodeForSignInRedirect=null),!A.command.userAreaCodeForSignInRedirect&&A.userAreasInRules.length&&(A.command.userAreaCodeForSignInRedirect=A.userAreasInRules[0].userAreaCode,console.log("setting vm.command.userAreaCodeForSignInRedirect",A.command.userAreaCodeForSignInRedirect))}function I(e){A.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}function C(e){A.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}A.save=f,A.close=l,A.add=R,A.deleteRule=m,A.globalLoadState=new n,A.saveLoadState=new n,A.formLoadState=new n(!0),A.urlLibrary=u,A.canManage=c.hasPermission(d.entityDefinitionCode+"ACCRUL"),A.editMode=A.canManage,h(A.formLoadState)}]);
+angular.module("cms.shared").factory("shared.entityVersionModalDialogService",["shared.entityVersionService","shared.modalDialogService",function(s,a){var t={},l={entityNameSingular:"Page"};return t.publish=function(t,e,i){var n=i||l,i={title:"Publish "+n.entityNameSingular,message:"Are you sure you want to publish this "+n.entityNameSingular.toLowerCase()+"?",okButtonTitle:"Yes, publish it",onOk:function(){return e(),s.publish(n.isCustomEntity,t)}};return a.confirm(i)},t.unpublish=function(t,e,i){var n=i||l,i={title:"Unpublish "+n.entityNameSingular,message:"Unpublishing this "+n.entityNameSingular.toLowerCase()+" will remove it from the live site and put it into draft status. Are you sure you want to continue?",okButtonTitle:"Yes, unpublish it",onOk:function(){return e(),s.unpublish(n.isCustomEntity,t)}};return a.confirm(i)},t.copyToDraft=function(t,e,i,n,r){var o=r||l,r={title:"Copy "+o.entityNameSingular+" Version",message:"A draft version of this "+o.entityNameSingular.toLowerCase()+" already exists. Copying this version will delete the current draft. Do you wish to continue?",okButtonTitle:"Yes, replace it",onOk:function(){return n(),s.removeDraft(o.isCustomEntity,t).then(u)}};return i?a.confirm(r):(n(),u());function u(){return s.duplicateDraft(o.isCustomEntity,t,e)}},t}]);
angular.module("cms.shared").directive("cmsForm",["shared.internalModulePath",function(e){return{restrict:"E",templateUrl:e+"UIComponents/Form/Form.html",replace:!0,transclude:!0,scope:{editMode:"=cmsEditMode",name:"@cmsName"},compile:function(e,n){angular.isDefined(n.cmsEditMode)||(n.cmsEditMode="true");return r},controller:["$scope",function(e){e.getForm=function(){return e[e.name]},this.getFormScope=function(){return e}}]};function r(e,n,r,t){(function e(n,r){var t=n.$parent;if(!t)return r||n;angular.isDefined(t.vm)&&(r=t.vm);return e(t,r)})(e)[e.name]=e.getForm()}}]);
angular.module("cms.shared").directive("cmsFormSection",["shared.internalModulePath","$timeout",function(e,n){return{restrict:"E",templateUrl:e+"UIComponents/Form/FormSection.html",scope:{title:"@cmsTitle"},replace:!0,transclude:!0,link:function(e,l,t){n(function(){var e=angular.element(l[0].querySelector(".help-inline")),t=angular.element(l[0].querySelector(".toggle-helpers"));e.length&&t.addClass("show").on("click",function(){t.toggleClass("active"),l.toggleClass("show-helpers")})},100)}}}]);
angular.module("cms.shared").directive("cmsFormSectionActions",["shared.internalModulePath","$timeout",function(e,t){return{restrict:"E",templateUrl:e+"UIComponents/Form/FormSectionActions.html",scope:{},replace:!0,transclude:!0,link:function(e,t,n){}}}]);
angular.module("cms.shared").directive("cmsFormSectionAuditData",["shared.internalModulePath",function(t){return{restrict:"E",templateUrl:t+"UIComponents/Form/FormSectionAuditData.html",scope:{auditData:"=cmsAuditData"}}}]);
angular.module("cms.shared").directive("cmsFormStatus",["_","shared.validationErrorService","shared.internalModulePath",function(t,s,r){return{restrict:"E",templateUrl:r+"UIComponents/Form/FormStatus.html",require:["^^cmsForm"],replace:!0,scope:!0,link:{post:function(r,e,n,o){(function(r,e){e=e.getFormScope().getForm(),r.success=function(r){i(this,r,"success")}.bind(r),r.error=function(r){i(this,r,"error")}.bind(r),r.errors=function(r,e){r=t.uniq(r,function(r){return r.message});i(this,e,"error",r)}.bind(r),r.clear=function(){i(this)}.bind(r),e.formStatus=r})(r,o[0]),function(r){s.addHandler("",r.errors),r.$on("$destroy",function(){s.removeHandler(r.errors)})}(r)}}};function i(r,e,n,o){r.message=e,r.errors=o,r.cls=n}}]);
+angular.module("cms.shared").directive("cmsFormDynamicFieldSet",["$compile","_","shared.stringUtilities","shared.internalModulePath","shared.LoadState",function(i,c,d,e,t){return{restrict:"E",replace:!0,scope:{dataSource:"=cmsDataSource",additionalParameters:"=cmsAdditionalParameters"},link:function(t,a,e,r){t.vm;var n=new l;function o(e){var r="";a.empty(),e&&e.modelMetaData.dataModelProperties.length&&(e.modelMetaData.dataModelProperties.forEach(function(e,a){var t=function(e){var t="cms-form-field-";switch(e.dataTemplateName){case"Single":case"Double":case"Decimal":return t+"number";case"String":return t+"text";case"Boolean":return t+"checkbox";case"MultilineText":return t+"text-area"}return t+d.toSnakeCase(e.dataTemplateName)}(e);r+="<"+t,r+=n.map("model",d.lowerCaseFirstWord(e.name)),r+=n.map("title",e.displayName),r+=n.map("required",e.isRequired&&!e.additionalAttributes.readonly),r+=n.map("description",e.description),e.additionalAttributes&&c.each(e.additionalAttributes,function(e,t){r+=n.map(t,e,a)}),r+=">"+t+">"}),a.append(i(r)(t)))}t.$watch("vm.dataSource",function(e){o(e)})},controller:["$scope",function(e){}],bindToController:!0,controllerAs:"vm"};function l(){var a="cms-",o={maxlength:i,minlength:i,min:i,max:i,pattern:i,step:i,placeholder:i,match:e,model:e,options:function(e,t,a){return c(e,"vm.dataSource.modelMetaData.dataModelProperties["+a+"].additionalAttributes['"+e+"']")},required:function(e,t){if(t)return r(e.toLowerCase());return""},rows:i,cols:i};function e(e,t){return c(e,t="vm.dataSource.model['"+t+"']")}function i(e,t){return r(d.toSnakeCase(e),t)}function c(e,t){return r(e=a+d.toSnakeCase(e),t)}function r(e,t){return t?" "+e+'="'+t+'"':" "+e}this.map=function(e,t,a){var r="ValMsg",n=o[e];return!n&&d.endsWith(e,r)?(r=e.substring(0,e.length-r.length),t&&o[r]===i&&(n=i)):n=n||c,n?n(e,t,a):""}}}]);
angular.module("cms.shared").factory("baseFormFieldFactory",["$timeout","shared.stringUtilities","shared.directiveUtilities","shared.validationErrorService",function(s,o,a,l){var e={},u=[{attr:"required",msg:"This field is required"},{attr:"maxlength",msg:"This field cannot be longer than {0} characters"},{attr:"minlength",msg:"This must be at least {0} characters long"}];function i(t){return t.find("input")}function m(e,t,r){var n=e.formScope.getForm()[e.modelName];e.resetCustomErrors(),n.$setValidity("server",!1),i(t).removeClass("ng-pristine").addClass("ng-dirty"),r.forEach(function(t){e.customErrors.push(t)})}return e.create=function(t){return angular.extend({},e.defaultConfig,t)},e.defaultConfig={restrict:"E",replace:!0,require:["^^cmsForm"],scope:{title:"@cmsTitle",description:"@cmsDescription",change:"&cmsChange",model:"=cmsModel",disabled:"=cmsDisabled",readonly:"=cmsReadonly"},compile:function(t,e){return function(t,r){var n=this.getInputEl(t);(this.passThroughAttributes||[]).forEach(function(t){var e=t.name||t;angular.isDefined(r[e])?n[0].setAttribute(r.$attr[e],r[e]):t.default&&n[0].setAttribute(e,t.default)})}.call(this,t,e),this.link.bind(this)},link:function(t,r,e,n){var i=t.vm,n=n[0];i.formScope=n.getFormScope(),i.form=i.formScope.getForm(),a.setModelName(i,e),function(n,i){var s=this;function a(t,e){t=_.find(t,function(t){return t.attr===e});if(t)return _.isFunction(t.msg)?t.msg(n.modelName,i):t.msg}n.validators=[],_.each(i.$attr,function(t,e){var r,t=o.endsWith(e,"ValMsg")?(r=t.substring(0,t.length-"-val-msg".length),i[e]):(r=t,a(s.defaultValidationMessages,e)||a(u,e));t&&n.validators.push({name:i.$normalize(r),message:o.format(t,i[e])})})}(i,e),i.onChange=function(){var t=this;t.resetCustomErrors(),t.change&&s(t.change,0)}.bind(i),i.resetCustomErrors=function(){var t=this.form[this.modelName];t&&t.$setValidity("server",!0);this.customErrors=[]}.bind(i),i.addOrUpdateValidator=function(e){var t=_.filter(this.validators,function(t){return t.name!==e.name});t.push(e),this.validators=t}.bind(i),i.resetCustomErrors(),function(t){var e=_.partial(m,t.vm,r);l.addHandler(t.vm.modelName,e),t.$on("$destroy",function(){l.removeHandler(e)})}(t),t.$watch("vm.model",function(){i.resetCustomErrors()})},controller:function(){},controllerAs:"vm",bindToController:!0,getInputEl:i,passThroughAttributes:[],defaultValidationMessages:[]},e}]);
angular.module("cms.shared").directive("cmsFormFieldChar",["_","shared.internalModulePath","baseFormFieldFactory",function(e,r,a){r={templateUrl:r+"UIComponents/FormFields/FormFieldChar.html",passThroughAttributes:["required","placeholder","pattern","disabled","cmsMatch"]};return a.create(r)}]);
angular.module("cms.shared").directive("cmsFormFieldCheckbox",["shared.internalModulePath","baseFormFieldFactory",function(e,r){e={templateUrl:e+"UIComponents/FormFields/FormFieldCheckbox.html",passThroughAttributes:["disabled"]};return r.create(e)}]);
@@ -532,13 +532,6 @@ angular.module("cms.shared").directive("cmsFormFieldUrl",["_","shared.internalMo
angular.module("cms.shared").directive("cmsFormFieldValidationSummary",["shared.internalModulePath",function(e){return{restrict:"E",templateUrl:e+"UIComponents/FormFields/FormFieldValidationSummary.html",replace:!0}}]);
angular.module("cms.shared").directive("cmsHttpPrefix",function(){return{restrict:"A",require:"ngModel",link:function(e,t,r,i){function n(e){var t="http://";return e&&!/^(https?):\/\//i.test(e)&&-1===t.indexOf(e)&&-1==="https://".indexOf(e)?(i.$setViewValue(t+e),i.$render(),t+e):e}i.$formatters.push(n),i.$parsers.splice(0,0,n)}}});
angular.module("cms.shared").directive("cmsMatch",["$parse","$timeout","shared.internalModulePath","shared.directiveUtilities",function(e,r,t,o){var s="cmsMatch";return{link:function(e,r,t,a){if(!t[s]||!a[1])return;var i=a[0],a=a[1],n=i.getFormScope().getForm(),c=o.parseModelName(t[s]);a.$validators[s]=function(e,r){var t=n[c];return!!t&&e===t.$viewValue}},restrict:"A",require:["^^cmsForm","?ngModel"]}}]);
-angular.module("cms.shared").directive("cmsFormFieldImageAnchorLocationSelector",["_","shared.internalModulePath",function(e,t){return{restrict:"E",templateUrl:t+"UIComponents/ImageAssets/FormFieldImageAnchorLocationSelector.html",scope:{model:"=cmsModel",readonly:"=cmsReadonly"},controller:function(){this.options=[{name:"Top Left",id:"TopLeft"},{name:"Top Center",id:"TopCenter"},{name:"Top Right",id:"TopRight"},{name:"Middle Left",id:"MiddleLeft"},{name:"Middle Center",id:"MiddleCenter"},{name:"Middle Right",id:"MiddleRight"},{name:"Bottom Left",id:"BottomLeft"},{name:"Bottom Center",id:"BottomCenter"},{name:"Bottom Right",id:"BottomRight"}]},controllerAs:"vm",bindToController:!0}}]);
-angular.module("cms.shared").directive("cmsFormFieldImageAsset",["_","shared.internalModulePath","shared.internalContentPath","shared.modalDialogService","shared.stringUtilities","shared.imageService","shared.urlLibrary","baseFormFieldFactory",function(c,u,e,h,g,v,A,t){var p=t.defaultConfig,e={templateUrl:u+"UIComponents/ImageAssets/FormFieldImageAsset.html",scope:c.extend(p.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",updateAsset:"@cmsUpdateAsset"}),passThroughAttributes:["required"],link:function(e,t,s,i){var r,a=e.vm,n=c.has(s,"required");return function(){a.urlLibrary=A,a.showPicker=l,a.remove=o,a.isRemovable=c.isObject(a.model)&&!n,a.filter=function(i){var r={};return e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),r;function e(e,t){var s=i["cms"+e];s&&(e=g.lowerCaseFirstWord(e),r[e]=t?parseInt(s):s)}}(s),a.previewWidth=s.cmsPreviewWidth||450,a.previewHeight=s.cmsPreviewHeight,e.$watch("vm.asset",d),e.$watch("vm.model",m)}(),p.link(e,t,s,i);function o(){d(null)}function l(){h.show({templateUrl:u+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{currentAsset:a.previewAsset,filter:a.filter,onSelected:function(e){!e&&a.previewAsset?(d(null),a.onChange()):(!a.previewAsset||e&&a.previewAsset.imageAssetId!==e.imageAssetId)&&(d(e),a.onChange())}}})}function m(e){e||(a.model=e=void 0),!e||a.previewAsset&&a.previewAsset.imageAssetId==e||v.getById(e).then(function(e){e&&d(e)})}function d(e){e?(a.previewAsset=e,a.isRemovable=!n,a.model=e.imageAssetId,a.updateAsset&&(a.asset=e)):r&&(a.previewAsset=null,a.isRemovable=!1,a.model&&(a.model=null),a.updateAsset&&(a.asset=null)),a.buttonText=a.model?"Change":"Select",r=!0}}};return t.create(e)}]);
-angular.module("cms.shared").directive("cmsFormFieldImageAssetCollection",["_","shared.internalModulePath","shared.LoadState","shared.imageService","shared.modalDialogService","shared.arrayUtilities","shared.stringUtilities","shared.urlLibrary","baseFormFieldFactory",function(l,g,m,c,u,h,f,I,e){var p="imageAssetId",v=e.defaultConfig,t={templateUrl:g+"UIComponents/ImageAssets/FormFieldImageAssetCollection.html",passThroughAttributes:["required","ngRequired"],link:function(e,t,i,r){var o=e.vm;return o.gridLoadState=new m,o.urlLibrary=I,o.showPicker=n,o.remove=a,o.onDrop=s,e.$watch("vm.model",d),v.link(e,t,i,r);function a(e){function t(e,t){t=e.indexOf(t);return 0<=t&&e.splice(t,1)}t(o.gridData,e),t(o.model,e.imageAssetId)}function n(){function e(e,t){var r=i["cms"+e];r&&(e=f.lowerCaseFirstWord(e),a[e]=t?parseInt(r):r)}var a;u.show({templateUrl:g+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{selectedIds:o.model||[],filter:(a={},e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),a),onSelected:function(e){d(o.model=e)}}})}function s(e,t){h.moveObject(o.gridData,t,e,p),o.model=l.pluck(o.gridData,p)}function d(e){e&&e.length?o.gridData&&l.pluck(o.gridData,p).join()==e.join()||(o.gridLoadState.on(),c.getByIdRange(e).then(function(e){o.gridData=e,o.gridLoadState.off()})):o.gridData=[]}}};return e.create(t)}]);
-angular.module("cms.shared").directive("cmsFormFieldImageUpload",["_","$timeout","shared.internalModulePath","shared.internalContentPath","shared.LoadState","shared.stringUtilities","shared.imageFileUtilities","shared.imageService","shared.urlLibrary","shared.validationErrorService","baseFormFieldFactory",function(m,h,e,i,f,u,c,g,v,p,t){var R=t.defaultConfig,i=i+"img/AssetReplacement/",U=i+"preview-not-supported.png",w=i+"image-replacement.png",e={templateUrl:e+"UIComponents/ImageAssets/FormFieldImageUpload.html",scope:m.extend(t.defaultConfig.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",filter:"=cmsFilter",onChange:"&cmsOnChange"}),link:function(e,i,t,n){var a=e.vm;return a.isRequired=m.has(t,"required"),a.remove=o,a.fileChanged=d,a.isRemovable=m.isObject(a.ngModel)&&!a.isRequired,a.mainLoadState=new f(!0),e.$watch("vm.asset",s),g.getSettings().then(r).then(a.mainLoadState.off),R.link(e,i,t,n);function r(e){a.settings=e}function o(){d()}function s(){var e=a.asset;e?(a.previewUrl=v.getImageUrl(e,{width:450}),a.isRemovable=!a.isRequired,a.model={name:e.fileName+"."+e.extension,size:e.fileSizeInBytes,isCurrentFile:!0}):(a.previewUrl=w,a.isRemovable=!1,a.model&&(a.model=void 0)),l()}function d(e){function i(){a.model=void 0,a.previewUrl=w,a.isRemovable=!1,a.isResized=!1,t()}function t(){l(),a.onChange&&a.onChange()}e&&e[0]?(a.mainLoadState.on(),c.getFileInfoAndResizeIfRequired(e[0],a.settings).then(function(e){e||i();a.model=e.file,function(e){var i=a.filter;function t(e,i){return!(1',replace:!0,transclude:!0}});
@@ -547,12 +540,17 @@ angular.module("cms.shared").directive("cmsPageFilter",function(){return{restric
angular.module("cms.shared").directive("cmsPageHeader",function(){return{restrict:"E",template:'',replace:!0,transclude:!0,scope:{title:"@cmsTitle",parentTitle:"@cmsParentTitle",parentHref:"@cmsParentHref"}}});
angular.module("cms.shared").directive("cmsPageHeaderButtons",function(){return{restrict:"E",template:'',replace:!0,transclude:!0}});
angular.module("cms.shared").directive("cmsPageSubHeader",function(){return{restrict:"E",template:'',replace:!0,transclude:!0}});
+angular.module("cms.shared").directive("cmsFormFieldImageAnchorLocationSelector",["_","shared.internalModulePath",function(e,t){return{restrict:"E",templateUrl:t+"UIComponents/ImageAssets/FormFieldImageAnchorLocationSelector.html",scope:{model:"=cmsModel",readonly:"=cmsReadonly"},controller:function(){this.options=[{name:"Top Left",id:"TopLeft"},{name:"Top Center",id:"TopCenter"},{name:"Top Right",id:"TopRight"},{name:"Middle Left",id:"MiddleLeft"},{name:"Middle Center",id:"MiddleCenter"},{name:"Middle Right",id:"MiddleRight"},{name:"Bottom Left",id:"BottomLeft"},{name:"Bottom Center",id:"BottomCenter"},{name:"Bottom Right",id:"BottomRight"}]},controllerAs:"vm",bindToController:!0}}]);
+angular.module("cms.shared").directive("cmsFormFieldImageAsset",["_","shared.internalModulePath","shared.internalContentPath","shared.modalDialogService","shared.stringUtilities","shared.imageService","shared.urlLibrary","baseFormFieldFactory",function(c,u,e,h,g,v,A,t){var p=t.defaultConfig,e={templateUrl:u+"UIComponents/ImageAssets/FormFieldImageAsset.html",scope:c.extend(p.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",updateAsset:"@cmsUpdateAsset"}),passThroughAttributes:["required"],link:function(e,t,s,i){var r,a=e.vm,n=c.has(s,"required");return function(){a.urlLibrary=A,a.showPicker=l,a.remove=o,a.isRemovable=c.isObject(a.model)&&!n,a.filter=function(i){var r={};return e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),r;function e(e,t){var s=i["cms"+e];s&&(e=g.lowerCaseFirstWord(e),r[e]=t?parseInt(s):s)}}(s),a.previewWidth=s.cmsPreviewWidth||450,a.previewHeight=s.cmsPreviewHeight,e.$watch("vm.asset",d),e.$watch("vm.model",m)}(),p.link(e,t,s,i);function o(){d(null)}function l(){h.show({templateUrl:u+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{currentAsset:a.previewAsset,filter:a.filter,onSelected:function(e){!e&&a.previewAsset?(d(null),a.onChange()):(!a.previewAsset||e&&a.previewAsset.imageAssetId!==e.imageAssetId)&&(d(e),a.onChange())}}})}function m(e){e||(a.model=e=void 0),!e||a.previewAsset&&a.previewAsset.imageAssetId==e||v.getById(e).then(function(e){e&&d(e)})}function d(e){e?(a.previewAsset=e,a.isRemovable=!n,a.model=e.imageAssetId,a.updateAsset&&(a.asset=e)):r&&(a.previewAsset=null,a.isRemovable=!1,a.model&&(a.model=null),a.updateAsset&&(a.asset=null)),a.buttonText=a.model?"Change":"Select",r=!0}}};return t.create(e)}]);
+angular.module("cms.shared").directive("cmsFormFieldImageAssetCollection",["_","shared.internalModulePath","shared.LoadState","shared.imageService","shared.modalDialogService","shared.arrayUtilities","shared.stringUtilities","shared.urlLibrary","baseFormFieldFactory",function(l,g,m,c,u,h,f,I,e){var p="imageAssetId",v=e.defaultConfig,t={templateUrl:g+"UIComponents/ImageAssets/FormFieldImageAssetCollection.html",passThroughAttributes:["required","ngRequired"],link:function(e,t,i,r){var o=e.vm;return o.gridLoadState=new m,o.urlLibrary=I,o.showPicker=n,o.remove=a,o.onDrop=s,e.$watch("vm.model",d),v.link(e,t,i,r);function a(e){function t(e,t){t=e.indexOf(t);return 0<=t&&e.splice(t,1)}t(o.gridData,e),t(o.model,e.imageAssetId)}function n(){function e(e,t){var r=i["cms"+e];r&&(e=f.lowerCaseFirstWord(e),a[e]=t?parseInt(r):r)}var a;u.show({templateUrl:g+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{selectedIds:o.model||[],filter:(a={},e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),a),onSelected:function(e){d(o.model=e)}}})}function s(e,t){h.moveObject(o.gridData,t,e,p),o.model=l.pluck(o.gridData,p)}function d(e){e&&e.length?o.gridData&&l.pluck(o.gridData,p).join()==e.join()||(o.gridLoadState.on(),c.getByIdRange(e).then(function(e){o.gridData=e,o.gridLoadState.off()})):o.gridData=[]}}};return e.create(t)}]);
+angular.module("cms.shared").directive("cmsFormFieldImageUpload",["_","$timeout","shared.internalModulePath","shared.internalContentPath","shared.LoadState","shared.stringUtilities","shared.imageFileUtilities","shared.imageService","shared.urlLibrary","shared.validationErrorService","baseFormFieldFactory",function(m,h,e,i,f,u,c,g,v,p,t){var R=t.defaultConfig,i=i+"img/AssetReplacement/",U=i+"preview-not-supported.png",w=i+"image-replacement.png",e={templateUrl:e+"UIComponents/ImageAssets/FormFieldImageUpload.html",scope:m.extend(t.defaultConfig.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",filter:"=cmsFilter",onChange:"&cmsOnChange"}),link:function(e,i,t,n){var a=e.vm;return a.isRequired=m.has(t,"required"),a.remove=o,a.fileChanged=d,a.isRemovable=m.isObject(a.ngModel)&&!a.isRequired,a.mainLoadState=new f(!0),e.$watch("vm.asset",s),g.getSettings().then(r).then(a.mainLoadState.off),R.link(e,i,t,n);function r(e){a.settings=e}function o(){d()}function s(){var e=a.asset;e?(a.previewUrl=v.getImageUrl(e,{width:450}),a.isRemovable=!a.isRequired,a.model={name:e.fileName+"."+e.extension,size:e.fileSizeInBytes,isCurrentFile:!0}):(a.previewUrl=w,a.isRemovable=!1,a.model&&(a.model=void 0)),l()}function d(e){function i(){a.model=void 0,a.previewUrl=w,a.isRemovable=!1,a.isResized=!1,t()}function t(){l(),a.onChange&&a.onChange()}e&&e[0]?(a.mainLoadState.on(),c.getFileInfoAndResizeIfRequired(e[0],a.settings).then(function(e){e||i();a.model=e.file,function(e){var i=a.filter;function t(e,i){return!(1