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: "![Alt " + alt + "](" + path + ")", - html: "" + alt + "", - 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: "![Alt " + alt + "](" + path + ")", + html: "" + alt + "", + 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: '

{{parentTitle}} > {{title}}

', + 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: '

{{parentTitle}} > {{title}}

', - 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"}),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:"![Alt "+a+"]("+t+")",html:""+a+"",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+=">"}),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:'

{{parentTitle}} > {{title}}

',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