Advertisement
Guest User

Untitled

a guest
Sep 10th, 2015
1,879
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*! umbraco
  2.  * https://github.com/umbraco/umbraco-cms/
  3.  * Copyright (c) 2015 Umbraco HQ;
  4.  * Licensed MIT
  5.  */
  6.  
  7. (function() {
  8.  
  9.  
  10. /**
  11.  * @ngdoc controller
  12.  * @name Umbraco.MainController
  13.  * @function
  14.  *
  15.  * @description
  16.  * The main application controller
  17.  *
  18.  */
  19. function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale) {
  20.  
  21.     //the null is important because we do an explicit bool check on this in the view
  22.     //the avatar is by default the umbraco logo    
  23.     $scope.authenticated = null;
  24.     $scope.avatar = "assets/img/application/logo.png";
  25.     $scope.touchDevice = appState.getGlobalState("touchDevice");
  26.     //subscribes to notifications in the notification service
  27.     $scope.notifications = notificationsService.current;
  28.     $scope.$watch('notificationsService.current', function (newVal, oldVal, scope) {
  29.         if (newVal) {
  30.             $scope.notifications = newVal;
  31.         }
  32.     });
  33.  
  34.     $scope.removeNotification = function (index) {
  35.         notificationsService.remove(index);
  36.     };
  37.  
  38.     $scope.closeDialogs = function (event) {
  39.         //only close dialogs if non-link and non-buttons are clicked
  40.         var el = event.target.nodeName;
  41.         var els = ["INPUT","A","BUTTON"];
  42.  
  43.         if(els.indexOf(el) >= 0){return;}
  44.  
  45.         var parents = $(event.target).parents("a,button");
  46.         if(parents.length > 0){
  47.             return;
  48.         }
  49.  
  50.         //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
  51.         var nav = $(event.target).parents("#dialog");
  52.         if (nav.length === 1) {
  53.             return;
  54.         }
  55.  
  56.         eventsService.emit("app.closeDialogs", event);
  57.     };
  58.  
  59.     var evts = [];
  60.  
  61.     //when a user logs out or timesout
  62.     evts.push(eventsService.on("app.notAuthenticated", function() {
  63.         $scope.authenticated = null;
  64.         $scope.user = null;
  65.     }));
  66.    
  67.     //when the app is read/user is logged in, setup the data
  68.     evts.push(eventsService.on("app.ready", function (evt, data) {
  69.        
  70.         $scope.authenticated = data.authenticated;
  71.         $scope.user = data.user;
  72.  
  73.         updateChecker.check().then(function(update){
  74.             if(update && update !== "null"){
  75.                 if(update.type !== "None"){
  76.                     var notification = {
  77.                            headline: "Update available",
  78.                            message: "Click to download",
  79.                            sticky: true,
  80.                            type: "info",
  81.                            url: update.url
  82.                     };
  83.                     notificationsService.add(notification);
  84.                 }
  85.             }
  86.         })
  87.  
  88.         //if the user has changed we need to redirect to the root so they don't try to continue editing the
  89.         //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
  90.         if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
  91.             $location.path("/").search("");
  92.             historyService.removeAll();
  93.             treeService.clearCache();
  94.         }
  95.  
  96.         //Load locale file
  97.         if ($scope.user.locale) {
  98.             tmhDynamicLocale.set($scope.user.locale);
  99.         }
  100.  
  101.         if($scope.user.emailHash){
  102.             $timeout(function () {                
  103.                 //yes this is wrong..
  104.                 $("#avatar-img").fadeTo(1000, 0, function () {
  105.                     $timeout(function () {
  106.                         //this can be null if they time out
  107.                         if ($scope.user && $scope.user.emailHash) {
  108.                             $scope.avatar = "//www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=mm";
  109.                         }
  110.                     });
  111.                     $("#avatar-img").fadeTo(1000, 1);
  112.                 });
  113.                
  114.               }, 3000);  
  115.         }
  116.  
  117.     }));
  118.  
  119.     //ensure to unregister from all events!
  120.     $scope.$on('$destroy', function () {
  121.         for (var e in evts) {
  122.             eventsService.unsubscribe(evts[e]);
  123.         }
  124.     });
  125.  
  126. }
  127.  
  128.  
  129. //register it
  130. angular.module('umbraco').controller("Umbraco.MainController", MainController).
  131.         config(function (tmhDynamicLocaleProvider) {
  132.             //Set url for locale files
  133.             tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js');
  134.         });
  135.  
  136. /**
  137.  * @ngdoc controller
  138.  * @name Umbraco.NavigationController
  139.  * @function
  140.  *
  141.  * @description
  142.  * Handles the section area of the app
  143.  *
  144.  * @param {navigationService} navigationService A reference to the navigationService
  145.  */
  146. function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
  147.  
  148.     //TODO: Need to think about this and an nicer way to acheive what this is doing.
  149.     //the tree event handler i used to subscribe to the main tree click events
  150.     $scope.treeEventHandler = $({});
  151.     navigationService.setupTreeEvents($scope.treeEventHandler);
  152.  
  153.     //Put the navigation service on this scope so we can use it's methods/properties in the view.
  154.     // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since
  155.     //   when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one.
  156.     $scope.nav = navigationService;
  157.     // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope,
  158.     // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice.
  159.     $rootScope.nav = navigationService;
  160.  
  161.     //set up our scope vars
  162.     $scope.showContextMenuDialog = false;
  163.     $scope.showContextMenu = false;
  164.     $scope.showSearchResults = false;
  165.     $scope.menuDialogTitle = null;
  166.     $scope.menuActions = [];
  167.     $scope.menuNode = null;
  168.  
  169.     $scope.currentSection = appState.getSectionState("currentSection");
  170.     $scope.showNavigation = appState.getGlobalState("showNavigation");
  171.  
  172.     //trigger search with a hotkey:
  173.     keyboardService.bind("ctrl+shift+s", function () {
  174.         navigationService.showSearch();
  175.     });
  176.  
  177.     //trigger dialods with a hotkey:
  178.     keyboardService.bind("esc", function () {
  179.         eventsService.emit("app.closeDialogs");
  180.     });
  181.  
  182.     $scope.selectedId = navigationService.currentId;
  183.  
  184.     var evts = [];
  185.  
  186.     //Listen for global state changes
  187.     evts.push(eventsService.on("appState.globalState.changed", function(e, args) {
  188.         if (args.key === "showNavigation") {
  189.             $scope.showNavigation = args.value;
  190.         }
  191.     }));
  192.  
  193.     //Listen for menu state changes
  194.     evts.push(eventsService.on("appState.menuState.changed", function(e, args) {
  195.         if (args.key === "showMenuDialog") {
  196.             $scope.showContextMenuDialog = args.value;
  197.         }
  198.         if (args.key === "showMenu") {
  199.             $scope.showContextMenu = args.value;
  200.         }
  201.         if (args.key === "dialogTitle") {
  202.             $scope.menuDialogTitle = args.value;
  203.         }
  204.         if (args.key === "menuActions") {
  205.             $scope.menuActions = args.value;
  206.         }
  207.         if (args.key === "currentNode") {
  208.             $scope.menuNode = args.value;
  209.         }
  210.     }));
  211.  
  212.     //Listen for section state changes
  213.     evts.push(eventsService.on("appState.treeState.changed", function(e, args) {
  214.         var f = args;
  215.         if (args.value.root && args.value.root.metaData.containsTrees === false) {
  216.             $rootScope.emptySection = true;
  217.         }
  218.         else {
  219.             $rootScope.emptySection = false;
  220.         }
  221.     }));
  222.  
  223.     //Listen for section state changes
  224.     evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
  225.         //section changed
  226.         if (args.key === "currentSection") {
  227.             $scope.currentSection = args.value;
  228.         }
  229.         //show/hide search results
  230.         if (args.key === "showSearchResults") {
  231.             $scope.showSearchResults = args.value;
  232.         }
  233.     }));
  234.  
  235.     //This reacts to clicks passed to the body element which emits a global call to close all dialogs
  236.     evts.push(eventsService.on("app.closeDialogs", function(event) {
  237.         if (appState.getGlobalState("stickyNavigation")) {
  238.             navigationService.hideNavigation();
  239.             //TODO: don't know why we need this? - we are inside of an angular event listener.
  240.             angularHelper.safeApply($scope);
  241.         }
  242.     }));
  243.  
  244.     //when a user logs out or timesout
  245.     evts.push(eventsService.on("app.notAuthenticated", function() {
  246.         $scope.authenticated = false;
  247.     }));
  248.  
  249.     //when the application is ready and the user is authorized setup the data
  250.     evts.push(eventsService.on("app.ready", function(evt, data) {
  251.         $scope.authenticated = true;
  252.     }));
  253.  
  254.     //this reacts to the options item in the tree
  255.     //todo, migrate to nav service
  256.     $scope.searchShowMenu = function (ev, args) {
  257.         //always skip default
  258.         args.skipDefault = true;
  259.         navigationService.showMenu(ev, args);
  260.     };
  261.  
  262.     //todo, migrate to nav service
  263.     $scope.searchHide = function () {
  264.         navigationService.hideSearch();
  265.     };
  266.  
  267.     //the below assists with hiding/showing the tree
  268.     var treeActive = false;
  269.  
  270.     //Sets a service variable as soon as the user hovers the navigation with the mouse
  271.     //used by the leaveTree method to delay hiding
  272.     $scope.enterTree = function (event) {
  273.         treeActive = true;
  274.     };
  275.  
  276.     // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
  277.     $scope.leaveTree = function(event) {
  278.         //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down
  279.         if (!event) {
  280.             return;
  281.         }
  282.         if (!appState.getGlobalState("touchDevice")) {
  283.             treeActive = false;
  284.             $timeout(function() {
  285.                 if (!treeActive) {
  286.                     navigationService.hideTree();
  287.                 }
  288.             }, 300);
  289.         }
  290.     };
  291.  
  292.     //ensure to unregister from all events!
  293.     $scope.$on('$destroy', function () {
  294.         for (var e in evts) {
  295.             eventsService.unsubscribe(evts[e]);
  296.         }
  297.     });
  298. }
  299.  
  300. //register it
  301. angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController);
  302.  
  303. /**
  304.  * @ngdoc controller
  305.  * @name Umbraco.SearchController
  306.  * @function
  307.  *
  308.  * @description
  309.  * Controls the search functionality in the site
  310.  *  
  311.  */
  312. function SearchController($scope, searchService, $log, $location, navigationService, $q) {
  313.  
  314.     $scope.searchTerm = null;
  315.     $scope.searchResults = [];
  316.     $scope.isSearching = false;
  317.     $scope.selectedResult = -1;
  318.  
  319.  
  320.     $scope.navigateResults = function(ev){
  321.         //38: up 40: down, 13: enter
  322.  
  323.         switch(ev.keyCode){
  324.             case 38:
  325.                     iterateResults(true);
  326.                 break;
  327.             case 40:
  328.                     iterateResults(false);
  329.                 break;
  330.             case 13:
  331.                 if ($scope.selectedItem) {
  332.                     $location.path($scope.selectedItem.editorPath);
  333.                     navigationService.hideSearch();
  334.                 }                
  335.                 break;
  336.         }
  337.     };
  338.  
  339.  
  340.     var group = undefined;
  341.     var groupIndex = -1;
  342.     var itemIndex = -1;
  343.     $scope.selectedItem = undefined;
  344.        
  345.  
  346.     function iterateResults(up){
  347.         //default group
  348.         if(!group){
  349.             group = $scope.groups[0];
  350.             groupIndex = 0;
  351.         }
  352.  
  353.         if(up){
  354.             if(itemIndex === 0){
  355.                 if(groupIndex === 0){
  356.                     gotoGroup($scope.groups.length-1, true);
  357.                 }else{
  358.                     gotoGroup(groupIndex-1, true);
  359.                 }
  360.             }else{
  361.                 gotoItem(itemIndex-1);
  362.             }
  363.         }else{
  364.             if(itemIndex < group.results.length-1){
  365.                 gotoItem(itemIndex+1);
  366.             }else{
  367.                 if(groupIndex === $scope.groups.length-1){
  368.                     gotoGroup(0);
  369.                 }else{
  370.                     gotoGroup(groupIndex+1);
  371.                 }
  372.             }
  373.         }
  374.     }
  375.  
  376.     function gotoGroup(index, up){
  377.         groupIndex = index;
  378.         group = $scope.groups[groupIndex];
  379.        
  380.         if(up){
  381.             gotoItem(group.results.length-1);
  382.         }else{
  383.             gotoItem(0);
  384.         }
  385.     }
  386.  
  387.     function gotoItem(index){
  388.         itemIndex = index;
  389.         $scope.selectedItem = group.results[itemIndex];
  390.     }
  391.  
  392.     //used to cancel any request in progress if another one needs to take it's place
  393.     var canceler = null;
  394.  
  395.     $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) {
  396.         $scope.$apply(function() {
  397.             if ($scope.searchTerm) {
  398.                 if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
  399.                     $scope.isSearching = true;
  400.                     navigationService.showSearch();
  401.                     $scope.selectedItem = undefined;
  402.  
  403.                     //a canceler exists, so perform the cancelation operation and reset
  404.                     if (canceler) {
  405.                         console.log("CANCELED!");
  406.                         canceler.resolve();
  407.                         canceler = $q.defer();
  408.                     }
  409.                     else {
  410.                         canceler = $q.defer();
  411.                     }
  412.  
  413.                     searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function(result) {
  414.                         $scope.groups = _.filter(result, function (group) { return group.results.length > 0; });
  415.                         //set back to null so it can be re-created
  416.                         canceler = null;
  417.                     });
  418.                 }
  419.             }
  420.             else {
  421.                 $scope.isSearching = false;
  422.                 navigationService.hideSearch();
  423.                 $scope.selectedItem = undefined;
  424.             }
  425.         });
  426.     }, 200));
  427.  
  428. }
  429. //register it
  430. angular.module('umbraco').controller("Umbraco.SearchController", SearchController);
  431.  
  432. /**
  433.  * @ngdoc controller
  434.  * @name Umbraco.MainController
  435.  * @function
  436.  *
  437.  * @description
  438.  * The controller for the AuthorizeUpgrade login page
  439.  *
  440.  */
  441. function AuthorizeUpgradeController($scope, $window) {
  442.    
  443.     //Add this method to the scope - this method will be called by the login dialog controller when the login is successful
  444.     // then we'll handle the redirect.
  445.     $scope.submit = function (event) {
  446.  
  447.         var qry = $window.location.search.trimStart("?").split("&");
  448.         var redir = _.find(qry, function(item) {
  449.             return item.startsWith("redir=");
  450.         });
  451.         if (redir) {
  452.             $window.location = decodeURIComponent(redir.split("=")[1]);
  453.         }
  454.         else {
  455.             $window.location = "/";
  456.         }
  457.        
  458.     };
  459.  
  460. }
  461.  
  462. angular.module('umbraco').controller("Umbraco.AuthorizeUpgradeController", AuthorizeUpgradeController);
  463. /**
  464.  * @ngdoc controller
  465.  * @name Umbraco.DashboardController
  466.  * @function
  467.  *
  468.  * @description
  469.  * Controls the dashboards of the application
  470.  *
  471.  */
  472.  
  473. function DashboardController($scope, $routeParams, dashboardResource, localizationService) {
  474.     $scope.dashboard = {};
  475.     localizationService.localize("sections_" + $routeParams.section).then(function(name){
  476.         $scope.dashboard.name = name;
  477.     });
  478.    
  479.     dashboardResource.getDashboard($routeParams.section).then(function(tabs){
  480.         $scope.dashboard.tabs = tabs;
  481.     });
  482. }
  483.  
  484.  
  485. //register it
  486. angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController);
  487. angular.module("umbraco")
  488.     .controller("Umbraco.Dialogs.ApprovedColorPickerController", function ($scope, $http, umbPropEditorHelper, assetsService) {
  489.         assetsService.loadJs("lib/cssparser/cssparser.js")
  490.                 .then(function () {
  491.  
  492.                     var cssPath = $scope.dialogData.cssPath;
  493.                     $scope.cssClass = $scope.dialogData.cssClass;
  494.  
  495.                     $scope.classes = [];
  496.  
  497.                     $scope.change = function (newClass) {
  498.                         $scope.model.value = newClass;
  499.                     }
  500.  
  501.                     $http.get(cssPath)
  502.                         .success(function (data) {
  503.                             var parser = new CSSParser();
  504.                             $scope.classes = parser.parse(data, false, false).cssRules;
  505.                             $scope.classes.splice(0, 0, "noclass");
  506.                         })
  507.  
  508.                     assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css");
  509.                     assetsService.loadCss(cssPath);
  510.                 });
  511. });
  512. function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
  513.    
  514.     $scope.defaultButton = null;
  515.     $scope.subButtons = [];
  516.     var dialogOptions = $scope.$parent.dialogOptions;
  517.  
  518.     // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
  519.     function performSave(args) {
  520.         contentEditingHelper.contentEditorPerformSave({
  521.             statusMessage: args.statusMessage,
  522.             saveMethod: args.saveMethod,
  523.             scope: $scope,
  524.             content: $scope.content
  525.         }).then(function (content) {
  526.             //success            
  527.             if (dialogOptions.closeOnSave) {
  528.                 $scope.submit(content);
  529.             }
  530.  
  531.         }, function(err) {
  532.             //error
  533.         });
  534.     }
  535.  
  536.     function filterTabs(entity, blackList) {
  537.         if (blackList) {
  538.             _.each(entity.tabs, function (tab) {
  539.                 tab.hide = _.contains(blackList, tab.alias);
  540.             });
  541.         }
  542.  
  543.         return entity;
  544.     };
  545.    
  546.     function init(content) {
  547.         var buttons = contentEditingHelper.configureContentEditorButtons({
  548.             create: $routeParams.create,
  549.             content: content,
  550.             methods: {
  551.                 saveAndPublish: $scope.saveAndPublish,
  552.                 sendToPublish: $scope.sendToPublish,
  553.                 save: $scope.save,
  554.                 unPublish: angular.noop
  555.             }
  556.         });
  557.         $scope.defaultButton = buttons.defaultButton;
  558.         $scope.subButtons = buttons.subButtons;
  559.  
  560.         //This is a total hack but we have really no other way of sharing data to the property editors of this
  561.         // content item, so we'll just set the property on the content item directly
  562.         $scope.content.isDialogEditor = true;
  563.  
  564.         editorState.set($scope.content);
  565.     }
  566.  
  567.     //check if the entity is being passed in, otherwise load it from the server
  568.     if (angular.isObject(dialogOptions.entity)) {
  569.         $scope.loaded = true;
  570.         $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter);
  571.         init($scope.content);
  572.     }
  573.     else {
  574.         contentResource.getById(dialogOptions.id)
  575.             .then(function(data) {
  576.                 $scope.loaded = true;
  577.                 $scope.content = filterTabs(data, dialogOptions.tabFilter);
  578.                 init($scope.content);
  579.                 //in one particular special case, after we've created a new item we redirect back to the edit
  580.                 // route but there might be server validation errors in the collection which we need to display
  581.                 // after the redirect, so we will bind all subscriptions which will show the server validation errors
  582.                 // if there are any and then clear them so the collection no longer persists them.
  583.                 serverValidationManager.executeAndClearAllSubscriptions();
  584.             });
  585.     }  
  586.  
  587.     $scope.sendToPublish = function () {
  588.         performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." });
  589.     };
  590.  
  591.     $scope.saveAndPublish = function () {
  592.         performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." });
  593.     };
  594.  
  595.     $scope.save = function () {
  596.         performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." });
  597.     };
  598.  
  599.     // this method is called for all action buttons and then we proxy based on the btn definition
  600.     $scope.performAction = function (btn) {
  601.  
  602.         if (!btn || !angular.isFunction(btn.handler)) {
  603.             throw "btn.handler must be a function reference";
  604.         }
  605.  
  606.         if (!$scope.busy) {
  607.             btn.handler.apply(this);
  608.         }
  609.     };
  610.  
  611. }
  612.  
  613.  
  614. angular.module("umbraco")
  615.     .controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController);
  616. angular.module("umbraco")
  617.     .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService) {
  618.         $scope.section = $routeParams.section;
  619.         $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
  620.        
  621.         if(!$scope.section){
  622.             $scope.section ="content";
  623.         }
  624.  
  625.         var rq = {};
  626.         rq.section = $scope.section;
  627.        
  628.         userService.getCurrentUser().then(function(user){
  629.            
  630.             rq.usertype = user.userType;
  631.             rq.lang = user.locale;
  632.  
  633.             if($routeParams.url){
  634.                 rq.path = decodeURIComponent($routeParams.url);
  635.                
  636.                 if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){
  637.                     rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);   
  638.                 }
  639.  
  640.                 if(rq.path.indexOf(".aspx") > 0){
  641.                     rq.path = rq.path.substring(0, rq.path.indexOf(".aspx"));
  642.                 }
  643.                        
  644.             }else{
  645.                 rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method;
  646.             }
  647.  
  648.             helpService.findHelp(rq).then(function(topics){
  649.                 $scope.topics = topics;
  650.             });
  651.  
  652.             helpService.findVideos(rq).then(function(videos){
  653.                 $scope.videos = videos;
  654.             });
  655.  
  656.         });
  657.  
  658.        
  659.     });
  660. //used for the icon picker dialog
  661. angular.module("umbraco")
  662.     .controller("Umbraco.Dialogs.IconPickerController",
  663.         function ($scope, iconHelper) {
  664.            
  665.             iconHelper.getIcons().then(function(icons){
  666.                 $scope.icons = icons;
  667.             });
  668.  
  669.             $scope.submitClass = function(icon){
  670.                 if($scope.color)
  671.                 {
  672.                     $scope.submit(icon + " " + $scope.color);
  673.                 }else{
  674.                     $scope.submit(icon);   
  675.                 }
  676.             };
  677.         }
  678.     );
  679. /**
  680.  * @ngdoc controller
  681.  * @name Umbraco.Dialogs.InsertMacroController
  682.  * @function
  683.  *
  684.  * @description
  685.  * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this
  686.  * is actually loaded into an iframe with full html.
  687.  */
  688. function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) {
  689.  
  690.     /** changes the view to edit the params of the selected macro */
  691.     function editParams() {
  692.         //get the macro params if there are any
  693.         macroResource.getMacroParameters($scope.selectedMacro.id)
  694.             .then(function (data) {
  695.  
  696.                 //go to next page if there are params otherwise we can just exit
  697.                 if (!angular.isArray(data) || data.length === 0) {
  698.                     //we can just exist!
  699.                     submitForm();
  700.  
  701.                 } else {
  702.                     $scope.wizardStep = "paramSelect";
  703.                     $scope.macroParams = data;
  704.                    
  705.                     //fill in the data if we are editing this macro
  706.                     if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) {
  707.                         _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) {
  708.                             var prop = _.find($scope.macroParams, function (item) {
  709.                                 return item.alias == key;
  710.                             });
  711.                             if (prop) {
  712.  
  713.                                 if (_.isString(val)) {
  714.                                     //we need to unescape values as they have most likely been escaped while inserted
  715.                                     val = _.unescape(val);
  716.  
  717.                                     //detect if it is a json string
  718.                                     if (val.detectIsJson()) {
  719.                                         try {
  720.                                             //Parse it to json
  721.                                             prop.value = angular.fromJson(val);
  722.                                         }
  723.                                         catch (e) {
  724.                                             // not json
  725.                                             prop.value = val;
  726.                                         }
  727.                                     }
  728.                                     else {
  729.                                         prop.value = val;
  730.                                     }
  731.                                 }
  732.                                 else {
  733.                                     prop.value = val;
  734.                                 }
  735.                             }
  736.                         });
  737.  
  738.                     }
  739.                 }
  740.             });
  741.     }
  742.  
  743.     /** submit the filled out macro params */
  744.     function submitForm() {
  745.        
  746.         //collect the value data, close the dialog and send the data back to the caller
  747.  
  748.         //create a dictionary for the macro params
  749.         var paramDictionary = {};
  750.         _.each($scope.macroParams, function (item) {
  751.  
  752.             var val = item.value;
  753.  
  754.             if (item.value != null && item.value != undefined && !_.isString(item.value)) {
  755.                 try {
  756.                     val = angular.toJson(val);
  757.                 }
  758.                 catch (e) {
  759.                     // not json          
  760.                 }
  761.             }
  762.            
  763.             //each value needs to be xml escaped!! since the value get's stored as an xml attribute
  764.             paramDictionary[item.alias] = _.escape(val);
  765.  
  766.         });
  767.        
  768.         //need to find the macro alias for the selected id
  769.         var macroAlias = $scope.selectedMacro.alias;
  770.  
  771.         //get the syntax based on the rendering engine
  772.         var syntax;
  773.         if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "WebForms") {
  774.             syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
  775.         }
  776.         else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "Mvc") {
  777.             syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
  778.         }
  779.         else {
  780.             syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
  781.         }
  782.  
  783.         $scope.submit({ syntax: syntax, macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
  784.     }
  785.  
  786.     $scope.macros = [];
  787.     $scope.selectedMacro = null;
  788.     $scope.wizardStep = "macroSelect";
  789.     $scope.macroParams = [];
  790.    
  791.     $scope.submitForm = function () {
  792.        
  793.         if (formHelper.submitForm({ scope: $scope })) {
  794.        
  795.             formHelper.resetForm({ scope: $scope });
  796.  
  797.             if ($scope.wizardStep === "macroSelect") {
  798.                 editParams();
  799.             }
  800.             else {
  801.                 submitForm();
  802.             }
  803.  
  804.         }
  805.     };
  806.  
  807.     //here we check to see if we've been passed a selected macro and if so we'll set the
  808.     //editor to start with parameter editing
  809.     if ($scope.dialogData && $scope.dialogData.macroData) {
  810.         $scope.wizardStep = "paramSelect";
  811.     }
  812.    
  813.     //get the macro list - pass in a filter if it is only for rte
  814.     entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null)
  815.         .then(function (data) {
  816.  
  817.             //if 'allowedMacros' is specified, we need to filter
  818.             if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) {
  819.                 $scope.macros = _.filter(data, function(d) {
  820.                     return _.contains($scope.dialogData.allowedMacros, d.alias);
  821.                 });
  822.             }
  823.             else {
  824.                 $scope.macros = data;
  825.             }
  826.            
  827.  
  828.             //check if there's a pre-selected macro and if it exists
  829.             if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
  830.                 var found = _.find(data, function (item) {
  831.                     return item.alias === $scope.dialogData.macroData.macroAlias;
  832.                 });
  833.                 if (found) {
  834.                     //select the macro and go to next screen
  835.                     $scope.selectedMacro = found;
  836.                     editParams();
  837.                     return;
  838.                 }
  839.             }
  840.             //we don't have a pre-selected macro so ensure the correct step is set
  841.             $scope.wizardStep = "macroSelect";
  842.         });
  843.  
  844.  
  845. }
  846.  
  847. angular.module("umbraco").controller("Umbraco.Dialogs.InsertMacroController", InsertMacroController);
  848.  
  849. /**
  850.  * @ngdoc controller
  851.  * @name Umbraco.Dialogs.LegacyDeleteController
  852.  * @function
  853.  *
  854.  * @description
  855.  * The controller for deleting content
  856.  */
  857. function LegacyDeleteController($scope, legacyResource, treeService, navigationService) {
  858.  
  859.     $scope.performDelete = function() {
  860.  
  861.         //mark it for deletion (used in the UI)
  862.         $scope.currentNode.loading = true;
  863.  
  864.         legacyResource.deleteItem({            
  865.             nodeId: $scope.currentNode.id,
  866.             nodeType: $scope.currentNode.nodeType,
  867.             alias: $scope.currentNode.name,
  868.         }).then(function () {
  869.             $scope.currentNode.loading = false;
  870.             //TODO: Need to sync tree, etc...
  871.             treeService.removeNode($scope.currentNode);
  872.             navigationService.hideMenu();
  873.         });
  874.  
  875.     };
  876.  
  877.     $scope.cancel = function() {
  878.         navigationService.hideDialog();
  879.     };
  880. }
  881.  
  882. angular.module("umbraco").controller("Umbraco.Dialogs.LegacyDeleteController", LegacyDeleteController);
  883.  
  884. //used for the media picker dialog
  885. angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
  886.     function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
  887.         var dialogOptions = $scope.dialogOptions;
  888.  
  889.         var searchText = "Search...";
  890.         localizationService.localize("general_search").then(function (value) {
  891.             searchText = value + "...";
  892.         });
  893.  
  894.         $scope.dialogTreeEventHandler = $({});
  895.         $scope.target = {};
  896.         $scope.searchInfo = {
  897.             searchFromId: null,
  898.             searchFromName: null,
  899.             showSearch: false,
  900.             results: [],
  901.             selectedSearchResults: []
  902.         }
  903.  
  904.         if (dialogOptions.currentTarget) {
  905.             $scope.target = dialogOptions.currentTarget;
  906.  
  907.             //if we have a node ID, we fetch the current node to build the form data
  908.             if ($scope.target.id) {
  909.  
  910.                 if (!$scope.target.path) {
  911.                     entityResource.getPath($scope.target.id, "Document").then(function (path) {
  912.                         $scope.target.path = path;
  913.                         //now sync the tree to this path
  914.                         $scope.dialogTreeEventHandler.syncTree({ path: $scope.target.path, tree: "content" });
  915.                     });
  916.                 }
  917.  
  918.                 contentResource.getNiceUrl($scope.target.id).then(function (url) {
  919.                     $scope.target.url = url;
  920.                 });
  921.             }
  922.         }
  923.  
  924.         function nodeSelectHandler(ev, args) {
  925.             args.event.preventDefault();
  926.             args.event.stopPropagation();
  927.  
  928.             if (args.node.metaData.listViewNode) {
  929.                 //check if list view 'search' node was selected
  930.  
  931.                 $scope.searchInfo.showSearch = true;
  932.                 $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  933.                 $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  934.             }        
  935.             else {
  936.                 eventsService.emit("dialogs.linkPicker.select", args);
  937.  
  938.                 if ($scope.currentNode) {
  939.                     //un-select if there's a current one selected
  940.                     $scope.currentNode.selected = false;
  941.                 }
  942.  
  943.                 $scope.currentNode = args.node;
  944.                 $scope.currentNode.selected = true;
  945.                 $scope.target.id = args.node.id;
  946.                 $scope.target.name = args.node.name;
  947.  
  948.                 if (args.node.id < 0) {
  949.                     $scope.target.url = "/";
  950.                 }
  951.                 else {
  952.                     contentResource.getNiceUrl(args.node.id).then(function (url) {
  953.                         $scope.target.url = url;
  954.                     });
  955.                 }
  956.  
  957.                 if (!angular.isUndefined($scope.target.isMedia)) {
  958.                     delete $scope.target.isMedia;
  959.                 }
  960.             }          
  961.         }
  962.  
  963.         function nodeExpandedHandler(ev, args) {
  964.             if (angular.isArray(args.children)) {
  965.  
  966.                 //iterate children
  967.                 _.each(args.children, function (child) {
  968.                     //check if any of the items are list views, if so we need to add a custom
  969.                     // child: A node to activate the search
  970.                     if (child.metaData.isContainer) {
  971.                         child.hasChildren = true;
  972.                         child.children = [
  973.                             {
  974.                                 level: child.level + 1,
  975.                                 hasChildren: false,
  976.                                 name: searchText,
  977.                                 metaData: {
  978.                                     listViewNode: child,
  979.                                 },
  980.                                 cssClass: "icon umb-tree-icon sprTree icon-search",
  981.                                 cssClasses: ["not-published"]
  982.                             }
  983.                         ];
  984.                     }                  
  985.                 });
  986.             }
  987.         }
  988.  
  989.         $scope.switchToMediaPicker = function () {
  990.             userService.getCurrentUser().then(function (userData) {
  991.                 dialogService.mediaPicker({
  992.                     startNodeId: userData.startMediaId,
  993.                     callback: function (media) {
  994.                         $scope.target.id = media.id;
  995.                         $scope.target.isMedia = true;
  996.                         $scope.target.name = media.name;
  997.                         $scope.target.url = mediaHelper.resolveFile(media);
  998.                     }
  999.                 });
  1000.             });
  1001.         };
  1002.  
  1003.         $scope.hideSearch = function () {
  1004.             $scope.searchInfo.showSearch = false;
  1005.             $scope.searchInfo.searchFromId = null;
  1006.             $scope.searchInfo.searchFromName = null;
  1007.             $scope.searchInfo.results = [];
  1008.         }
  1009.  
  1010.         // method to select a search result
  1011.         $scope.selectResult = function (evt, result) {
  1012.             result.selected = result.selected === true ? false : true;
  1013.             nodeSelectHandler(evt, {event: evt, node: result});
  1014.         };
  1015.  
  1016.         //callback when there are search results
  1017.         $scope.onSearchResults = function (results) {
  1018.             $scope.searchInfo.results = results;
  1019.             $scope.searchInfo.showSearch = true;
  1020.         };
  1021.  
  1022.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  1023.         $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  1024.  
  1025.         $scope.$on('$destroy', function () {
  1026.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  1027.             $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  1028.         });
  1029.     });
  1030. angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", function ($scope, localizationService, userService) {
  1031.    
  1032.     /**
  1033.      * @ngdoc function
  1034.      * @name signin
  1035.      * @methodOf MainController
  1036.      * @function
  1037.      *
  1038.      * @description
  1039.      * signs the user in
  1040.      */
  1041.     var d = new Date();
  1042.     //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
  1043.     localizationService.localize("login_greeting"+d.getDay()).then(function(label){
  1044.         $scope.greeting = label;
  1045.     }); // weekday[d.getDay()];
  1046.    
  1047.     $scope.errorMsg = "";
  1048.    
  1049.     $scope.loginSubmit = function (login, password) {
  1050.        
  1051.         //if the login and password are not empty we need to automatically
  1052.         // validate them - this is because if there are validation errors on the server
  1053.         // then the user has to change both username & password to resubmit which isn't ideal,
  1054.         // so if they're not empty , we'l just make sure to set them to valid.
  1055.         if (login && password && login.length > 0 && password.length > 0) {
  1056.             $scope.loginForm.username.$setValidity('auth', true);
  1057.             $scope.loginForm.password.$setValidity('auth', true);
  1058.         }
  1059.        
  1060.        
  1061.         if ($scope.loginForm.$invalid) {
  1062.             return;
  1063.         }
  1064.  
  1065.         userService.authenticate(login, password)
  1066.             .then(function (data) {              
  1067.                 $scope.submit(true);
  1068.             }, function (reason) {
  1069.                 $scope.errorMsg = reason.errorMsg;
  1070.                
  1071.                 //set the form inputs to invalid
  1072.                 $scope.loginForm.username.$setValidity("auth", false);
  1073.                 $scope.loginForm.password.$setValidity("auth", false);
  1074.             });
  1075.        
  1076.         //setup a watch for both of the model values changing, if they change
  1077.         // while the form is invalid, then revalidate them so that the form can
  1078.         // be submitted again.
  1079.         $scope.loginForm.username.$viewChangeListeners.push(function () {
  1080.             if ($scope.loginForm.username.$invalid) {
  1081.                 $scope.loginForm.username.$setValidity('auth', true);
  1082.             }
  1083.         });
  1084.         $scope.loginForm.password.$viewChangeListeners.push(function () {
  1085.             if ($scope.loginForm.password.$invalid) {
  1086.                 $scope.loginForm.password.$setValidity('auth', true);
  1087.             }
  1088.         });
  1089.     };
  1090. });
  1091.  
  1092. //used for the macro picker dialog
  1093. angular.module("umbraco").controller("Umbraco.Dialogs.MacroPickerController", function ($scope, macroFactory, umbPropEditorHelper) {
  1094.     $scope.macros = macroFactory.all(true);
  1095.     $scope.dialogMode = "list";
  1096.  
  1097.     $scope.configureMacro = function(macro){
  1098.         $scope.dialogMode = "configure";
  1099.         $scope.dialogData.macro = macroFactory.getMacro(macro.alias);
  1100.         //set the correct view for each item
  1101.         for (var i = 0; i < dialogData.macro.properties.length; i++) {
  1102.             dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view);
  1103.         }
  1104.     };
  1105. });
  1106. //used for the media picker dialog
  1107. angular.module("umbraco")
  1108.     .controller("Umbraco.Dialogs.MediaPickerController",
  1109.         function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout) {
  1110.  
  1111.             var dialogOptions = $scope.dialogOptions;
  1112.  
  1113.             $scope.onlyImages = dialogOptions.onlyImages;
  1114.             $scope.showDetails = dialogOptions.showDetails;
  1115.             $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
  1116.             $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
  1117.             $scope.cropSize = dialogOptions.cropSize;
  1118.            
  1119.             $scope.filesUploading = 0;
  1120.             $scope.dropping = false;
  1121.             $scope.progress = 0;
  1122.  
  1123.             $scope.options = {
  1124.                 url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile") + "?origin=blueimp",
  1125.                 autoUpload: true,
  1126.                 dropZone: $element.find(".umb-dialogs-mediapicker.browser"),
  1127.                 fileInput: $element.find("input.uploader"),
  1128.                 formData: {
  1129.                     currentFolder: -1
  1130.                 }
  1131.             };
  1132.  
  1133.             //preload selected item
  1134.             $scope.target = undefined;
  1135.             if(dialogOptions.currentTarget){
  1136.                 $scope.target = dialogOptions.currentTarget;
  1137.             }
  1138.  
  1139.             $scope.submitFolder = function(e) {
  1140.                 if (e.keyCode === 13) {
  1141.                     e.preventDefault();
  1142.                     $scope.showFolderInput = false;
  1143.  
  1144.                     mediaResource
  1145.                         .addFolder($scope.newFolderName, $scope.options.formData.currentFolder)
  1146.                         .then(function(data) {
  1147.  
  1148.                             //we've added a new folder so lets clear the tree cache for that specific item
  1149.                             treeService.clearCache({
  1150.                                 cacheKey: "__media", //this is the main media tree cache key
  1151.                                 childrenOf: data.parentId //clear the children of the parent
  1152.                             });
  1153.  
  1154.                             $scope.gotoFolder(data);
  1155.                         });
  1156.                 }
  1157.             };
  1158.  
  1159.             $scope.gotoFolder = function(folder) {
  1160.  
  1161.                 if(!folder){
  1162.                     folder = {id: -1, name: "Media", icon: "icon-folder"};
  1163.                 }
  1164.  
  1165.                 if (folder.id > 0) {
  1166.                     entityResource.getAncestors(folder.id, "media")
  1167.                         .then(function(anc) {
  1168.                             // anc.splice(0,1);  
  1169.                             $scope.path = _.filter(anc, function (f) {
  1170.                                 return f.path.indexOf($scope.startNodeId) !== -1;
  1171.                             });
  1172.                         });
  1173.                 }
  1174.                 else {
  1175.                     $scope.path = [];
  1176.                 }
  1177.  
  1178.                 //mediaResource.rootMedia()
  1179.                 mediaResource.getChildren(folder.id)
  1180.                     .then(function(data) {
  1181.                         $scope.searchTerm = "";
  1182.                         $scope.images = data.items ? data.items : [];
  1183.                     });
  1184.  
  1185.                 $scope.options.formData.currentFolder = folder.id;
  1186.                 $scope.currentFolder = folder;      
  1187.             };
  1188.  
  1189.             //This executes prior to the whole processing which we can use to get the UI going faster,
  1190.             //this also gives us the start callback to invoke to kick of the whole thing
  1191.             $scope.$on('fileuploadadd', function (e, data) {
  1192.                 $scope.$apply(function () {
  1193.                     $scope.filesUploading++;
  1194.                 });
  1195.             });
  1196.  
  1197.             //when one is finished
  1198.             $scope.$on('fileuploaddone', function (e, data) {
  1199.                 $scope.filesUploading--;
  1200.                 if ($scope.filesUploading == 0) {
  1201.                     $scope.$apply(function () {
  1202.                         $scope.progress = 0;
  1203.                         $scope.gotoFolder($scope.currentFolder);
  1204.                     });
  1205.                 }
  1206.             });
  1207.  
  1208.             // All these sit-ups are to add dropzone area and make sure it gets removed if dragging is aborted!
  1209.             $scope.$on('fileuploaddragover', function (e, data) {
  1210.                 if (!$scope.dragClearTimeout) {
  1211.                     $scope.$apply(function () {
  1212.                         $scope.dropping = true;
  1213.                     });
  1214.                 }
  1215.                 else {
  1216.                     $timeout.cancel($scope.dragClearTimeout);
  1217.                 }
  1218.                 $scope.dragClearTimeout = $timeout(function () {
  1219.                     $scope.dropping = null;
  1220.                     $scope.dragClearTimeout = null;
  1221.                 }, 300);
  1222.             });
  1223.  
  1224.             $scope.clickHandler = function(image, ev, select) {
  1225.                 ev.preventDefault();
  1226.                
  1227.                 if (image.isFolder && !select) {
  1228.                     $scope.gotoFolder(image);
  1229.                 }else{
  1230.                     eventsService.emit("dialogs.mediaPicker.select", image);
  1231.                    
  1232.                     //we have 3 options add to collection (if multi) show details, or submit it right back to the callback
  1233.                     if ($scope.multiPicker) {
  1234.                         $scope.select(image);
  1235.                         image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : "";
  1236.                     }else if($scope.showDetails) {
  1237.                         $scope.target= image;
  1238.                         $scope.target.url = mediaHelper.resolveFile(image);
  1239.                     }else{
  1240.                         $scope.submit(image);
  1241.                     }
  1242.                 }
  1243.             };
  1244.  
  1245.             $scope.exitDetails = function(){
  1246.                 if(!$scope.currentFolder){
  1247.                     $scope.gotoFolder();
  1248.                 }
  1249.  
  1250.                 $scope.target = undefined;
  1251.             };
  1252.  
  1253.            
  1254.  
  1255.             //default root item
  1256.             if(!$scope.target){
  1257.                 $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });  
  1258.             }
  1259.         });
  1260. //used for the member picker dialog
  1261. angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerController",
  1262.     function($scope, eventsService, entityResource, searchService, $log) {
  1263.         var dialogOptions = $scope.dialogOptions;
  1264.         $scope.dialogTreeEventHandler = $({});
  1265.         $scope.multiPicker = dialogOptions.multiPicker;
  1266.  
  1267.         /** Method used for selecting a node */
  1268.         function select(text, id) {
  1269.  
  1270.             if (dialogOptions.multiPicker) {
  1271.                 $scope.select(id);              
  1272.             }
  1273.             else {
  1274.                 $scope.submit(id);              
  1275.             }
  1276.         }
  1277.        
  1278.         function nodeSelectHandler(ev, args) {
  1279.             args.event.preventDefault();
  1280.             args.event.stopPropagation();
  1281.            
  1282.             eventsService.emit("dialogs.memberGroupPicker.select", args);
  1283.            
  1284.             //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
  1285.             //from the server in this method.
  1286.             select(args.node.name, args.node.id);
  1287.  
  1288.             //toggle checked state
  1289.             args.node.selected = args.node.selected === true ? false : true;
  1290.         }
  1291.  
  1292.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  1293.  
  1294.         $scope.$on('$destroy', function () {
  1295.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  1296.         });
  1297.     });
  1298. angular.module("umbraco").controller("Umbraco.Dialogs.RteEmbedController", function ($scope, $http, umbRequestHelper) {
  1299.     $scope.form = {};
  1300.     $scope.form.url = "";
  1301.     $scope.form.width = 360;
  1302.     $scope.form.height = 240;
  1303.     $scope.form.constrain = true;
  1304.     $scope.form.preview = "";
  1305.     $scope.form.success = false;
  1306.     $scope.form.info = "";
  1307.     $scope.form.supportsDimensions = false;
  1308.    
  1309.     var origWidth = 500;
  1310.     var origHeight = 300;
  1311.    
  1312.     $scope.showPreview = function() {
  1313.  
  1314.         if ($scope.form.url) {
  1315.             $scope.form.show = true;
  1316.             $scope.form.preview = "<div class=\"umb-loader\" style=\"height: 10px; margin: 10px 0px;\"></div>";
  1317.             $scope.form.info = "";
  1318.             $scope.form.success = false;
  1319.  
  1320.             $http({ method: 'GET', url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), params: { url: $scope.form.url, width: $scope.form.width, height: $scope.form.height } })
  1321.                 .success(function (data) {
  1322.                    
  1323.                     $scope.form.preview = "";
  1324.                    
  1325.                     switch (data.Status) {
  1326.                         case 0:
  1327.                             //not supported
  1328.                             $scope.form.info = "Not supported";
  1329.                             break;
  1330.                         case 1:
  1331.                             //error
  1332.                             $scope.form.info = "Computer says no";
  1333.                             break;
  1334.                         case 2:
  1335.                             $scope.form.preview = data.Markup;
  1336.                             $scope.form.supportsDimensions = data.SupportsDimensions;
  1337.                             $scope.form.success = true;
  1338.                             break;
  1339.                     }
  1340.                 })
  1341.                 .error(function () {
  1342.                     $scope.form.supportsDimensions = false;
  1343.                     $scope.form.preview = "";
  1344.                     $scope.form.info = "Computer says no";
  1345.                 });
  1346.         } else {
  1347.             $scope.form.supportsDimensions = false;
  1348.             $scope.form.preview = "";
  1349.             $scope.form.info = "Please enter a URL";
  1350.         }
  1351.     };
  1352.  
  1353.     $scope.changeSize = function (type) {
  1354.         var width, height;
  1355.        
  1356.         if ($scope.form.constrain) {
  1357.             width = parseInt($scope.form.width, 10);
  1358.             height = parseInt($scope.form.height, 10);
  1359.             if (type == 'width') {
  1360.                 origHeight = Math.round((width / origWidth) * height);
  1361.                 $scope.form.height = origHeight;
  1362.             } else {
  1363.                 origWidth = Math.round((height / origHeight) * width);
  1364.                 $scope.form.width = origWidth;
  1365.             }
  1366.         }
  1367.         if ($scope.form.url != "") {
  1368.             $scope.showPreview();
  1369.         }
  1370.  
  1371.     };
  1372.    
  1373.     $scope.insert = function(){
  1374.         $scope.submit($scope.form.preview);
  1375.     };
  1376. });
  1377. angular.module("umbraco").controller('Umbraco.Dialogs.Template.QueryBuilderController',
  1378.         function($scope, $http, dialogService){
  1379.            
  1380.  
  1381.             $http.get("backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties").then(function(response) {
  1382.                 $scope.properties = response.data;
  1383.             });
  1384.  
  1385.             $http.get("backoffice/UmbracoApi/TemplateQuery/GetContentTypes").then(function (response) {
  1386.                 $scope.contentTypes = response.data;
  1387.             });
  1388.  
  1389.             $http.get("backoffice/UmbracoApi/TemplateQuery/GetFilterConditions").then(function (response) {
  1390.                 $scope.conditions = response.data;
  1391.             });
  1392.  
  1393.  
  1394.             $scope.query = {
  1395.                 contentType: {
  1396.                     name: "Everything"
  1397.                 },
  1398.                 source:{
  1399.                     name: "My website"
  1400.                 },
  1401.                 filters:[
  1402.                     {
  1403.                         property:undefined,
  1404.                         operator: undefined
  1405.                     }
  1406.                 ],
  1407.                 sort:{
  1408.                     property:{
  1409.                         alias: "",
  1410.                         name: "",
  1411.                     },
  1412.                     direction: "Ascending"
  1413.                 }
  1414.             };
  1415.  
  1416.  
  1417.  
  1418.             $scope.chooseSource = function(query){
  1419.                 dialogService.contentPicker({
  1420.                     callback: function (data) {
  1421.  
  1422.                         if (data.id > 0) {
  1423.                             query.source = { id: data.id, name: data.name };
  1424.                         } else {
  1425.                             query.source.name = "My website";
  1426.                             delete query.source.id;
  1427.                         }
  1428.                     }
  1429.                 });
  1430.             };
  1431.  
  1432.             var throttledFunc = _.throttle(function() {
  1433.  
  1434.                 $http.post("backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery", $scope.query).then(function (response) {
  1435.                     $scope.result = response.data;
  1436.                 });
  1437.  
  1438.             }, 200);
  1439.  
  1440.             $scope.$watch("query", function(value) {
  1441.                 throttledFunc();
  1442.             }, true);
  1443.  
  1444.             $scope.getPropertyOperators = function (property) {
  1445.  
  1446.                 var conditions = _.filter($scope.conditions, function(condition) {
  1447.                     var index = condition.appliesTo.indexOf(property.type);
  1448.                     return index >= 0;
  1449.                 });
  1450.                 return conditions;
  1451.             };
  1452.  
  1453.            
  1454.             $scope.addFilter = function(query){            
  1455.                 query.filters.push({});
  1456.             };
  1457.  
  1458.             $scope.trashFilter = function (query) {
  1459.                 query.filters.splice(query,1);
  1460.             };
  1461.  
  1462.             $scope.changeSortOrder = function(query){
  1463.                 if(query.sort.direction === "ascending"){
  1464.                     query.sort.direction = "descending";
  1465.                 }else{
  1466.                     query.sort.direction = "ascending";
  1467.                 }
  1468.             };
  1469.  
  1470.             $scope.setSortProperty = function(query, property){
  1471.                 query.sort.property = property;
  1472.                 if(property.type === "datetime"){
  1473.                     query.sort.direction = "descending";
  1474.                 }else{
  1475.                     query.sort.direction = "ascending";
  1476.                 }
  1477.             };
  1478.         });
  1479. //used for the media picker dialog
  1480. angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
  1481.     function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
  1482.  
  1483.         var tree = null;
  1484.         var dialogOptions = $scope.dialogOptions;
  1485.         $scope.dialogTreeEventHandler = $({});
  1486.         $scope.section = dialogOptions.section;
  1487.         $scope.treeAlias = dialogOptions.treeAlias;
  1488.         $scope.multiPicker = dialogOptions.multiPicker;
  1489.         $scope.hideHeader = true;              
  1490.         $scope.searchInfo = {
  1491.             searchFromId: dialogOptions.startNodeId,
  1492.             searchFromName: null,
  1493.             showSearch: false,
  1494.             results: [],
  1495.             selectedSearchResults: []
  1496.         }
  1497.  
  1498.         //create the custom query string param for this tree
  1499.         $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
  1500.         $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
  1501.  
  1502.         var searchText = "Search...";
  1503.         localizationService.localize("general_search").then(function (value) {
  1504.             searchText = value + "...";
  1505.         });
  1506.  
  1507.         var entityType = "Document";
  1508.        
  1509.  
  1510.         //min / max values
  1511.         if (dialogOptions.minNumber) {
  1512.             dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
  1513.         }
  1514.         if (dialogOptions.maxNumber) {
  1515.             dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
  1516.         }
  1517.  
  1518.         if (dialogOptions.section === "member") {
  1519.             entityType = "Member";            
  1520.         }
  1521.         else if (dialogOptions.section === "media") {      
  1522.             entityType = "Media";
  1523.         }
  1524.  
  1525.         //Configures filtering
  1526.         if (dialogOptions.filter) {
  1527.  
  1528.             dialogOptions.filterExclude = false;
  1529.             dialogOptions.filterAdvanced = false;
  1530.  
  1531.             //used advanced filtering
  1532.             if (angular.isFunction(dialogOptions.filter)) {
  1533.                 dialogOptions.filterAdvanced = true;
  1534.             }
  1535.             else if (angular.isObject(dialogOptions.filter)) {
  1536.                 dialogOptions.filterAdvanced = true;
  1537.             }
  1538.             else {
  1539.                 if (dialogOptions.filter.startsWith("!")) {
  1540.                     dialogOptions.filterExclude = true;
  1541.                     dialogOptions.filter = dialogOptions.filter.substring(1);
  1542.                 }
  1543.  
  1544.                 //used advanced filtering
  1545.                 if (dialogOptions.filter.startsWith("{")) {
  1546.                     dialogOptions.filterAdvanced = true;
  1547.                     //convert to object
  1548.                     dialogOptions.filter = angular.fromJson(dialogOptions.filter);
  1549.                 }
  1550.             }
  1551.         }
  1552.  
  1553.         function nodeExpandedHandler(ev, args) {            
  1554.             if (angular.isArray(args.children)) {
  1555.  
  1556.                 //iterate children
  1557.                 _.each(args.children, function (child) {
  1558.  
  1559.                     //check if any of the items are list views, if so we need to add some custom
  1560.                     // children: A node to activate the search, any nodes that have already been
  1561.                     // selected in the search
  1562.                     if (child.metaData.isContainer) {
  1563.                         child.hasChildren = true;
  1564.                         child.children = [
  1565.                             {
  1566.                                 level: child.level + 1,
  1567.                                 hasChildren: false,
  1568.                                 parent: function () {
  1569.                                     return child;
  1570.                                 },
  1571.                                 name: searchText,
  1572.                                 metaData: {
  1573.                                     listViewNode: child,
  1574.                                 },
  1575.                                 cssClass: "icon-search",
  1576.                                 cssClasses: ["not-published"]
  1577.                             }
  1578.                         ];
  1579.                         //add base transition classes to this node
  1580.                         child.cssClasses.push("tree-node-slide-up");
  1581.  
  1582.                         var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) {
  1583.                             return i.parentId == child.id;
  1584.                         });
  1585.                         _.each(listViewResults, function(item) {
  1586.                             child.children.unshift({
  1587.                                 id: item.id,
  1588.                                 name: item.name,
  1589.                                 cssClass: "icon umb-tree-icon sprTree " + item.icon,
  1590.                                 level: child.level + 1,
  1591.                                 metaData: {
  1592.                                     isSearchResult: true
  1593.                                 },
  1594.                                 hasChildren: false,
  1595.                                 parent: function () {
  1596.                                     return child;
  1597.                                 }
  1598.                             });
  1599.                         });
  1600.                     }
  1601.  
  1602.                     //now we need to look in the already selected search results and
  1603.                     // toggle the check boxes for those ones that are listed
  1604.                     var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
  1605.                         return child.id == selected.id;
  1606.                     });
  1607.                     if (exists) {
  1608.                         child.selected = true;
  1609.                     }
  1610.                 });
  1611.  
  1612.                 //check filter
  1613.                 performFiltering(args.children);               
  1614.             }
  1615.         }
  1616.  
  1617.         //gets the tree object when it loads
  1618.         function treeLoadedHandler(ev, args) {
  1619.             tree = args.tree;
  1620.         }
  1621.  
  1622.         //wires up selection
  1623.         function nodeSelectHandler(ev, args) {
  1624.             args.event.preventDefault();
  1625.             args.event.stopPropagation();
  1626.            
  1627.             if (args.node.metaData.listViewNode) {
  1628.                 //check if list view 'search' node was selected
  1629.  
  1630.                 $scope.searchInfo.showSearch = true;                
  1631.                 $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  1632.                 $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  1633.  
  1634.                 //add transition classes
  1635.                 var listViewNode = args.node.parent();
  1636.                 listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
  1637.             }
  1638.             else if (args.node.metaData.isSearchResult) {
  1639.                 //check if the item selected was a search result from a list view
  1640.  
  1641.                 //unselect
  1642.                 select(args.node.name, args.node.id);
  1643.  
  1644.                 //remove it from the list view children
  1645.                 var listView = args.node.parent();
  1646.                 listView.children = _.reject(listView.children, function(child) {
  1647.                     return child.id == args.node.id;
  1648.                 });
  1649.  
  1650.                 //remove it from the custom tracked search result list
  1651.                 $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
  1652.                     return i.id == args.node.id;
  1653.                 });
  1654.             }
  1655.             else {
  1656.                 eventsService.emit("dialogs.treePickerController.select", args);
  1657.  
  1658.                 if (args.node.filtered) {
  1659.                     return;
  1660.                 }
  1661.  
  1662.                 //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
  1663.                 //from the server in this method.
  1664.                 select(args.node.name, args.node.id);
  1665.  
  1666.                 //toggle checked state
  1667.                 args.node.selected = args.node.selected === true ? false : true;
  1668.             }          
  1669.         }
  1670.  
  1671.         /** Method used for selecting a node */
  1672.         function select(text, id, entity) {
  1673.             //if we get the root, we just return a constructed entity, no need for server data
  1674.             if (id < 0) {
  1675.                 if ($scope.multiPicker) {
  1676.                     $scope.select(id);
  1677.                 }
  1678.                 else {
  1679.                     var node = {
  1680.                         alias: null,
  1681.                         icon: "icon-folder",
  1682.                         id: id,
  1683.                         name: text
  1684.                     };
  1685.                     $scope.submit(node);
  1686.                 }
  1687.             }
  1688.             else {
  1689.                
  1690.                 if ($scope.multiPicker) {
  1691.                     $scope.select(Number(id));
  1692.                 }
  1693.                 else {
  1694.                    
  1695.                     $scope.hideSearch();
  1696.  
  1697.                     //if an entity has been passed in, use it
  1698.                     if (entity) {
  1699.                         $scope.submit(entity);
  1700.                     } else {
  1701.                         //otherwise we have to get it from the server
  1702.                         entityResource.getById(id, entityType).then(function (ent) {
  1703.                             $scope.submit(ent);
  1704.                         });
  1705.                     }
  1706.                 }
  1707.             }
  1708.         }
  1709.  
  1710.         function performFiltering(nodes) {
  1711.  
  1712.             if (!dialogOptions.filter) {
  1713.                 return;
  1714.             }
  1715.  
  1716.             //remove any list view search nodes from being filtered since these are special nodes that always must
  1717.             // be allowed to be clicked on
  1718.             nodes = _.filter(nodes, function(n) {
  1719.                 return !angular.isObject(n.metaData.listViewNode);
  1720.             });
  1721.  
  1722.             if (dialogOptions.filterAdvanced) {
  1723.  
  1724.                 //filter either based on a method or an object
  1725.                 var filtered = angular.isFunction(dialogOptions.filter)
  1726.                     ? _.filter(nodes, dialogOptions.filter)
  1727.                     : _.where(nodes, dialogOptions.filter);
  1728.  
  1729.                 angular.forEach(filtered, function (value, key) {
  1730.                     value.filtered = true;
  1731.                     if (dialogOptions.filterCssClass) {
  1732.                         if (!value.cssClasses) {
  1733.                             value.cssClasses = [];
  1734.                         }
  1735.                         value.cssClasses.push(dialogOptions.filterCssClass);
  1736.                     }
  1737.                 });
  1738.             } else {
  1739.                 var a = dialogOptions.filter.toLowerCase().split(',');
  1740.                 angular.forEach(nodes, function (value, key) {
  1741.  
  1742.                     var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
  1743.  
  1744.                     if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
  1745.                         value.filtered = true;
  1746.  
  1747.                         if (dialogOptions.filterCssClass) {
  1748.                             if (!value.cssClasses) {
  1749.                                 value.cssClasses = [];
  1750.                             }
  1751.                             value.cssClasses.push(dialogOptions.filterCssClass);
  1752.                         }
  1753.                     }
  1754.                 });
  1755.             }
  1756.         }
  1757.        
  1758.         $scope.multiSubmit = function (result) {
  1759.             entityResource.getByIds(result, entityType).then(function (ents) {
  1760.                 $scope.submit(ents);
  1761.             });
  1762.         };
  1763.        
  1764.         /** method to select a search result */
  1765.         $scope.selectResult = function (evt, result) {
  1766.  
  1767.             if (result.filtered) {
  1768.                 return;
  1769.             }
  1770.  
  1771.             result.selected = result.selected === true ? false : true;
  1772.  
  1773.             //since result = an entity, we'll pass it in so we don't have to go back to the server
  1774.             select(result.name, result.id, result);
  1775.  
  1776.             //add/remove to our custom tracked list of selected search results
  1777.             if (result.selected) {
  1778.                 $scope.searchInfo.selectedSearchResults.push(result);
  1779.             }
  1780.             else {
  1781.                 $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) {
  1782.                     return i.id == result.id;
  1783.                 });
  1784.             }
  1785.  
  1786.             //ensure the tree node in the tree is checked/unchecked if it already exists there
  1787.             if (tree) {            
  1788.                 var found = treeService.getDescendantNode(tree.root, result.id);
  1789.                 if (found) {
  1790.                     found.selected = result.selected;
  1791.                 }
  1792.             }
  1793.            
  1794.         };
  1795.  
  1796.         $scope.hideSearch = function () {
  1797.                    
  1798.             //Traverse the entire displayed tree and update each node to sync with the selected search results
  1799.             if (tree) {
  1800.  
  1801.                 //we need to ensure that any currently displayed nodes that get selected
  1802.                 // from the search get updated to have a check box!
  1803.                 function checkChildren(children) {
  1804.                     _.each(children, function (child) {
  1805.                         //check if the id is in the selection, if so ensure it's flagged as selected
  1806.                         var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
  1807.                             return child.id == selected.id;
  1808.                         });
  1809.                         //if the curr node exists in selected search results, ensure it's checked
  1810.                         if (exists) {
  1811.                             child.selected = true;
  1812.                         }
  1813.                         //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
  1814.                         else if (child.metaData.isSearchResult) {
  1815.                             //if this tree node is under a list view it means that the node was added
  1816.                             // to the tree dynamically under the list view that was searched, so we actually want to remove
  1817.                             // it all together from the tree
  1818.                             var listView = child.parent();
  1819.                             listView.children = _.reject(listView.children, function(c) {
  1820.                                 return c.id == child.id;
  1821.                             });
  1822.                         }
  1823.                        
  1824.                         //check if the current node is a list view and if so, check if there's any new results
  1825.                         // that need to be added as child nodes to it based on search results selected
  1826.                         if (child.metaData.isContainer) {
  1827.  
  1828.                             child.cssClasses = _.reject(child.cssClasses, function(c) {
  1829.                                 return c === 'tree-node-slide-up-hide-active';
  1830.                             });
  1831.  
  1832.                             var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
  1833.                                 return i.parentId == child.id;
  1834.                             });
  1835.                             _.each(listViewResults, function (item) {
  1836.                                 var childExists = _.find(child.children, function(c) {
  1837.                                     return c.id == item.id;
  1838.                                 });
  1839.                                 if (!childExists) {
  1840.                                     var parent = child;
  1841.                                     child.children.unshift({
  1842.                                         id: item.id,
  1843.                                         name: item.name,
  1844.                                         cssClass: "icon umb-tree-icon sprTree " + item.icon,
  1845.                                         level: child.level + 1,
  1846.                                         metaData: {
  1847.                                             isSearchResult: true
  1848.                                         },
  1849.                                         hasChildren: false,
  1850.                                         parent: function () {
  1851.                                             return parent;
  1852.                                         }
  1853.                                     });
  1854.                                 }                                
  1855.                             });
  1856.                         }
  1857.  
  1858.                         //recurse
  1859.                         if (child.children && child.children.length > 0) {
  1860.                             checkChildren(child.children);
  1861.                         }
  1862.                     });
  1863.                 }
  1864.                 checkChildren(tree.root.children);
  1865.             }
  1866.            
  1867.  
  1868.             $scope.searchInfo.showSearch = false;            
  1869.             $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
  1870.             $scope.searchInfo.searchFromName = null;
  1871.             $scope.searchInfo.results = [];
  1872.         }
  1873.  
  1874.         $scope.onSearchResults = function(results) {
  1875.            
  1876.             //filter all items - this will mark an item as filtered
  1877.             performFiltering(results);
  1878.  
  1879.             //now actually remove all filtered items so they are not even displayed
  1880.             results = _.filter(results, function(item) {
  1881.                 return !item.filtered;
  1882.             });
  1883.  
  1884.             $scope.searchInfo.results = results;
  1885.  
  1886.             //sync with the curr selected results
  1887.             _.each($scope.searchInfo.results, function (result) {
  1888.                 var exists = _.find($scope.dialogData.selection, function (selectedId) {
  1889.                     return result.id == selectedId;
  1890.                 });
  1891.                 if (exists) {
  1892.                     result.selected = true;
  1893.                 }              
  1894.             });
  1895.  
  1896.             $scope.searchInfo.showSearch = true;
  1897.         };
  1898.  
  1899.         $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
  1900.         $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  1901.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  1902.  
  1903.         $scope.$on('$destroy', function () {
  1904.             $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
  1905.             $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  1906.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  1907.         });
  1908.     });
  1909. angular.module("umbraco")
  1910.     .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService) {
  1911.  
  1912.         $scope.user = userService.getCurrentUser();
  1913.         $scope.history = historyService.getCurrent();
  1914.         $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
  1915.  
  1916.         var evts = [];
  1917.         evts.push(eventsService.on("historyService.add", function (e, args) {
  1918.             $scope.history = args.all;
  1919.         }));
  1920.         evts.push(eventsService.on("historyService.remove", function (e, args) {
  1921.             $scope.history = args.all;
  1922.         }));
  1923.         evts.push(eventsService.on("historyService.removeAll", function (e, args) {
  1924.             $scope.history = [];
  1925.         }));
  1926.  
  1927.         $scope.logout = function () {
  1928.  
  1929.             //Add event listener for when there are pending changes on an editor which means our route was not successful
  1930.             var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) {
  1931.                 //one time listener, remove the event
  1932.                 pendingChangeEvent();
  1933.                 $scope.close();
  1934.             });
  1935.  
  1936.  
  1937.             //perform the path change, if it is successful then the promise will resolve otherwise it will fail
  1938.             $scope.close();
  1939.             $location.path("/logout");
  1940.         };
  1941.  
  1942.         $scope.gotoHistory = function (link) {
  1943.             $location.path(link);
  1944.             $scope.close();
  1945.         };
  1946.  
  1947.         //Manually update the remaining timeout seconds
  1948.         function updateTimeout() {
  1949.             $timeout(function () {
  1950.                 if ($scope.remainingAuthSeconds > 0) {
  1951.                     $scope.remainingAuthSeconds--;
  1952.                     $scope.$digest();
  1953.                     //recurse
  1954.                     updateTimeout();
  1955.                 }
  1956.  
  1957.             }, 1000, false); // 1 second, do NOT execute a global digest    
  1958.         }
  1959.  
  1960.         //get the user
  1961.         userService.getCurrentUser().then(function (user) {
  1962.             $scope.user = user;
  1963.             if ($scope.user) {
  1964.                 $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
  1965.                 $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
  1966.                 //set the timer
  1967.                 updateTimeout();
  1968.             }
  1969.         });
  1970.  
  1971.         //remove all event handlers
  1972.         $scope.$on('$destroy', function () {
  1973.             for (var e = 0; e < evts.length; e++) {
  1974.                 evts[e]();
  1975.             }
  1976.  
  1977.         });
  1978.  
  1979.     });
  1980. /**
  1981.  * @ngdoc controller
  1982.  * @name Umbraco.Dialogs.LegacyDeleteController
  1983.  * @function
  1984.  *
  1985.  * @description
  1986.  * The controller for deleting content
  1987.  */
  1988. function YsodController($scope, legacyResource, treeService, navigationService) {
  1989.    
  1990.     if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
  1991.         //trim whitespace
  1992.         $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
  1993.     }
  1994.  
  1995.     $scope.closeDialog = function() {
  1996.         $scope.dismiss();
  1997.     };
  1998.  
  1999. }
  2000.  
  2001. angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController);
  2002.  
  2003. /**
  2004.  * @ngdoc controller
  2005.  * @name Umbraco.LegacyController
  2006.  * @function
  2007.  *
  2008.  * @description
  2009.  * A controller to control the legacy iframe injection
  2010.  *
  2011. */
  2012. function LegacyController($scope, $routeParams, $element) {
  2013.  
  2014.     var url = decodeURIComponent($routeParams.url.toLowerCase().replace(/javascript\:/g, ""));
  2015.     //split into path and query
  2016.     var urlParts = url.split("?");
  2017.     var extIndex = urlParts[0].lastIndexOf(".");
  2018.     var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex);
  2019.     //path cannot be a js file
  2020.     if (ext !== ".js" || ext === "") {
  2021.         //path cannot contain any of these chars
  2022.         var toClean = "*(){}[];:<>\\|'\"";
  2023.         for (var i = 0; i < toClean.length; i++) {
  2024.             var reg = new RegExp("\\" + toClean[i], "g");
  2025.             urlParts[0] = urlParts[0].replace(reg, "");
  2026.         }
  2027.         //join cleaned path and query back together
  2028.         url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1]));
  2029.         $scope.legacyPath = url;
  2030.     }
  2031.     else {
  2032.         throw "Invalid url";
  2033.     }
  2034. }
  2035.  
  2036. angular.module("umbraco").controller('Umbraco.LegacyController', LegacyController);
  2037. /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
  2038. angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) {
  2039.  
  2040.     userService._showLoginDialog();
  2041.        
  2042.     var evtOn = eventsService.on("app.ready", function(evt, data){
  2043.         $scope.avatar = "assets/img/application/logo.png";
  2044.  
  2045.         var path = "/";
  2046.  
  2047.         //check if there's a returnPath query string, if so redirect to it
  2048.         var locationObj = $location.search();
  2049.         if (locationObj.returnPath) {
  2050.             path = decodeURIComponent(locationObj.returnPath);
  2051.         }
  2052.  
  2053.         $location.url(path);
  2054.     });
  2055.  
  2056.     $scope.$on('$destroy', function () {
  2057.         eventsService.unsubscribe(evtOn);
  2058.     });
  2059.  
  2060. });
  2061.  
  2062. //used for the media picker dialog
  2063. angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeController",
  2064.     function ($scope, $location, $log, notificationsService) { 
  2065.  
  2066.         $scope.discard = function(not){
  2067.             not.args.listener();
  2068.            
  2069.             $location.search("");
  2070.  
  2071.             //we need to break the path up into path and query
  2072.             var parts = not.args.path.split("?");
  2073.             var query = {};
  2074.             if (parts.length > 1) {
  2075.                 _.each(parts[1].split("&"), function(q) {
  2076.                     var keyVal = q.split("=");
  2077.                     query[keyVal[0]] = keyVal[1];
  2078.                 });
  2079.             }
  2080.  
  2081.             $location.path(parts[0]).search(query);
  2082.             notificationsService.remove(not);
  2083.         };
  2084.  
  2085.         $scope.stay = function(not){
  2086.             notificationsService.remove(not);
  2087.         };
  2088.  
  2089.     });
  2090. angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
  2091.     function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) {
  2092.  
  2093.         var dialogOptions = $scope.dialogOptions;
  2094.         var searchText = "Search...";
  2095.         localizationService.localize("general_search").then(function (value) {
  2096.             searchText = value + "...";
  2097.         });
  2098.  
  2099.         $scope.relateToOriginal = false;
  2100.         $scope.dialogTreeEventHandler = $({});
  2101.         $scope.busy = false;
  2102.         $scope.searchInfo = {
  2103.             searchFromId: null,
  2104.             searchFromName: null,
  2105.             showSearch: false,
  2106.             results: [],
  2107.             selectedSearchResults: []
  2108.         }
  2109.  
  2110.         var node = dialogOptions.currentNode;
  2111.  
  2112.         function nodeSelectHandler(ev, args) {
  2113.             args.event.preventDefault();
  2114.             args.event.stopPropagation();
  2115.  
  2116.             if (args.node.metaData.listViewNode) {
  2117.                 //check if list view 'search' node was selected
  2118.  
  2119.                 $scope.searchInfo.showSearch = true;
  2120.                 $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  2121.                 $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  2122.             }
  2123.             else {
  2124.                 eventsService.emit("editors.content.copyController.select", args);
  2125.  
  2126.                 if ($scope.target) {
  2127.                     //un-select if there's a current one selected
  2128.                     $scope.target.selected = false;
  2129.                 }
  2130.  
  2131.                 $scope.target = args.node;
  2132.                 $scope.target.selected = true;
  2133.             }
  2134.            
  2135.         }
  2136.  
  2137.         function nodeExpandedHandler(ev, args) {
  2138.             if (angular.isArray(args.children)) {
  2139.  
  2140.                 //iterate children
  2141.                 _.each(args.children, function (child) {
  2142.                     //check if any of the items are list views, if so we need to add a custom
  2143.                     // child: A node to activate the search
  2144.                     if (child.metaData.isContainer) {
  2145.                         child.hasChildren = true;
  2146.                         child.children = [
  2147.                             {
  2148.                                 level: child.level + 1,
  2149.                                 hasChildren: false,
  2150.                                 name: searchText,
  2151.                                 metaData: {
  2152.                                     listViewNode: child,
  2153.                                 },
  2154.                                 cssClass: "icon umb-tree-icon sprTree icon-search",
  2155.                                 cssClasses: ["not-published"]
  2156.                             }
  2157.                         ];
  2158.                     }
  2159.                 });
  2160.             }
  2161.         }
  2162.  
  2163.         $scope.hideSearch = function () {
  2164.             $scope.searchInfo.showSearch = false;
  2165.             $scope.searchInfo.searchFromId = null;
  2166.             $scope.searchInfo.searchFromName = null;
  2167.             $scope.searchInfo.results = [];
  2168.         }
  2169.  
  2170.         // method to select a search result
  2171.         $scope.selectResult = function (evt, result) {
  2172.             result.selected = result.selected === true ? false : true;
  2173.             nodeSelectHandler(evt, { event: evt, node: result });
  2174.         };
  2175.  
  2176.         //callback when there are search results
  2177.         $scope.onSearchResults = function (results) {
  2178.             $scope.searchInfo.results = results;
  2179.             $scope.searchInfo.showSearch = true;
  2180.         };
  2181.        
  2182.         $scope.copy = function () {
  2183.  
  2184.             $scope.busy = true;
  2185.             $scope.error = false;
  2186.  
  2187.             contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal })
  2188.                 .then(function (path) {
  2189.                     $scope.error = false;
  2190.                     $scope.success = true;
  2191.                     $scope.busy = false;
  2192.  
  2193.                     //get the currently edited node (if any)
  2194.                     var activeNode = appState.getTreeState("selectedNode");
  2195.  
  2196.                     //we need to do a double sync here: first sync to the copied content - but don't activate the node,
  2197.                     //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
  2198.  
  2199.                     navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
  2200.                         if (activeNode) {
  2201.                             var activeNodePath = treeService.getPath(activeNode).join();
  2202.                             //sync to this node now - depending on what was copied this might already be synced but might not be
  2203.                             navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
  2204.                         }
  2205.                     });
  2206.  
  2207.                 }, function (err) {
  2208.                     $scope.success = false;
  2209.                     $scope.error = err;
  2210.                     $scope.busy = false;
  2211.                 });
  2212.         };
  2213.  
  2214.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  2215.         $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  2216.  
  2217.         $scope.$on('$destroy', function () {
  2218.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  2219.             $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  2220.         });
  2221.     });
  2222. /**
  2223.  * @ngdoc controller
  2224.  * @name Umbraco.Editors.Content.CreateController
  2225.  * @function
  2226.  *
  2227.  * @description
  2228.  * The controller for the content creation dialog
  2229.  */
  2230. function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) {
  2231.  
  2232.     contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
  2233.         $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
  2234.     });
  2235. }
  2236.  
  2237. angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController);
  2238. /**
  2239.  * @ngdoc controller
  2240.  * @name Umbraco.Editors.ContentDeleteController
  2241.  * @function
  2242.  *
  2243.  * @description
  2244.  * The controller for deleting content
  2245.  */
  2246. function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location) {
  2247.  
  2248.     $scope.performDelete = function() {
  2249.  
  2250.         //mark it for deletion (used in the UI)
  2251.         $scope.currentNode.loading = true;
  2252.  
  2253.         contentResource.deleteById($scope.currentNode.id).then(function () {
  2254.             $scope.currentNode.loading = false;
  2255.  
  2256.             //get the root node before we remove it
  2257.             var rootNode = treeService.getTreeRoot($scope.currentNode);
  2258.  
  2259.             treeService.removeNode($scope.currentNode);
  2260.  
  2261.             if (rootNode) {
  2262.                 //ensure the recycle bin has child nodes now            
  2263.                 var recycleBin = treeService.getDescendantNode(rootNode, -20);
  2264.                 if (recycleBin) {
  2265.                     recycleBin.hasChildren = true;
  2266.                 }
  2267.             }
  2268.            
  2269.             //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
  2270.             if (editorState.current && editorState.current.id == $scope.currentNode.id) {
  2271.                 $location.path("/content/content/edit/" + $scope.currentNode.parentId);
  2272.             }
  2273.  
  2274.             navigationService.hideMenu();
  2275.         });
  2276.  
  2277.     };
  2278.  
  2279.     $scope.cancel = function() {
  2280.         navigationService.hideDialog();
  2281.     };
  2282. }
  2283.  
  2284. angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController);
  2285.  
  2286. /**
  2287.  * @ngdoc controller
  2288.  * @name Umbraco.Editors.Content.EditController
  2289.  * @function
  2290.  *
  2291.  * @description
  2292.  * The controller for the content editor
  2293.  */
  2294. function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
  2295.  
  2296.     //setup scope vars
  2297.     $scope.defaultButton = null;
  2298.     $scope.subButtons = [];    
  2299.     $scope.currentSection = appState.getSectionState("currentSection");
  2300.     $scope.currentNode = null; //the editors affiliated node
  2301.     $scope.isNew = $routeParams.create;
  2302.    
  2303.     function init(content) {
  2304.  
  2305.         var buttons = contentEditingHelper.configureContentEditorButtons({
  2306.             create: $routeParams.create,
  2307.             content: content,
  2308.             methods: {
  2309.                 saveAndPublish: $scope.saveAndPublish,
  2310.                 sendToPublish: $scope.sendToPublish,
  2311.                 save: $scope.save,
  2312.                 unPublish: $scope.unPublish
  2313.             }
  2314.         });
  2315.         $scope.defaultButton = buttons.defaultButton;
  2316.         $scope.subButtons = buttons.subButtons;
  2317.  
  2318.         editorState.set($scope.content);
  2319.  
  2320.         //We fetch all ancestors of the node to generate the footer breadcrumb navigation
  2321.         if (!$routeParams.create) {
  2322.             if (content.parentId && content.parentId != -1) {
  2323.                 entityResource.getAncestors(content.id, "document")
  2324.                .then(function (anc) {
  2325.                    $scope.ancestors = anc;
  2326.                });
  2327.             }
  2328.         }
  2329.     }
  2330.  
  2331.     /** Syncs the content item to it's tree node - this occurs on first load and after saving */
  2332.     function syncTreeNode(content, path, initialLoad) {
  2333.  
  2334.         if (!$scope.content.isChildOfListView) {
  2335.             navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
  2336.                 $scope.currentNode = syncArgs.node;
  2337.             });
  2338.         }
  2339.         else if (initialLoad === true) {
  2340.  
  2341.             //it's a child item, just sync the ui node to the parent
  2342.             navigationService.syncTree({ tree: "content", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
  2343.            
  2344.             //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
  2345.             // from the server so that we can load in the actions menu.
  2346.             umbRequestHelper.resourcePromise(
  2347.                 $http.get(content.treeNodeUrl),
  2348.                 'Failed to retrieve data for child node ' + content.id).then(function (node) {
  2349.                     $scope.currentNode = node;
  2350.                 });
  2351.         }
  2352.     }
  2353.  
  2354.     // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
  2355.     function performSave(args) {
  2356.         var deferred = $q.defer();
  2357.  
  2358.         contentEditingHelper.contentEditorPerformSave({
  2359.             statusMessage: args.statusMessage,
  2360.             saveMethod: args.saveMethod,
  2361.             scope: $scope,
  2362.             content: $scope.content
  2363.         }).then(function (data) {
  2364.             //success            
  2365.             init($scope.content);
  2366.             syncTreeNode($scope.content, data.path);
  2367.  
  2368.             deferred.resolve(data);
  2369.         }, function (err) {
  2370.             //error
  2371.             if (err) {
  2372.                 editorState.set($scope.content);
  2373.             }
  2374.             deferred.reject(err);
  2375.         });
  2376.  
  2377.         return deferred.promise;
  2378.     }
  2379.  
  2380.     function resetLastListPageNumber(content) {
  2381.         // We're using rootScope to store the page number for list views, so if returning to the list
  2382.         // we can restore the page.  If we've moved on to edit a piece of content that's not the list or it's children
  2383.         // we should remove this so as not to confuse if navigating to a different list
  2384.         if (!content.isChildOfListView && !content.isContainer) {
  2385.             $rootScope.lastListViewPageViewed = null;
  2386.         }
  2387.     }
  2388.  
  2389.     if ($routeParams.create) {
  2390.         //we are creating so get an empty content item
  2391.         contentResource.getScaffold($routeParams.id, $routeParams.doctype)
  2392.             .then(function (data) {
  2393.                 $scope.loaded = true;
  2394.                 $scope.content = data;
  2395.  
  2396.                 init($scope.content);                
  2397.  
  2398.                 resetLastListPageNumber($scope.content);
  2399.             });
  2400.     }
  2401.     else {
  2402.         //we are editing so get the content item from the server
  2403.         contentResource.getById($routeParams.id)
  2404.             .then(function (data) {
  2405.                 $scope.loaded = true;
  2406.                 $scope.content = data;
  2407.  
  2408.                 if (data.isChildOfListView && data.trashed === false) {
  2409.                     $scope.listViewPath = ($routeParams.page)
  2410.                         ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page
  2411.                         : "/content/content/edit/" + data.parentId;
  2412.                 }
  2413.  
  2414.                 init($scope.content);
  2415.  
  2416.                 //in one particular special case, after we've created a new item we redirect back to the edit
  2417.                 // route but there might be server validation errors in the collection which we need to display
  2418.                 // after the redirect, so we will bind all subscriptions which will show the server validation errors
  2419.                 // if there are any and then clear them so the collection no longer persists them.
  2420.                 serverValidationManager.executeAndClearAllSubscriptions();
  2421.  
  2422.                 syncTreeNode($scope.content, data.path, true);
  2423.  
  2424.                 resetLastListPageNumber($scope.content);
  2425.             });
  2426.     }
  2427.  
  2428.  
  2429.     $scope.unPublish = function () {
  2430.  
  2431.         if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {
  2432.  
  2433.             contentResource.unPublish($scope.content.id)
  2434.                 .then(function (data) {
  2435.  
  2436.                     formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  2437.  
  2438.                     contentEditingHelper.handleSuccessfulSave({
  2439.                         scope: $scope,
  2440.                         savedContent: data,
  2441.                         rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
  2442.                     });
  2443.  
  2444.                     init($scope.content);
  2445.  
  2446.                     syncTreeNode($scope.content, data.path);
  2447.  
  2448.                 });
  2449.         }
  2450.  
  2451.     };
  2452.  
  2453.     $scope.sendToPublish = function () {
  2454.         return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." });
  2455.     };
  2456.  
  2457.     $scope.saveAndPublish = function () {
  2458.         return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." });
  2459.     };
  2460.  
  2461.     $scope.save = function () {
  2462.         return performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." });
  2463.     };
  2464.  
  2465.     $scope.preview = function (content) {
  2466.  
  2467.  
  2468.         if (!$scope.busy) {
  2469.  
  2470.             // Chromes popup blocker will kick in if a window is opened
  2471.             // outwith the initial scoped request. This trick will fix that.
  2472.             //  
  2473.             var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
  2474.             $scope.save().then(function (data) {
  2475.                 // Build the correct path so both /#/ and #/ work.
  2476.                 var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
  2477.                 previewWindow.location.href = redirect;
  2478.             });
  2479.  
  2480.  
  2481.         }
  2482.  
  2483.     };
  2484.  
  2485.     // this method is called for all action buttons and then we proxy based on the btn definition
  2486.     $scope.performAction = function (btn) {
  2487.  
  2488.         if (!btn || !angular.isFunction(btn.handler)) {
  2489.             throw "btn.handler must be a function reference";
  2490.         }
  2491.  
  2492.         if (!$scope.busy) {
  2493.             btn.handler.apply(this);
  2494.         }
  2495.     };
  2496.  
  2497. }
  2498.  
  2499. angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController);
  2500.  
  2501. /**
  2502.  * @ngdoc controller
  2503.  * @name Umbraco.Editors.Content.EmptyRecycleBinController
  2504.  * @function
  2505.  *
  2506.  * @description
  2507.  * The controller for deleting content
  2508.  */
  2509. function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService) {
  2510.  
  2511.     $scope.performDelete = function() {
  2512.  
  2513.         //(used in the UI)
  2514.         $scope.currentNode.loading = true;
  2515.  
  2516.         contentResource.emptyRecycleBin($scope.currentNode.id).then(function () {
  2517.             $scope.currentNode.loading = false;
  2518.             //TODO: Need to sync tree, etc...
  2519.             treeService.removeChildNodes($scope.currentNode);
  2520.             navigationService.hideMenu();
  2521.         });
  2522.  
  2523.     };
  2524.  
  2525.     $scope.cancel = function() {
  2526.         navigationService.hideDialog();
  2527.     };
  2528. }
  2529.  
  2530. angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController);
  2531.  
  2532. angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController",
  2533.     function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) {
  2534.  
  2535.         var dialogOptions = $scope.dialogOptions;
  2536.         var searchText = "Search...";
  2537.         localizationService.localize("general_search").then(function (value) {
  2538.             searchText = value + "...";
  2539.         });
  2540.  
  2541.         $scope.dialogTreeEventHandler = $({});
  2542.         $scope.busy = false;
  2543.         $scope.searchInfo = {
  2544.             searchFromId: null,
  2545.             searchFromName: null,
  2546.             showSearch: false,
  2547.             results: [],
  2548.             selectedSearchResults: []
  2549.         }
  2550.  
  2551.         var node = dialogOptions.currentNode;
  2552.  
  2553.         function nodeSelectHandler(ev, args) {
  2554.             args.event.preventDefault();
  2555.             args.event.stopPropagation();
  2556.  
  2557.             if (args.node.metaData.listViewNode) {
  2558.                 //check if list view 'search' node was selected
  2559.  
  2560.                 $scope.searchInfo.showSearch = true;
  2561.                 $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  2562.                 $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  2563.             }
  2564.             else {
  2565.                 eventsService.emit("editors.content.moveController.select", args);
  2566.  
  2567.                 if ($scope.target) {
  2568.                     //un-select if there's a current one selected
  2569.                     $scope.target.selected = false;
  2570.                 }
  2571.  
  2572.                 $scope.target = args.node;
  2573.                 $scope.target.selected = true;
  2574.             }          
  2575.         }
  2576.  
  2577.         function nodeExpandedHandler(ev, args) {
  2578.             if (angular.isArray(args.children)) {
  2579.  
  2580.                 //iterate children
  2581.                 _.each(args.children, function (child) {
  2582.                     //check if any of the items are list views, if so we need to add a custom
  2583.                     // child: A node to activate the search
  2584.                     if (child.metaData.isContainer) {
  2585.                         child.hasChildren = true;
  2586.                         child.children = [
  2587.                             {
  2588.                                 level: child.level + 1,
  2589.                                 hasChildren: false,
  2590.                                 name: searchText,
  2591.                                 metaData: {
  2592.                                     listViewNode: child,
  2593.                                 },
  2594.                                 cssClass: "icon umb-tree-icon sprTree icon-search",
  2595.                                 cssClasses: ["not-published"]
  2596.                             }
  2597.                         ];
  2598.                     }
  2599.                 });
  2600.             }
  2601.         }
  2602.  
  2603.         $scope.hideSearch = function () {
  2604.             $scope.searchInfo.showSearch = false;
  2605.             $scope.searchInfo.searchFromId = null;
  2606.             $scope.searchInfo.searchFromName = null;
  2607.             $scope.searchInfo.results = [];
  2608.         }
  2609.  
  2610.         // method to select a search result
  2611.         $scope.selectResult = function (evt, result) {
  2612.             result.selected = result.selected === true ? false : true;
  2613.             nodeSelectHandler(evt, { event: evt, node: result });
  2614.         };
  2615.  
  2616.         //callback when there are search results
  2617.         $scope.onSearchResults = function (results) {
  2618.             $scope.searchInfo.results = results;
  2619.             $scope.searchInfo.showSearch = true;
  2620.         };
  2621.  
  2622.         $scope.move = function () {
  2623.  
  2624.             $scope.busy = true;
  2625.             $scope.error = false;
  2626.  
  2627.             contentResource.move({ parentId: $scope.target.id, id: node.id })
  2628.                 .then(function (path) {
  2629.                     $scope.error = false;
  2630.                     $scope.success = true;
  2631.                     $scope.busy = false;
  2632.  
  2633.                     //first we need to remove the node that launched the dialog
  2634.                     treeService.removeNode($scope.currentNode);
  2635.  
  2636.                     //get the currently edited node (if any)
  2637.                     var activeNode = appState.getTreeState("selectedNode");
  2638.  
  2639.                     //we need to do a double sync here: first sync to the moved content - but don't activate the node,
  2640.                     //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
  2641.  
  2642.                     navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
  2643.                         if (activeNode) {
  2644.                             var activeNodePath = treeService.getPath(activeNode).join();
  2645.                             //sync to this node now - depending on what was copied this might already be synced but might not be
  2646.                             navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
  2647.                         }
  2648.                     });
  2649.  
  2650.                 }, function (err) {
  2651.                     $scope.success = false;
  2652.                     $scope.error = err;
  2653.                     $scope.busy = false;
  2654.                 });
  2655.         };
  2656.  
  2657.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  2658.         $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  2659.  
  2660.         $scope.$on('$destroy', function () {
  2661.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  2662.             $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  2663.         });
  2664.     });
  2665. /**
  2666.  * @ngdoc controller
  2667.  * @name Umbraco.Editors.Content.RecycleBinController
  2668.  * @function
  2669.  *
  2670.  * @description
  2671.  * Controls the recycle bin for content
  2672.  *
  2673.  */
  2674.  
  2675. function ContentRecycleBinController($scope, $routeParams, dataTypeResource) {
  2676.  
  2677.     //ensures the list view doesn't actually load until we query for the list view config
  2678.     // for the section
  2679.     $scope.listViewPath = null;
  2680.  
  2681.     $routeParams.id = "-20";
  2682.     dataTypeResource.getById(-95).then(function (result) {
  2683.         _.each(result.preValues, function (i) {
  2684.             $scope.model.config[i.key] = i.value;
  2685.         });
  2686.         $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
  2687.     });
  2688.  
  2689.     $scope.model = { config: { entityType: $routeParams.section } };
  2690.  
  2691. }
  2692.  
  2693. angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController);
  2694.  
  2695. /**
  2696.  * @ngdoc controller
  2697.  * @name Umbraco.Editors.ContentType.EditController
  2698.  * @function
  2699.  *
  2700.  * @description
  2701.  * The controller for the content type editor
  2702.  */
  2703. function ContentTypeEditController($scope, $routeParams, $log, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, entityResource) {
  2704.    
  2705.     $scope.tabs = [];
  2706.     $scope.page = {};
  2707.     $scope.contentType = {tabs: [], name: "My content type", alias:"myType", icon:"icon-folder", allowedChildren: [], allowedTemplate: []};
  2708.     $scope.contentType.tabs = [
  2709.             {name: "Content", properties:[ {name: "test"}]},
  2710.             {name: "Generic Properties", properties:[]}
  2711.         ];
  2712.  
  2713.  
  2714.        
  2715.     $scope.dataTypesOptions ={
  2716.         group: "properties",
  2717.         onDropHandler: function(item, args){
  2718.             args.sourceScope.move(args);
  2719.         },
  2720.         onReleaseHandler: function(item, args){
  2721.             var a = args;
  2722.         }
  2723.     };
  2724.  
  2725.     $scope.tabOptions ={
  2726.         group: "tabs",
  2727.         drop: false,
  2728.         nested: true,
  2729.         onDropHandler: function(item, args){
  2730.            
  2731.         },
  2732.         onReleaseHandler: function(item, args){
  2733.            
  2734.         }
  2735.     };
  2736.  
  2737.     $scope.propertiesOptions ={
  2738.         group: "properties",
  2739.         onDropHandler: function(item, args){
  2740.             //alert("dropped on properties");
  2741.             //args.targetScope.ngModel.$modelValue.push({name: "bong"});
  2742.         },
  2743.         onReleaseHandler: function(item, args){
  2744.             //alert("released from properties");
  2745.             //args.targetScope.ngModel.$modelValue.push({name: "bong"});
  2746.         },
  2747.     };
  2748.  
  2749.  
  2750.     $scope.omg = function(){
  2751.         alert("wat");
  2752.     };  
  2753.  
  2754.     entityResource.getAll("Datatype").then(function(data){
  2755.         $scope.page.datatypes = data;
  2756.     });
  2757. }
  2758.  
  2759. angular.module("umbraco").controller("Umbraco.Editors.ContentType.EditController", ContentTypeEditController);
  2760. function startUpVideosDashboardController($scope, xmlhelper, $log, $http) {
  2761.     $scope.videos = [];
  2762.     $scope.init = function(url){
  2763.         var proxyUrl = "dashboard/feedproxy.aspx?url=" + url;
  2764.         $http.get(proxyUrl).then(function(data){
  2765.               var feed = $(data.data);
  2766.               $('item', feed).each(function (i, item) {
  2767.                   var video = {};
  2768.                   video.thumbnail = $(item).find('thumbnail').attr('url');
  2769.                   video.title = $("title", item).text();
  2770.                   video.link = $("guid", item).text();
  2771.                   $scope.videos.push(video);
  2772.               });
  2773.         });
  2774.     };
  2775. }
  2776. angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController);
  2777.  
  2778.  
  2779. function FormsController($scope, $route, $cookieStore, packageResource) {
  2780.     $scope.installForms = function(){
  2781.         $scope.state = "Installng package";
  2782.         packageResource
  2783.             .fetch("CD44CF39-3D71-4C19-B6EE-948E1FAF0525")
  2784.             .then(function(pack){
  2785.               $scope.state = "importing";
  2786.               return packageResource.import(pack);
  2787.             }, $scope.error)
  2788.             .then(function(pack){
  2789.               $scope.state = "Installing";
  2790.               return packageResource.installFiles(pack);
  2791.             }, $scope.error)
  2792.             .then(function(pack){
  2793.               $scope.state = "Restarting, please hold...";
  2794.               return packageResource.installData(pack);
  2795.             }, $scope.error)
  2796.             .then(function(pack){
  2797.               $scope.state = "All done, your browser will now refresh";
  2798.               return packageResource.cleanUp(pack);
  2799.             }, $scope.error)
  2800.             .then($scope.complete, $scope.error);
  2801.     };
  2802.  
  2803.     $scope.complete = function(result){
  2804.         var url = window.location.href + "?init=true";
  2805.         $cookieStore.put("umbPackageInstallId", result.packageGuid);
  2806.         window.location.reload(true);
  2807.     };
  2808.  
  2809.     $scope.error = function(err){
  2810.         $scope.state = undefined;
  2811.         $scope.error = err;
  2812.     };
  2813.  
  2814.  
  2815.     function Video_player (videoId) {
  2816.       // Get dom elements
  2817.       this.container      = document.getElementById(videoId);
  2818.       this.video          = this.container.getElementsByTagName('video')[0];
  2819.  
  2820.       //Create controls
  2821.       this.controls = document.createElement('div');
  2822.       this.controls.className="video-controls";
  2823.  
  2824.       this.seek_bar = document.createElement('input');
  2825.       this.seek_bar.className="seek-bar";
  2826.       this.seek_bar.type="range";
  2827.       this.seek_bar.setAttribute('value', '0');
  2828.  
  2829.       this.loader = document.createElement('div');
  2830.       this.loader.className="loader";
  2831.  
  2832.       this.progress_bar = document.createElement('span');
  2833.       this.progress_bar.className="progress-bar";
  2834.  
  2835.       // Insert controls
  2836.       this.controls.appendChild(this.seek_bar);
  2837.       this.container.appendChild(this.controls);
  2838.       this.controls.appendChild(this.loader);
  2839.       this.loader.appendChild(this.progress_bar);
  2840.     }
  2841.  
  2842.  
  2843.     Video_player.prototype
  2844.       .seeking = function() {
  2845.         // get the value of the seekbar (hidden input[type="range"])
  2846.         var time = this.video.duration * (this.seek_bar.value / 100);
  2847.  
  2848.         // Update video to seekbar value
  2849.         this.video.currentTime = time;
  2850.       };
  2851.  
  2852.     // Stop video when user initiates seeking
  2853.     Video_player.prototype
  2854.       .start_seek = function() {
  2855.         this.video.pause();
  2856.       };
  2857.  
  2858.     // Start video when user stops seeking
  2859.     Video_player.prototype
  2860.       .stop_seek = function() {
  2861.         this.video.play();
  2862.       };
  2863.  
  2864.     // Update the progressbar (span.loader) according to video.currentTime
  2865.     Video_player.prototype
  2866.       .update_progress_bar = function() {
  2867.         // Get video progress in %
  2868.         var value = (100 / this.video.duration) * this.video.currentTime;
  2869.  
  2870.         // Update progressbar
  2871.         this.progress_bar.style.width = value + '%';
  2872.       };
  2873.  
  2874.     // Bind progressbar to mouse when seeking
  2875.     Video_player.prototype
  2876.       .handle_mouse_move = function(event) {
  2877.         // Get position of progressbar relative to browser window
  2878.         var pos = this.progress_bar.getBoundingClientRect().left;
  2879.  
  2880.         // Make sure event is reckonized cross-browser
  2881.         event = event || window.event;
  2882.  
  2883.         // Update progressbar
  2884.         this.progress_bar.style.width = (event.clientX - pos) + "px";
  2885.       };
  2886.  
  2887.     // Eventlisteners for seeking
  2888.     Video_player.prototype
  2889.       .video_event_handler = function(videoPlayer, interval) {
  2890.         // Update the progress bar
  2891.         var animate_progress_bar = setInterval(function () {
  2892.               videoPlayer.update_progress_bar();
  2893.             }, interval);
  2894.  
  2895.         // Fire when input value changes (user seeking)
  2896.         videoPlayer.seek_bar
  2897.           .addEventListener("change", function() {
  2898.               videoPlayer.seeking();
  2899.           });
  2900.  
  2901.         // Fire when user clicks on seekbar
  2902.         videoPlayer.seek_bar
  2903.           .addEventListener("mousedown", function (clickEvent) {
  2904.               // Pause video playback
  2905.               videoPlayer.start_seek();
  2906.  
  2907.               // Stop updating progressbar according to video progress
  2908.               clearInterval(animate_progress_bar);
  2909.  
  2910.               // Update progressbar to where user clicks
  2911.               videoPlayer.handle_mouse_move(clickEvent);
  2912.  
  2913.               // Bind progressbar to cursor
  2914.               window.onmousemove = function(moveEvent){
  2915.                 videoPlayer.handle_mouse_move(moveEvent);
  2916.               };
  2917.           });
  2918.  
  2919.         // Fire when user releases seekbar
  2920.         videoPlayer.seek_bar
  2921.           .addEventListener("mouseup", function () {
  2922.  
  2923.               // Unbind progressbar from cursor
  2924.               window.onmousemove = null;
  2925.  
  2926.               // Start video playback
  2927.               videoPlayer.stop_seek();
  2928.  
  2929.               // Animate the progressbar
  2930.               animate_progress_bar = setInterval(function () {
  2931.                   videoPlayer.update_progress_bar();
  2932.               }, interval);
  2933.           });
  2934.       };
  2935.  
  2936.  
  2937.     var videoPlayer = new Video_player('video_1');
  2938.     videoPlayer.video_event_handler(videoPlayer, 17);
  2939. }
  2940.  
  2941. angular.module("umbraco").controller("Umbraco.Dashboard.FormsDashboardController", FormsController);
  2942.  
  2943. function startupLatestEditsController($scope) {
  2944.  
  2945. }
  2946. angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController);
  2947.  
  2948. function MediaFolderBrowserDashboardController($rootScope, $scope, assetsService, $routeParams, $timeout, $element, $location, umbRequestHelper,navigationService, mediaResource, $cookies) {
  2949.         var dialogOptions = $scope.dialogOptions;
  2950.  
  2951.         $scope.filesUploading = [];
  2952.         $scope.nodeId = -1;
  2953.  
  2954.         $scope.onUploadComplete = function () {
  2955.             navigationService.reloadSection("media");
  2956.         }
  2957.  
  2958. }
  2959. angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController);
  2960.  
  2961.  
  2962. function ChangePasswordDashboardController($scope, xmlhelper, $log, currentUserResource, formHelper) {
  2963.  
  2964.     //create the initial model for change password property editor
  2965.     $scope.changePasswordModel = {
  2966.         alias: "_umb_password",
  2967.         view: "changepassword",
  2968.         config: {},
  2969.         value: {}
  2970.     };
  2971.  
  2972.     //go get the config for the membership provider and add it to the model
  2973.     currentUserResource.getMembershipProviderConfig().then(function(data) {
  2974.         $scope.changePasswordModel.config = data;
  2975.         //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
  2976.         //this will ensure the oldPassword is shown so they can change it
  2977.         $scope.changePasswordModel.config.hasPassword = true;
  2978.         $scope.changePasswordModel.config.disableToggle = true;
  2979.     });
  2980.  
  2981.     ////this is the model we will pass to the service
  2982.     //$scope.profile = {};
  2983.  
  2984.     $scope.changePassword = function() {
  2985.  
  2986.         if (formHelper.submitForm({ scope: $scope })) {
  2987.             currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) {
  2988.  
  2989.                 //if the password has been reset, then update our model
  2990.                 if (data.value) {
  2991.                     $scope.changePasswordModel.value.generatedPassword = data.value;
  2992.                 }
  2993.  
  2994.                 formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  2995.  
  2996.             }, function (err) {
  2997.  
  2998.                 formHelper.handleError(err);
  2999.  
  3000.             });
  3001.         }
  3002.     };
  3003. }
  3004. angular.module("umbraco").controller("Umbraco.Dashboard.StartupChangePasswordController", ChangePasswordDashboardController);
  3005.  
  3006. function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
  3007.  
  3008.     $scope.indexerDetails = [];
  3009.     $scope.searcherDetails = [];
  3010.     $scope.loading = true;
  3011.  
  3012.     function checkProcessing(indexer, checkActionName) {
  3013.         umbRequestHelper.resourcePromise(
  3014.                 $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", checkActionName, { indexerName: indexer.name })),
  3015.                 'Failed to check index processing')
  3016.             .then(function(data) {
  3017.  
  3018.                 if (data) {
  3019.  
  3020.                     //copy all resulting properties
  3021.                     for (var k in data) {
  3022.                         indexer[k] = data[k];
  3023.                     }
  3024.                     indexer.isProcessing = false;
  3025.                 }
  3026.                 else {
  3027.                     $timeout(function () {
  3028.                         //don't continue if we've tried 100 times
  3029.                         if (indexer.processingAttempts < 100) {
  3030.                             checkProcessing(indexer, checkActionName);
  3031.                             //add an attempt
  3032.                             indexer.processingAttempts++;
  3033.                         }
  3034.                         else {
  3035.                             //we've exceeded 100 attempts, stop processing
  3036.                             indexer.isProcessing = false;
  3037.                         }
  3038.                     }, 1000);
  3039.                 }
  3040.             });
  3041.     }
  3042.  
  3043.     $scope.search = function (searcher, e) {
  3044.         if (e && e.keyCode !== 13) {
  3045.             return;
  3046.         }
  3047.  
  3048.         umbRequestHelper.resourcePromise(
  3049.                 $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", {
  3050.                     searcherName: searcher.name,
  3051.                     query: searcher.searchText,
  3052.                     queryType: searcher.searchType
  3053.                 })),
  3054.                 'Failed to search')
  3055.             .then(function(searchResults) {
  3056.                 searcher.isSearching = true;
  3057.                 searcher.searchResults = searchResults;
  3058.             });
  3059.     }
  3060.    
  3061.     $scope.toggle = function(provider, propName) {
  3062.         if (provider[propName] !== undefined) {
  3063.             provider[propName] = !provider[propName];
  3064.         }
  3065.         else {
  3066.             provider[propName] = true;
  3067.         }
  3068.     }
  3069.  
  3070.     $scope.rebuildIndex = function(indexer) {
  3071.         if (confirm("This will cause the index to be rebuilt. " +
  3072.                         "Depending on how much content there is in your site this could take a while. " +
  3073.                         "It is not recommended to rebuild an index during times of high website traffic " +
  3074.                         "or when editors are editing content.")) {
  3075.             indexer.isProcessing = true;
  3076.  
  3077.             umbRequestHelper.resourcePromise(
  3078.                     $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })),
  3079.                     'Failed to rebuild index')
  3080.                 .then(function () {
  3081.  
  3082.                     //rebuilding has started, nothing is returned accept a 200 status code.
  3083.                     //lets poll to see if it is done.
  3084.                     $timeout(function () {
  3085.                         checkProcessing(indexer, "PostCheckRebuildIndex");
  3086.                     }, 1000);
  3087.  
  3088.                 });
  3089.         }
  3090.     }
  3091.  
  3092.     $scope.optimizeIndex = function(indexer) {
  3093.         if (confirm("This will cause the index to be optimized which will improve its performance. " +
  3094.                         "It is not recommended to optimize an index during times of high website traffic " +
  3095.                         "or when editors are editing content.")) {
  3096.             indexer.isProcessing = true;
  3097.  
  3098.             umbRequestHelper.resourcePromise(
  3099.                     $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostOptimizeIndex", { indexerName: indexer.name })),
  3100.                     'Failed to optimize index')
  3101.                 .then(function () {
  3102.  
  3103.                     //optimizing has started, nothing is returned accept a 200 status code.
  3104.                     //lets poll to see if it is done.
  3105.                     $timeout(function () {
  3106.                         checkProcessing(indexer, "PostCheckOptimizeIndex");
  3107.                     }, 1000);
  3108.  
  3109.                 });
  3110.         }
  3111.     }
  3112.  
  3113.     $scope.closeSearch = function(searcher) {
  3114.         searcher.isSearching = true;
  3115.     }
  3116.  
  3117.      
  3118.     //go get the data
  3119.  
  3120.     //combine two promises and execute when they are both done
  3121.     $q.all([
  3122.  
  3123.         //get the indexer details
  3124.         umbRequestHelper.resourcePromise(
  3125.             $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")),
  3126.             'Failed to retrieve indexer details')
  3127.         .then(function(data) {
  3128.             $scope.indexerDetails = data;
  3129.         }),
  3130.  
  3131.         //get the searcher details
  3132.         umbRequestHelper.resourcePromise(
  3133.             $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")),
  3134.             'Failed to retrieve searcher details')
  3135.         .then(function(data) {
  3136.             $scope.searcherDetails = data;
  3137.             for (var s in $scope.searcherDetails) {
  3138.                 $scope.searcherDetails[s].searchType = "text";
  3139.             }
  3140.         })
  3141.  
  3142.     ]).then(function () {
  3143.         //all init loading is complete
  3144.         $scope.loading = false;
  3145.     });
  3146.  
  3147.  
  3148. }
  3149. angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", examineMgmtController);
  3150. function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
  3151.  
  3152.     function check(item) {
  3153.         var action = item.check;
  3154.         umbRequestHelper.resourcePromise(
  3155.                 $http.get(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
  3156.                 'Failed to retrieve data integrity status')
  3157.             .then(function(result) {
  3158.                 item.checking = false;
  3159.                 item.invalid = result === "false";
  3160.             });
  3161.     }
  3162.  
  3163.     $scope.fix = function(item) {
  3164.         var action = item.fix;
  3165.         if (item.fix) {
  3166.             if (confirm("This will cause all xml structures for this type to be rebuilt. " +
  3167.                 "Depending on how much content there is in your site this could take a while. " +
  3168.                 "It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic " +
  3169.                 "or when editors are editing content.")) {
  3170.                 item.fixing = true;
  3171.                 umbRequestHelper.resourcePromise(
  3172.                     $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
  3173.                     'Failed to retrieve data integrity status')
  3174.                 .then(function (result) {
  3175.                     item.fixing = false;
  3176.                     item.invalid = result === "false";
  3177.                 });
  3178.             }
  3179.         }
  3180.     }
  3181.  
  3182.     $scope.items = {
  3183.         "contentXml": {
  3184.             label: "Content in the cmsContentXml table",
  3185.             checking: true,
  3186.             fixing: false,
  3187.             fix: "FixContentXmlTable",
  3188.             check: "CheckContentXmlTable"
  3189.         },
  3190.         "mediaXml": {
  3191.             label: "Media in the cmsContentXml table",
  3192.             checking: true,
  3193.             fixing: false,
  3194.             fix: "FixMediaXmlTable",
  3195.             check: "CheckMediaXmlTable"
  3196.         },
  3197.         "memberXml": {
  3198.             label: "Members in the cmsContentXml table",
  3199.             checking: true,
  3200.             fixing: false,
  3201.             fix: "FixMembersXmlTable",
  3202.             check: "CheckMembersXmlTable"
  3203.         }
  3204.     };
  3205.  
  3206.     for (var i in $scope.items) {
  3207.         check($scope.items[i]);
  3208.     }
  3209.  
  3210. }
  3211. angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", xmlDataIntegrityReportController);
  3212. /**
  3213.  * @ngdoc controller
  3214.  * @name Umbraco.Editors.ContentDeleteController
  3215.  * @function
  3216.  *
  3217.  * @description
  3218.  * The controller for deleting content
  3219.  */
  3220. function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) {
  3221.  
  3222.     $scope.performDelete = function() {
  3223.  
  3224.         //mark it for deletion (used in the UI)
  3225.         $scope.currentNode.loading = true;
  3226.         dataTypeResource.deleteById($scope.currentNode.id).then(function () {
  3227.             $scope.currentNode.loading = false;
  3228.  
  3229.             //get the root node before we remove it
  3230.             var rootNode = treeService.getTreeRoot($scope.currentNode);
  3231.            
  3232.             //TODO: Need to sync tree, etc...
  3233.             treeService.removeNode($scope.currentNode);
  3234.             navigationService.hideMenu();
  3235.         });
  3236.  
  3237.     };
  3238.  
  3239.     $scope.cancel = function() {
  3240.         navigationService.hideDialog();
  3241.     };
  3242. }
  3243.  
  3244. angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController);
  3245.  
  3246. /**
  3247.  * @ngdoc controller
  3248.  * @name Umbraco.Editors.DataType.EditController
  3249.  * @function
  3250.  *
  3251.  * @description
  3252.  * The controller for the content editor
  3253.  */
  3254. function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService,  angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState) {
  3255.  
  3256.     //setup scope vars    
  3257.     $scope.currentSection = appState.getSectionState("currentSection");
  3258.     $scope.currentNode = null; //the editors affiliated node
  3259.  
  3260.     //method used to configure the pre-values when we retrieve them from the server
  3261.     function createPreValueProps(preVals) {
  3262.         $scope.preValues = [];
  3263.         for (var i = 0; i < preVals.length; i++) {
  3264.             $scope.preValues.push({
  3265.                 hideLabel: preVals[i].hideLabel,
  3266.                 alias: preVals[i].key,
  3267.                 description: preVals[i].description,
  3268.                 label: preVals[i].label,
  3269.                 view: preVals[i].view,
  3270.                 value: preVals[i].value
  3271.             });
  3272.         }
  3273.     }
  3274.  
  3275.     //set up the standard data type props
  3276.     $scope.properties = {
  3277.         selectedEditor: {
  3278.             alias: "selectedEditor",
  3279.             description: "Select a property editor",
  3280.             label: "Property editor"
  3281.         },
  3282.         selectedEditorId: {
  3283.             alias: "selectedEditorId",
  3284.             label: "Property editor alias"
  3285.         }
  3286.     };
  3287.    
  3288.     //setup the pre-values as props
  3289.     $scope.preValues = [];
  3290.  
  3291.     if ($routeParams.create) {
  3292.         //we are creating so get an empty data type item
  3293.         dataTypeResource.getScaffold()
  3294.             .then(function(data) {
  3295.                 $scope.loaded = true;
  3296.                 $scope.preValuesLoaded = true;
  3297.                 $scope.content = data;
  3298.  
  3299.                 //set a shared state
  3300.                 editorState.set($scope.content);
  3301.             });
  3302.     }
  3303.     else {
  3304.         //we are editing so get the content item from the server
  3305.         dataTypeResource.getById($routeParams.id)
  3306.             .then(function(data) {
  3307.                 $scope.loaded = true;
  3308.                 $scope.preValuesLoaded = true;
  3309.                 $scope.content = data;
  3310.  
  3311.                 createPreValueProps($scope.content.preValues);
  3312.                
  3313.                 //share state
  3314.                 editorState.set($scope.content);
  3315.  
  3316.                 //in one particular special case, after we've created a new item we redirect back to the edit
  3317.                 // route but there might be server validation errors in the collection which we need to display
  3318.                 // after the redirect, so we will bind all subscriptions which will show the server validation errors
  3319.                 // if there are any and then clear them so the collection no longer persists them.
  3320.                 serverValidationManager.executeAndClearAllSubscriptions();
  3321.                
  3322.                 navigationService.syncTree({ tree: "datatype", path: [String(data.id)] }).then(function (syncArgs) {
  3323.                     $scope.currentNode = syncArgs.node;
  3324.                 });
  3325.             });
  3326.     }
  3327.    
  3328.     $scope.$watch("content.selectedEditor", function (newVal, oldVal) {
  3329.  
  3330.         //when the value changes, we need to dynamically load in the new editor
  3331.         if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
  3332.             //we are editing so get the content item from the server
  3333.             var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
  3334.             dataTypeResource.getPreValues(newVal, currDataTypeId)
  3335.                 .then(function (data) {
  3336.                     $scope.preValuesLoaded = true;
  3337.                     $scope.content.preValues = data;
  3338.                     createPreValueProps($scope.content.preValues);
  3339.                    
  3340.                     //share state
  3341.                     editorState.set($scope.content);
  3342.                 });
  3343.         }
  3344.     });
  3345.  
  3346.     $scope.save = function() {
  3347.  
  3348.         if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
  3349.            
  3350.             dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create)
  3351.                 .then(function(data) {
  3352.  
  3353.                     formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  3354.  
  3355.                     contentEditingHelper.handleSuccessfulSave({
  3356.                         scope: $scope,
  3357.                         savedContent: data,
  3358.                         rebindCallback: function() {
  3359.                             createPreValueProps(data.preValues);
  3360.                         }
  3361.                     });
  3362.  
  3363.                     //share state
  3364.                     editorState.set($scope.content);
  3365.  
  3366.                     navigationService.syncTree({ tree: "datatype", path: [String(data.id)], forceReload: true }).then(function (syncArgs) {
  3367.                         $scope.currentNode = syncArgs.node;
  3368.                     });
  3369.                    
  3370.                 }, function(err) {
  3371.  
  3372.                     //NOTE: in the case of data type values we are setting the orig/new props
  3373.                     // to be the same thing since that only really matters for content/media.
  3374.                     contentEditingHelper.handleSaveError({
  3375.                         redirectOnFailure: false,
  3376.                         err: err
  3377.                     });
  3378.                    
  3379.                     //share state
  3380.                     editorState.set($scope.content);
  3381.                 });
  3382.         }
  3383.  
  3384.     };
  3385.  
  3386. }
  3387.  
  3388. angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController);
  3389.  
  3390. /**
  3391.  * @ngdoc controller
  3392.  * @name Umbraco.Editors.Media.CreateController
  3393.  * @function
  3394.  *
  3395.  * @description
  3396.  * The controller for the media creation dialog
  3397.  */
  3398. function mediaCreateController($scope, $routeParams, mediaTypeResource, iconHelper) {
  3399.    
  3400.     mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
  3401.         $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
  3402.     });
  3403.    
  3404. }
  3405.  
  3406. angular.module('umbraco').controller("Umbraco.Editors.Media.CreateController", mediaCreateController);
  3407. /**
  3408.  * @ngdoc controller
  3409.  * @name Umbraco.Editors.ContentDeleteController
  3410.  * @function
  3411.  *
  3412.  * @description
  3413.  * The controller for deleting content
  3414.  */
  3415. function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location) {
  3416.  
  3417.     $scope.performDelete = function() {
  3418.  
  3419.         //mark it for deletion (used in the UI)
  3420.         $scope.currentNode.loading = true;
  3421.  
  3422.         mediaResource.deleteById($scope.currentNode.id).then(function () {
  3423.             $scope.currentNode.loading = false;
  3424.  
  3425.             //get the root node before we remove it
  3426.             var rootNode = treeService.getTreeRoot($scope.currentNode);
  3427.  
  3428.             treeService.removeNode($scope.currentNode);
  3429.  
  3430.             if (rootNode) {
  3431.                 //ensure the recycle bin has child nodes now            
  3432.                 var recycleBin = treeService.getDescendantNode(rootNode, -21);
  3433.                 if (recycleBin) {
  3434.                     recycleBin.hasChildren = true;
  3435.                 }
  3436.             }
  3437.            
  3438.             //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
  3439.             if (editorState.current && editorState.current.id == $scope.currentNode.id) {
  3440.                 $location.path("/media/media/edit/" + $scope.currentNode.parentId);
  3441.             }
  3442.  
  3443.             navigationService.hideMenu();
  3444.  
  3445.         },function() {
  3446.             $scope.currentNode.loading = false;
  3447.         });
  3448.     };
  3449.  
  3450.     $scope.cancel = function() {
  3451.         navigationService.hideDialog();
  3452.     };
  3453. }
  3454.  
  3455. angular.module("umbraco").controller("Umbraco.Editors.Media.DeleteController", MediaDeleteController);
  3456.  
  3457. /**
  3458.  * @ngdoc controller
  3459.  * @name Umbraco.Editors.Media.EditController
  3460.  * @function
  3461.  *
  3462.  * @description
  3463.  * The controller for the media editor
  3464.  */
  3465. function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
  3466.  
  3467.     //setup scope vars
  3468.     $scope.currentSection = appState.getSectionState("currentSection");
  3469.     $scope.currentNode = null; //the editors affiliated node
  3470.  
  3471.     /** Syncs the content item to it's tree node - this occurs on first load and after saving */
  3472.     function syncTreeNode(content, path, initialLoad) {
  3473.  
  3474.         if (!$scope.content.isChildOfListView) {
  3475.             navigationService.syncTree({ tree: "media", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
  3476.                 $scope.currentNode = syncArgs.node;
  3477.             });
  3478.         }
  3479.         else if (initialLoad === true) {
  3480.  
  3481.             //it's a child item, just sync the ui node to the parent
  3482.             navigationService.syncTree({ tree: "media", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
  3483.  
  3484.             //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
  3485.             // from the server so that we can load in the actions menu.
  3486.             umbRequestHelper.resourcePromise(
  3487.                 $http.get(content.treeNodeUrl),
  3488.                 'Failed to retrieve data for child node ' + content.id).then(function (node) {
  3489.                     $scope.currentNode = node;
  3490.                 });
  3491.         }
  3492.     }
  3493.  
  3494.     if ($routeParams.create) {
  3495.  
  3496.         mediaResource.getScaffold($routeParams.id, $routeParams.doctype)
  3497.             .then(function (data) {
  3498.                 $scope.loaded = true;
  3499.                 $scope.content = data;
  3500.  
  3501.                 editorState.set($scope.content);
  3502.             });
  3503.     }
  3504.     else {
  3505.         mediaResource.getById($routeParams.id)
  3506.             .then(function (data) {
  3507.                 $scope.loaded = true;
  3508.                 $scope.content = data;
  3509.                
  3510.                 if (data.isChildOfListView && data.trashed === false) {
  3511.                     $scope.listViewPath = ($routeParams.page)
  3512.                         ? "/media/media/edit/" + data.parentId + "?page=" + $routeParams.page
  3513.                         : "/media/media/edit/" + data.parentId;
  3514.                 }
  3515.  
  3516.                 editorState.set($scope.content);
  3517.  
  3518.                 //in one particular special case, after we've created a new item we redirect back to the edit
  3519.                 // route but there might be server validation errors in the collection which we need to display
  3520.                 // after the redirect, so we will bind all subscriptions which will show the server validation errors
  3521.                 // if there are any and then clear them so the collection no longer persists them.
  3522.                 serverValidationManager.executeAndClearAllSubscriptions();
  3523.  
  3524.                 syncTreeNode($scope.content, data.path, true);
  3525.                
  3526.                 if ($scope.content.parentId && $scope.content.parentId != -1) {
  3527.                     //We fetch all ancestors of the node to generate the footer breadcrump navigation
  3528.                     entityResource.getAncestors($routeParams.id, "media")
  3529.                         .then(function (anc) {
  3530.                             $scope.ancestors = anc;
  3531.                         });
  3532.                 }
  3533.  
  3534.             });  
  3535.     }
  3536.    
  3537.     $scope.save = function () {
  3538.  
  3539.         if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
  3540.  
  3541.             $scope.busy = true;
  3542.  
  3543.             mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles())
  3544.                 .then(function(data) {
  3545.  
  3546.                     formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  3547.  
  3548.                     contentEditingHelper.handleSuccessfulSave({
  3549.                         scope: $scope,
  3550.                         savedContent: data,
  3551.                         rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
  3552.                     });
  3553.  
  3554.                     editorState.set($scope.content);
  3555.                     $scope.busy = false;
  3556.  
  3557.                     syncTreeNode($scope.content, data.path);
  3558.  
  3559.                 }, function(err) {
  3560.  
  3561.                     contentEditingHelper.handleSaveError({
  3562.                         err: err,
  3563.                         redirectOnFailure: true,
  3564.                         rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
  3565.                     });
  3566.                    
  3567.                     editorState.set($scope.content);
  3568.                     $scope.busy = false;
  3569.                 });
  3570.         }else{
  3571.             $scope.busy = false;
  3572.         }
  3573.        
  3574.     };
  3575. }
  3576.  
  3577. angular.module("umbraco")
  3578.     .controller("Umbraco.Editors.Media.EditController", mediaEditController);
  3579.  
  3580. /**
  3581.  * @ngdoc controller
  3582.  * @name Umbraco.Editors.Media.EmptyRecycleBinController
  3583.  * @function
  3584.  *
  3585.  * @description
  3586.  * The controller for deleting media
  3587.  */
  3588. function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService) {
  3589.  
  3590.     $scope.performDelete = function() {
  3591.  
  3592.         //(used in the UI)
  3593.         $scope.currentNode.loading = true;
  3594.  
  3595.         mediaResource.emptyRecycleBin($scope.currentNode.id).then(function () {
  3596.             $scope.currentNode.loading = false;
  3597.             //TODO: Need to sync tree, etc...
  3598.             treeService.removeChildNodes($scope.currentNode);
  3599.             navigationService.hideMenu();
  3600.         });
  3601.  
  3602.     };
  3603.  
  3604.     $scope.cancel = function() {
  3605.         navigationService.hideDialog();
  3606.     };
  3607. }
  3608.  
  3609. angular.module("umbraco").controller("Umbraco.Editors.Media.EmptyRecycleBinController", MediaEmptyRecycleBinController);
  3610.  
  3611. //used for the media picker dialog
  3612. angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController",
  3613.     function ($scope, eventsService, mediaResource, appState, treeService, navigationService) {
  3614.         var dialogOptions = $scope.dialogOptions;
  3615.  
  3616.         $scope.dialogTreeEventHandler = $({});
  3617.         var node = dialogOptions.currentNode;
  3618.  
  3619.         function nodeSelectHandler(ev, args) {
  3620.             args.event.preventDefault();
  3621.             args.event.stopPropagation();
  3622.  
  3623.             eventsService.emit("editors.media.moveController.select", args);
  3624.  
  3625.             if ($scope.target) {
  3626.                 //un-select if there's a current one selected
  3627.                 $scope.target.selected = false;
  3628.             }
  3629.  
  3630.             $scope.target = args.node;
  3631.             $scope.target.selected = true;
  3632.         }
  3633.  
  3634.         $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  3635.  
  3636.  
  3637.         $scope.move = function () {
  3638.             mediaResource.move({ parentId: $scope.target.id, id: node.id })
  3639.                 .then(function (path) {
  3640.                     $scope.error = false;
  3641.                     $scope.success = true;
  3642.  
  3643.                     //first we need to remove the node that launched the dialog
  3644.                     treeService.removeNode($scope.currentNode);
  3645.  
  3646.                     //get the currently edited node (if any)
  3647.                     var activeNode = appState.getTreeState("selectedNode");
  3648.  
  3649.                     //we need to do a double sync here: first sync to the moved content - but don't activate the node,
  3650.                     //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
  3651.  
  3652.                     navigationService.syncTree({ tree: "media", path: path, forceReload: true, activate: false }).then(function (args) {
  3653.                         if (activeNode) {
  3654.                             var activeNodePath = treeService.getPath(activeNode).join();
  3655.                             //sync to this node now - depending on what was copied this might already be synced but might not be
  3656.                             navigationService.syncTree({ tree: "media", path: activeNodePath, forceReload: false, activate: true });
  3657.                         }
  3658.                     });
  3659.  
  3660.                 }, function (err) {
  3661.                     $scope.success = false;
  3662.                     $scope.error = err;
  3663.                 });
  3664.         };
  3665.  
  3666.         $scope.$on('$destroy', function () {
  3667.             $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  3668.         });
  3669.     });
  3670. /**
  3671.  * @ngdoc controller
  3672.  * @name Umbraco.Editors.Content.MediaRecycleBinController
  3673.  * @function
  3674.  *
  3675.  * @description
  3676.  * Controls the recycle bin for media
  3677.  *
  3678.  */
  3679.  
  3680. function MediaRecycleBinController($scope, $routeParams, dataTypeResource) {
  3681.  
  3682.     //ensures the list view doesn't actually load until we query for the list view config
  3683.     // for the section
  3684.     $scope.listViewPath = null;
  3685.  
  3686.     $routeParams.id = "-21";
  3687.     dataTypeResource.getById(-96).then(function (result) {
  3688.         _.each(result.preValues, function (i) {
  3689.             $scope.model.config[i.key] = i.value;
  3690.         });
  3691.         $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
  3692.     });
  3693.  
  3694.     $scope.model = { config: { entityType: $routeParams.section } };
  3695.  
  3696. }
  3697.  
  3698. angular.module('umbraco').controller("Umbraco.Editors.Media.RecycleBinController", MediaRecycleBinController);
  3699.  
  3700. /**
  3701.  * @ngdoc controller
  3702.  * @name Umbraco.Editors.Member.CreateController
  3703.  * @function
  3704.  *
  3705.  * @description
  3706.  * The controller for the member creation dialog
  3707.  */
  3708. function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) {
  3709.    
  3710.     memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
  3711.         $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
  3712.     });
  3713.    
  3714. }
  3715.  
  3716. angular.module('umbraco').controller("Umbraco.Editors.Member.CreateController", memberCreateController);
  3717. /**
  3718.  * @ngdoc controller
  3719.  * @name Umbraco.Editors.Member.DeleteController
  3720.  * @function
  3721.  *
  3722.  * @description
  3723.  * The controller for deleting content
  3724.  */
  3725. function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
  3726.  
  3727.     $scope.performDelete = function() {
  3728.  
  3729.         //mark it for deletion (used in the UI)
  3730.         $scope.currentNode.loading = true;
  3731.  
  3732.         memberResource.deleteByKey($scope.currentNode.id).then(function () {
  3733.             $scope.currentNode.loading = false;
  3734.  
  3735.             treeService.removeNode($scope.currentNode);
  3736.            
  3737.             //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
  3738.             if (editorState.current && editorState.current.key == $scope.currentNode.id) {
  3739.                 $location.path("/member/member/list/" + ($routeParams.listName ? $routeParams.listName : 'all-members'));
  3740.             }
  3741.  
  3742.             navigationService.hideMenu();
  3743.         });
  3744.  
  3745.     };
  3746.  
  3747.     $scope.cancel = function() {
  3748.         navigationService.hideDialog();
  3749.     };
  3750. }
  3751.  
  3752. angular.module("umbraco").controller("Umbraco.Editors.Member.DeleteController", MemberDeleteController);
  3753.  
  3754. /**
  3755.  * @ngdoc controller
  3756.  * @name Umbraco.Editors.Member.EditController
  3757.  * @function
  3758.  *
  3759.  * @description
  3760.  * The controller for the member editor
  3761.  */
  3762. function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
  3763.    
  3764.     //setup scope vars
  3765.     $scope.currentSection = appState.getSectionState("currentSection");
  3766.     $scope.currentNode = null; //the editors affiliated node
  3767.  
  3768.     $scope.listViewPath = ($routeParams.page && $routeParams.listName)
  3769.         ? "/member/member/list/" + $routeParams.listName + "?page=" + $routeParams.page
  3770.         : null;
  3771.  
  3772.     //build a path to sync the tree with
  3773.     function buildTreePath(data) {
  3774.         return $routeParams.listName ? "-1," + $routeParams.listName : "-1";
  3775.     }
  3776.  
  3777.     if ($routeParams.create) {
  3778.        
  3779.         //if there is no doc type specified then we are going to assume that
  3780.         // we are not using the umbraco membership provider
  3781.         if ($routeParams.doctype) {
  3782.             //we are creating so get an empty member item
  3783.             memberResource.getScaffold($routeParams.doctype)
  3784.                 .then(function(data) {
  3785.                     $scope.loaded = true;
  3786.                     $scope.content = data;
  3787.  
  3788.                     editorState.set($scope.content);
  3789.                 });
  3790.         }
  3791.         else {
  3792.             memberResource.getScaffold()
  3793.                 .then(function (data) {
  3794.                     $scope.loaded = true;
  3795.                     $scope.content = data;
  3796.  
  3797.                     editorState.set($scope.content);
  3798.                 });
  3799.         }
  3800.        
  3801.     }
  3802.     else {
  3803.         //so, we usually refernce all editors with the Int ID, but with members we have
  3804.         //a different pattern, adding a route-redirect here to handle this:
  3805.         //isNumber doesnt work here since its seen as a string
  3806.  
  3807.         //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers.
  3808.  
  3809.         if ($routeParams.id && $routeParams.id.length < 9) {
  3810.             entityResource.getById($routeParams.id, "Member").then(function(entity) {
  3811.                 $location.path("member/member/edit/" + entity.key);
  3812.             });
  3813.         }
  3814.         else {
  3815.             //we are editing so get the content item from the server
  3816.             memberResource.getByKey($routeParams.id)
  3817.                 .then(function(data) {
  3818.                     $scope.loaded = true;
  3819.                     $scope.content = data;
  3820.  
  3821.                     editorState.set($scope.content);
  3822.                    
  3823.                     var path = buildTreePath(data);
  3824.  
  3825.                     //sync the tree (only for ui purposes)
  3826.                     navigationService.syncTree({ tree: "member", path: path.split(",") });
  3827.  
  3828.                     //it's the initial load of the editor, we need to get the tree node
  3829.                     // from the server so that we can load in the actions menu.
  3830.                     umbRequestHelper.resourcePromise(
  3831.                         $http.get(data.treeNodeUrl),
  3832.                         'Failed to retrieve data for child node ' + data.key).then(function (node) {
  3833.                             $scope.currentNode = node;
  3834.                         });
  3835.  
  3836.                     //in one particular special case, after we've created a new item we redirect back to the edit
  3837.                     // route but there might be server validation errors in the collection which we need to display
  3838.                     // after the redirect, so we will bind all subscriptions which will show the server validation errors
  3839.                     // if there are any and then clear them so the collection no longer persists them.
  3840.                     serverValidationManager.executeAndClearAllSubscriptions();
  3841.                 });
  3842.         }
  3843.  
  3844.     }
  3845.    
  3846.     $scope.save = function() {
  3847.  
  3848.         if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
  3849.            
  3850.             $scope.busy = true;
  3851.  
  3852.             memberResource.save($scope.content, $routeParams.create, fileManager.getFiles())
  3853.                 .then(function(data) {
  3854.  
  3855.                     formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  3856.  
  3857.                     contentEditingHelper.handleSuccessfulSave({
  3858.                         scope: $scope,
  3859.                         savedContent: data,
  3860.                         //specify a custom id to redirect to since we want to use the GUID
  3861.                         redirectId: data.key,
  3862.                         rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
  3863.                     });
  3864.                    
  3865.                     editorState.set($scope.content);
  3866.                     $scope.busy = false;
  3867.                    
  3868.                     var path = buildTreePath(data);
  3869.  
  3870.                     //sync the tree (only for ui purposes)
  3871.                     navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true });
  3872.  
  3873.             }, function (err) {
  3874.                    
  3875.                     contentEditingHelper.handleSaveError({
  3876.                         redirectOnFailure: false,
  3877.                         err: err,
  3878.                         rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
  3879.                     });
  3880.                    
  3881.                     editorState.set($scope.content);
  3882.                     $scope.busy = false;
  3883.                 });
  3884.         }else{
  3885.             $scope.busy = false;
  3886.         }
  3887.        
  3888.     };
  3889.  
  3890. }
  3891.  
  3892. angular.module("umbraco").controller("Umbraco.Editors.Member.EditController", MemberEditController);
  3893.  
  3894. /**
  3895.  * @ngdoc controller
  3896.  * @name Umbraco.Editors.Member.ListController
  3897.  * @function
  3898.  *
  3899.  * @description
  3900.  * The controller for the member list view
  3901.  */
  3902. function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState) {
  3903.    
  3904.     //setup scope vars
  3905.     $scope.currentSection = appState.getSectionState("currentSection");
  3906.     $scope.currentNode = null; //the editors affiliated node
  3907.    
  3908.     //we are editing so get the content item from the server
  3909.     memberResource.getListNode($routeParams.id)
  3910.         .then(function (data) {
  3911.             $scope.loaded = true;
  3912.             $scope.content = data;
  3913.  
  3914.             editorState.set($scope.content);
  3915.  
  3916.             navigationService.syncTree({ tree: "member", path: data.path.split(",") }).then(function (syncArgs) {
  3917.                 $scope.currentNode = syncArgs.node;
  3918.             });
  3919.  
  3920.             //in one particular special case, after we've created a new item we redirect back to the edit
  3921.             // route but there might be server validation errors in the collection which we need to display
  3922.             // after the redirect, so we will bind all subscriptions which will show the server validation errors
  3923.             // if there are any and then clear them so the collection no longer persists them.
  3924.             serverValidationManager.executeAndClearAllSubscriptions();
  3925.         });
  3926. }
  3927.  
  3928. angular.module("umbraco").controller("Umbraco.Editors.Member.ListController", MemberListController);
  3929.  
  3930. function imageFilePickerController($scope, dialogService, mediaHelper) {
  3931.  
  3932.     $scope.pick = function() {
  3933.         dialogService.mediaPicker({
  3934.             multiPicker: false,
  3935.             callback: function(data) {
  3936.                  $scope.model.value = mediaHelper.resolveFile(data, false);
  3937.             }
  3938.         });
  3939.     };
  3940.  
  3941. }
  3942.  
  3943. angular.module('umbraco').controller("Umbraco.PrevalueEditors.ImageFilePickerController",imageFilePickerController);
  3944.  
  3945. //this controller simply tells the dialogs service to open a mediaPicker window
  3946. //with a specified callback, this callback will receive an object with a selection on it
  3947. function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) {
  3948.  
  3949.     function trim(str, chr) {
  3950.         var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
  3951.         return str.replace(rgxtrim, '');
  3952.     }
  3953.  
  3954.     $scope.renderModel = [];  
  3955.  
  3956.     var dialogOptions = {
  3957.         multiPicker: false,
  3958.         entityType: "Media",
  3959.         section: "media",
  3960.         treeAlias: "media",
  3961.         callback: function(data) {
  3962.             if (angular.isArray(data)) {
  3963.                 _.each(data, function (item, i) {
  3964.                     $scope.add(item);
  3965.                 });
  3966.             }
  3967.             else {
  3968.                 $scope.clear();
  3969.                 $scope.add(data);
  3970.             }
  3971.         }
  3972.     };
  3973.  
  3974.     $scope.openContentPicker = function(){
  3975.         var d = dialogService.treePicker(dialogOptions);
  3976.     };
  3977.  
  3978.     $scope.remove =function(index){
  3979.         $scope.renderModel.splice(index, 1);
  3980.     };
  3981.  
  3982.     $scope.clear = function() {
  3983.         $scope.renderModel = [];
  3984.     };
  3985.  
  3986.     $scope.add = function (item) {
  3987.         var currIds = _.map($scope.renderModel, function (i) {
  3988.             return i.id;
  3989.         });
  3990.         if (currIds.indexOf(item.id) < 0) {
  3991.             item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  3992.             $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
  3993.         }  
  3994.     };
  3995.  
  3996.     var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  3997.         var currIds = _.map($scope.renderModel, function (i) {
  3998.             return i.id;
  3999.         });
  4000.         $scope.model.value = trim(currIds.join(), ",");
  4001.     });
  4002.  
  4003.     //when the scope is destroyed we need to unsubscribe
  4004.     $scope.$on('$destroy', function () {
  4005.         unsubscribe();
  4006.     });
  4007.  
  4008.     //load media data
  4009.     var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
  4010.     entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
  4011.         _.each(data, function (item, i) {
  4012.             item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4013.             $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
  4014.         });
  4015.     });
  4016.    
  4017. }
  4018.  
  4019. angular.module('umbraco').controller("Umbraco.PrevalueEditors.MediaPickerController",mediaPickerController);
  4020. angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesController",
  4021.     function ($scope, $timeout) {
  4022.        
  4023.         //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
  4024.  
  4025.         $scope.newItem = "";
  4026.         $scope.hasError = false;
  4027.        
  4028.         if (!angular.isArray($scope.model.value)) {
  4029.  
  4030.             //make an array from the dictionary
  4031.             var items = [];
  4032.             for (var i in $scope.model.value) {
  4033.                 items.push({
  4034.                     value: $scope.model.value[i].value,
  4035.                     sortOrder: $scope.model.value[i].sortOrder,
  4036.                     id: i
  4037.                 });
  4038.             }
  4039.  
  4040.             //ensure the items are sorted by the provided sort order
  4041.             items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  4042.            
  4043.             //now make the editor model the array
  4044.             $scope.model.value = items;
  4045.         }
  4046.  
  4047.         $scope.remove = function(item, evt) {
  4048.             evt.preventDefault();
  4049.  
  4050.             $scope.model.value = _.reject($scope.model.value, function (x) {
  4051.                 return x.value === item.value;
  4052.             });
  4053.            
  4054.         };
  4055.  
  4056.         $scope.add = function (evt) {
  4057.             evt.preventDefault();
  4058.            
  4059.            
  4060.             if ($scope.newItem) {
  4061.                 if (!_.contains($scope.model.value, $scope.newItem)) {                
  4062.                     $scope.model.value.push({ value: $scope.newItem });
  4063.                     $scope.newItem = "";
  4064.                     $scope.hasError = false;
  4065.                     return;
  4066.                 }
  4067.             }
  4068.  
  4069.             //there was an error, do the highlight (will be set back by the directive)
  4070.             $scope.hasError = true;            
  4071.         };
  4072.  
  4073.         $scope.sortableOptions = {
  4074.             axis: 'y',
  4075.             containment: 'parent',
  4076.             cursor: 'move',
  4077.             items: '> div.control-group',
  4078.             tolerance: 'pointer',
  4079.             update: function (e, ui) {
  4080.                 // Get the new and old index for the moved element (using the text as the identifier, so
  4081.                 // we'd have a problem if two prevalues were the same, but that would be unlikely)
  4082.                 var newIndex = ui.item.index();
  4083.                 var movedPrevalueText = $('input[type="text"]', ui.item).val();
  4084.                 var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
  4085.  
  4086.                 // Move the element in the model
  4087.                 if (originalIndex > -1) {
  4088.                     var movedElement = $scope.model.value[originalIndex];
  4089.                     $scope.model.value.splice(originalIndex, 1);
  4090.                     $scope.model.value.splice(newIndex, 0, movedElement);
  4091.                 }
  4092.             }
  4093.         };
  4094.  
  4095.         function getElementIndexByPrevalueText(value) {
  4096.             for (var i = 0; i < $scope.model.value.length; i++) {
  4097.                 if ($scope.model.value[i].value === value) {
  4098.                     return i;
  4099.                 }
  4100.             }
  4101.  
  4102.             return -1;
  4103.         }
  4104.  
  4105.     });
  4106.  
  4107. //this controller simply tells the dialogs service to open a mediaPicker window
  4108. //with a specified callback, this callback will receive an object with a selection on it
  4109. angular.module('umbraco')
  4110. .controller("Umbraco.PrevalueEditors.TreePickerController",
  4111.    
  4112.     function($scope, dialogService, entityResource, $log, iconHelper){
  4113.         $scope.renderModel = [];
  4114.         $scope.ids = [];
  4115.  
  4116.  
  4117.         var config = {
  4118.             multiPicker: false,
  4119.             entityType: "Document",
  4120.             type: "content",
  4121.             treeAlias: "content"
  4122.         };
  4123.        
  4124.         if($scope.model.value){
  4125.             $scope.ids = $scope.model.value.split(',');
  4126.             entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
  4127.                 _.each(data, function (item, i) {
  4128.                     item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4129.                     $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
  4130.                 });
  4131.             });
  4132.         }
  4133.        
  4134.  
  4135.         $scope.openContentPicker =function() {
  4136.             var d = dialogService.treePicker({
  4137.                 section: config.type,
  4138.                 treeAlias: config.type,
  4139.                 multiPicker: config.multiPicker,
  4140.                 callback: populate
  4141.             });
  4142.         };
  4143.  
  4144.         $scope.remove =function(index){
  4145.             $scope.renderModel.splice(index, 1);
  4146.             $scope.ids.splice(index, 1);
  4147.             $scope.model.value = trim($scope.ids.join(), ",");
  4148.         };
  4149.  
  4150.         $scope.clear = function() {
  4151.             $scope.model.value = "";
  4152.             $scope.renderModel = [];
  4153.             $scope.ids = [];
  4154.         };
  4155.        
  4156.         $scope.add =function(item){
  4157.             if($scope.ids.indexOf(item.id) < 0){
  4158.                 item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4159.  
  4160.                 $scope.ids.push(item.id);
  4161.                 $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
  4162.                 $scope.model.value = trim($scope.ids.join(), ",");
  4163.             }  
  4164.         };
  4165.  
  4166.  
  4167.         var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  4168.             $scope.model.value = trim($scope.ids.join(), ",");
  4169.         });
  4170.  
  4171.         //when the scope is destroyed we need to unsubscribe
  4172.         $scope.$on('$destroy', function () {
  4173.             unsubscribe();
  4174.         });
  4175.  
  4176.         function trim(str, chr) {
  4177.             var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
  4178.             return str.replace(rgxtrim, '');
  4179.         }
  4180.  
  4181.         function populate(data){
  4182.             if(angular.isArray(data)){
  4183.                 _.each(data, function (item, i) {
  4184.                     $scope.add(item);
  4185.                 });
  4186.             }else{
  4187.                 $scope.clear();
  4188.                 $scope.add(data);
  4189.             }
  4190.         }
  4191. });
  4192. //this controller simply tells the dialogs service to open a mediaPicker window
  4193. //with a specified callback, this callback will receive an object with a selection on it
  4194. angular.module('umbraco')
  4195. .controller("Umbraco.PrevalueEditors.TreeSourceController",
  4196.    
  4197.     function($scope, dialogService, entityResource, $log, iconHelper){
  4198.  
  4199.         if (!$scope.model) {
  4200.             $scope.model = {};
  4201.         }
  4202.         if (!$scope.model.value) {
  4203.             $scope.model.value = {
  4204.                 type: "content"
  4205.             };
  4206.         }
  4207.  
  4208.         if($scope.model.value.id && $scope.model.value.type !== "member"){
  4209.             var ent = "Document";
  4210.             if($scope.model.value.type === "media"){
  4211.                 ent = "Media";
  4212.             }
  4213.            
  4214.             entityResource.getById($scope.model.value.id, ent).then(function(item){
  4215.                 item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4216.                 $scope.node = item;
  4217.             });
  4218.         }
  4219.  
  4220.  
  4221.         $scope.openContentPicker =function(){
  4222.             var d = dialogService.treePicker({
  4223.                                 section: $scope.model.value.type,
  4224.                                 treeAlias: $scope.model.value.type,
  4225.                                 multiPicker: false,
  4226.                                 callback: populate});
  4227.         };
  4228.  
  4229.         $scope.clear = function() {
  4230.             $scope.model.value.id = undefined;
  4231.             $scope.node = undefined;
  4232.             $scope.model.value.query = undefined;
  4233.         };
  4234.        
  4235.  
  4236.         //we always need to ensure we dont submit anything broken
  4237.         var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  4238.             if($scope.model.value.type === "member"){
  4239.                 $scope.model.value.id = -1;
  4240.                 $scope.model.value.query = "";
  4241.             }
  4242.         });
  4243.  
  4244.         //when the scope is destroyed we need to unsubscribe
  4245.         $scope.$on('$destroy', function () {
  4246.             unsubscribe();
  4247.         });
  4248.  
  4249.         function populate(item){
  4250.                 $scope.clear();
  4251.                 item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4252.                 $scope.node = item;
  4253.                 $scope.model.value.id = item.id;
  4254.         }
  4255. });
  4256. function booleanEditorController($scope, $rootScope, assetsService) {
  4257.  
  4258.     function setupViewModel() {
  4259.         $scope.renderModel = {
  4260.             value: false
  4261.         };
  4262.         if ($scope.model && $scope.model.value && ($scope.model.value.toString() === "1" || angular.lowercase($scope.model.value) === "true")) {
  4263.             $scope.renderModel.value = true;
  4264.         }
  4265.     }
  4266.  
  4267.     setupViewModel();
  4268.  
  4269.     $scope.$watch("renderModel.value", function (newVal) {
  4270.         $scope.model.value = newVal === true ? "1" : "0";
  4271.     });
  4272.    
  4273.     //here we declare a special method which will be called whenever the value has changed from the server
  4274.     //this is instead of doing a watch on the model.value = faster
  4275.     $scope.model.onValueChanged = function (newVal, oldVal) {
  4276.         //update the display val again if it has changed from the server
  4277.         setupViewModel();
  4278.     };
  4279.  
  4280. }
  4281. angular.module("umbraco").controller("Umbraco.PropertyEditors.BooleanController", booleanEditorController);
  4282. angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController",
  4283.     function ($scope, $routeParams) {
  4284.        
  4285.         function resetModel(isNew) {
  4286.             //the model config will contain an object, if it does not we'll create defaults
  4287.             //NOTE: We will not support doing the password regex on the client side because the regex on the server side
  4288.             //based on the membership provider cannot always be ported to js from .net directly.        
  4289.             /*
  4290.             {
  4291.                 hasPassword: true/false,
  4292.                 requiresQuestionAnswer: true/false,
  4293.                 enableReset: true/false,
  4294.                 enablePasswordRetrieval: true/false,
  4295.                 minPasswordLength: 10
  4296.             }
  4297.             */
  4298.  
  4299.             //set defaults if they are not available
  4300.             if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
  4301.                 $scope.model.config.disableToggle = false;
  4302.             }
  4303.             if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
  4304.                 $scope.model.config.hasPassword = false;
  4305.             }
  4306.             if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
  4307.                 $scope.model.config.enablePasswordRetrieval = true;
  4308.             }
  4309.             if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
  4310.                 $scope.model.config.requiresQuestionAnswer = false;
  4311.             }
  4312.             if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
  4313.                 $scope.model.config.enableReset = true;
  4314.             }
  4315.             if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
  4316.                 $scope.model.config.minPasswordLength = 0;
  4317.             }
  4318.            
  4319.             //set the model defaults
  4320.             if (!angular.isObject($scope.model.value)) {
  4321.                 //if it's not an object then just create a new one
  4322.                 $scope.model.value = {
  4323.                     newPassword: null,
  4324.                     oldPassword: null,
  4325.                     reset: null,
  4326.                     answer: null
  4327.                 };
  4328.             }
  4329.             else {
  4330.                 //just reset the values
  4331.  
  4332.                 if (!isNew) {
  4333.                     //if it is new, then leave the generated pass displayed
  4334.                     $scope.model.value.newPassword = null;
  4335.                     $scope.model.value.oldPassword = null;
  4336.                 }
  4337.                 $scope.model.value.reset = null;
  4338.                 $scope.model.value.answer = null;
  4339.             }
  4340.  
  4341.             //the value to compare to match passwords
  4342.             if (!isNew) {
  4343.                 $scope.model.confirm = "";
  4344.             }
  4345.             else if ($scope.model.value.newPassword.length > 0) {
  4346.                 //if it is new and a new password has been set, then set the confirm password too
  4347.                 $scope.model.confirm = $scope.model.value.newPassword;
  4348.             }
  4349.            
  4350.         }
  4351.  
  4352.         resetModel($routeParams.create);
  4353.  
  4354.         //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
  4355.         //with validators turned on.
  4356.         $scope.changing = $scope.model.config.disableToggle === true || !$scope.model.config.hasPassword;
  4357.  
  4358.         //we're not currently changing so set the model to null
  4359.         if (!$scope.changing) {
  4360.             $scope.model.value = null;
  4361.         }
  4362.  
  4363.         $scope.doChange = function() {
  4364.             resetModel();
  4365.             $scope.changing = true;
  4366.             //if there was a previously generated password displaying, clear it
  4367.             $scope.model.value.generatedPassword = null;
  4368.         };
  4369.  
  4370.         $scope.cancelChange = function() {
  4371.             $scope.changing = false;
  4372.             //set model to null
  4373.             $scope.model.value = null;
  4374.         };
  4375.  
  4376.         var unsubscribe = [];
  4377.  
  4378.         //listen for the saved event, when that occurs we'll
  4379.         //change to changing = false;
  4380.         unsubscribe.push($scope.$on("formSubmitted", function() {
  4381.             if ($scope.model.config.disableToggle === false) {
  4382.                 $scope.changing = false;
  4383.             }
  4384.         }));
  4385.         unsubscribe.push($scope.$on("formSubmitting", function() {
  4386.             //if there was a previously generated password displaying, clear it
  4387.             if ($scope.changing && $scope.model.value) {
  4388.                 $scope.model.value.generatedPassword = null;
  4389.             }
  4390.             else if (!$scope.changing) {
  4391.                 //we are not changing, so the model needs to be null
  4392.                 $scope.model.value = null;
  4393.             }
  4394.         }));
  4395.  
  4396.         //when the scope is destroyed we need to unsubscribe
  4397.         $scope.$on('$destroy', function () {
  4398.             for (var u in unsubscribe) {
  4399.                 unsubscribe[u]();
  4400.             }
  4401.         });
  4402.  
  4403.         $scope.showReset = function() {
  4404.             return $scope.model.config.hasPassword && $scope.model.config.enableReset;
  4405.         };
  4406.  
  4407.         $scope.showOldPass = function() {
  4408.             return $scope.model.config.hasPassword &&
  4409.                 !$scope.model.config.allowManuallyChangingPassword &&
  4410.                 !$scope.model.config.enablePasswordRetrieval && !$scope.model.value.reset;
  4411.         };
  4412.  
  4413.         $scope.showNewPass = function () {
  4414.             return !$scope.model.value.reset;
  4415.         };
  4416.  
  4417.         $scope.showConfirmPass = function() {
  4418.             return !$scope.model.value.reset;
  4419.         };
  4420.        
  4421.         $scope.showCancelBtn = function() {
  4422.             return $scope.model.config.disableToggle !== true && $scope.model.config.hasPassword;
  4423.         };
  4424.  
  4425.     });
  4426.  
  4427. angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListController",
  4428.     function($scope) {
  4429.        
  4430.         if (angular.isObject($scope.model.config.items)) {
  4431.            
  4432.             //now we need to format the items in the dictionary because we always want to have an array
  4433.             var newItems = [];
  4434.             var vals = _.values($scope.model.config.items);
  4435.             var keys = _.keys($scope.model.config.items);
  4436.             for (var i = 0; i < vals.length; i++) {
  4437.                 newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
  4438.             }
  4439.  
  4440.             //ensure the items are sorted by the provided sort order
  4441.             newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  4442.  
  4443.             //re-assign
  4444.             $scope.model.config.items = newItems;
  4445.  
  4446.         }
  4447.  
  4448.         function setupViewModel() {
  4449.             $scope.selectedItems = [];
  4450.  
  4451.             //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
  4452.             // to "" gets selected by default
  4453.             if ($scope.model.value === null || $scope.model.value === undefined) {
  4454.                 $scope.model.value = [];
  4455.             }
  4456.  
  4457.             for (var i = 0; i < $scope.model.config.items.length; i++) {
  4458.                 var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id);
  4459.                 $scope.selectedItems.push({
  4460.                     checked: isChecked,
  4461.                     key: $scope.model.config.items[i].id,
  4462.                     val: $scope.model.config.items[i].value
  4463.                 });
  4464.             }
  4465.  
  4466.         }
  4467.  
  4468.         setupViewModel();
  4469.        
  4470.  
  4471.         //update the model when the items checked changes
  4472.         $scope.$watch("selectedItems", function(newVal, oldVal) {
  4473.  
  4474.             $scope.model.value = [];
  4475.             for (var x = 0; x < $scope.selectedItems.length; x++) {
  4476.                 if ($scope.selectedItems[x].checked) {
  4477.                     $scope.model.value.push($scope.selectedItems[x].key);
  4478.                 }
  4479.             }
  4480.  
  4481.         }, true);
  4482.        
  4483.         //here we declare a special method which will be called whenever the value has changed from the server
  4484.         //this is instead of doing a watch on the model.value = faster
  4485.         $scope.model.onValueChanged = function (newVal, oldVal) {
  4486.             //update the display val again if it has changed from the server
  4487.             setupViewModel();
  4488.         };
  4489.  
  4490.     });
  4491.  
  4492. function ColorPickerController($scope) {
  4493.     $scope.toggleItem = function (color) {
  4494.         if ($scope.model.value == color) {
  4495.             $scope.model.value = "";
  4496.             //this is required to re-validate
  4497.             $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
  4498.         }
  4499.         else {
  4500.             $scope.model.value = color;
  4501.             //this is required to re-validate
  4502.             $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
  4503.         }
  4504.     };
  4505.     // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
  4506.     $scope.validateMandatory = function () {
  4507.         return {
  4508.             isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""),
  4509.             errorMsg: "Value cannot be empty",
  4510.             errorKey: "required"
  4511.         };
  4512.     }
  4513.     $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
  4514. }
  4515.  
  4516. angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController);
  4517.  
  4518. angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController",
  4519.     function ($scope, $timeout, assetsService, angularHelper, $element) {
  4520.         //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
  4521.         var defaultColor = "000000";
  4522.        
  4523.         $scope.newColor = defaultColor;
  4524.         $scope.hasError = false;
  4525.  
  4526.         assetsService.load([
  4527.             //"lib/spectrum/tinycolor.js",
  4528.             "lib/spectrum/spectrum.js"          
  4529.         ]).then(function () {
  4530.             var elem = $element.find("input");
  4531.             elem.spectrum({
  4532.                 color: null,
  4533.                 showInitial: false,
  4534.                 chooseText: "choose", // TODO: These can be localised
  4535.                 cancelText: "cancel", // TODO: These can be localised
  4536.                 preferredFormat: "hex",
  4537.                 showInput: true,
  4538.                 clickoutFiresChange: true,
  4539.                 hide: function (color) {
  4540.                     //show the add butotn
  4541.                     $element.find(".btn.add").show();                    
  4542.                 },
  4543.                 change: function (color) {
  4544.                     angularHelper.safeApply($scope, function () {
  4545.                         $scope.newColor = color.toHexString().trimStart("#"); // #ff0000
  4546.                     });
  4547.                 },
  4548.                 show: function() {
  4549.                     //hide the add butotn
  4550.                     $element.find(".btn.add").hide();
  4551.                 }
  4552.             });
  4553.         });
  4554.  
  4555.         if (!angular.isArray($scope.model.value)) {
  4556.             //make an array from the dictionary
  4557.             var items = [];
  4558.             for (var i in $scope.model.value) {
  4559.                 items.push({
  4560.                     value: $scope.model.value[i],
  4561.                     id: i
  4562.                 });
  4563.             }
  4564.             //now make the editor model the array
  4565.             $scope.model.value = items;
  4566.         }
  4567.  
  4568.         $scope.remove = function (item, evt) {
  4569.  
  4570.             evt.preventDefault();
  4571.  
  4572.             $scope.model.value = _.reject($scope.model.value, function (x) {
  4573.                 return x.value === item.value;
  4574.             });
  4575.  
  4576.         };
  4577.  
  4578.         $scope.add = function (evt) {
  4579.  
  4580.             evt.preventDefault();
  4581.  
  4582.             if ($scope.newColor) {
  4583.                 var exists = _.find($scope.model.value, function(item) {
  4584.                     return item.value.toUpperCase() == $scope.newColor.toUpperCase();
  4585.                 });
  4586.                 if (!exists) {
  4587.                     $scope.model.value.push({ value: $scope.newColor });
  4588.                     //$scope.newColor = defaultColor;
  4589.                     // set colorpicker to default color
  4590.                     //var elem = $element.find("input");
  4591.                     //elem.spectrum("set", $scope.newColor);
  4592.                     $scope.hasError = false;
  4593.                     return;
  4594.                 }
  4595.  
  4596.                 //there was an error, do the highlight (will be set back by the directive)
  4597.                 $scope.hasError = true;
  4598.             }
  4599.  
  4600.         };
  4601.  
  4602.         //load the separate css for the editor to avoid it blocking our js loading
  4603.         assetsService.loadCss("lib/spectrum/spectrum.css");
  4604.     });
  4605.  
  4606. //this controller simply tells the dialogs service to open a mediaPicker window
  4607. //with a specified callback, this callback will receive an object with a selection on it
  4608.  
  4609. function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper) {
  4610.  
  4611.     function trim(str, chr) {
  4612.         var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
  4613.         return str.replace(rgxtrim, '');
  4614.     }
  4615.  
  4616.     function startWatch() {
  4617.         //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
  4618.         // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
  4619.         // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
  4620.         // In their source code there is no event so we need to just subscribe to our model changes here.
  4621.         //This also makes it easier to manage models, we update one and the rest will just work.
  4622.         $scope.$watch(function () {
  4623.             //return the joined Ids as a string to watch
  4624.             return _.map($scope.renderModel, function (i) {
  4625.                 return i.id;
  4626.             }).join();
  4627.         }, function (newVal) {
  4628.             var currIds = _.map($scope.renderModel, function (i) {
  4629.                 return i.id;
  4630.             });
  4631.             $scope.model.value = trim(currIds.join(), ",");
  4632.  
  4633.             //Validate!
  4634.             if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
  4635.                 $scope.contentPickerForm.minCount.$setValidity("minCount", false);
  4636.             }
  4637.             else {
  4638.                 $scope.contentPickerForm.minCount.$setValidity("minCount", true);
  4639.             }
  4640.  
  4641.             if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
  4642.                 $scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
  4643.             }
  4644.             else {
  4645.                 $scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
  4646.             }
  4647.         });
  4648.     }
  4649.  
  4650.     $scope.renderModel = [];
  4651.        
  4652.     $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
  4653.  
  4654.     //the default pre-values
  4655.     var defaultConfig = {
  4656.         multiPicker: false,
  4657.         showEditButton: false,        
  4658.         startNode: {
  4659.             query: "",
  4660.             type: "content",
  4661.                 id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
  4662.         }
  4663.     };
  4664.  
  4665.     if ($scope.model.config) {
  4666.         //merge the server config on top of the default config, then set the server config to use the result
  4667.         $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
  4668.     }
  4669.  
  4670.     //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
  4671.     $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
  4672.     $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
  4673.  
  4674.     var entityType = $scope.model.config.startNode.type === "member"
  4675.         ? "Member"
  4676.         : $scope.model.config.startNode.type === "media"
  4677.         ? "Media"
  4678.         : "Document";
  4679.  
  4680.     //the dialog options for the picker
  4681.     var dialogOptions = {
  4682.         multiPicker: $scope.model.config.multiPicker,
  4683.         entityType: entityType,
  4684.         filterCssClass: "not-allowed not-published",
  4685.         startNodeId: null,
  4686.         callback: function (data) {
  4687.             if (angular.isArray(data)) {
  4688.                 _.each(data, function (item, i) {
  4689.                     $scope.add(item);
  4690.                 });
  4691.             } else {
  4692.                 $scope.clear();
  4693.                 $scope.add(data);
  4694.             }
  4695.         },
  4696.         treeAlias: $scope.model.config.startNode.type,
  4697.         section: $scope.model.config.startNode.type
  4698.     };
  4699.  
  4700.     //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
  4701.     // pre-value config on to the dialog options
  4702.     angular.extend(dialogOptions, $scope.model.config);
  4703.  
  4704.     //We need to manually handle the filter for members here since the tree displayed is different and only contains
  4705.     // searchable list views
  4706.     if (entityType === "Member") {
  4707.         //first change the not allowed filter css class
  4708.         dialogOptions.filterCssClass = "not-allowed";
  4709.         var currFilter = dialogOptions.filter;
  4710.         //now change the filter to be a method
  4711.         dialogOptions.filter = function(i) {
  4712.             //filter out the list view nodes
  4713.             if (i.metaData.isContainer) {
  4714.                 return true;
  4715.             }
  4716.             if (!currFilter) {
  4717.                 return false;
  4718.             }
  4719.             //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller,
  4720.             // but not much we can do about that since members require special filtering.
  4721.             var filterItem = currFilter.toLowerCase().split(',');
  4722.             var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
  4723.             if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
  4724.                 return true;
  4725.             }
  4726.  
  4727.             return false;
  4728.         }
  4729.     }
  4730.  
  4731.  
  4732.     //if we have a query for the startnode, we will use that.
  4733.     if ($scope.model.config.startNode.query) {
  4734.         var rootId = $routeParams.id;
  4735.         entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
  4736.             dialogOptions.startNodeId = ent.id;
  4737.         });
  4738.     } else {
  4739.         dialogOptions.startNodeId = $scope.model.config.startNode.id;
  4740.     }        
  4741.    
  4742.     //dialog
  4743.     $scope.openContentPicker = function () {                
  4744.         var d = dialogService.treePicker(dialogOptions);
  4745.     };
  4746.  
  4747.     $scope.remove = function (index) {
  4748.         $scope.renderModel.splice(index, 1);
  4749.     };
  4750.        
  4751.     $scope.add = function (item) {
  4752.         var currIds = _.map($scope.renderModel, function (i) {
  4753.             return i.id;
  4754.         });
  4755.  
  4756.         if (currIds.indexOf(item.id) < 0) {
  4757.             item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  4758.             $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
  4759.         }
  4760.     };
  4761.  
  4762.     $scope.clear = function () {
  4763.         $scope.renderModel = [];
  4764.     };
  4765.        
  4766.     var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  4767.         var currIds = _.map($scope.renderModel, function (i) {
  4768.             return i.id;
  4769.         });
  4770.         $scope.model.value = trim(currIds.join(), ",");
  4771.     });
  4772.  
  4773.     //when the scope is destroyed we need to unsubscribe
  4774.     $scope.$on('$destroy', function () {
  4775.         unsubscribe();
  4776.     });
  4777.  
  4778.     //load current data
  4779.     var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
  4780.     entityResource.getByIds(modelIds, entityType).then(function (data) {
  4781.  
  4782.         //Ensure we populate the render model in the same order that the ids were stored!
  4783.         _.each(modelIds, function (id, i) {
  4784.             var entity = _.find(data, function (d) {                
  4785.                 return d.id == id;
  4786.             });
  4787.            
  4788.             if(entity) {
  4789.                 entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
  4790.                 $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon });
  4791.             }
  4792.            
  4793.         });
  4794.  
  4795.         //everything is loaded, start the watch on the model
  4796.         startWatch();
  4797.  
  4798.     });
  4799. }
  4800.  
  4801. angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController);
  4802.  
  4803. function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element) {
  4804.  
  4805.     //setup the default config
  4806.     var config = {
  4807.         pickDate: true,
  4808.         pickTime: true,
  4809.         useSeconds: true,
  4810.         format: "YYYY-MM-DD HH:mm:ss",
  4811.         icons: {
  4812.                     time: "icon-time",
  4813.                     date: "icon-calendar",
  4814.                     up: "icon-chevron-up",
  4815.                     down: "icon-chevron-down"
  4816.                 }
  4817.  
  4818.     };
  4819.  
  4820.     //map the user config
  4821.     $scope.model.config = angular.extend(config, $scope.model.config);
  4822.  
  4823.     $scope.hasDatetimePickerValue = $scope.model.value ? true : false;
  4824.     $scope.datetimePickerValue = null;
  4825.  
  4826.     //hide picker if clicking on the document
  4827.     $scope.hidePicker = function () {
  4828.         //$element.find("div:first").datetimepicker("hide");
  4829.         // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
  4830.         var dtp = $element.find("div:first");
  4831.         if (dtp && dtp.datetimepicker) {
  4832.             dtp.datetimepicker("hide");
  4833.         }
  4834.     };
  4835.     $(document).bind("click", $scope.hidePicker);
  4836.  
  4837.     //handles the date changing via the api
  4838.     function applyDate(e) {
  4839.         angularHelper.safeApply($scope, function() {
  4840.             // when a date is changed, update the model
  4841.             if (e.date && e.date.isValid()) {
  4842.                 $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
  4843.                 $scope.hasDatetimePickerValue = true;
  4844.                 if (!$scope.model.config.format) {
  4845.                     $scope.datetimePickerValue = e.date;
  4846.                 }
  4847.                 else {
  4848.                     $scope.datetimePickerValue = e.date.format($scope.model.config.format);
  4849.                 }
  4850.             }
  4851.             else {
  4852.                 $scope.hasDatetimePickerValue = false;
  4853.                 $scope.datetimePickerValue = null;
  4854.             }
  4855.            
  4856.             if (!$scope.model.config.pickTime) {
  4857.                 $element.find("div:first").datetimepicker("hide", 0);
  4858.             }
  4859.         });
  4860.     }
  4861.  
  4862.     var picker = null;
  4863.  
  4864.     $scope.clearDate = function() {
  4865.         $scope.hasDatetimePickerValue = false;
  4866.         $scope.datetimePickerValue = null;
  4867.         $scope.model.value = null;
  4868.         $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
  4869.     }
  4870.  
  4871.     //get the current user to see if we can localize this picker
  4872.     userService.getCurrentUser().then(function (user) {
  4873.  
  4874.         assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css').then(function() {
  4875.  
  4876.         var filesToLoad = ["lib/moment/moment-with-locales.js",
  4877.                            "lib/datetimepicker/bootstrap-datetimepicker.js"];
  4878.  
  4879.            
  4880.         $scope.model.config.language = user.locale;
  4881.        
  4882.  
  4883.         assetsService.load(filesToLoad).then(
  4884.             function () {
  4885.                 //The Datepicker js and css files are available and all components are ready to use.
  4886.  
  4887.                 // Get the id of the datepicker button that was clicked
  4888.                 var pickerId = $scope.model.alias;
  4889.  
  4890.                 var element = $element.find("div:first");
  4891.  
  4892.                 // Open the datepicker and add a changeDate eventlistener
  4893.                 element
  4894.                     .datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config))
  4895.                     .on("dp.change", applyDate)
  4896.                     .on("dp.error", function(a, b, c) {
  4897.                         $scope.hasDatetimePickerValue = false;
  4898.                         $scope.datePickerForm.datepicker.$setValidity("pickerError", false);
  4899.                     });
  4900.  
  4901.                 if ($scope.hasDatetimePickerValue) {
  4902.  
  4903.                     // ----> MML FIX BEGIN http://issues.umbraco.org/issue/U4-6953
  4904.                     //assign value to plugin/picker
  4905.                     var dateVal = $scope.model.value ? moment($scope.model.value, $scope.model.config.format) : moment();
  4906.                     element.datetimepicker("setValue", dateVal);
  4907.                     $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format);
  4908.                     // MML FIX END <-----
  4909.                 }
  4910.  
  4911.                 element.find("input").bind("blur", function() {
  4912.                     //we need to force an apply here
  4913.                     $scope.$apply();
  4914.                 });
  4915.  
  4916.                 //Ensure to remove the event handler when this instance is destroyted
  4917.                 $scope.$on('$destroy', function () {
  4918.                     element.find("input").unbind("blur");
  4919.                     element.datetimepicker("destroy");
  4920.                 });
  4921.             });
  4922.         });
  4923.  
  4924.        
  4925.     });
  4926.  
  4927.     var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  4928.         if ($scope.hasDatetimePickerValue) {
  4929.             if ($scope.model.config.pickTime) {
  4930.                 $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss");
  4931.             }
  4932.             else {
  4933.                 $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD");
  4934.             }
  4935.         }
  4936.         else {
  4937.             $scope.model.value = null;
  4938.         }
  4939.     });
  4940.  
  4941.     //unbind doc click event!
  4942.     $scope.$on('$destroy', function () {
  4943.         $(document).unbind("click", $scope.hidePicker);
  4944.         unsubscribe();
  4945.     });
  4946. }
  4947.  
  4948. angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController);
  4949.  
  4950. angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownController",
  4951.     function($scope) {
  4952.  
  4953.         //setup the default config
  4954.         var config = {
  4955.             items: [],
  4956.             multiple: false
  4957.         };
  4958.  
  4959.         //map the user config
  4960.         angular.extend(config, $scope.model.config);
  4961.  
  4962.         //map back to the model
  4963.         $scope.model.config = config;
  4964.        
  4965.         function convertArrayToDictionaryArray(model){
  4966.             //now we need to format the items in the dictionary because we always want to have an array
  4967.             var newItems = [];
  4968.             for (var i = 0; i < model.length; i++) {
  4969.                 newItems.push({ id: model[i], sortOrder: 0, value: model[i] });
  4970.             }
  4971.  
  4972.             return newItems;
  4973.         }
  4974.  
  4975.  
  4976.         function convertObjectToDictionaryArray(model){
  4977.             //now we need to format the items in the dictionary because we always want to have an array
  4978.             var newItems = [];
  4979.             var vals = _.values($scope.model.config.items);
  4980.             var keys = _.keys($scope.model.config.items);
  4981.  
  4982.             for (var i = 0; i < vals.length; i++) {
  4983.                 var label = vals[i].value ? vals[i].value : vals[i];
  4984.                 newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label });
  4985.             }
  4986.  
  4987.             return newItems;
  4988.         }
  4989.  
  4990.         if (angular.isArray($scope.model.config.items)) {
  4991.             //PP: I dont think this will happen, but we have tests that expect it to happen..
  4992.             //if array is simple values, convert to array of objects
  4993.             if(!angular.isObject($scope.model.config.items[0])){
  4994.                 $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
  4995.             }
  4996.         }
  4997.         else if (angular.isObject($scope.model.config.items)) {
  4998.             $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
  4999.         }
  5000.         else {
  5001.             throw "The items property must be either an array or a dictionary";
  5002.         }
  5003.        
  5004.  
  5005.         //sort the values
  5006.         $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  5007.  
  5008.         //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
  5009.         // to "" gets selected by default
  5010.         if ($scope.model.value === null || $scope.model.value === undefined) {
  5011.             if ($scope.model.config.multiple) {
  5012.                 $scope.model.value = [];
  5013.             }
  5014.             else {
  5015.                 $scope.model.value = "";
  5016.             }
  5017.         }
  5018.        
  5019.     });
  5020.  
  5021. /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
  5022. function entityPicker($scope, entityResource) {
  5023.  
  5024.     //set the default to DocumentType
  5025.     if (!$scope.model.config.entityType) {
  5026.         $scope.model.config.entityType = "DocumentType";
  5027.     }
  5028.  
  5029.     //Determine the select list options and which value to publish
  5030.     if (!$scope.model.config.publishBy) {
  5031.         $scope.selectOptions = "entity.id as entity.name for entity in entities";
  5032.     }
  5033.     else {
  5034.         $scope.selectOptions = "entity." + $scope.model.config.publishBy + " as entity.name for entity in entities";
  5035.     }
  5036.  
  5037.     entityResource.getAll($scope.model.config.entityType).then(function (data) {
  5038.         //convert the ids to strings so the drop downs work properly when comparing
  5039.         _.each(data, function(d) {
  5040.             d.id = d.id.toString();
  5041.         });
  5042.         $scope.entities = data;
  5043.     });
  5044.  
  5045.     if ($scope.model.value === null || $scope.model.value === undefined) {
  5046.         if ($scope.model.config.multiple) {
  5047.             $scope.model.value = [];
  5048.         }
  5049.         else {
  5050.             $scope.model.value = "";
  5051.         }
  5052.     }
  5053.     else {
  5054.         //if it's multiple, change the value to an array
  5055.         if ($scope.model.config.multiple === "1") {
  5056.             if (_.isString($scope.model.value)) {
  5057.                 $scope.model.value = $scope.model.value.split(',');
  5058.             }
  5059.         }
  5060.     }
  5061. }
  5062. angular.module('umbraco').controller("Umbraco.PropertyEditors.EntityPickerController", entityPicker);
  5063. /**
  5064.  * @ngdoc controller
  5065.  * @name Umbraco.Editors.FileUploadController
  5066.  * @function
  5067.  *
  5068.  * @description
  5069.  * The controller for the file upload property editor. It is important to note that the $scope.model.value
  5070.  *  doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we
  5071.  *  are submitting files because in that case, we are adding files to the fileManager which is what gets peristed
  5072.  *  on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}"
  5073.  *  to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to
  5074.  *  be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows
  5075.  *  for the editors to check if the value has changed and to re-bind the property if that is true.
  5076.  *
  5077. */
  5078. function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) {
  5079.  
  5080.     /** Clears the file collections when content is saving (if we need to clear) or after saved */
  5081.     function clearFiles() {
  5082.         //clear the files collection (we don't want to upload any!)
  5083.         fileManager.setFiles($scope.model.alias, []);
  5084.         //clear the current files
  5085.         $scope.files = [];
  5086.         if ($scope.propertyForm.fileCount) {
  5087.             //this is required to re-validate
  5088.             $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
  5089.         }
  5090.        
  5091.     }
  5092.  
  5093.     /** this method is used to initialize the data and to re-initialize it if the server value is changed */
  5094.     function initialize(index) {
  5095.  
  5096.         clearFiles();
  5097.  
  5098.         if (!index) {
  5099.             index = 1;
  5100.         }
  5101.  
  5102.         //this is used in order to tell the umb-single-file-upload directive to
  5103.         //rebuild the html input control (and thus clearing the selected file) since
  5104.         //that is the only way to manipulate the html for the file input control.
  5105.         $scope.rebuildInput = {
  5106.             index: index
  5107.         };
  5108.         //clear the current files
  5109.         $scope.files = [];
  5110.         //store the original value so we can restore it if the user clears and then cancels clearing.
  5111.         $scope.originalValue = $scope.model.value;
  5112.  
  5113.         //create the property to show the list of files currently saved
  5114.         if ($scope.model.value != "") {
  5115.  
  5116.             var images = $scope.model.value.split(",");
  5117.  
  5118.             $scope.persistedFiles = _.map(images, function (item) {
  5119.                 return { file: item, isImage: imageHelper.detectIfImageByExtension(item) };
  5120.             });
  5121.         }
  5122.         else {
  5123.             $scope.persistedFiles = [];
  5124.         }
  5125.  
  5126.         _.each($scope.persistedFiles, function (file) {
  5127.  
  5128.             var thumbnailUrl = umbRequestHelper.getApiUrl(
  5129.                         "imagesApiBaseUrl",
  5130.                         "GetBigThumbnail",
  5131.                         [{ originalImagePath: file.file }]);
  5132.  
  5133.             file.thumbnail = thumbnailUrl;
  5134.         });
  5135.  
  5136.         $scope.clearFiles = false;
  5137.     }
  5138.  
  5139.     initialize();
  5140.  
  5141.     // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected)
  5142.     $scope.validateMandatory = function () {
  5143.         return {
  5144.             isValid: !$scope.model.validation.mandatory || ((($scope.persistedFiles != null && $scope.persistedFiles.length > 0) || ($scope.files != null && $scope.files.length > 0)) && !$scope.clearFiles),
  5145.             errorMsg: "Value cannot be empty",
  5146.             errorKey: "required"
  5147.         };
  5148.     }
  5149.  
  5150.     //listen for clear files changes to set our model to be sent up to the server
  5151.     $scope.$watch("clearFiles", function (isCleared) {
  5152.         if (isCleared == true) {
  5153.             $scope.model.value = { clearFiles: true };
  5154.             clearFiles();
  5155.         }
  5156.         else {
  5157.             //reset to original value
  5158.             $scope.model.value = $scope.originalValue;
  5159.             //this is required to re-validate
  5160.             $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
  5161.         }
  5162.     });
  5163.  
  5164.     //listen for when a file is selected
  5165.     $scope.$on("filesSelected", function (event, args) {
  5166.         $scope.$apply(function () {
  5167.             //set the files collection
  5168.             fileManager.setFiles($scope.model.alias, args.files);
  5169.             //clear the current files
  5170.             $scope.files = [];
  5171.             var newVal = "";
  5172.             for (var i = 0; i < args.files.length; i++) {
  5173.                 //save the file object to the scope's files collection
  5174.                 $scope.files.push({ alias: $scope.model.alias, file: args.files[i] });
  5175.                 newVal += args.files[i].name + ",";
  5176.             }
  5177.  
  5178.             //this is required to re-validate
  5179.             $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
  5180.  
  5181.             //set clear files to false, this will reset the model too
  5182.             $scope.clearFiles = false;
  5183.             //set the model value to be the concatenation of files selected. Please see the notes
  5184.             // in the description of this controller, it states that this value isn't actually used for persistence,
  5185.             // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation.
  5186.             $scope.model.value = { selectedFiles: newVal.trimEnd(",") };
  5187.         });
  5188.     });
  5189.  
  5190.     //listen for when the model value has changed
  5191.     $scope.$watch("model.value", function (newVal, oldVal) {
  5192.         //cannot just check for !newVal because it might be an empty string which we
  5193.         //want to look for.
  5194.         if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
  5195.             //now we need to check if we need to re-initialize our structure which is kind of tricky
  5196.             // since we only want to do that if the server has changed the value, not if this controller
  5197.             // has changed the value. There's only 2 scenarios where we change the value internall so
  5198.             // we know what those values can be, if they are not either of them, then we'll re-initialize.
  5199.  
  5200.             if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) {
  5201.                 initialize($scope.rebuildInput.index + 1);
  5202.             }
  5203.  
  5204.         }
  5205.     });
  5206. };
  5207. angular.module("umbraco")
  5208.     .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
  5209.     .run(function(mediaHelper, umbRequestHelper){
  5210.         if (mediaHelper && mediaHelper.registerFileResolver) {
  5211.  
  5212.             //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
  5213.             // they contain different data structures so if we need to query against it we need to be aware of this.
  5214.             mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
  5215.                 if (thumbnail) {
  5216.  
  5217.                     if (mediaHelper.detectIfImageByExtension(property.value)) {
  5218.  
  5219.                         var thumbnailUrl = umbRequestHelper.getApiUrl(
  5220.                             "imagesApiBaseUrl",
  5221.                             "GetBigThumbnail",
  5222.                             [{ originalImagePath: property.value }]);
  5223.                            
  5224.                         return thumbnailUrl;
  5225.                     }
  5226.                     else {
  5227.                         return null;
  5228.                     }
  5229.                    
  5230.                 }
  5231.                 else {
  5232.                     return property.value;
  5233.                 }
  5234.             });
  5235.         }
  5236.     });
  5237. angular.module("umbraco")
  5238.  
  5239. .controller("Umbraco.PropertyEditors.FolderBrowserController",
  5240.     function ($rootScope, $scope, $routeParams, $timeout, editorState, navigationService) {
  5241.  
  5242.         var dialogOptions = $scope.dialogOptions;
  5243.         $scope.creating = $routeParams.create;
  5244.         $scope.nodeId = $routeParams.id;
  5245.  
  5246.         $scope.onUploadComplete = function () {
  5247.  
  5248.             //sync the tree - don't force reload since we're not updating this particular node (i.e. its name or anything),
  5249.             // then we'll get the resulting tree node which we can then use to reload it's children.
  5250.             var path = editorState.current.path;
  5251.             navigationService.syncTree({ tree: "media", path: path, forceReload: false }).then(function (syncArgs) {
  5252.                 navigationService.reloadNode(syncArgs.node);
  5253.             });
  5254.  
  5255.         }
  5256. });
  5257.  
  5258. angular.module("umbraco")
  5259. .controller("Umbraco.PropertyEditors.GoogleMapsController",
  5260.     function ($rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) {
  5261.  
  5262.         assetsService.loadJs('http://www.google.com/jsapi')
  5263.             .then(function () {
  5264.                 google.load("maps", "3",
  5265.                             {
  5266.                                 callback: initMap,
  5267.                                 other_params: "sensor=false"
  5268.                             });
  5269.             });
  5270.  
  5271.         function initMap() {
  5272.             //Google maps is available and all components are ready to use.
  5273.             var valueArray = $scope.model.value.split(',');
  5274.             var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]);
  5275.             var mapDiv = document.getElementById($scope.model.alias + '_map');
  5276.             var mapOptions = {
  5277.                 zoom: $scope.model.config.zoom,
  5278.                 center: latLng,
  5279.                 mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType]
  5280.             };
  5281.             var geocoder = new google.maps.Geocoder();
  5282.             var map = new google.maps.Map(mapDiv, mapOptions);
  5283.  
  5284.             var marker = new google.maps.Marker({
  5285.                 map: map,
  5286.                 position: latLng,
  5287.                 draggable: true
  5288.             });
  5289.  
  5290.             google.maps.event.addListener(map, 'click', function (event) {
  5291.  
  5292.                 dialogService.mediaPicker({
  5293.                     callback: function (data) {
  5294.                         var image = data.selection[0].src;
  5295.  
  5296.                         var latLng = event.latLng;
  5297.                         var marker = new google.maps.Marker({
  5298.                             map: map,
  5299.                             icon: image,
  5300.                             position: latLng,
  5301.                             draggable: true
  5302.                         });
  5303.  
  5304.                         google.maps.event.addListener(marker, "dragend", function (e) {
  5305.                             var newLat = marker.getPosition().lat();
  5306.                             var newLng = marker.getPosition().lng();
  5307.  
  5308.                             codeLatLng(marker.getPosition(), geocoder);
  5309.  
  5310.                             //set the model value
  5311.                             $scope.model.vvalue = newLat + "," + newLng;
  5312.                         });
  5313.  
  5314.                     }
  5315.                 });
  5316.             });
  5317.  
  5318.             $('a[data-toggle="tab"]').on('shown', function (e) {
  5319.                 google.maps.event.trigger(map, 'resize');
  5320.             });
  5321.         }
  5322.  
  5323.         function codeLatLng(latLng, geocoder) {
  5324.             geocoder.geocode({ 'latLng': latLng },
  5325.                 function (results, status) {
  5326.                     if (status == google.maps.GeocoderStatus.OK) {
  5327.                         var location = results[0].formatted_address;
  5328.                         $rootScope.$apply(function () {
  5329.                             notificationsService.success("Peter just went to: ", location);
  5330.                         });
  5331.                     }
  5332.                 });
  5333.         }
  5334.  
  5335.         //here we declare a special method which will be called whenever the value has changed from the server
  5336.         //this is instead of doing a watch on the model.value = faster
  5337.         $scope.model.onValueChanged = function (newVal, oldVal) {
  5338.             //update the display val again if it has changed from the server
  5339.             initMap();
  5340.         };
  5341.     });
  5342. angular.module("umbraco")
  5343.     .controller("Umbraco.PropertyEditors.GridController.Dialogs.Config",
  5344.     function ($scope, $http) {
  5345.  
  5346.         var placeHolder = "{0}";
  5347.         var addModifier = function(val, modifier){
  5348.             if (!modifier || modifier.indexOf(placeHolder) < 0) {
  5349.                 return val;
  5350.             } else {
  5351.                 return modifier.replace(placeHolder, val);
  5352.             }
  5353.         }
  5354.  
  5355.         var stripModifier = function (val, modifier) {
  5356.             if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
  5357.                 return val;
  5358.             } else {
  5359.                 var paddArray = modifier.split(placeHolder);
  5360.                 if(paddArray.length == 1){
  5361.                     if (modifier.indexOf(placeHolder) === 0) {
  5362.                         return val.slice(0, -paddArray[0].length);
  5363.                     } else {
  5364.                         return val.slice(paddArray[0].length, 0);
  5365.                     }
  5366.                 }else{
  5367.                     return val.slice(paddArray[0].length, -paddArray[1].length);
  5368.                 }
  5369.             }
  5370.         }
  5371.  
  5372.  
  5373.         $scope.styles = _.filter( angular.copy($scope.dialogOptions.config.items.styles), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); });
  5374.         $scope.config = _.filter( angular.copy($scope.dialogOptions.config.items.config), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); });
  5375.  
  5376.  
  5377.         var element = $scope.dialogOptions.gridItem;
  5378.         if(angular.isObject(element.config)){
  5379.             _.each($scope.config, function(cfg){
  5380.                 var val = element.config[cfg.key];
  5381.                 if(val){
  5382.                     cfg.value = stripModifier(val, cfg.modifier);
  5383.                 }
  5384.             });
  5385.         }
  5386.  
  5387.         if(angular.isObject(element.styles)){
  5388.             _.each($scope.styles, function(style){
  5389.                 var val = element.styles[style.key];
  5390.                 if(val){
  5391.                     style.value = stripModifier(val, style.modifier);
  5392.                 }
  5393.             });
  5394.         }
  5395.  
  5396.  
  5397.         $scope.saveAndClose = function(){
  5398.             var styleObject = {};
  5399.             var configObject = {};
  5400.  
  5401.             _.each($scope.styles, function(style){
  5402.                 if(style.value){
  5403.                     styleObject[style.key] = addModifier(style.value, style.modifier);
  5404.                 }
  5405.             });
  5406.             _.each($scope.config, function (cfg) {
  5407.                 if (cfg.value) {
  5408.                     configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
  5409.                 }
  5410.             });
  5411.  
  5412.             $scope.submit({config: configObject, styles: styleObject});
  5413.         };
  5414.  
  5415.     });
  5416.  
  5417. angular.module("umbraco")
  5418.     .controller("Umbraco.PropertyEditors.GridPrevalueEditorController.Dialogs.EditConfig",
  5419.     function ($scope, $http) {
  5420.  
  5421.             $scope.renderModel = {};
  5422.             $scope.renderModel.name = $scope.dialogOptions.name;
  5423.             $scope.renderModel.config = $scope.dialogOptions.config;
  5424.  
  5425.             $scope.saveAndClose = function(isValid){
  5426.                 if(isValid){
  5427.                     $scope.submit($scope.renderModel.config);
  5428.                 }
  5429.             };
  5430.  
  5431.     });
  5432.  
  5433. angular.module("umbraco")
  5434.     .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController",
  5435.     function ($scope) {
  5436.  
  5437.             $scope.currentLayout = $scope.dialogOptions.currentLayout;
  5438.             $scope.columns = $scope.dialogOptions.columns;
  5439.             $scope.rows = $scope.dialogOptions.rows;
  5440.  
  5441.             $scope.scaleUp = function(section, max, overflow){
  5442.                var add = 1;
  5443.                if(overflow !== true){
  5444.                     add = (max > 1) ? 1 : max;
  5445.                }
  5446.                //var add = (max > 1) ? 1 : max;
  5447.                section.grid = section.grid+add;
  5448.             };
  5449.  
  5450.             $scope.scaleDown = function(section){
  5451.                var remove = (section.grid > 1) ? 1 : section.grid;
  5452.                section.grid = section.grid-remove;
  5453.             };
  5454.  
  5455.             $scope.percentage = function(spans){
  5456.                 return ((spans / $scope.columns) * 100).toFixed(1);
  5457.             };
  5458.  
  5459.             $scope.toggleCollection = function(collection, toggle){
  5460.                 if(toggle){
  5461.                     collection = [];
  5462.                 }else{
  5463.                     delete collection;
  5464.                 }
  5465.             };
  5466.  
  5467.  
  5468.  
  5469.             /****************
  5470.                 Section
  5471.             *****************/
  5472.             $scope.configureSection = function(section, template){
  5473.                if(section === undefined){
  5474.                     var space = ($scope.availableLayoutSpace > 4) ? 4 : $scope.availableLayoutSpace;
  5475.                     section = {
  5476.                         grid: space
  5477.                     };
  5478.                     template.sections.push(section);
  5479.                 }
  5480.                
  5481.                 $scope.currentSection = section;
  5482.             };
  5483.  
  5484.  
  5485.             $scope.deleteSection = function(index){
  5486.                 $scope.currentTemplate.sections.splice(index, 1);
  5487.             };
  5488.            
  5489.             $scope.closeSection = function(){
  5490.                 $scope.currentSection = undefined;
  5491.             };
  5492.  
  5493.             $scope.$watch("currentLayout", function(layout){
  5494.                 if(layout){
  5495.                     var total = 0;
  5496.                     _.forEach(layout.sections, function(section){
  5497.                         total = (total + section.grid);
  5498.                     });
  5499.  
  5500.                     $scope.availableLayoutSpace = $scope.columns - total;
  5501.                 }
  5502.             }, true);
  5503.     });
  5504. function RowConfigController($scope) {
  5505.    
  5506.     $scope.currentRow = angular.copy($scope.dialogOptions.currentRow);
  5507.     $scope.editors = $scope.dialogOptions.editors;
  5508.     $scope.columns = $scope.dialogOptions.columns;
  5509.  
  5510.     $scope.scaleUp = function(section, max, overflow) {
  5511.         var add = 1;
  5512.         if (overflow !== true) {
  5513.             add = (max > 1) ? 1 : max;
  5514.         }
  5515.         //var add = (max > 1) ? 1 : max;
  5516.         section.grid = section.grid + add;
  5517.     };
  5518.  
  5519.     $scope.scaleDown = function(section) {
  5520.         var remove = (section.grid > 1) ? 1 : section.grid;
  5521.         section.grid = section.grid - remove;
  5522.     };
  5523.  
  5524.     $scope.percentage = function(spans) {
  5525.         return ((spans / $scope.columns) * 100).toFixed(1);
  5526.     };
  5527.  
  5528.     $scope.toggleCollection = function(collection, toggle) {
  5529.         if (toggle) {
  5530.             collection = [];
  5531.         }
  5532.         else {
  5533.             delete collection;
  5534.         }
  5535.     };
  5536.  
  5537.  
  5538.     /****************
  5539.         area
  5540.     *****************/
  5541.     $scope.configureCell = function(cell, row) {
  5542.         if ($scope.currentCell && $scope.currentCell === cell) {
  5543.             delete $scope.currentCell;
  5544.         }
  5545.         else {
  5546.             if (cell === undefined) {
  5547.                 var available = $scope.availableRowSpace;
  5548.                 var space = 4;
  5549.  
  5550.                 if (available < 4 && available > 0) {
  5551.                     space = available;
  5552.                 }
  5553.  
  5554.                 cell = {
  5555.                     grid: space
  5556.                 };
  5557.  
  5558.                 row.areas.push(cell);
  5559.             }
  5560.             $scope.currentCell = cell;
  5561.         }
  5562.     };
  5563.  
  5564.     $scope.deleteArea = function(index) {
  5565.         $scope.currentRow.areas.splice(index, 1);
  5566.     };
  5567.     $scope.closeArea = function() {
  5568.         $scope.currentCell = undefined;
  5569.     };
  5570.  
  5571.     $scope.nameChanged = false;
  5572.     var originalName = $scope.currentRow.name;
  5573.     $scope.$watch("currentRow", function(row) {
  5574.         if (row) {
  5575.  
  5576.             var total = 0;
  5577.             _.forEach(row.areas, function(area) {
  5578.                 total = (total + area.grid);
  5579.             });
  5580.  
  5581.             $scope.availableRowSpace = $scope.columns - total;
  5582.  
  5583.             if (originalName) {
  5584.                 if (originalName != row.name) {
  5585.                     $scope.nameChanged = true;
  5586.                 }
  5587.                 else {
  5588.                     $scope.nameChanged = false;
  5589.                 }
  5590.             }
  5591.         }
  5592.     }, true);
  5593.  
  5594.     $scope.complete = function () {
  5595.         angular.extend($scope.dialogOptions.currentRow, $scope.currentRow);
  5596.         $scope.close();
  5597.     }
  5598. }
  5599.  
  5600. angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController);
  5601. angular.module("umbraco")
  5602.     .controller("Umbraco.PropertyEditors.Grid.EmbedController",
  5603.     function ($scope, $rootScope, $timeout, dialogService) {
  5604.  
  5605.         $scope.setEmbed = function(){
  5606.             dialogService.embedDialog({
  5607.                     callback: function (data) {
  5608.                         $scope.control.value = data;
  5609.                     }
  5610.                 });
  5611.         };
  5612.  
  5613.         $timeout(function(){
  5614.             if($scope.control.$initializing){
  5615.                 $scope.setEmbed();
  5616.             }
  5617.         }, 200);
  5618. });
  5619.  
  5620.  
  5621. angular.module("umbraco")
  5622.     .controller("Umbraco.PropertyEditors.Grid.MacroController",
  5623.     function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService,  $routeParams) {
  5624.  
  5625.         $scope.title = "Click to insert macro";
  5626.  
  5627.         $scope.setMacro = function(){
  5628.             dialogService.macroPicker({
  5629.                 dialogData: {
  5630.                     richTextEditor: true,  
  5631.                     macroData: $scope.control.value || {
  5632.                         macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias
  5633.                           ? $scope.control.editor.config.macroAlias : ""
  5634.                     }
  5635.                 },
  5636.                 callback: function (data) {
  5637.                     $scope.control.value = {
  5638.                             macroAlias: data.macroAlias,
  5639.                             macroParamsDictionary: data.macroParamsDictionary
  5640.                     };
  5641.  
  5642.                     $scope.setPreview($scope.control.value );
  5643.                 }
  5644.             });
  5645.         };
  5646.  
  5647.         $scope.setPreview = function(macro){
  5648.             var contentId = $routeParams.id;
  5649.  
  5650.             macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary)
  5651.             .then(function (htmlResult) {
  5652.                 $scope.title = macro.macroAlias;
  5653.                 if(htmlResult.trim().length > 0 && htmlResult.indexOf("Macro:") < 0){
  5654.                     $scope.preview = htmlResult;
  5655.                 }
  5656.             });
  5657.  
  5658.         };
  5659.  
  5660.         $timeout(function(){
  5661.             if($scope.control.$initializing){
  5662.                 $scope.setMacro();
  5663.             }else if($scope.control.value){
  5664.                 $scope.setPreview($scope.control.value);
  5665.             }
  5666.         }, 200);
  5667. });
  5668.  
  5669.  
  5670. angular.module("umbraco")
  5671.     .controller("Umbraco.PropertyEditors.Grid.MediaController",
  5672.     function ($scope, $rootScope, $timeout, dialogService) {
  5673.  
  5674.         $scope.setImage = function(){
  5675.  
  5676.             dialogService.mediaPicker({
  5677.                 startNodeId: $scope.control.editor.config && $scope.control.editor.config.startNodeId ? $scope.control.editor.config.startNodeId : undefined,
  5678.                 multiPicker: false,
  5679.                 cropSize:  $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined,
  5680.                 showDetails: true,
  5681.                 callback: function (data) {
  5682.  
  5683.                     $scope.control.value = {
  5684.                         focalPoint: data.focalPoint,
  5685.                         id: data.id,
  5686.                         image: data.image
  5687.                     };
  5688.  
  5689.                     $scope.setUrl();
  5690.                 }
  5691.             });
  5692.         };
  5693.  
  5694.         $scope.setUrl = function(){
  5695.  
  5696.             if($scope.control.value.image){
  5697.                 var url = $scope.control.value.image;
  5698.  
  5699.                 if($scope.control.editor.config && $scope.control.editor.config.size){
  5700.                     url += "?width=" + $scope.control.editor.config.size.width;
  5701.                     url += "&height=" + $scope.control.editor.config.size.height;
  5702.  
  5703.                     if($scope.control.value.focalPoint){
  5704.                         url += "&center=" + $scope.control.value.focalPoint.top +"," + $scope.control.value.focalPoint.left;
  5705.                         url += "&mode=crop";
  5706.                     }
  5707.                 }
  5708.  
  5709.                 $scope.url = url;
  5710.             }
  5711.         };
  5712.  
  5713.         $timeout(function(){
  5714.             if($scope.control.$initializing){
  5715.                 $scope.setImage();
  5716.             }else{
  5717.                 $scope.setUrl();
  5718.             }
  5719.         }, 200);
  5720. });
  5721.  
  5722. angular.module("umbraco")
  5723.     .controller("Umbraco.PropertyEditors.Grid.TextStringController",
  5724.     function ($scope, $rootScope, $timeout, dialogService) {
  5725.  
  5726.        
  5727.  
  5728.     });
  5729.  
  5730.  
  5731. angular.module("umbraco")
  5732.     .controller("Umbraco.PropertyEditors.GridController",
  5733.     function ($scope, $http, assetsService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper) {
  5734.  
  5735.         // Grid status variables
  5736.         $scope.currentRow = null;
  5737.         $scope.currentCell = null;
  5738.         $scope.currentToolsControl = null;
  5739.         $scope.currentControl = null;
  5740.         $scope.openRTEToolbarId = null;
  5741.         $scope.hasSettings = false;
  5742.  
  5743.         // *********************************************
  5744.         // Sortable options
  5745.         // *********************************************
  5746.  
  5747.         var draggedRteSettings;
  5748.  
  5749.         $scope.sortableOptions = {
  5750.             distance: 10,
  5751.             cursor: "move",
  5752.             placeholder: 'ui-sortable-placeholder',
  5753.             handle: '.cell-tools-move',
  5754.             forcePlaceholderSize: true,
  5755.             tolerance: "pointer",
  5756.             zIndex: 999999999999999999,
  5757.             scrollSensitivity: 100,
  5758.             cursorAt: {
  5759.                 top: 45,
  5760.                 left: 90
  5761.             },
  5762.  
  5763.             sort: function (event, ui) {
  5764.                 /* prevent vertical scroll out of the screen */
  5765.                 var max = $(".usky-grid").width() - 150;
  5766.                 if (parseInt(ui.helper.css('left')) > max) {
  5767.                     ui.helper.css({ 'left': max + "px" })
  5768.                 }
  5769.                 if (parseInt(ui.helper.css('left')) < 20) {
  5770.                     ui.helper.css({ 'left': 20 })
  5771.                 }
  5772.             },
  5773.  
  5774.             start: function (e, ui) {
  5775.                 draggedRteSettings = {};
  5776.                 ui.item.find('.mceNoEditor').each(function () {
  5777.                     // remove all RTEs in the dragged row and save their settings
  5778.                     var id = $(this).attr('id');
  5779.                     draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings;
  5780.                     tinyMCE.execCommand('mceRemoveEditor', false, id);
  5781.                 });
  5782.             },
  5783.  
  5784.             stop: function (e, ui) {
  5785.                 // reset all RTEs affected by the dragging
  5786.                 ui.item.parents(".usky-column").find('.mceNoEditor').each(function () {
  5787.                     var id = $(this).attr('id');
  5788.                     draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings;
  5789.                     tinyMCE.execCommand('mceRemoveEditor', false, id);
  5790.                     tinyMCE.init(draggedRteSettings[id]);
  5791.                 });
  5792.             }
  5793.         };
  5794.  
  5795.         var notIncludedRte = [];
  5796.         var cancelMove = false;
  5797.  
  5798.         $scope.sortableOptionsCell = {
  5799.             distance: 10,
  5800.             cursor: "move",
  5801.             placeholder: "ui-sortable-placeholder",
  5802.             handle: '.cell-tools-move',
  5803.             connectWith: ".usky-cell",
  5804.             forcePlaceholderSize: true,
  5805.             tolerance: "pointer",
  5806.             zIndex: 999999999999999999,
  5807.             scrollSensitivity: 100,
  5808.             cursorAt: {
  5809.                 top: 45,
  5810.                 left: 90
  5811.             },
  5812.  
  5813.             sort: function (event, ui) {
  5814.                 /* prevent vertical scroll out of the screen */
  5815.                 var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($(".usky-grid").offset().left);
  5816.                 var max = $(".usky-grid").width() - 220;
  5817.                 if (position > max) {
  5818.                     ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" })
  5819.                 }
  5820.                 if (position < 0) {
  5821.                     ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" })
  5822.                 }
  5823.             },
  5824.  
  5825.             over: function (event, ui) {
  5826.                 allowedEditors = $(event.target).scope().area.allowed;
  5827.  
  5828.                 if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) {
  5829.                     ui.placeholder.hide();
  5830.                     cancelMove = true;
  5831.                 }
  5832.                 else {
  5833.                     ui.placeholder.show();
  5834.                     cancelMove = false;
  5835.                 }
  5836.  
  5837.             },
  5838.  
  5839.             update: function (event, ui) {
  5840.                 // add all RTEs which are affected by the dragging
  5841.                 if (!ui.sender) {
  5842.                     if (cancelMove) {
  5843.                         ui.item.sortable.cancel();
  5844.                     }
  5845.                     ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () {
  5846.                         if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
  5847.                             notIncludedRte.splice(0, 0, $(this).attr('id'));
  5848.                         }
  5849.                     });
  5850.                 }
  5851.                 else {
  5852.                     $(event.target).find('.mceNoEditor').each(function () {
  5853.                         if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
  5854.                             notIncludedRte.splice(0, 0, $(this).attr('id'));
  5855.                         }
  5856.                     });
  5857.                 }
  5858.  
  5859.             },
  5860.  
  5861.             start: function (e, ui) {
  5862.                 ui.item.find('.mceNoEditor').each(function () {
  5863.                     notIncludedRte = [];
  5864.  
  5865.                     // save the dragged RTE settings
  5866.                     draggedRteSettings = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') }).settings;
  5867.  
  5868.                     // remove the dragged RTE
  5869.                     tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
  5870.                 });
  5871.             },
  5872.  
  5873.             stop: function (e, ui) {
  5874.                 ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () {
  5875.                     if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
  5876.                         // add all dragged's neighbouring RTEs in the new cell
  5877.                         notIncludedRte.splice(0, 0, $(this).attr('id'));
  5878.                     }
  5879.                 });
  5880.                 $timeout(function () {
  5881.                     // reconstruct the dragged RTE
  5882.                     tinyMCE.init(draggedRteSettings);
  5883.  
  5884.                     _.forEach(notIncludedRte, function (id) {
  5885.                         // reset all the other RTEs
  5886.                         if (id != draggedRteSettings.id) {
  5887.                             var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
  5888.                             tinyMCE.execCommand('mceRemoveEditor', false, id);
  5889.                             tinyMCE.init(rteSettings);
  5890.                         }
  5891.                     });
  5892.                 }, 500, false);
  5893.             }
  5894.  
  5895.         };
  5896.  
  5897.         // *********************************************
  5898.         // Add items overlay menu
  5899.         // *********************************************
  5900.         $scope.overlayMenu = {
  5901.             show: false,
  5902.             style: {},
  5903.             area: undefined,
  5904.             key: undefined
  5905.         };
  5906.  
  5907.         $scope.addItemOverlay = function (event, area, index, key) {
  5908.             $scope.overlayMenu.area = area;
  5909.             $scope.overlayMenu.index = index;
  5910.             $scope.overlayMenu.style = {};
  5911.             $scope.overlayMenu.key = key;
  5912.  
  5913.             //todo calculate position...
  5914.             var offset = $(event.target).offset();
  5915.             var height = $(window).height();
  5916.             var width = $(window).width();
  5917.  
  5918.             if ((height - offset.top) < 250) {
  5919.                 $scope.overlayMenu.style.bottom = 0;
  5920.                 $scope.overlayMenu.style.top = "initial";
  5921.             } else if (offset.top < 300) {
  5922.                 $scope.overlayMenu.style.top = 190;
  5923.             }
  5924.  
  5925.             $scope.overlayMenu.show = true;
  5926.         };
  5927.  
  5928.         $scope.closeItemOverlay = function () {
  5929.             $scope.currentControl = null;
  5930.             $scope.overlayMenu.show = false;
  5931.             $scope.overlayMenu.key = undefined;
  5932.         };
  5933.  
  5934.         // *********************************************
  5935.         // Template management functions
  5936.         // *********************************************
  5937.  
  5938.         $scope.addTemplate = function (template) {
  5939.             $scope.model.value = angular.copy(template);
  5940.  
  5941.             //default row data
  5942.             _.forEach($scope.model.value.sections, function (section) {
  5943.                 $scope.initSection(section);
  5944.             });
  5945.         };
  5946.  
  5947.  
  5948.         // *********************************************
  5949.         // Row management function
  5950.         // *********************************************
  5951.  
  5952.         $scope.setCurrentRow = function (row) {
  5953.             $scope.currentRow = row;
  5954.         };
  5955.  
  5956.         $scope.disableCurrentRow = function () {
  5957.             $scope.currentRow = null;
  5958.         };
  5959.  
  5960.         $scope.setWarnighlightRow = function (row) {
  5961.             $scope.currentWarnhighlightRow = row;
  5962.         };
  5963.  
  5964.         $scope.disableWarnhighlightRow = function () {
  5965.             $scope.currentWarnhighlightRow = null;
  5966.         };
  5967.  
  5968.         $scope.setInfohighlightRow = function (row) {
  5969.             $scope.currentInfohighlightRow = row;
  5970.         };
  5971.  
  5972.         $scope.disableInfohighlightRow = function () {
  5973.             $scope.currentInfohighlightRow = null;
  5974.         };
  5975.  
  5976.         $scope.getAllowedLayouts = function (column) {
  5977.             var layouts = $scope.model.config.items.layouts;
  5978.  
  5979.             if (column.allowed && column.allowed.length > 0) {
  5980.                 return _.filter(layouts, function (layout) {
  5981.                     return _.indexOf(column.allowed, layout.name) >= 0;
  5982.                 });
  5983.             } else {
  5984.                 return layouts;
  5985.             }
  5986.         };
  5987.  
  5988.         $scope.addRow = function (section, layout) {
  5989.  
  5990.             //copy the selected layout into the rows collection
  5991.             var row = angular.copy(layout);
  5992.  
  5993.             // Init row value
  5994.             row = $scope.initRow(row);
  5995.  
  5996.             // Push the new row
  5997.             if (row) {
  5998.                 section.rows.push(row);
  5999.             }
  6000.         };
  6001.  
  6002.         $scope.removeRow = function (section, $index) {
  6003.             if (section.rows.length > 0) {
  6004.                 section.rows.splice($index, 1);
  6005.                 $scope.currentRow = null;
  6006.                 $scope.openRTEToolbarId = null;
  6007.                 $scope.initContent();
  6008.             }
  6009.         };
  6010.  
  6011.         $scope.editGridItemSettings = function (gridItem, itemType) {
  6012.  
  6013.             dialogService.open(
  6014.                 {
  6015.                     template: "views/propertyeditors/grid/dialogs/config.html",
  6016.                     gridItem: gridItem,
  6017.                     config: $scope.model.config,
  6018.                     itemType: itemType,
  6019.                     callback: function (data) {
  6020.  
  6021.                         gridItem.styles = data.styles;
  6022.                         gridItem.config = data.config;
  6023.  
  6024.                     }
  6025.                 });
  6026.  
  6027.         };
  6028.  
  6029.         // *********************************************
  6030.         // Area management functions
  6031.         // *********************************************
  6032.  
  6033.         $scope.setCurrentCell = function (cell) {
  6034.             $scope.currentCell = cell;
  6035.         };
  6036.  
  6037.         $scope.disableCurrentCell = function () {
  6038.             $scope.currentCell = null;
  6039.         };
  6040.  
  6041.         $scope.cellPreview = function (cell) {
  6042.             if (cell && cell.$allowedEditors) {
  6043.                 var editor = cell.$allowedEditors[0];
  6044.                 return editor.icon;
  6045.             } else {
  6046.                 return "icon-layout";
  6047.             }
  6048.         };
  6049.  
  6050.         $scope.setInfohighlightArea = function (cell) {
  6051.             $scope.currentInfohighlightArea = cell;
  6052.         };
  6053.  
  6054.         $scope.disableInfohighlightArea = function () {
  6055.             $scope.currentInfohighlightArea = null;
  6056.         };
  6057.  
  6058.  
  6059.         // *********************************************
  6060.         // Control management functions
  6061.         // *********************************************
  6062.         $scope.setCurrentControl = function (Control) {
  6063.             $scope.currentControl = Control;
  6064.         };
  6065.  
  6066.         $scope.disableCurrentControl = function (Control) {
  6067.             $scope.currentControl = null;
  6068.         };
  6069.  
  6070.         $scope.setCurrentToolsControl = function (Control) {
  6071.             $scope.currentToolsControl = Control;
  6072.         };
  6073.  
  6074.         $scope.disableCurrentToolsControl = function (Control) {
  6075.             $scope.currentToolsControl = null;
  6076.         };
  6077.  
  6078.         $scope.setWarnhighlightControl = function (Control) {
  6079.             $scope.currentWarnhighlightControl = Control;
  6080.         };
  6081.  
  6082.         $scope.disableWarnhighlightControl = function (Control) {
  6083.             $scope.currentWarnhighlightControl = null;
  6084.         };
  6085.  
  6086.         $scope.setInfohighlightControl = function (Control) {
  6087.             $scope.currentInfohighlightControl = Control;
  6088.         };
  6089.  
  6090.         $scope.disableInfohighlightControl = function (Control) {
  6091.             $scope.currentInfohighlightControl = null;
  6092.         };
  6093.  
  6094.         $scope.setUniqueId = function (cell, index) {
  6095.             return guid();
  6096.         };
  6097.  
  6098.         var guid = (function () {
  6099.             function s4() {
  6100.                 return Math.floor((1 + Math.random()) * 0x10000)
  6101.                            .toString(16)
  6102.                            .substring(1);
  6103.             }
  6104.             return function () {
  6105.                 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
  6106.                        s4() + '-' + s4() + s4() + s4();
  6107.             };
  6108.         })();
  6109.  
  6110.         $scope.addControl = function (editor, cell, index) {
  6111.             $scope.closeItemOverlay();
  6112.  
  6113.             var newControl = {
  6114.                 value: null,
  6115.                 editor: editor,
  6116.                 $initializing: true
  6117.             };
  6118.  
  6119.             if (index === undefined) {
  6120.                 index = cell.controls.length;
  6121.             }
  6122.  
  6123.             //populate control
  6124.             $scope.initControl(newControl, index + 1);
  6125.  
  6126.             cell.controls.splice(index + 1, 0, newControl);
  6127.         };
  6128.  
  6129.         $scope.addTinyMce = function (cell) {
  6130.             var rte = $scope.getEditor("rte");
  6131.             $scope.addControl(rte, cell);
  6132.         };
  6133.  
  6134.         $scope.getEditor = function (alias) {
  6135.             return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; });
  6136.         };
  6137.  
  6138.         $scope.removeControl = function (cell, $index) {
  6139.             $scope.currentControl = null;
  6140.             cell.controls.splice($index, 1);
  6141.         };
  6142.  
  6143.         $scope.percentage = function (spans) {
  6144.             return ((spans / $scope.model.config.items.columns) * 100).toFixed(1);
  6145.         };
  6146.  
  6147.  
  6148.         $scope.clearPrompt = function (scopedObject, e) {
  6149.             scopedObject.deletePrompt = false;
  6150.             e.preventDefault();
  6151.             e.stopPropagation();
  6152.         }
  6153.  
  6154.         $scope.showPrompt = function (scopedObject) {
  6155.             scopedObject.deletePrompt = true;
  6156.         }
  6157.  
  6158.  
  6159.         // *********************************************
  6160.         // INITIALISATION
  6161.         // these methods are called from ng-init on the template
  6162.         // so we can controll their first load data
  6163.         //
  6164.         // intialisation sets non-saved data like percentage sizing, allowed editors and
  6165.         // other data that should all be pre-fixed with $ to strip it out on save
  6166.         // *********************************************
  6167.  
  6168.         // *********************************************
  6169.         // Init template + sections
  6170.         // *********************************************
  6171.         $scope.initContent = function () {
  6172.             var clear = true;
  6173.  
  6174.             //settings indicator shortcut
  6175.             if ($scope.model.config.items.config || $scope.model.config.items.styles) {
  6176.                 $scope.hasSettings = true;
  6177.             }
  6178.  
  6179.             //ensure the grid has a column value set, if nothing is found, set it to 12
  6180.             if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) {
  6181.                 $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
  6182.             } else {
  6183.                 $scope.model.config.items.columns = 12;
  6184.             }
  6185.  
  6186.             if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0) {
  6187.                 _.forEach($scope.model.value.sections, function (section, index) {
  6188.  
  6189.                     if (section.grid > 0) {
  6190.                         $scope.initSection(section);
  6191.  
  6192.                         //we do this to ensure that the grid can be reset by deleting the last row
  6193.                         if (section.rows.length > 0) {
  6194.                             clear = false;
  6195.                         }
  6196.                     } else {
  6197.                         $scope.model.value.sections.splice(index, 1);
  6198.                     }
  6199.                 });
  6200.             } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
  6201.                 $scope.addTemplate($scope.model.config.items.templates[0]);
  6202.             }
  6203.  
  6204.             if (clear) {
  6205.                 $scope.model.value = undefined;
  6206.             }
  6207.         };
  6208.  
  6209.         $scope.initSection = function (section) {
  6210.             section.$percentage = $scope.percentage(section.grid);
  6211.  
  6212.             var layouts = $scope.model.config.items.layouts;
  6213.  
  6214.             if (section.allowed && section.allowed.length > 0) {
  6215.                 section.$allowedLayouts = _.filter(layouts, function (layout) {
  6216.                     return _.indexOf(section.allowed, layout.name) >= 0;
  6217.                 });
  6218.             } else {
  6219.                 section.$allowedLayouts = layouts;
  6220.             }
  6221.  
  6222.             if (!section.rows) {
  6223.                 section.rows = [];
  6224.             } else {
  6225.                 _.forEach(section.rows, function (row, index) {
  6226.                     if (!row.$initialized) {
  6227.                         var initd = $scope.initRow(row);
  6228.  
  6229.                         //if init fails, remove
  6230.                         if (!initd) {
  6231.                             section.rows.splice(index, 1);
  6232.                         } else {
  6233.                             section.rows[index] = initd;
  6234.                         }
  6235.                     }
  6236.                 });
  6237.             }
  6238.         };
  6239.  
  6240.  
  6241.         // *********************************************
  6242.         // Init layout / row
  6243.         // *********************************************
  6244.         $scope.initRow = function (row) {
  6245.  
  6246.             //merge the layout data with the original config data
  6247.             //if there are no config info on this, splice it out
  6248.             var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; });
  6249.  
  6250.             if (!original) {
  6251.                 return null;
  6252.             } else {
  6253.                 //make a copy to not touch the original config
  6254.                 original = angular.copy(original);
  6255.                 original.styles = row.styles;
  6256.                 original.config = row.config;
  6257.  
  6258.                 //sync area configuration
  6259.                 _.each(original.areas, function (area, areaIndex) {
  6260.  
  6261.  
  6262.                     if (area.grid > 0) {
  6263.                         var currentArea = row.areas[areaIndex];
  6264.  
  6265.                         if (currentArea) {
  6266.                             area.config = currentArea.config;
  6267.                             area.styles = currentArea.styles;
  6268.                         }
  6269.  
  6270.                         //copy over existing controls into the new areas
  6271.                         if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
  6272.                             area.controls = currentArea.controls;
  6273.  
  6274.                             _.forEach(area.controls, function (control, controlIndex) {
  6275.                                 $scope.initControl(control, controlIndex);
  6276.                             });
  6277.  
  6278.                         } else {
  6279.                             area.controls = [];
  6280.                         }
  6281.  
  6282.                         //set width
  6283.                         area.$percentage = $scope.percentage(area.grid);
  6284.                         area.$uniqueId = $scope.setUniqueId();
  6285.  
  6286.                         //set editor permissions
  6287.                         if (!area.allowed || area.allowAll === true) {
  6288.                             area.$allowedEditors = $scope.availableEditors;
  6289.                             area.$allowsRTE = true;
  6290.                         } else {
  6291.                             area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
  6292.                                 return _.indexOf(area.allowed, editor.alias) >= 0;
  6293.                             });
  6294.  
  6295.                             if (_.indexOf(area.allowed, "rte") >= 0) {
  6296.                                 area.$allowsRTE = true;
  6297.                             }
  6298.                         }
  6299.                     } else {
  6300.                         original.areas.splice(areaIndex, 1);
  6301.                     }
  6302.                 });
  6303.  
  6304.                 //replace the old row
  6305.                 original.$initialized = true;
  6306.  
  6307.                 //set a disposable unique ID
  6308.                 original.$uniqueId = $scope.setUniqueId();
  6309.  
  6310.                 //set a no disposable unique ID (util for row styling)
  6311.                 original.id = !row.id ? $scope.setUniqueId() : row.id;
  6312.  
  6313.                 return original;
  6314.             }
  6315.  
  6316.         };
  6317.  
  6318.  
  6319.         // *********************************************
  6320.         // Init control
  6321.         // *********************************************
  6322.  
  6323.         $scope.initControl = function (control, index) {
  6324.             control.$index = index;
  6325.             control.$uniqueId = $scope.setUniqueId();
  6326.  
  6327.             //error handling in case of missing editor..
  6328.             //should only happen if stripped earlier
  6329.             if (!control.editor) {
  6330.                 control.$editorPath = "views/propertyeditors/grid/editors/error.html";
  6331.             }
  6332.  
  6333.             if (!control.$editorPath) {
  6334.                 var editorConfig = $scope.getEditor(control.editor.alias);
  6335.  
  6336.                 if (editorConfig) {
  6337.                     control.editor = editorConfig;
  6338.  
  6339.                     //if its an absolute path
  6340.                     if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) {
  6341.                         control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
  6342.                     }
  6343.                     else {
  6344.                         //use convention
  6345.                         control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html";
  6346.                     }
  6347.                 }
  6348.                 else {
  6349.                     control.$editorPath = "views/propertyeditors/grid/editors/error.html";
  6350.                 }
  6351.             }
  6352.  
  6353.  
  6354.         };
  6355.  
  6356.  
  6357.         gridService.getGridEditors().then(function (response) {
  6358.             $scope.availableEditors = response.data;
  6359.  
  6360.             $scope.contentReady = true;
  6361.  
  6362.             // *********************************************
  6363.             // Init grid
  6364.             // *********************************************
  6365.             $scope.initContent();
  6366.  
  6367.         });
  6368.     });
  6369.  
  6370. angular.module("umbraco")
  6371.     .controller("Umbraco.PropertyEditors.GridPrevalueEditorController",
  6372.     function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) {
  6373.  
  6374.         var emptyModel = {
  6375.             styles:[
  6376.                 {
  6377.                     label: "Set a background image",
  6378.                     description: "Set a row background",
  6379.                     key: "background-image",
  6380.                     view: "imagepicker",
  6381.                     modifier: "url({0})"
  6382.                 }
  6383.             ],
  6384.  
  6385.             config:[
  6386.                 {
  6387.                     label: "Class",
  6388.                     description: "Set a css class",
  6389.                     key: "class",
  6390.                     view: "textstring"
  6391.                 }
  6392.             ],
  6393.  
  6394.             columns: 12,
  6395.             templates:[
  6396.                 {
  6397.                     name: "1 column layout",
  6398.                     sections: [
  6399.                         {
  6400.                             grid: 12,
  6401.                         }
  6402.                     ]
  6403.                 },
  6404.                 {
  6405.                     name: "2 column layout",
  6406.                     sections: [
  6407.                         {
  6408.                             grid: 4,
  6409.                         },
  6410.                         {
  6411.                             grid: 8
  6412.                         }
  6413.                     ]
  6414.                 }
  6415.             ],
  6416.  
  6417.  
  6418.             layouts:[
  6419.                 {
  6420.                     name: "Headline",
  6421.                     areas: [
  6422.                         {
  6423.                             grid: 12,
  6424.                             editors: ["headline"]
  6425.                         }
  6426.                     ]
  6427.                 },
  6428.                 {
  6429.                     name: "Article",
  6430.                     areas: [
  6431.                         {
  6432.                             grid: 4
  6433.                         },
  6434.                         {
  6435.                             grid: 8
  6436.                         }
  6437.                     ]
  6438.                 }
  6439.             ]
  6440.         };
  6441.  
  6442.         /****************
  6443.             template
  6444.         *****************/
  6445.  
  6446.         $scope.configureTemplate = function(template){
  6447.            if(template === undefined){
  6448.                 template = {
  6449.                     name: "",
  6450.                     sections:[
  6451.  
  6452.                     ]
  6453.                 };
  6454.                 $scope.model.value.templates.push(template);
  6455.            }    
  6456.            
  6457.            dialogService.open(
  6458.                {
  6459.                    template: "views/propertyEditors/grid/dialogs/layoutconfig.html",
  6460.                    currentLayout: template,
  6461.                    rows: $scope.model.value.layouts,
  6462.                    columns: $scope.model.value.columns
  6463.                }
  6464.            );
  6465.  
  6466.         };
  6467.  
  6468.         $scope.deleteTemplate = function(index){
  6469.             $scope.model.value.templates.splice(index, 1);
  6470.         };
  6471.        
  6472.  
  6473.         /****************
  6474.             Row
  6475.         *****************/
  6476.  
  6477.         $scope.configureLayout = function(layout){
  6478.  
  6479.             if(layout === undefined){
  6480.                  layout = {
  6481.                      name: "",
  6482.                      areas:[
  6483.  
  6484.                      ]
  6485.                  };
  6486.                  $scope.model.value.layouts.push(layout);
  6487.             }
  6488.  
  6489.             dialogService.open(
  6490.                 {
  6491.                     template: "views/propertyEditors/grid/dialogs/rowconfig.html",
  6492.                     currentRow: layout,
  6493.                     editors: $scope.editors,
  6494.                     columns: $scope.model.value.columns
  6495.                 }
  6496.             );
  6497.         };
  6498.  
  6499.         //var rowDeletesPending = false;
  6500.         $scope.deleteLayout = function (index) {
  6501.             //rowDeletesPending = true;
  6502.  
  6503.             //show ok/cancel dialog
  6504.             var confirmDialog = dialogService.open(
  6505.                {
  6506.                    template: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html",
  6507.                    show: true,
  6508.                    callback: function() {
  6509.                        $scope.model.value.layouts.splice(index, 1);
  6510.                    },
  6511.                    dialogData: {
  6512.                        rowName: $scope.model.value.layouts[index].name
  6513.                    }
  6514.                }
  6515.            );
  6516.            
  6517.         };
  6518.        
  6519.  
  6520.  
  6521.         /****************
  6522.             utillities
  6523.         *****************/
  6524.         $scope.toggleCollection = function(collection, toggle){
  6525.             if(toggle){
  6526.                 collection = [];
  6527.             }else{
  6528.                 delete collection;
  6529.             }
  6530.         };
  6531.  
  6532.         $scope.percentage = function(spans){
  6533.             return ((spans / $scope.model.value.columns) * 100).toFixed(1);
  6534.         };
  6535.  
  6536.         $scope.zeroWidthFilter = function (cell) {
  6537.                 return cell.grid > 0;
  6538.         };
  6539.  
  6540.         /****************
  6541.             Config
  6542.         *****************/
  6543.  
  6544.         $scope.removeConfigValue = function(collection, index){
  6545.             collection.splice(index, 1);
  6546.         };
  6547.  
  6548.         var editConfigCollection = function(configValues, title, callbackOnSave){
  6549.             dialogService.open(
  6550.                 {
  6551.                     template: "views/propertyeditors/grid/dialogs/editconfig.html",
  6552.                     config: configValues,
  6553.                     name: title,
  6554.                     callback: function(data){
  6555.                         callbackOnSave(data);
  6556.                     }
  6557.                 });
  6558.         };
  6559.  
  6560.         $scope.editConfig = function(){
  6561.             editConfigCollection($scope.model.value.config, "Settings", function(data){
  6562.                 $scope.model.value.config = data;
  6563.             });
  6564.         };
  6565.  
  6566.         $scope.editStyles = function(){
  6567.             editConfigCollection($scope.model.value.styles, "Styling", function(data){
  6568.                 $scope.model.value.styles = data;
  6569.             });
  6570.         };
  6571.  
  6572.  
  6573.         /****************
  6574.             editors
  6575.         *****************/
  6576.         gridService.getGridEditors().then(function(response){
  6577.             $scope.editors = response.data;
  6578.         });
  6579.  
  6580.  
  6581.         /* init grid data */
  6582.         if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) {
  6583.             $scope.model.value = emptyModel;
  6584.         } else {
  6585.  
  6586.             if (!$scope.model.value.columns) {
  6587.                 $scope.model.value.columns = emptyModel.columns;
  6588.             }
  6589.  
  6590.  
  6591.             if (!$scope.model.value.config) {
  6592.                 $scope.model.value.config = [];
  6593.             }
  6594.  
  6595.             if (!$scope.model.value.styles) {
  6596.                 $scope.model.value.styles = [];
  6597.             }
  6598.         }
  6599.  
  6600.         /****************
  6601.             Clean up
  6602.         *****************/
  6603.         var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  6604.             var ts = $scope.model.value.templates;
  6605.             var ls = $scope.model.value.layouts;
  6606.  
  6607.             _.each(ts, function(t){
  6608.                 _.each(t.sections, function(section, index){
  6609.                    if(section.grid === 0){
  6610.                     t.sections.splice(index, 1);
  6611.                    }
  6612.                });
  6613.             });
  6614.  
  6615.             _.each(ls, function(l){
  6616.                 _.each(l.areas, function(area, index){
  6617.                    if(area.grid === 0){
  6618.                     l.areas.splice(index, 1);
  6619.                    }
  6620.                });
  6621.             });
  6622.         });
  6623.  
  6624.         //when the scope is destroyed we need to unsubscribe
  6625.         $scope.$on('$destroy', function () {
  6626.             unsubscribe();
  6627.         });
  6628.  
  6629.     });
  6630.  
  6631. //this controller simply tells the dialogs service to open a mediaPicker window
  6632. //with a specified callback, this callback will receive an object with a selection on it
  6633. angular.module('umbraco')
  6634.     .controller("Umbraco.PropertyEditors.ImageCropperController",
  6635.     function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager) {
  6636.  
  6637.         var config = angular.copy($scope.model.config);
  6638.  
  6639.         //move previously saved value to the editor
  6640.         if ($scope.model.value) {
  6641.             //backwards compat with the old file upload (incase some-one swaps them..)
  6642.             if (angular.isString($scope.model.value)) {
  6643.                 config.src = $scope.model.value;
  6644.                 $scope.model.value = config;
  6645.             } else if ($scope.model.value.crops) {
  6646.                 //sync any config changes with the editor and drop outdated crops
  6647.                 _.each($scope.model.value.crops, function (saved) {
  6648.                     var configured = _.find(config.crops, function (item) { return item.alias === saved.alias });
  6649.  
  6650.                     if (configured && configured.height === saved.height && configured.width === saved.width) {
  6651.                         configured.coordinates = saved.coordinates;
  6652.                     }
  6653.                 });
  6654.                 $scope.model.value.crops = config.crops;
  6655.  
  6656.                 //restore focalpoint if missing
  6657.                 if (!$scope.model.value.focalPoint) {
  6658.                     $scope.model.value.focalPoint = { left: 0.5, top: 0.5 };
  6659.                 }
  6660.             }
  6661.  
  6662.             $scope.imageSrc = $scope.model.value.src;
  6663.         }
  6664.  
  6665.  
  6666.         //crop a specific crop
  6667.         $scope.crop = function (crop) {
  6668.             $scope.currentCrop = crop;
  6669.             $scope.currentPoint = undefined;
  6670.         };
  6671.  
  6672.         //done cropping
  6673.         $scope.done = function () {
  6674.             $scope.currentCrop = undefined;
  6675.             $scope.currentPoint = undefined;
  6676.         };
  6677.  
  6678.         //crop a specific crop
  6679.         $scope.clear = function (crop) {
  6680.             //clear current uploaded files
  6681.             fileManager.setFiles($scope.model.alias, []);
  6682.  
  6683.             //clear the ui
  6684.             $scope.imageSrc = undefined;
  6685.             if ($scope.model.value) {
  6686.                 delete $scope.model.value;
  6687.             }
  6688.         };
  6689.  
  6690.         //show previews
  6691.         $scope.togglePreviews = function () {
  6692.             if ($scope.showPreviews) {
  6693.                 $scope.showPreviews = false;
  6694.                 $scope.tempShowPreviews = false;
  6695.             } else {
  6696.                 $scope.showPreviews = true;
  6697.             }
  6698.         };
  6699.  
  6700.         //on image selected, update the cropper
  6701.         $scope.$on("filesSelected", function (ev, args) {
  6702.             $scope.model.value = config;
  6703.  
  6704.             if (args.files && args.files[0]) {
  6705.  
  6706.                 fileManager.setFiles($scope.model.alias, args.files);
  6707.  
  6708.                 var reader = new FileReader();
  6709.                 reader.onload = function (e) {
  6710.  
  6711.                     $scope.$apply(function () {
  6712.                         $scope.imageSrc = e.target.result;
  6713.                     });
  6714.  
  6715.                 };
  6716.  
  6717.                 reader.readAsDataURL(args.files[0]);
  6718.             }
  6719.         });
  6720.  
  6721.  
  6722.         //here we declare a special method which will be called whenever the value has changed from the server
  6723.         $scope.model.onValueChanged = function (newVal, oldVal) {
  6724.             //clear current uploaded files
  6725.             fileManager.setFiles($scope.model.alias, []);
  6726.         };
  6727.  
  6728.         var unsubscribe = $scope.$on("formSubmitting", function () {
  6729.             $scope.done();
  6730.         });
  6731.  
  6732.         $scope.$on('$destroy', function () {
  6733.             unsubscribe();
  6734.         });
  6735.     })
  6736.     .run(function (mediaHelper, umbRequestHelper) {
  6737.         if (mediaHelper && mediaHelper.registerFileResolver) {
  6738.  
  6739.             //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
  6740.             // they contain different data structures so if we need to query against it we need to be aware of this.
  6741.             mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) {
  6742.                 if (property.value.src) {
  6743.  
  6744.                     if (thumbnail === true) {
  6745.                         return property.value.src + "?width=500&mode=max";
  6746.                     }
  6747.                     else {
  6748.                         return property.value.src;
  6749.                     }
  6750.  
  6751.                     //this is a fallback in case the cropper has been asssigned a upload field
  6752.                 }
  6753.                 else if (angular.isString(property.value)) {
  6754.                     if (thumbnail) {
  6755.  
  6756.                         if (mediaHelper.detectIfImageByExtension(property.value)) {
  6757.  
  6758.                             var thumbnailUrl = umbRequestHelper.getApiUrl(
  6759.                                 "imagesApiBaseUrl",
  6760.                                 "GetBigThumbnail",
  6761.                                 [{ originalImagePath: property.value }]);
  6762.  
  6763.                             return thumbnailUrl;
  6764.                         }
  6765.                         else {
  6766.                             return null;
  6767.                         }
  6768.  
  6769.                     }
  6770.                     else {
  6771.                         return property.value;
  6772.                     }
  6773.                 }
  6774.  
  6775.                 return null;
  6776.             });
  6777.         }
  6778.     });
  6779. angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesController",
  6780.     function ($scope, $timeout) {
  6781.  
  6782.         if (!$scope.model.value) {
  6783.             $scope.model.value = [];
  6784.         }
  6785.  
  6786.         $scope.remove = function (item, evt) {
  6787.             evt.preventDefault();
  6788.             $scope.model.value = _.reject($scope.model.value, function (x) {
  6789.                 return x.alias === item.alias;
  6790.             });
  6791.         };
  6792.  
  6793.         $scope.edit = function (item, evt) {
  6794.             evt.preventDefault();
  6795.             $scope.newItem = item;
  6796.         };
  6797.  
  6798.         $scope.cancel = function (evt) {
  6799.             evt.preventDefault();
  6800.             $scope.newItem = null;
  6801.         };
  6802.  
  6803.         $scope.add = function (evt) {
  6804.             evt.preventDefault();
  6805.  
  6806.             if ($scope.newItem && $scope.newItem.alias &&
  6807.                 angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) &&
  6808.                 $scope.newItem.width > 0 && $scope.newItem.height > 0) {
  6809.  
  6810.                 var exists = _.find($scope.model.value, function (item) { return $scope.newItem.alias === item.alias; });
  6811.                 if (!exists) {
  6812.                     $scope.model.value.push($scope.newItem);
  6813.                     $scope.newItem = {};
  6814.                     $scope.hasError = false;
  6815.                     return;
  6816.                 }
  6817.             }
  6818.  
  6819.             //there was an error, do the highlight (will be set back by the directive)
  6820.             $scope.hasError = true;
  6821.         };
  6822.     });
  6823. function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
  6824.  
  6825.     if (!$scope.model.value) {
  6826.         $scope.model.value = [];
  6827.     }
  6828.  
  6829.     $scope.propertyAliases = [];
  6830.     $scope.selectedField = null;
  6831.     $scope.systemFields = [
  6832.         { value: "sortOrder" },
  6833.         { value: "updateDate" },
  6834.         { value: "updater" },
  6835.         { value: "createDate" },
  6836.         { value: "owner" },
  6837.         { value: "published"},
  6838.         { value: "contentTypeAlias" },
  6839.         { value: "email" },
  6840.         { value: "username" }
  6841.     ];
  6842.  
  6843.     $scope.getLocalizedKey = function(alias) {
  6844.         switch (alias) {
  6845.             case "name":
  6846.                 return "general_name";
  6847.             case "sortOrder":
  6848.                 return "general_sort";
  6849.             case "updateDate":
  6850.                 return "content_updateDate";
  6851.             case "updater":
  6852.                 return "content_updatedBy";
  6853.             case "createDate":
  6854.                 return "content_createDate";
  6855.             case "owner":
  6856.                 return "content_createBy";
  6857.             case "published":
  6858.                 return "content_isPublished";
  6859.             case "contentTypeAlias":
  6860.                 //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias'
  6861.                 return "content_documentType";
  6862.             case "email":
  6863.                 return "general_email";
  6864.             case "username":
  6865.                 return "general_username";
  6866.         }
  6867.         return alias;
  6868.     }
  6869.  
  6870.     $scope.removeField = function(e) {
  6871.         $scope.model.value = _.reject($scope.model.value, function (x) {
  6872.             return x.alias === e.alias;
  6873.         });
  6874.     }
  6875.  
  6876.     //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
  6877.     _.each($scope.systemFields, function (e, i) {
  6878.         var key = $scope.getLocalizedKey(e.value);
  6879.         localizationService.localize(key).then(function (v) {
  6880.             e.name = v;
  6881.  
  6882.             switch (e.value) {
  6883.                 case "updater":
  6884.                     e.name += " (Content only)";
  6885.                     break;
  6886.                 case "published":
  6887.                     e.name += " (Content only)";
  6888.                     break;
  6889.                 case "email":
  6890.                     e.name += " (Members only)";
  6891.                     break;
  6892.                 case "username":
  6893.                     e.name += " (Members only)";
  6894.                     break;
  6895.             }
  6896.  
  6897.         });
  6898.     });
  6899.  
  6900.     // Return a helper with preserved width of cells
  6901.     var fixHelper = function (e, ui) {
  6902.         var h = ui.clone();
  6903.  
  6904.         h.children().each(function () {
  6905.             $(this).width($(this).width());            
  6906.         });
  6907.         h.css("background-color", "lightgray");
  6908.  
  6909.         return h;
  6910.     };
  6911.  
  6912.     $scope.sortableOptions = {
  6913.         helper: fixHelper,
  6914.         handle: ".handle",
  6915.         opacity: 0.5,
  6916.         axis: 'y',
  6917.         containment: 'parent',
  6918.         cursor: 'move',
  6919.         items: '> tr',
  6920.         tolerance: 'pointer',
  6921.         update: function (e, ui) {
  6922.            
  6923.             // Get the new and old index for the moved element (using the text as the identifier)
  6924.             var newIndex = ui.item.index();
  6925.             var movedAlias = $('.alias-value', ui.item).text().trim();
  6926.             var originalIndex = getAliasIndexByText(movedAlias);
  6927.  
  6928.             // Move the element in the model
  6929.             if (originalIndex > -1) {
  6930.                 var movedElement = $scope.model.value[originalIndex];
  6931.                 $scope.model.value.splice(originalIndex, 1);
  6932.                 $scope.model.value.splice(newIndex, 0, movedElement);
  6933.             }
  6934.         }
  6935.     };
  6936.  
  6937.     contentTypeResource.getAllPropertyTypeAliases().then(function(data) {
  6938.         $scope.propertyAliases = data;
  6939.     });
  6940.  
  6941.     $scope.addField = function () {
  6942.  
  6943.         var val = $scope.selectedField;
  6944.         var isSystem = val.startsWith("_system_");
  6945.         if (isSystem) {
  6946.             val = val.trimStart("_system_");
  6947.         }
  6948.  
  6949.         var exists = _.find($scope.model.value, function (i) {
  6950.             return i.alias === val;
  6951.         });
  6952.         if (!exists) {
  6953.             $scope.model.value.push({
  6954.                 alias: val,
  6955.                 isSystem: isSystem ? 1 : 0
  6956.             });
  6957.         }
  6958.     }
  6959.  
  6960.     function getAliasIndexByText(value) {
  6961.         for (var i = 0; i < $scope.model.value.length; i++) {
  6962.             if ($scope.model.value[i].alias === value) {
  6963.                 return i;
  6964.             }
  6965.         }
  6966.  
  6967.         return -1;
  6968.     }
  6969.  
  6970. }
  6971.  
  6972.  
  6973. angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesListViewController", includePropsPreValsController);
  6974. function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState) {
  6975.  
  6976.     //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
  6977.     // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
  6978.     // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
  6979.     // the list view tab entirely when it's new.
  6980.     if ($routeParams.create) {
  6981.         $scope.isNew = true;
  6982.         return;
  6983.     }
  6984.  
  6985.     //Now we need to check if this is for media, members or content because that will depend on the resources we use
  6986.     var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
  6987.    
  6988.     //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
  6989.     if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) {
  6990.         $scope.entityType = "member";
  6991.         contentResource = $injector.get('memberResource');
  6992.         getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
  6993.         getListResultsCallback = contentResource.getPagedResults;
  6994.         deleteItemCallback = contentResource.deleteByKey;
  6995.         getIdCallback = function(selected) {
  6996.             return selected.key;
  6997.         };
  6998.         createEditUrlCallback = function(item) {
  6999.             return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId;
  7000.         };
  7001.     }
  7002.     else {
  7003.         //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
  7004.         if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) {
  7005.             $scope.entityType = "media";
  7006.             contentResource = $injector.get('mediaResource');
  7007.             getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;                        
  7008.         }
  7009.         else {
  7010.             $scope.entityType = "content";
  7011.             contentResource = $injector.get('contentResource');
  7012.             getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;            
  7013.         }
  7014.         getListResultsCallback = contentResource.getChildren;
  7015.         deleteItemCallback = contentResource.deleteById;
  7016.         getIdCallback = function(selected) {
  7017.             return selected.id;
  7018.         };
  7019.         createEditUrlCallback = function(item) {
  7020.             return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber;
  7021.         };
  7022.     }
  7023.  
  7024.     $scope.pagination = [];
  7025.     $scope.isNew = false;
  7026.     $scope.actionInProgress = false;
  7027.     $scope.listViewResultSet = {
  7028.         totalPages: 0,
  7029.         items: []
  7030.     };
  7031.  
  7032.     $scope.options = {
  7033.         pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
  7034.         pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1,
  7035.         filter: '',
  7036.         orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
  7037.         orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc",
  7038.         includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
  7039.             { alias: 'updateDate', header: 'Last edited', isSystem: 1 },
  7040.             { alias: 'updater', header: 'Last edited by', isSystem: 1 }
  7041.         ],
  7042.         allowBulkPublish: true,
  7043.         allowBulkUnpublish: true,
  7044.         allowBulkDelete: true,        
  7045.     };
  7046.  
  7047.     //update all of the system includeProperties to enable sorting
  7048.     _.each($scope.options.includeProperties, function(e, i) {
  7049.        
  7050.         if (e.isSystem) {
  7051.  
  7052.             //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted
  7053.             // to do that, we'd need to update the base query for content to include the content type alias column
  7054.             // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff?
  7055.             if (e.alias != "contentTypeAlias") {
  7056.                 e.allowSorting = true;
  7057.             }
  7058.            
  7059.             //localize the header
  7060.             var key = getLocalizedKey(e.alias);
  7061.             localizationService.localize(key).then(function (v) {
  7062.                 e.header = v;
  7063.             });
  7064.         }
  7065.     });
  7066.  
  7067.     $scope.isSortDirection = function (col, direction) {
  7068.         return $scope.options.orderBy.toUpperCase() == col.toUpperCase() && $scope.options.orderDirection == direction;
  7069.     }
  7070.  
  7071.     $scope.next = function() {
  7072.         if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
  7073.             $scope.options.pageNumber++;
  7074.             $scope.reloadView($scope.contentId);
  7075.             //TODO: this would be nice but causes the whole view to reload
  7076.             //$location.search("page", $scope.options.pageNumber);
  7077.         }
  7078.     };
  7079.  
  7080.     $scope.goToPage = function(pageNumber) {
  7081.         $scope.options.pageNumber = pageNumber + 1;
  7082.         $scope.reloadView($scope.contentId);
  7083.         //TODO: this would be nice but causes the whole view to reload
  7084.         //$location.search("page", $scope.options.pageNumber);
  7085.     };
  7086.  
  7087.     $scope.sort = function(field, allow) {
  7088.         if (allow) {
  7089.             $scope.options.orderBy = field;
  7090.  
  7091.             if ($scope.options.orderDirection === "desc") {
  7092.                 $scope.options.orderDirection = "asc";
  7093.             }
  7094.             else {
  7095.                 $scope.options.orderDirection = "desc";
  7096.             }
  7097.  
  7098.             $scope.reloadView($scope.contentId);
  7099.         }
  7100.     };
  7101.  
  7102.     $scope.prev = function() {
  7103.         if ($scope.options.pageNumber > 1) {
  7104.             $scope.options.pageNumber--;
  7105.             $scope.reloadView($scope.contentId);
  7106.             //TODO: this would be nice but causes the whole view to reload
  7107.             //$location.search("page", $scope.options.pageNumber);
  7108.         }
  7109.     };
  7110.    
  7111.  
  7112.     /*Loads the search results, based on parameters set in prev,next,sort and so on*/
  7113.     /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
  7114.     with simple values */
  7115.  
  7116.     $scope.reloadView = function(id) {
  7117.         getListResultsCallback(id, $scope.options).then(function (data) {
  7118.             $scope.listViewResultSet = data;
  7119.  
  7120.             //update all values for display
  7121.             if ($scope.listViewResultSet.items) {
  7122.                 _.each($scope.listViewResultSet.items, function (e, index) {
  7123.                     setPropertyValues(e);
  7124.                 });
  7125.             }
  7126.  
  7127.             //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
  7128.             // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
  7129.             // available page and then re-load again
  7130.             if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
  7131.                 $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
  7132.  
  7133.                 //reload!
  7134.                 $scope.reloadView(id);
  7135.             }
  7136.  
  7137.             $scope.pagination = [];
  7138.  
  7139.             //list 10 pages as per normal
  7140.             if ($scope.listViewResultSet.totalPages <= 10) {
  7141.                 for (var i = 0; i < $scope.listViewResultSet.totalPages; i++) {
  7142.                     $scope.pagination.push({
  7143.                         val: (i + 1),
  7144.                         isActive: $scope.options.pageNumber == (i + 1)
  7145.                     });
  7146.                 }
  7147.             }
  7148.             else {
  7149.                 //if there is more than 10 pages, we need to do some fancy bits
  7150.  
  7151.                 //get the max index to start
  7152.                 var maxIndex = $scope.listViewResultSet.totalPages - 10;
  7153.                 //set the start, but it can't be below zero
  7154.                 var start = Math.max($scope.options.pageNumber - 5, 0);
  7155.                 //ensure that it's not too far either
  7156.                 start = Math.min(maxIndex, start);
  7157.  
  7158.                 for (var i = start; i < (10 + start) ; i++) {
  7159.                     $scope.pagination.push({
  7160.                         val: (i + 1),
  7161.                         isActive: $scope.options.pageNumber == (i + 1)
  7162.                     });
  7163.                 }
  7164.  
  7165.                 //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing
  7166.                 if (start > 0) {
  7167.                     $scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false});
  7168.                 }
  7169.  
  7170.                 //same for the end
  7171.                 if (start < maxIndex) {
  7172.                     $scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: $scope.listViewResultSet.totalPages, isActive: false });
  7173.                 }
  7174.             }
  7175.  
  7176.         });
  7177.     };
  7178.  
  7179.     //assign debounce method to the search to limit the queries
  7180.     $scope.search = _.debounce(function() {
  7181.         $scope.options.pageNumber = 1;
  7182.         $scope.reloadView($scope.contentId);
  7183.     }, 100);
  7184.  
  7185.     $scope.enterSearch = function($event) {
  7186.         $($event.target).next().focus();
  7187.     }
  7188.  
  7189.     $scope.selectAll = function($event) {
  7190.         var checkbox = $event.target;
  7191.         if (!angular.isArray($scope.listViewResultSet.items)) {
  7192.             return;
  7193.         }
  7194.         for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
  7195.             var entity = $scope.listViewResultSet.items[i];
  7196.             entity.selected = checkbox.checked;
  7197.         }
  7198.     };
  7199.  
  7200.     $scope.isSelectedAll = function() {
  7201.         if (!angular.isArray($scope.listViewResultSet.items)) {
  7202.             return false;
  7203.         }
  7204.         return _.every($scope.listViewResultSet.items, function(item) {
  7205.             return item.selected;
  7206.         });
  7207.     };
  7208.  
  7209.     $scope.isAnythingSelected = function() {
  7210.         if (!angular.isArray($scope.listViewResultSet.items)) {
  7211.             return false;
  7212.         }
  7213.         return _.some($scope.listViewResultSet.items, function(item) {
  7214.             return item.selected;
  7215.         });
  7216.     };
  7217.  
  7218.     $scope.getIcon = function(entry) {
  7219.         return iconHelper.convertFromLegacyIcon(entry.icon);
  7220.     };
  7221.  
  7222.     $scope.delete = function() {
  7223.         var selected = _.filter($scope.listViewResultSet.items, function(item) {
  7224.             return item.selected;
  7225.         });
  7226.         var total = selected.length;
  7227.         if (total === 0) {
  7228.             return;
  7229.         }
  7230.  
  7231.         if (confirm("Sure you want to delete?") == true) {
  7232.             $scope.actionInProgress = true;
  7233.             $scope.bulkStatus = "Starting with delete";
  7234.             var current = 1;
  7235.  
  7236.             var pluralSuffix = total == 1 ? "" : "s";
  7237.  
  7238.             for (var i = 0; i < selected.length; i++) {
  7239.                 $scope.bulkStatus = "Deleted item " + current + " out of " + total + " item" + pluralSuffix;
  7240.                 deleteItemCallback(getIdCallback(selected[i])).then(function (data) {
  7241.                     if (current === total) {
  7242.                         notificationsService.success("Bulk action", "Deleted " + total + " item" + pluralSuffix);
  7243.                         $scope.bulkStatus = "";
  7244.                         $scope.reloadView($scope.contentId);
  7245.                         $scope.actionInProgress = false;
  7246.                     }
  7247.                     current++;
  7248.                 });
  7249.             }
  7250.         }
  7251.  
  7252.     };
  7253.  
  7254.     $scope.publish = function() {
  7255.         var selected = _.filter($scope.listViewResultSet.items, function(item) {
  7256.             return item.selected;
  7257.         });
  7258.         var total = selected.length;
  7259.         if (total === 0) {
  7260.             return;
  7261.         }
  7262.  
  7263.         $scope.actionInProgress = true;
  7264.         $scope.bulkStatus = "Starting with publish";
  7265.         var current = 1;
  7266.  
  7267.         var pluralSuffix = total == 1 ? "" : "s";
  7268.  
  7269.         for (var i = 0; i < selected.length; i++) {
  7270.             $scope.bulkStatus = "Publishing " + current + " out of " + total + " document" + pluralSuffix;
  7271.  
  7272.             contentResource.publishById(getIdCallback(selected[i]))
  7273.                 .then(function(content) {
  7274.                     if (current == total) {
  7275.                         notificationsService.success("Bulk action", "Published " + total + " document" + pluralSuffix);
  7276.                         $scope.bulkStatus = "";
  7277.                         $scope.reloadView($scope.contentId);
  7278.                         $scope.actionInProgress = false;
  7279.                     }
  7280.                     current++;
  7281.                 }, function(err) {
  7282.  
  7283.                     $scope.bulkStatus = "";
  7284.                     $scope.reloadView($scope.contentId);
  7285.                     $scope.actionInProgress = false;
  7286.  
  7287.                     //if there are validation errors for publishing then we need to show them
  7288.                     if (err.status === 400 && err.data && err.data.Message) {
  7289.                         notificationsService.error("Publish error", err.data.Message);
  7290.                     }
  7291.                     else {
  7292.                         dialogService.ysodDialog(err);
  7293.                     }
  7294.                 });
  7295.  
  7296.         }
  7297.     };
  7298.  
  7299.     $scope.unpublish = function() {
  7300.         var selected = _.filter($scope.listViewResultSet.items, function(item) {
  7301.             return item.selected;
  7302.         });
  7303.         var total = selected.length;
  7304.         if (total === 0) {
  7305.             return;
  7306.         }
  7307.  
  7308.         $scope.actionInProgress = true;
  7309.         $scope.bulkStatus = "Starting with publish";
  7310.         var current = 1;
  7311.  
  7312.         var pluralSuffix = total == 1 ? "" : "s";
  7313.  
  7314.         for (var i = 0; i < selected.length; i++) {
  7315.             $scope.bulkStatus = "Unpublishing " + current + " out of " + total + " document" + pluralSuffix;
  7316.  
  7317.             contentResource.unPublish(getIdCallback(selected[i]))
  7318.                 .then(function(content) {
  7319.  
  7320.                     if (current == total) {
  7321.                         notificationsService.success("Bulk action", "Unpublished " + total + " document" + pluralSuffix);
  7322.                         $scope.bulkStatus = "";
  7323.                         $scope.reloadView($scope.contentId);
  7324.                         $scope.actionInProgress = false;
  7325.                     }
  7326.  
  7327.                     current++;
  7328.                 });
  7329.         }
  7330.     };
  7331.  
  7332.     function getCustomPropertyValue(alias, properties) {
  7333.         var value = '';
  7334.         var index = 0;
  7335.         var foundAlias = false;
  7336.         for (var i = 0; i < properties.length; i++) {
  7337.             if (properties[i].alias == alias) {
  7338.                 foundAlias = true;
  7339.                 break;
  7340.             }
  7341.             index++;
  7342.         }
  7343.  
  7344.         if (foundAlias) {
  7345.             value = properties[index].value;
  7346.         }
  7347.  
  7348.         return value;
  7349.     };
  7350.  
  7351.     /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */
  7352.     function setPropertyValues(result) {
  7353.  
  7354.         //set the edit url
  7355.         result.editPath = createEditUrlCallback(result);
  7356.  
  7357.         _.each($scope.options.includeProperties, function (e, i) {
  7358.  
  7359.             var alias = e.alias;
  7360.  
  7361.             // First try to pull the value directly from the alias (e.g. updatedBy)        
  7362.             var value = result[alias];
  7363.            
  7364.             // If this returns an object, look for the name property of that (e.g. owner.name)
  7365.             if (value === Object(value)) {
  7366.                 value = value['name'];
  7367.             }
  7368.  
  7369.             // If we've got nothing yet, look at a user defined property
  7370.             if (typeof value === 'undefined') {
  7371.                 value = getCustomPropertyValue(alias, result.properties);
  7372.             }
  7373.  
  7374.             // If we have a date, format it
  7375.             if (isDate(value)) {
  7376.                 value = value.substring(0, value.length - 3);
  7377.             }
  7378.  
  7379.             // set what we've got on the result
  7380.             result[alias] = value;
  7381.         });
  7382.  
  7383.  
  7384.     };
  7385.  
  7386.     function isDate(val) {
  7387.         if (angular.isString(val)) {
  7388.             return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
  7389.         }
  7390.         return false;
  7391.     };
  7392.  
  7393.     function initView() {
  7394.         if ($routeParams.id) {
  7395.             $scope.listViewAllowedTypes = getContentTypesCallback($routeParams.id);
  7396.  
  7397.             $scope.contentId = $routeParams.id;
  7398.             $scope.isTrashed = $routeParams.id === "-20" || $routeParams.id === "-21";
  7399.  
  7400.             $scope.reloadView($scope.contentId);
  7401.         }
  7402.     };
  7403.  
  7404.     function getLocalizedKey(alias) {
  7405.  
  7406.         switch (alias) {
  7407.             case "sortOrder":
  7408.                 return "general_sort";
  7409.             case "updateDate":
  7410.                 return "content_updateDate";
  7411.             case "updater":
  7412.                 return "content_updatedBy";
  7413.             case "createDate":
  7414.                 return "content_createDate";
  7415.             case "owner":
  7416.                 return "content_createBy";
  7417.             case "published":
  7418.                 return "content_isPublished";
  7419.             case "contentTypeAlias":
  7420.                 //TODO: Check for members
  7421.                 return $scope.entityType === "content" ? "content_documentType" : "content_mediatype";
  7422.             case "email":
  7423.                 return "general_email";
  7424.             case "username":
  7425.                 return "general_username";
  7426.         }
  7427.         return alias;
  7428.     }
  7429.  
  7430.     //GO!
  7431.     initView();
  7432. }
  7433.  
  7434.  
  7435. angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController);
  7436. function sortByPreValsController($rootScope, $scope, localizationService) {
  7437.  
  7438.     $scope.sortByFields = [
  7439.         { value: "SortOrder", key: "general_sort" },
  7440.         { value: "Name", key: "general_name" },
  7441.         { value: "VersionDate", key: "content_updateDate" },
  7442.         { value: "Updater", key: "content_updatedBy" },
  7443.         { value: "CreateDate", key: "content_createDate" },
  7444.         { value: "Owner", key: "content_createBy" },
  7445.         { value: "ContentTypeAlias", key: "content_documentType" },
  7446.         { value: "Published", key: "content_isPublished" },
  7447.         { value: "Email", key: "general_email" },
  7448.         { value: "Username", key: "general_username" }
  7449.     ];
  7450.    
  7451.     //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
  7452.     _.each($scope.sortByFields, function (e, i) {
  7453.         localizationService.localize(e.key).then(function (v) {
  7454.             e.name = v;
  7455.  
  7456.             switch (e.value) {
  7457.                 case "Updater":
  7458.                     e.name += " (Content only)";
  7459.                     break;
  7460.                 case "Published":
  7461.                     e.name += " (Content only)";
  7462.                     break;
  7463.                 case "Email":
  7464.                     e.name += " (Members only)";
  7465.                     break;
  7466.                 case "Username":
  7467.                     e.name += " (Members only)";
  7468.                     break;
  7469.             }
  7470.         });
  7471.     });
  7472.  
  7473. }
  7474.  
  7475.  
  7476. angular.module("umbraco").controller("Umbraco.PrevalueEditors.SortByListViewController", sortByPreValsController);
  7477. //DO NOT DELETE THIS, this is in use...
  7478. angular.module('umbraco')
  7479. .controller("Umbraco.PropertyEditors.MacroContainerController",
  7480.    
  7481.     function($scope, dialogService, entityResource, macroService){
  7482.         $scope.renderModel = [];
  7483.        
  7484.         if($scope.model.value){
  7485.             var macros = $scope.model.value.split('>');
  7486.  
  7487.             angular.forEach(macros, function(syntax, key){
  7488.                 if(syntax && syntax.length > 10){
  7489.                     //re-add the char we split on
  7490.                     syntax = syntax + ">";
  7491.                     var parsed = macroService.parseMacroSyntax(syntax);
  7492.                     if(!parsed){
  7493.                         parsed = {};
  7494.                     }
  7495.                    
  7496.                     parsed.syntax = syntax;
  7497.                     collectDetails(parsed);
  7498.                     $scope.renderModel.push(parsed);
  7499.                 }
  7500.             });
  7501.         }
  7502.  
  7503.        
  7504.         function collectDetails(macro){
  7505.             macro.details = "";
  7506.             if(macro.macroParamsDictionary){
  7507.                 angular.forEach((macro.macroParamsDictionary), function(value, key){
  7508.                     macro.details += key + ": " + value + " "; 
  7509.                 });
  7510.             }      
  7511.         }
  7512.  
  7513.         function openDialog(index){
  7514.             var dialogData = {
  7515.                 allowedMacros: $scope.model.config.allowed
  7516.             };
  7517.  
  7518.             if(index !== null && $scope.renderModel[index]) {
  7519.                 var macro = $scope.renderModel[index];
  7520.                 dialogData["macroData"] = macro;
  7521.             }
  7522.            
  7523.             dialogService.macroPicker({
  7524.                 dialogData : dialogData,
  7525.                 callback: function(data) {
  7526.  
  7527.                     collectDetails(data);
  7528.  
  7529.                     //update the raw syntax and the list...
  7530.                     if(index !== null && $scope.renderModel[index]) {
  7531.                         $scope.renderModel[index] = data;
  7532.                     } else {
  7533.                         $scope.renderModel.push(data);
  7534.                     }
  7535.                 }
  7536.             });
  7537.         }
  7538.  
  7539.  
  7540.  
  7541.         $scope.edit =function(index){
  7542.                 openDialog(index);
  7543.         };
  7544.  
  7545.         $scope.add = function () {
  7546.  
  7547.             if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) {
  7548.                 //cannot add more than the max
  7549.                 return;
  7550.             }
  7551.            
  7552.             openDialog();
  7553.         };
  7554.  
  7555.         $scope.remove =function(index){
  7556.             $scope.renderModel.splice(index, 1);
  7557.         };
  7558.  
  7559.         $scope.clear = function() {
  7560.             $scope.model.value = "";
  7561.             $scope.renderModel = [];
  7562.         };
  7563.  
  7564.         var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {   
  7565.             var syntax = [];
  7566.             angular.forEach($scope.renderModel, function(value, key){
  7567.                 syntax.push(value.syntax);
  7568.             });
  7569.  
  7570.             $scope.model.value = syntax.join("");
  7571.         });
  7572.  
  7573.         //when the scope is destroyed we need to unsubscribe
  7574.         $scope.$on('$destroy', function () {
  7575.             unsubscribe();
  7576.         });
  7577.  
  7578.  
  7579.         function trim(str, chr) {
  7580.             var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
  7581.             return str.replace(rgxtrim, '');
  7582.         }
  7583.  
  7584. });
  7585.  
  7586. function MacroListController($scope, entityResource) {
  7587.  
  7588.     $scope.items = [];
  7589.    
  7590.     entityResource.getAll("Macro").then(function(items) {
  7591.         _.each(items, function(i) {
  7592.             $scope.items.push({ name: i.name, alias: i.alias });
  7593.         });
  7594.        
  7595.     });
  7596.  
  7597.  
  7598. }
  7599.  
  7600. angular.module("umbraco").controller("Umbraco.PrevalueEditors.MacroList", MacroListController);
  7601.  
  7602. //inject umbracos assetsServce and dialog service
  7603. function MarkdownEditorController($scope, assetsService, dialogService, $timeout) {
  7604.  
  7605.     //tell the assets service to load the markdown.editor libs from the markdown editors
  7606.     //plugin folder
  7607.  
  7608.     if ($scope.model.value === null || $scope.model.value === "") {
  7609.         $scope.model.value = $scope.model.config.defaultValue;
  7610.     }
  7611.  
  7612.     assetsService
  7613.         .load([
  7614.             "lib/markdown/markdown.converter.js",
  7615.             "lib/markdown/markdown.sanitizer.js",
  7616.             "lib/markdown/markdown.editor.js"
  7617.         ])
  7618.         .then(function () {
  7619.  
  7620.             // we need a short delay to wait for the textbox to appear.
  7621.             setTimeout(function () {
  7622.                 //this function will execute when all dependencies have loaded
  7623.                 // but in the case that they've been previously loaded, we can only
  7624.                 // init the md editor after this digest because the DOM needs to be ready first
  7625.                 // so run the init on a timeout
  7626.                 $timeout(function () {
  7627.                     var converter2 = new Markdown.Converter();
  7628.                     var editor2 = new Markdown.Editor(converter2, "-" + $scope.model.alias);
  7629.                     editor2.run();
  7630.  
  7631.                     //subscribe to the image dialog clicks
  7632.                     editor2.hooks.set("insertImageDialog", function (callback) {
  7633.  
  7634.                         dialogService.mediaPicker({
  7635.                             callback: function (data) {
  7636.                                 callback(data.image);
  7637.                             }
  7638.                         });
  7639.  
  7640.                         return true; // tell the editor that we'll take care of getting the image url
  7641.                     });
  7642.                 }, 200);
  7643.             });
  7644.  
  7645.             //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK
  7646.             assetsService.loadCss("lib/markdown/markdown.css");
  7647.         })
  7648. }
  7649.  
  7650. angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController);
  7651. //this controller simply tells the dialogs service to open a mediaPicker window
  7652. //with a specified callback, this callback will receive an object with a selection on it
  7653. angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController",
  7654.     function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService) {
  7655.  
  7656.         //check the pre-values for multi-picker
  7657.         var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
  7658.  
  7659.         if (!$scope.model.config.startNodeId) {
  7660.             userService.getCurrentUser().then(function (userData) {
  7661.                 $scope.model.config.startNodeId = userData.startMediaId;
  7662.             });
  7663.         }
  7664.            
  7665.  
  7666.          
  7667.         function setupViewModel() {
  7668.             $scope.images = [];
  7669.             $scope.ids = [];
  7670.  
  7671.             if ($scope.model.value) {
  7672.                 var ids = $scope.model.value.split(',');
  7673.  
  7674.                 //NOTE: We need to use the entityResource NOT the mediaResource here because
  7675.                 // the mediaResource has server side auth configured for which the user must have
  7676.                 // access to the media section, if they don't they'll get auth errors. The entityResource
  7677.                 // acts differently in that it allows access if the user has access to any of the apps that
  7678.                 // might require it's use. Therefore we need to use the metatData property to get at the thumbnail
  7679.                 // value.
  7680.  
  7681.                 entityResource.getByIds(ids, "Media").then(function (medias) {
  7682.  
  7683.                     _.each(medias, function (media, i) {
  7684.                        
  7685.                         //only show non-trashed items
  7686.                         if (media.parentId >= -1) {
  7687.  
  7688.                             if (!media.thumbnail) {
  7689.                                 media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
  7690.                             }
  7691.  
  7692.                             $scope.images.push(media);
  7693.                             $scope.ids.push(media.id);  
  7694.                         }
  7695.                     });
  7696.  
  7697.                     $scope.sync();
  7698.                 });
  7699.             }
  7700.         }
  7701.  
  7702.         setupViewModel();
  7703.  
  7704.         $scope.remove = function(index) {
  7705.             $scope.images.splice(index, 1);
  7706.             $scope.ids.splice(index, 1);
  7707.             $scope.sync();
  7708.         };
  7709.  
  7710.         $scope.add = function() {
  7711.             dialogService.mediaPicker({
  7712.                 startNodeId: $scope.model.config.startNodeId,
  7713.                 multiPicker: multiPicker,
  7714.                 callback: function(data) {
  7715.                    
  7716.                     //it's only a single selector, so make it into an array
  7717.                     if (!multiPicker) {
  7718.                         data = [data];
  7719.                     }
  7720.                    
  7721.                     _.each(data, function(media, i) {
  7722.  
  7723.                         if (!media.thumbnail) {
  7724.                             media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
  7725.                         }
  7726.  
  7727.                         $scope.images.push(media);
  7728.                         $scope.ids.push(media.id);
  7729.                     });
  7730.  
  7731.                     $scope.sync();
  7732.                 }
  7733.             });
  7734.         };
  7735.  
  7736.        $scope.sortableOptions = {
  7737.            update: function(e, ui) {
  7738.                var r = [];
  7739.                //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the
  7740.                // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the
  7741.                // watch do all the rest.
  7742.                 $timeout(function(){
  7743.                     angular.forEach($scope.images, function(value, key){
  7744.                         r.push(value.id);
  7745.                     });
  7746.  
  7747.                     $scope.ids = r;
  7748.                     $scope.sync();
  7749.                 }, 500, false);
  7750.             }
  7751.         };
  7752.  
  7753.         $scope.sync = function() {
  7754.             $scope.model.value = $scope.ids.join();
  7755.         };
  7756.  
  7757.         $scope.showAdd = function () {
  7758.             if (!multiPicker) {
  7759.                 if ($scope.model.value && $scope.model.value !== "") {
  7760.                     return false;
  7761.                 }
  7762.             }
  7763.             return true;
  7764.         };
  7765.  
  7766.         //here we declare a special method which will be called whenever the value has changed from the server
  7767.         //this is instead of doing a watch on the model.value = faster
  7768.         $scope.model.onValueChanged = function (newVal, oldVal) {
  7769.             //update the display val again if it has changed from the server
  7770.             setupViewModel();
  7771.         };
  7772.  
  7773.     });
  7774.  
  7775. //this controller simply tells the dialogs service to open a memberPicker window
  7776. //with a specified callback, this callback will receive an object with a selection on it
  7777. function memberGroupPicker($scope, dialogService){
  7778.  
  7779.     function trim(str, chr) {
  7780.         var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
  7781.         return str.replace(rgxtrim, '');
  7782.     }
  7783.  
  7784.     $scope.renderModel = [];
  7785.    
  7786.     if ($scope.model.value) {
  7787.         var modelIds = $scope.model.value.split(',');
  7788.         _.each(modelIds, function (item, i) {
  7789.             $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
  7790.         });
  7791.     }
  7792.        
  7793.     var dialogOptions = {
  7794.         multiPicker: true,
  7795.         entityType: "MemberGroup",
  7796.         section: "membergroup",
  7797.         treeAlias: "memberGroup",
  7798.         filter: "",
  7799.         filterCssClass: "not-allowed",
  7800.         callback: function (data) {
  7801.             if (angular.isArray(data)) {
  7802.                 _.each(data, function (item, i) {
  7803.                     $scope.add(item);
  7804.                 });
  7805.             } else {
  7806.                 $scope.clear();
  7807.                 $scope.add(data);
  7808.  
  7809.             }
  7810.         }
  7811.     };
  7812.  
  7813.     //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
  7814.     // pre-value config on to the dialog options
  7815.     if($scope.model.config){
  7816.         angular.extend(dialogOptions, $scope.model.config);
  7817.     }
  7818.  
  7819.     $scope.openMemberGroupPicker =function() {
  7820.         var d = dialogService.memberGroupPicker(dialogOptions);
  7821.     };
  7822.  
  7823.  
  7824.     $scope.remove =function(index){
  7825.         $scope.renderModel.splice(index, 1);
  7826.     };
  7827.  
  7828.     $scope.add = function (item) {
  7829.         var currIds = _.map($scope.renderModel, function (i) {
  7830.             return i.id;
  7831.         });
  7832.  
  7833.         if (currIds.indexOf(item) < 0) {
  7834.             $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
  7835.         }  
  7836.     };
  7837.  
  7838.     $scope.clear = function() {
  7839.         $scope.renderModel = [];
  7840.     };
  7841.  
  7842.     var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  7843.         var currIds = _.map($scope.renderModel, function (i) {
  7844.             return i.id;
  7845.         });
  7846.         $scope.model.value = trim(currIds.join(), ",");
  7847.     });
  7848.  
  7849.     //when the scope is destroyed we need to unsubscribe
  7850.     $scope.$on('$destroy', function () {
  7851.         unsubscribe();
  7852.     });
  7853.  
  7854. }
  7855.  
  7856. angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker);
  7857. function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) {
  7858.  
  7859.     //set the available to the keys of the dictionary who's value is true
  7860.     $scope.getAvailable = function () {
  7861.         var available = [];
  7862.         for (var n in $scope.model.value) {
  7863.             if ($scope.model.value[n] === false) {
  7864.                 available.push(n);
  7865.             }
  7866.         }
  7867.         return available;
  7868.     };
  7869.     //set the selected to the keys of the dictionary who's value is true
  7870.     $scope.getSelected = function () {
  7871.         var selected = [];
  7872.         for (var n in $scope.model.value) {
  7873.             if ($scope.model.value[n] === true) {
  7874.                 selected.push(n);
  7875.             }
  7876.         }
  7877.         return selected;
  7878.     };
  7879.  
  7880.     $scope.addItem = function(item) {
  7881.         //keep the model up to date
  7882.         $scope.model.value[item] = true;
  7883.     };
  7884.    
  7885.     $scope.removeItem = function (item) {
  7886.         //keep the model up to date
  7887.         $scope.model.value[item] = false;
  7888.     };
  7889.  
  7890.  
  7891. }
  7892. angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupController", memberGroupController);
  7893. //this controller simply tells the dialogs service to open a memberPicker window
  7894. //with a specified callback, this callback will receive an object with a selection on it
  7895. function memberPickerController($scope, dialogService, entityResource, $log, iconHelper){
  7896.  
  7897.     function trim(str, chr) {
  7898.         var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
  7899.         return str.replace(rgxtrim, '');
  7900.     }
  7901.  
  7902.     $scope.renderModel = [];
  7903.  
  7904.     var dialogOptions = {
  7905.         multiPicker: false,
  7906.         entityType: "Member",
  7907.         section: "member",
  7908.         treeAlias: "member",
  7909.         filter: function(i) {
  7910.             return i.metaData.isContainer == true;
  7911.         },
  7912.         filterCssClass: "not-allowed",
  7913.         callback: function(data) {
  7914.             if (angular.isArray(data)) {
  7915.                 _.each(data, function (item, i) {
  7916.                     $scope.add(item);
  7917.                 });
  7918.             } else {
  7919.                 $scope.clear();
  7920.                 $scope.add(data);
  7921.             }
  7922.         }
  7923.     };
  7924.  
  7925.     //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
  7926.     // pre-value config on to the dialog options
  7927.     if ($scope.model.config) {
  7928.         angular.extend(dialogOptions, $scope.model.config);
  7929.     }
  7930.    
  7931.     $scope.openMemberPicker =function() {
  7932.         var d = dialogService.memberPicker(dialogOptions);
  7933.     };
  7934.  
  7935.  
  7936.     $scope.remove =function(index){
  7937.         $scope.renderModel.splice(index, 1);
  7938.     };
  7939.  
  7940.     $scope.add = function (item) {
  7941.         var currIds = _.map($scope.renderModel, function (i) {
  7942.             return i.id;
  7943.         });
  7944.  
  7945.         if (currIds.indexOf(item.id) < 0) {
  7946.             item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  7947.             $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});              
  7948.         }  
  7949.     };
  7950.  
  7951.     $scope.clear = function() {
  7952.         $scope.renderModel = [];
  7953.     };
  7954.    
  7955.     var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  7956.         var currIds = _.map($scope.renderModel, function (i) {
  7957.             return i.id;
  7958.         });
  7959.         $scope.model.value = trim(currIds.join(), ",");
  7960.     });
  7961.  
  7962.     //when the scope is destroyed we need to unsubscribe
  7963.     $scope.$on('$destroy', function () {
  7964.         unsubscribe();
  7965.     });
  7966.  
  7967.     //load member data
  7968.     var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
  7969.     entityResource.getByIds(modelIds, "Member").then(function (data) {
  7970.         _.each(data, function (item, i) {
  7971.             item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  7972.             $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
  7973.         });
  7974.     });
  7975. }
  7976.  
  7977.  
  7978. angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController);
  7979. function MultipleTextBoxController($scope) {
  7980.  
  7981.     $scope.sortableOptions = {
  7982.         axis: 'y',
  7983.         containment: 'parent',
  7984.         cursor: 'move',
  7985.         items: '> div.control-group',
  7986.         tolerance: 'pointer'
  7987.     };
  7988.  
  7989.     if (!$scope.model.value) {
  7990.         $scope.model.value = [];
  7991.     }
  7992.    
  7993.     //add any fields that there isn't values for
  7994.     if ($scope.model.config.min > 0) {
  7995.         for (var i = 0; i < $scope.model.config.min; i++) {
  7996.             if ((i + 1) > $scope.model.value.length) {
  7997.                 $scope.model.value.push({ value: "" });
  7998.             }
  7999.         }
  8000.     }
  8001.  
  8002.     $scope.add = function () {
  8003.         if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
  8004.             $scope.model.value.push({ value: "" });
  8005.         }
  8006.     };
  8007.  
  8008.     $scope.remove = function(index) {
  8009.         var remainder = [];
  8010.         for (var x = 0; x < $scope.model.value.length; x++) {
  8011.             if (x !== index) {
  8012.                 remainder.push($scope.model.value[x]);
  8013.             }
  8014.         }
  8015.         $scope.model.value = remainder;
  8016.     };
  8017.  
  8018. }
  8019.  
  8020. angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController);
  8021.  
  8022. angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController",
  8023.     function($scope) {
  8024.        
  8025.         if (angular.isObject($scope.model.config.items)) {
  8026.            
  8027.             //now we need to format the items in the dictionary because we always want to have an array
  8028.             var newItems = [];
  8029.             var vals = _.values($scope.model.config.items);
  8030.             var keys = _.keys($scope.model.config.items);
  8031.             for (var i = 0; i < vals.length; i++) {
  8032.                 newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
  8033.             }
  8034.  
  8035.             //ensure the items are sorted by the provided sort order
  8036.             newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  8037.  
  8038.             //re-assign
  8039.             $scope.model.config.items = newItems;
  8040.  
  8041.         }
  8042.  
  8043.     });
  8044.  
  8045. /**
  8046.  * @ngdoc controller
  8047.  * @name Umbraco.Editors.ReadOnlyValueController
  8048.  * @function
  8049.  *
  8050.  * @description
  8051.  * The controller for the readonlyvalue property editor.
  8052.  *  This controller offer more functionality than just a simple label as it will be able to apply formatting to the
  8053.  *  value to be displayed. This means that we also have to apply more complex logic of watching the model value when
  8054.  *  it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
  8055.  *  In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of
  8056.  *  readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
  8057. */
  8058. function ReadOnlyValueController($rootScope, $scope, $filter) {
  8059.  
  8060.     function formatDisplayValue() {
  8061.        
  8062.         if ($scope.model.config &&
  8063.         angular.isArray($scope.model.config) &&
  8064.         $scope.model.config.length > 0 &&
  8065.         $scope.model.config[0] &&
  8066.         $scope.model.config.filter) {
  8067.  
  8068.             if ($scope.model.config.format) {
  8069.                 $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
  8070.             } else {
  8071.                 $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
  8072.             }
  8073.         } else {
  8074.             $scope.displayvalue = $scope.model.value;
  8075.         }
  8076.  
  8077.     }
  8078.  
  8079.     //format the display value on init:
  8080.     formatDisplayValue();
  8081.    
  8082.     $scope.$watch("model.value", function (newVal, oldVal) {
  8083.         //cannot just check for !newVal because it might be an empty string which we
  8084.         //want to look for.
  8085.         if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
  8086.             //update the display val again
  8087.             formatDisplayValue();
  8088.         }
  8089.     });
  8090. }
  8091.  
  8092. angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController);
  8093. angular.module("umbraco")
  8094.     .controller("Umbraco.PropertyEditors.RelatedLinksController",
  8095.         function ($rootScope, $scope, dialogService) {
  8096.  
  8097.             if (!$scope.model.value) {
  8098.                 $scope.model.value = [];
  8099.             }
  8100.            
  8101.             $scope.newCaption = '';
  8102.             $scope.newLink = 'http://';
  8103.             $scope.newNewWindow = false;
  8104.             $scope.newInternal = null;
  8105.             $scope.newInternalName = '';
  8106.             $scope.addExternal = true;
  8107.             $scope.currentEditLink = null;
  8108.             $scope.hasError = false;
  8109.  
  8110.             $scope.internal = function ($event) {
  8111.                 $scope.currentEditLink = null;
  8112.                 var d = dialogService.contentPicker({ multipicker: false, callback: select });
  8113.                 $event.preventDefault();
  8114.             };
  8115.            
  8116.             $scope.selectInternal = function ($event, link) {
  8117.  
  8118.                 $scope.currentEditLink = link;
  8119.                 var d = dialogService.contentPicker({ multipicker: false, callback: select });
  8120.                 $event.preventDefault();
  8121.             };
  8122.  
  8123.             $scope.edit = function (idx) {
  8124.                 for (var i = 0; i < $scope.model.value.length; i++) {
  8125.                     $scope.model.value[i].edit = false;
  8126.                 }
  8127.                 $scope.model.value[idx].edit = true;
  8128.             };
  8129.  
  8130.  
  8131.             $scope.saveEdit = function (idx) {
  8132.                 $scope.model.value[idx].title = $scope.model.value[idx].caption;
  8133.                 $scope.model.value[idx].edit = false;
  8134.             };
  8135.  
  8136.             $scope.delete = function (idx) {              
  8137.                 $scope.model.value.splice(idx, 1);              
  8138.             };
  8139.  
  8140.             $scope.add = function ($event) {
  8141.                 if ($scope.newCaption == "") {
  8142.                     $scope.hasError = true;
  8143.                 } else {
  8144.                     if ($scope.addExternal) {
  8145.                         var newExtLink = new function() {
  8146.                             this.caption = $scope.newCaption;
  8147.                             this.link = $scope.newLink;
  8148.                             this.newWindow = $scope.newNewWindow;
  8149.                             this.edit = false;
  8150.                             this.isInternal = false;
  8151.                             this.type = "external";
  8152.                             this.title = $scope.newCaption;
  8153.                         };
  8154.                         $scope.model.value.push(newExtLink);
  8155.                     } else {
  8156.                         var newIntLink = new function() {
  8157.                             this.caption = $scope.newCaption;
  8158.                             this.link = $scope.newInternal;
  8159.                             this.newWindow = $scope.newNewWindow;
  8160.                             this.internal = $scope.newInternal;
  8161.                             this.edit = false;
  8162.                             this.isInternal = true;
  8163.                             this.internalName = $scope.newInternalName;
  8164.                             this.type = "internal";
  8165.                             this.title = $scope.newCaption;
  8166.                         };
  8167.                         $scope.model.value.push(newIntLink);
  8168.                     }
  8169.                     $scope.newCaption = '';
  8170.                     $scope.newLink = 'http://';
  8171.                     $scope.newNewWindow = false;
  8172.                     $scope.newInternal = null;
  8173.                     $scope.newInternalName = '';
  8174.  
  8175.                 }
  8176.                 $event.preventDefault();
  8177.             };
  8178.  
  8179.             $scope.switch = function ($event) {
  8180.                 $scope.addExternal = !$scope.addExternal;
  8181.                 $event.preventDefault();
  8182.             };
  8183.            
  8184.             $scope.switchLinkType = function ($event, link) {
  8185.                 link.isInternal = !link.isInternal;                
  8186.                 link.type = link.isInternal ? "internal" : "external";
  8187.                 if (!link.isInternal)
  8188.                     link.link = $scope.newLink;
  8189.                 $event.preventDefault();
  8190.             };
  8191.  
  8192.             $scope.move = function (index, direction) {
  8193.                 var temp = $scope.model.value[index];
  8194.                 $scope.model.value[index] = $scope.model.value[index + direction];
  8195.                 $scope.model.value[index + direction] = temp;                
  8196.             };
  8197.  
  8198.             $scope.sortableOptions = {
  8199.                 containment: 'parent',
  8200.                 cursor: 'move',
  8201.                 helper: function (e, ui) {
  8202.                     // When sorting <trs>, the cells collapse.  This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
  8203.                     ui.children().each(function () {
  8204.                         $(this).width($(this).width());
  8205.                     });
  8206.                     return ui;
  8207.                 },
  8208.                 items: '> tr',
  8209.                 tolerance: 'pointer',
  8210.                 update: function (e, ui) {
  8211.                     // Get the new and old index for the moved element (using the URL as the identifier)
  8212.                     var newIndex = ui.item.index();
  8213.                     var movedLinkUrl = ui.item.attr('data-link');
  8214.                     var originalIndex = getElementIndexByUrl(movedLinkUrl);
  8215.  
  8216.                     // Move the element in the model
  8217.                     var movedElement = $scope.model.value[originalIndex];
  8218.                     $scope.model.value.splice(originalIndex, 1);
  8219.                     $scope.model.value.splice(newIndex, 0, movedElement);
  8220.                 }
  8221.             };
  8222.  
  8223.             function getElementIndexByUrl(url) {
  8224.                 for (var i = 0; i < $scope.model.value.length; i++) {
  8225.                     if ($scope.model.value[i].link == url) {
  8226.                         return i;
  8227.                     }
  8228.                 }
  8229.  
  8230.                 return -1;
  8231.             }
  8232.  
  8233.             function select(data) {
  8234.                 if ($scope.currentEditLink != null) {
  8235.                     $scope.currentEditLink.internal = data.id;
  8236.                     $scope.currentEditLink.internalName = data.name;
  8237.                     $scope.currentEditLink.link = data.id;
  8238.                 } else {
  8239.                     $scope.newInternal = data.id;
  8240.                     $scope.newInternalName = data.name;
  8241.                 }
  8242.             }            
  8243.         });
  8244. angular.module("umbraco")
  8245.     .controller("Umbraco.PropertyEditors.RTEController",
  8246.     function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource) {
  8247.        
  8248.         $scope.isLoading = true;
  8249.  
  8250.         //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
  8251.         // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
  8252.         // we have this mini content editor panel that can be launched with MNTP.
  8253.         var d = new Date();
  8254.         var n = d.getTime();
  8255.         $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte";
  8256.  
  8257.         var alreadyDirty = false;
  8258.         function syncContent(editor){
  8259.             editor.save();
  8260.             angularHelper.safeApply($scope, function () {
  8261.                 $scope.model.value = editor.getContent();
  8262.             });
  8263.  
  8264.             if (!alreadyDirty) {
  8265.                 //make the form dirty manually so that the track changes works, setting our model doesn't trigger
  8266.                 // the angular bits because tinymce replaces the textarea.
  8267.                 var currForm = angularHelper.getCurrentForm($scope);
  8268.                 currForm.$setDirty();
  8269.                 alreadyDirty = true;
  8270.             }
  8271.         }
  8272.  
  8273.         tinyMceService.configuration().then(function (tinyMceConfig) {
  8274.  
  8275.             //config value from general tinymce.config file
  8276.             var validElements = tinyMceConfig.validElements;
  8277.  
  8278.             //These are absolutely required in order for the macros to render inline
  8279.             //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
  8280.             var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style]";
  8281.  
  8282.             var invalidElements = tinyMceConfig.inValidElements;
  8283.             var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
  8284.                 if (plugin.useOnFrontend) {
  8285.                     return plugin.name;
  8286.                 }
  8287.             }).join(" ");
  8288.  
  8289.             var editorConfig = $scope.model.config.editor;
  8290.             if (!editorConfig || angular.isString(editorConfig)) {
  8291.                 editorConfig = tinyMceService.defaultPrevalues();
  8292.             }
  8293.  
  8294.             //config value on the data type
  8295.             var toolbar = editorConfig.toolbar.join(" | ");
  8296.             var stylesheets = [];
  8297.             var styleFormats = [];
  8298.             var await = [];
  8299.             if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
  8300.                 editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
  8301.             }
  8302.  
  8303.             //queue file loading
  8304.             if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded
  8305.                 await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
  8306.             }
  8307.  
  8308.             //queue rules loading
  8309.             angular.forEach(editorConfig.stylesheets, function (val, key) {
  8310.                 stylesheets.push("../css/" + val + ".css?" + new Date().getTime());
  8311.  
  8312.                 await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
  8313.                     angular.forEach(rules, function (rule) {
  8314.                         var r = {};
  8315.                         r.title = rule.name;
  8316.                         if (rule.selector[0] == ".") {
  8317.                             r.inline = "span";
  8318.                             r.classes = rule.selector.substring(1);
  8319.                         }
  8320.                         else if (rule.selector[0] == "#") {
  8321.                             r.inline = "span";
  8322.                             r.attributes = { id: rule.selector.substring(1) };
  8323.                         }
  8324.                         else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) {
  8325.                             var split = rule.selector.split(".");
  8326.                             r.block = split[0];
  8327.                             r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " ");
  8328.                         }
  8329.                         else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) {
  8330.                             var split = rule.selector.split("#");
  8331.                             r.block = split[0];
  8332.                             r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1);
  8333.                         }
  8334.                         else {
  8335.                             r.block = rule.selector;
  8336.                         }
  8337.  
  8338.                         styleFormats.push(r);
  8339.                     });
  8340.                 }));
  8341.             });
  8342.  
  8343.  
  8344.             //stores a reference to the editor
  8345.             var tinyMceEditor = null;
  8346.  
  8347.             //wait for queue to end
  8348.             $q.all(await).then(function () {
  8349.  
  8350.                 //create a baseline Config to exten upon
  8351.                 var baseLineConfigObj = {
  8352.                     mode: "exact",
  8353.                     skin: "umbraco",
  8354.                     plugins: plugins,
  8355.                     valid_elements: validElements,
  8356.                     invalid_elements: invalidElements,
  8357.                     extended_valid_elements: extendedValidElements,
  8358.                     menubar: false,
  8359.                     statusbar: false,
  8360.                     height: editorConfig.dimensions.height,
  8361.                     width: editorConfig.dimensions.width,
  8362.                     maxImageSize: editorConfig.maxImageSize,
  8363.                     toolbar: toolbar,
  8364.                     content_css: stylesheets.join(','),
  8365.                     relative_urls: false,
  8366.                     style_formats: styleFormats
  8367.                 };
  8368.  
  8369.  
  8370.                 if (tinyMceConfig.customConfig) {
  8371.  
  8372.                     //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to
  8373.                     // convert it to json instead of having it as a string since this is what tinymce requires
  8374.                     for (var i in tinyMceConfig.customConfig) {
  8375.                         var val = tinyMceConfig.customConfig[i];
  8376.                         if (val) {
  8377.                             val = val.toString().trim();
  8378.                             if (val.detectIsJson()) {
  8379.                                 try {
  8380.                                     tinyMceConfig.customConfig[i] = JSON.parse(val);
  8381.                                     //now we need to check if this custom config key is defined in our baseline, if it is we don't want to
  8382.                                     //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
  8383.                                     //if it's an object it will overwrite the baseline
  8384.                                     if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
  8385.                                         //concat it and below this concat'd array will overwrite the baseline in angular.extend
  8386.                                         tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
  8387.                                     }
  8388.                                 }
  8389.                                 catch (e) {
  8390.                                     //cannot parse, we'll just leave it
  8391.                                 }
  8392.                             }
  8393.                         }
  8394.                     }
  8395.  
  8396.                     angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
  8397.                 }
  8398.  
  8399.                 //set all the things that user configs should not be able to override
  8400.                 baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace!
  8401.                 baseLineConfigObj.setup = function (editor) {
  8402.  
  8403.                     //set the reference
  8404.                     tinyMceEditor = editor;
  8405.  
  8406.                     //enable browser based spell checking
  8407.                     editor.on('init', function (e) {
  8408.                         editor.getBody().setAttribute('spellcheck', true);
  8409.                     });
  8410.  
  8411.                     //We need to listen on multiple things here because of the nature of tinymce, it doesn't
  8412.                     //fire events when you think!
  8413.                     //The change event doesn't fire when content changes, only when cursor points are changed and undo points
  8414.                     //are created. the blur event doesn't fire if you insert content into the editor with a button and then
  8415.                     //press save.
  8416.                     //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
  8417.                     //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
  8418.                     //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
  8419.                     //save before the timeout elapsed.
  8420.  
  8421.                     //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce
  8422.                     // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers
  8423.                     // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed.
  8424.                     // see: http://issues.umbraco.org/issue/U4-4485
  8425.                     //var alreadyDirty = false;
  8426.                     //editor.on('change', function (e) {
  8427.                     //    angularHelper.safeApply($scope, function () {
  8428.                     //        $scope.model.value = editor.getContent();
  8429.  
  8430.                     //        if (!alreadyDirty) {
  8431.                     //            //make the form dirty manually so that the track changes works, setting our model doesn't trigger
  8432.                     //            // the angular bits because tinymce replaces the textarea.
  8433.                     //            var currForm = angularHelper.getCurrentForm($scope);
  8434.                     //            currForm.$setDirty();
  8435.                     //            alreadyDirty = true;
  8436.                     //        }
  8437.                            
  8438.                     //    });
  8439.                     //});
  8440.  
  8441.                     //when we leave the editor (maybe)
  8442.                     editor.on('blur', function (e) {
  8443.                         editor.save();
  8444.                         angularHelper.safeApply($scope, function () {
  8445.                             $scope.model.value = editor.getContent();
  8446.                         });
  8447.                     });
  8448.  
  8449.                     //when buttons modify content
  8450.                     editor.on('ExecCommand', function (e) {
  8451.                         syncContent(editor);
  8452.                     });
  8453.  
  8454.                     // Update model on keypress
  8455.                     editor.on('KeyUp', function (e) {
  8456.                         syncContent(editor);
  8457.                     });
  8458.  
  8459.                     // Update model on change, i.e. copy/pasted text, plugins altering content
  8460.                     editor.on('SetContent', function (e) {
  8461.                         if (!e.initial) {
  8462.                             syncContent(editor);
  8463.                         }
  8464.                     });
  8465.  
  8466.  
  8467.                     editor.on('ObjectResized', function (e) {
  8468.                         var qs = "?width=" + e.width + "px&height=" + e.height + "px";
  8469.                         var srcAttr = $(e.target).attr("src");
  8470.                         var path = srcAttr.split("?")[0];
  8471.                         $(e.target).attr("data-mce-src", path + qs);
  8472.  
  8473.                         syncContent(editor);
  8474.                     });
  8475.  
  8476.  
  8477.                     //Create the insert media plugin
  8478.                     tinyMceService.createMediaPicker(editor, $scope);
  8479.  
  8480.                     //Create the embedded plugin
  8481.                     tinyMceService.createInsertEmbeddedMedia(editor, $scope);
  8482.  
  8483.                     //Create the insert macro plugin
  8484.                     tinyMceService.createInsertMacro(editor, $scope);
  8485.                 };
  8486.  
  8487.  
  8488.  
  8489.  
  8490.                 /** Loads in the editor */
  8491.                 function loadTinyMce() {
  8492.  
  8493.                     //we need to add a timeout here, to force a redraw so TinyMCE can find
  8494.                     //the elements needed
  8495.                     $timeout(function () {
  8496.                         tinymce.DOM.events.domLoaded = true;
  8497.                         tinymce.init(baseLineConfigObj);
  8498.  
  8499.                         $scope.isLoading = false;
  8500.  
  8501.                     }, 200, false);
  8502.                 }
  8503.  
  8504.  
  8505.  
  8506.  
  8507.                 loadTinyMce();
  8508.  
  8509.                 //here we declare a special method which will be called whenever the value has changed from the server
  8510.                 //this is instead of doing a watch on the model.value = faster
  8511.                 $scope.model.onValueChanged = function (newVal, oldVal) {
  8512.                     //update the display val again if it has changed from the server;
  8513.                     tinyMceEditor.setContent(newVal, { format: 'raw' });
  8514.                     //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
  8515.                     // is required for our plugins listening to this event to execute
  8516.                     tinyMceEditor.fire('LoadContent', null);
  8517.                 };
  8518.  
  8519.                 //listen for formSubmitting event (the result is callback used to remove the event subscription)
  8520.                 var unsubscribe = $scope.$on("formSubmitting", function () {
  8521.                     //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
  8522.                     // we do parse it out on the server side but would be nice to do that on the client side before as well.
  8523.                     $scope.model.value = tinyMceEditor.getContent();
  8524.                 });
  8525.  
  8526.                 //when the element is disposed we need to unsubscribe!
  8527.                 // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
  8528.                 // element might still be there even after the modal has been hidden.
  8529.                 $scope.$on('$destroy', function () {
  8530.                     unsubscribe();
  8531.                 });
  8532.             });
  8533.         });
  8534.  
  8535.     });
  8536.  
  8537. angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
  8538.     function ($scope, $timeout, $log, tinyMceService, stylesheetResource) {
  8539.         var cfg = tinyMceService.defaultPrevalues();
  8540.  
  8541.         if($scope.model.value){
  8542.             if(angular.isString($scope.model.value)){
  8543.                 $scope.model.value = cfg;
  8544.             }
  8545.         }else{
  8546.             $scope.model.value = cfg;
  8547.         }
  8548.  
  8549.         if (!$scope.model.value.stylesheets) {
  8550.             $scope.model.value.stylesheets = [];
  8551.         }
  8552.         if (!$scope.model.value.toolbar) {
  8553.             $scope.model.value.toolbar = [];
  8554.         }
  8555.         if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
  8556.             $scope.model.value.maxImageSize = cfg.maxImageSize;
  8557.         }
  8558.  
  8559.         tinyMceService.configuration().then(function(config){
  8560.             $scope.tinyMceConfig = config;
  8561.  
  8562.         });
  8563.  
  8564.         stylesheetResource.getAll().then(function(stylesheets){
  8565.             $scope.stylesheets = stylesheets;
  8566.         });
  8567.  
  8568.         $scope.selected = function(cmd, alias, lookup){
  8569.             if (lookup && angular.isArray(lookup)) {
  8570.                 cmd.selected = lookup.indexOf(alias) >= 0;
  8571.                 return cmd.selected;
  8572.             }
  8573.             return false;
  8574.         };
  8575.  
  8576.         $scope.selectCommand = function(command){
  8577.             var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand);
  8578.  
  8579.             if(command.selected && index === -1){
  8580.                 $scope.model.value.toolbar.push(command.frontEndCommand);
  8581.             }else if(index >= 0){
  8582.                 $scope.model.value.toolbar.splice(index, 1);
  8583.             }
  8584.         };
  8585.  
  8586.         $scope.selectStylesheet = function (css) {
  8587.            
  8588.             var index = $scope.model.value.stylesheets.indexOf(css.name);
  8589.  
  8590.             if(css.selected && index === -1){
  8591.                 $scope.model.value.stylesheets.push(css.name);
  8592.             }else if(index >= 0){
  8593.                 $scope.model.value.stylesheets.splice(index, 1);
  8594.             }
  8595.         };
  8596.  
  8597.         var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  8598.  
  8599.             var commands = _.where($scope.tinyMceConfig.commands, {selected: true});
  8600.             $scope.model.value.toolbar = _.pluck(commands, "frontEndCommand");
  8601.            
  8602.         });
  8603.  
  8604.         //when the scope is destroyed we need to unsubscribe
  8605.         $scope.$on('$destroy', function () {
  8606.             unsubscribe();
  8607.         });
  8608.  
  8609.     });
  8610.  
  8611. function sliderController($scope, $log, $element, assetsService, angularHelper) {
  8612.  
  8613.     //configure some defaults
  8614.     if (!$scope.model.config.orientation) {
  8615.         $scope.model.config.orientation = "horizontal";
  8616.     }
  8617.     if (!$scope.model.config.initVal1) {
  8618.         $scope.model.config.initVal1 = 0;
  8619.     }
  8620.     else {
  8621.         $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1);
  8622.     }
  8623.     if (!$scope.model.config.initVal2) {
  8624.         $scope.model.config.initVal2 = 0;
  8625.     }
  8626.     else {
  8627.         $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2);
  8628.     }
  8629.     if (!$scope.model.config.minVal) {
  8630.         $scope.model.config.minVal = 0;
  8631.     }
  8632.     else {
  8633.         $scope.model.config.minVal = parseFloat($scope.model.config.minVal);
  8634.     }
  8635.     if (!$scope.model.config.maxVal) {
  8636.         $scope.model.config.maxVal = 100;
  8637.     }
  8638.     else {
  8639.         $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal);
  8640.     }
  8641.     if (!$scope.model.config.step) {
  8642.         $scope.model.config.step = 1;
  8643.     }
  8644.     else {
  8645.         $scope.model.config.step = parseFloat($scope.model.config.step);
  8646.     }
  8647.    
  8648.     /** This creates the slider with the model values - it's called on startup and if the model value changes */
  8649.     function createSlider() {
  8650.  
  8651.         //the value that we'll give the slider - if it's a range, we store our value as a comma seperated val but this slider expects an array
  8652.         var sliderVal = null;
  8653.  
  8654.         //configure the model value based on if range is enabled or not
  8655.         if ($scope.model.config.enableRange === "1") {
  8656.             //If no value saved yet - then use default value
  8657.             if (!$scope.model.value) {
  8658.                 var i1 = parseFloat($scope.model.config.initVal1);
  8659.                 var i2 = parseFloat($scope.model.config.initVal2);
  8660.                 sliderVal = [
  8661.                     isNaN(i1) ? $scope.model.config.minVal : (i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal),
  8662.                     isNaN(i2) ? $scope.model.config.maxVal : (i2 > i1 ? (i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal) : $scope.model.config.maxVal)
  8663.                 ];
  8664.             }
  8665.             else {
  8666.                 //this will mean it's a delimited value stored in the db, convert it to an array
  8667.                 sliderVal = _.map($scope.model.value.split(','), function (item) {
  8668.                     return parseFloat(item);
  8669.                 });
  8670.             }
  8671.         }
  8672.         else {
  8673.             //If no value saved yet - then use default value
  8674.             if ($scope.model.value) {
  8675.                 sliderVal = parseFloat($scope.model.value);
  8676.             }
  8677.             else {
  8678.                 sliderVal = $scope.model.config.initVal1;
  8679.             }
  8680.         }
  8681.  
  8682.         // Initialise model value if not set
  8683.         if (!$scope.model.value) {
  8684.             setModelValueFromSlider(sliderVal);
  8685.         }
  8686.  
  8687.         //initiate slider, add event handler and get the instance reference (stored in data)
  8688.         var slider = $element.find('.slider-item').slider({
  8689.             max: $scope.model.config.maxVal,
  8690.             min: $scope.model.config.minVal,
  8691.             orientation: $scope.model.config.orientation,
  8692.             selection: "after",
  8693.             step: $scope.model.config.step,
  8694.             tooltip: "show",
  8695.             //set the slider val - we cannot do this with data- attributes when using ranges
  8696.             value: sliderVal
  8697.         }).on('slideStop', function () {
  8698.             angularHelper.safeApply($scope, function () {
  8699.                 setModelValueFromSlider(slider.getValue());
  8700.             });
  8701.         }).data('slider');
  8702.     }
  8703.  
  8704.     /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates
  8705.         the model with the currently selected slider value(s) **/
  8706.     function setModelValueFromSlider(sliderVal) {
  8707.         //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value
  8708.         if ($scope.model.config.enableRange === "1") {
  8709.             $scope.model.value = sliderVal.join(",");
  8710.         }
  8711.         else {
  8712.             $scope.model.value = sliderVal.toString();
  8713.         }
  8714.     }
  8715.  
  8716.     //tell the assetsService to load the bootstrap slider
  8717.     //libs from the plugin folder
  8718.     assetsService
  8719.         .loadJs("lib/slider/js/bootstrap-slider.js")
  8720.         .then(function () {
  8721.  
  8722.             createSlider();
  8723.            
  8724.             //here we declare a special method which will be called whenever the value has changed from the server
  8725.             //this is instead of doing a watch on the model.value = faster
  8726.             $scope.model.onValueChanged = function (newVal, oldVal) {                
  8727.                 if (newVal != oldVal) {
  8728.                     createSlider();
  8729.                 }
  8730.             };
  8731.  
  8732.         });
  8733.  
  8734.     //load the seperate css for the editor to avoid it blocking our js loading
  8735.     assetsService.loadCss("lib/slider/slider.css");
  8736.  
  8737. }
  8738. angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController);
  8739. angular.module("umbraco")
  8740. .controller("Umbraco.PropertyEditors.TagsController",
  8741.     function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
  8742.  
  8743.         var $typeahead;
  8744.  
  8745.         $scope.isLoading = true;
  8746.         $scope.tagToAdd = "";
  8747.  
  8748.         assetsService.loadJs("lib/typeahead-js/typeahead.bundle.min.js").then(function () {
  8749.  
  8750.             $scope.isLoading = false;
  8751.  
  8752.             //load current value
  8753.  
  8754.             if ($scope.model.value) {
  8755.                 if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
  8756.                     //it is csv
  8757.                     if (!$scope.model.value) {
  8758.                         $scope.model.value = [];
  8759.                     }
  8760.                     else {
  8761.                         $scope.model.value = $scope.model.value.split(",");
  8762.                     }
  8763.                 }
  8764.             }
  8765.             else {
  8766.                 $scope.model.value = [];
  8767.             }
  8768.  
  8769.             // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
  8770.             $scope.validateMandatory = function () {
  8771.                 return {
  8772.                     isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0),
  8773.                     errorMsg: "Value cannot be empty",
  8774.                     errorKey: "required"
  8775.                 };
  8776.             }
  8777.  
  8778.             //Helper method to add a tag on enter or on typeahead select
  8779.             function addTag(tagToAdd) {
  8780.                 if (tagToAdd != null && tagToAdd.length > 0) {
  8781.                     if ($scope.model.value.indexOf(tagToAdd) < 0) {
  8782.                         $scope.model.value.push(tagToAdd);
  8783.                         //this is required to re-validate
  8784.                         $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
  8785.                     }
  8786.                 }
  8787.             }
  8788.  
  8789.             $scope.addTagOnEnter = function (e) {
  8790.                 var code = e.keyCode || e.which;
  8791.                 if (code == 13) { //Enter keycode  
  8792.                     if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) {
  8793.                         //this is required, otherwise the html form will attempt to submit.
  8794.                         e.preventDefault();
  8795.                         $scope.addTag();
  8796.                     }
  8797.                 }
  8798.             };
  8799.  
  8800.             $scope.addTag = function () {
  8801.                 //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
  8802.                 //we need to use jquery because typeahead duplicates the text box
  8803.                 addTag($scope.tagToAdd);
  8804.                 $scope.tagToAdd = "";
  8805.                 //this clears the value stored in typeahead so it doesn't try to add the text again
  8806.                 // http://issues.umbraco.org/issue/U4-4947
  8807.                 $typeahead.typeahead('val', '');
  8808.             };
  8809.  
  8810.  
  8811.  
  8812.             $scope.removeTag = function (tag) {
  8813.                 var i = $scope.model.value.indexOf(tag);
  8814.                 if (i >= 0) {
  8815.                     $scope.model.value.splice(i, 1);
  8816.                     //this is required to re-validate
  8817.                     $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
  8818.                 }
  8819.             };
  8820.  
  8821.             //vice versa
  8822.             $scope.model.onValueChanged = function (newVal, oldVal) {
  8823.                 //update the display val again if it has changed from the server
  8824.                 $scope.model.value = newVal;
  8825.  
  8826.                 if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
  8827.                     //it is csv
  8828.                     if (!$scope.model.value) {
  8829.                         $scope.model.value = [];
  8830.                     }
  8831.                     else {
  8832.                         $scope.model.value = $scope.model.value.split(",");
  8833.                     }
  8834.                 }
  8835.             };
  8836.  
  8837.             //configure the tags data source
  8838.  
  8839.             //helper method to format the data for bloodhound
  8840.             function dataTransform(list) {
  8841.                 //transform the result to what bloodhound wants
  8842.                 var tagList = _.map(list, function (i) {
  8843.                     return { value: i.text };
  8844.                 });
  8845.                 // remove current tags from the list
  8846.                 return $.grep(tagList, function (tag) {
  8847.                     return ($.inArray(tag.value, $scope.model.value) === -1);
  8848.                 });
  8849.             }
  8850.  
  8851.             // helper method to remove current tags
  8852.             function removeCurrentTagsFromSuggestions(suggestions) {
  8853.                 return $.grep(suggestions, function (suggestion) {
  8854.                     return ($.inArray(suggestion.value, $scope.model.value) === -1);
  8855.                 });
  8856.             }
  8857.  
  8858.             var tagsHound = new Bloodhound({
  8859.                 datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
  8860.                 queryTokenizer: Bloodhound.tokenizers.whitespace,
  8861.                 dupDetector : function(remoteMatch, localMatch) {
  8862.                     return (remoteMatch["value"] == localMatch["value"]);
  8863.                 },
  8864.                 //pre-fetch the tags for this category
  8865.                 prefetch: {
  8866.                     url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
  8867.                     //TTL = 5 minutes
  8868.                     ttl: 300000,
  8869.                     filter: dataTransform
  8870.                 },
  8871.                 //dynamically get the tags for this category (they may have changed on the server)
  8872.                 remote: {
  8873.                     url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
  8874.                     filter: dataTransform
  8875.                 }
  8876.             });
  8877.  
  8878.             tagsHound.initialize(true);
  8879.  
  8880.             //configure the type ahead
  8881.             $timeout(function () {
  8882.  
  8883.                 $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead(
  8884.                 {
  8885.                     //This causes some strangeness as it duplicates the textbox, best leave off for now.
  8886.                     hint: false,
  8887.                     highlight: true,
  8888.                     cacheKey: new Date(),  // Force a cache refresh each time the control is initialized
  8889.                     minLength: 1
  8890.                 }, {
  8891.                     //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
  8892.                     // name = the data set name, we'll make this the tag group name
  8893.                     name: $scope.model.config.group,
  8894.                     displayKey: "value",
  8895.                     source: function (query, cb) {
  8896.                         tagsHound.get(query, function (suggestions) {
  8897.                             cb(removeCurrentTagsFromSuggestions(suggestions));
  8898.                         });
  8899.                     },
  8900.                 }).bind("typeahead:selected", function (obj, datum, name) {
  8901.                     angularHelper.safeApply($scope, function () {
  8902.                         addTag(datum["value"]);
  8903.                         $scope.tagToAdd = "";
  8904.                         // clear the typed text
  8905.                         $typeahead.typeahead('val', '');
  8906.                     });
  8907.  
  8908.                 }).bind("typeahead:autocompleted", function (obj, datum, name) {
  8909.                     angularHelper.safeApply($scope, function () {
  8910.                         addTag(datum["value"]);
  8911.                         $scope.tagToAdd = "";
  8912.                     });
  8913.  
  8914.                 }).bind("typeahead:opened", function (obj) {
  8915.                     //console.log("opened ");
  8916.                 });
  8917.             });
  8918.  
  8919.             $scope.$on('$destroy', function () {
  8920.                 tagsHound.clearPrefetchCache();
  8921.                 tagsHound.clearRemoteCache();
  8922.                 $element.find('.tags-' + $scope.model.alias).typeahead('destroy');
  8923.                 delete tagsHound;
  8924.             });
  8925.  
  8926.         });
  8927.  
  8928.     }
  8929. );
  8930. //this controller simply tells the dialogs service to open a mediaPicker window
  8931. //with a specified callback, this callback will receive an object with a selection on it
  8932. angular.module('umbraco').controller("Umbraco.PropertyEditors.EmbeddedContentController",
  8933.     function($rootScope, $scope, $log){
  8934.    
  8935.     $scope.showForm = false;
  8936.     $scope.fakeData = [];
  8937.  
  8938.     $scope.create = function(){
  8939.         $scope.showForm = true;
  8940.         $scope.fakeData = angular.copy($scope.model.config.fields);
  8941.     };
  8942.  
  8943.     $scope.show = function(){
  8944.         $scope.showCode = true;
  8945.     };
  8946.  
  8947.     $scope.add = function(){
  8948.         $scope.showForm = false;
  8949.         if ( !($scope.model.value instanceof Array)) {
  8950.             $scope.model.value = [];
  8951.         }
  8952.  
  8953.         $scope.model.value.push(angular.copy($scope.fakeData));
  8954.         $scope.fakeData = [];
  8955.     };
  8956. });
  8957. angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController",
  8958.     function($rootScope, $scope, $filter) {
  8959.  
  8960.         function formatDisplayValue() {
  8961.             $scope.renderModel = _.map($scope.model.value.split(","), function (item) {
  8962.                 return {
  8963.                     url: item,
  8964.                     urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank"
  8965.                 };
  8966.             });
  8967.         }
  8968.  
  8969.         $scope.getUrl = function(valueUrl) {
  8970.             if (valueUrl.indexOf("/") >= 0) {
  8971.                 return valueUrl;
  8972.             }
  8973.             return "#";
  8974.         };
  8975.  
  8976.         formatDisplayValue();
  8977.        
  8978.         //here we declare a special method which will be called whenever the value has changed from the server
  8979.         //this is instead of doing a watch on the model.value = faster
  8980.         $scope.model.onValueChanged = function(newVal, oldVal) {
  8981.             //update the display val again
  8982.             formatDisplayValue();
  8983.         };
  8984.  
  8985.     });
  8986.  
  8987. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement