(function () {
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.MainController
 * @function
 *
 * @description
 * The main application controller
 *
 */
    function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) {
        //the null is important because we do an explicit bool check on this in the view
        $scope.authenticated = null;
        $scope.touchDevice = appState.getGlobalState('touchDevice');
        $scope.infiniteMode = false;
        $scope.overlay = {};
        $scope.drawer = {};
        $scope.search = {};
        $scope.login = {};
        $scope.tabbingActive = false;
        // Load TinyMCE assets ahead of time in the background for the user
        // To help with first load of the RTE
        tinyMceAssets.forEach(function (tinyJsAsset) {
            assetsService.loadJs(tinyJsAsset, $scope);
        });
        // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution.
        // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
        function handleFirstTab(evt) {
            if (evt.keyCode === 9) {
                enableTabbingActive();
            }
        }
        function enableTabbingActive() {
            $scope.tabbingActive = true;
            $scope.$digest();
            window.addEventListener('mousedown', disableTabbingActive);
            window.removeEventListener('keydown', handleFirstTab);
        }
        function disableTabbingActive(evt) {
            $scope.tabbingActive = false;
            $scope.$digest();
            window.removeEventListener('mousedown', disableTabbingActive);
            window.addEventListener('keydown', handleFirstTab);
        }
        window.addEventListener('keydown', handleFirstTab);
        $scope.$on('showFocusOutline', function () {
            $scope.tabbingActive = true;
            window.addEventListener('mousedown', disableTabbingActive);
            window.removeEventListener('keydown', handleFirstTab);
        });
        $scope.removeNotification = function (index) {
            notificationsService.remove(index);
        };
        $scope.closeSearch = function () {
            appState.setSearchState('show', false);
        };
        $scope.showLoginScreen = function (isTimedOut) {
            $scope.login.pageTitle = $scope.$root.locationTitle;
            $scope.login.isTimedOut = isTimedOut;
            $scope.login.show = true;
        };
        $scope.hideLoginScreen = function () {
            $scope.$root.locationTitle = $scope.login.pageTitle;
            $scope.login.show = false;
        };
        var evts = [];
        //when a user logs out or timesout
        evts.push(eventsService.on('app.notAuthenticated', function (evt, data) {
            $scope.authenticated = null;
            $scope.user = null;
            var isTimedOut = data && data.isTimedOut ? true : false;
            $scope.showLoginScreen(isTimedOut);
            // Remove the localstorage items for tours shown
            // Means that when next logged in they can be re-shown if not already dismissed etc
            localStorageService.remove('emailMarketingTourShown');
            localStorageService.remove('introTourShown');
        }));
        evts.push(eventsService.on('app.userRefresh', function (evt) {
            userService.refreshCurrentUser().then(function (data) {
                $scope.user = data;
                //Load locale file
                if ($scope.user.locale) {
                    tmhDynamicLocale.set($scope.user.locale);
                }
            });
        }));
        //when the app is ready/user is logged in, setup the data
        evts.push(eventsService.on('app.ready', function (evt, data) {
            $scope.authenticated = data.authenticated;
            $scope.user = data.user;
            updateChecker.check().then(function (update) {
                if (update && update !== 'null') {
                    if (update.type !== 'None') {
                        var notification = {
                            headline: 'Update available',
                            message: 'Click to download',
                            sticky: true,
                            type: 'info',
                            url: update.url,
                            target: '_blank'
                        };
                        notificationsService.add(notification);
                    }
                }
            });
            //if the user has changed we need to redirect to the root so they don't try to continue editing the
            //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)
            if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
                var section = appState.getSectionState('currentSection');
                if (section) {
                    //if there's a section already assigned, reload it so the tree is cleared
                    navigationService.reloadSection(section);
                }
                $location.path('/').search('');
                historyService.removeAll();
                treeService.clearCache();
                editorService.closeAll();
                overlayService.close();
                //if the user changed, clearout local storage too - could contain sensitive data
                localStorageService.clearAll();
            }
            //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data
            if (data.loginType === 'credentials') {
                localStorageService.clearAll();
            }
            //Load locale file
            if ($scope.user.locale) {
                tmhDynamicLocale.set($scope.user.locale);
            }
        }));
        // events for search
        evts.push(eventsService.on('appState.searchState.changed', function (e, args) {
            if (args.key === 'show') {
                $scope.search.show = args.value;
            }
        }));
        // events for drawer
        // manage the help dialog by subscribing to the showHelp appState
        evts.push(eventsService.on('appState.drawerState.changed', function (e, args) {
            // set view
            if (args.key === 'view') {
                $scope.drawer.view = args.value;
            }
            // set custom model
            if (args.key === 'model') {
                $scope.drawer.model = args.value;
            }
            // show / hide drawer
            if (args.key === 'showDrawer') {
                $scope.drawer.show = args.value;
            }
        }));
        // events for overlays
        evts.push(eventsService.on('appState.overlay', function (name, args) {
            $scope.overlay = args;
        }));
        // events for tours
        evts.push(eventsService.on('appState.tour.start', function (name, args) {
            $scope.tour = args;
            $scope.tour.show = true;
        }));
        evts.push(eventsService.on('appState.tour.end', function () {
            $scope.tour = null;
        }));
        evts.push(eventsService.on('appState.tour.complete', function () {
            $scope.tour = null;
        }));
        // events for backdrop
        evts.push(eventsService.on('appState.backdrop', function (name, args) {
            $scope.backdrop = args;
        }));
        // event for infinite editors
        evts.push(eventsService.on('appState.editors.open', function (name, args) {
            $scope.infiniteMode = args && args.editors.length > 0 ? true : false;
        }));
        evts.push(eventsService.on('appState.editors.close', function (name, args) {
            $scope.infiniteMode = args && args.editors.length > 0 ? true : false;
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    //register it
    angular.module('umbraco').controller('Umbraco.MainController', MainController).config(function (tmhDynamicLocaleProvider) {
        //Set url for locale files
        tmhDynamicLocaleProvider.localeLocationPattern('lib/angular-i18n/angular-locale_{{locale}}.js');
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.NavigationController
 * @function
 *
 * @description
 * Handles the section area of the app
 *
 * @param {navigationService} navigationService A reference to the navigationService
 */
    function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) {
        //this is used to trigger the tree to start loading once everything is ready
        var treeInitPromise = $q.defer();
        $scope.treeApi = {};
        //Bind to the main tree events
        $scope.onTreeInit = function () {
            $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
            //when a tree is loaded into a section, we need to put it into appState
            $scope.treeApi.callbacks.treeLoaded(function (args) {
                appState.setTreeState('currentRootNode', args.tree);
            });
            //when a tree node is synced this event will fire, this allows us to set the currentNode
            $scope.treeApi.callbacks.treeSynced(function (args) {
                if (args.activate === undefined || args.activate === true) {
                    //set the current selected node
                    appState.setTreeState('selectedNode', args.node);    //when a node is activated, this is the same as clicking it and we need to set the
                                                                         //current menu item to be this node as well.
                                                                         //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog.
                }
            });
            //this reacts to the options item in the tree
            $scope.treeApi.callbacks.treeOptionsClick(function (args) {
                args.event.stopPropagation();
                args.event.preventDefault();
                //Set the current action node (this is not the same as the current selected node!)
                //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog.
                if (args.event && args.event.altKey) {
                    args.skipDefault = true;
                }
                navigationService.showMenu(args);
            });
            $scope.treeApi.callbacks.treeNodeAltSelect(function (args) {
                args.event.stopPropagation();
                args.event.preventDefault();
                args.skipDefault = true;
                navigationService.showMenu(args);
            });
            //this reacts to tree items themselves being clicked
            //the tree directive should not contain any handling, simply just bubble events
            $scope.treeApi.callbacks.treeNodeSelect(function (args) {
                var n = args.node;
                args.event.stopPropagation();
                args.event.preventDefault();
                if (n.metaData && n.metaData['jsClickCallback'] && Utilities.isString(n.metaData['jsClickCallback']) && n.metaData['jsClickCallback'] !== '') {
                    //this is a legacy tree node!
                    var jsPrefix = 'javascript:';
                    var js;
                    if (n.metaData['jsClickCallback'].startsWith(jsPrefix)) {
                        js = n.metaData['jsClickCallback'].substr(jsPrefix.length);
                    } else {
                        js = n.metaData['jsClickCallback'];
                    }
                    try {
                        var func = eval(js);
                        //this is normally not necessary since the eval above should execute the method and will return nothing.
                        if (func != null && typeof func === 'function') {
                            func.call();
                        }
                    } catch (ex) {
                        $log.error('Error evaluating js callback from legacy tree node: ' + ex);
                    }
                } else if (n.routePath) {
                    //add action to the history service
                    historyService.add({
                        name: n.name,
                        link: n.routePath,
                        icon: n.icon
                    });
                    //put this node into the tree state
                    appState.setTreeState('selectedNode', args.node);
                    //when a node is clicked we also need to set the active menu node to this node
                    //appState.setMenuState("currentNode", args.node);
                    //not legacy, lets just set the route value and clear the query string if there is one.
                    $location.path(n.routePath);
                    navigationService.clearSearch();
                } else if (n.section) {
                    $location.path(n.section);
                    navigationService.clearSearch();
                }
                navigationService.hideNavigation();
            });
            return treeInitPromise.promise;
        };
        //set up our scope vars
        $scope.showContextMenuDialog = false;
        $scope.showContextMenu = false;
        $scope.showSearchResults = false;
        $scope.menuDialogTitle = null;
        $scope.menuActions = [];
        $scope.menuNode = null;
        $scope.languages = [];
        $scope.selectedLanguage = {};
        $scope.page = {};
        $scope.page.languageSelectorIsOpen = false;
        $scope.currentSection = null;
        $scope.customTreeParams = null;
        $scope.treeCacheKey = '_';
        $scope.showNavigation = appState.getGlobalState('showNavigation');
        // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths
        var expandedPaths = [];
        //trigger search with a hotkey:
        keyboardService.bind('ctrl+shift+s', function () {
            navigationService.showSearch();
        });
        //// TODO: remove this it's not a thing
        //$scope.selectedId = navigationService.currentId;
        var isInit = false;
        var evts = [];
        //Listen for global state changes
        evts.push(eventsService.on('appState.globalState.changed', function (e, args) {
            if (args.key === 'showNavigation') {
                $scope.showNavigation = args.value;
            }
        }));
        //Listen for menu state changes
        evts.push(eventsService.on('appState.menuState.changed', function (e, args) {
            if (args.key === 'showMenuDialog') {
                $scope.showContextMenuDialog = args.value;
            }
            if (args.key === 'dialogTemplateUrl') {
                $scope.dialogTemplateUrl = args.value;
            }
            if (args.key === 'showMenu') {
                $scope.showContextMenu = args.value;
            }
            if (args.key === 'dialogTitle') {
                $scope.menuDialogTitle = args.value;
            }
            if (args.key === 'menuActions') {
                $scope.menuActions = args.value;
            }
            if (args.key === 'currentNode') {
                $scope.menuNode = args.value;
            }
        }));
        //Listen for tree state changes
        evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
            if (args.key === 'currentRootNode') {
                //if the changed state is the currentRootNode, determine if this is a full screen app
                if (args.value.root && args.value.root.containsTrees === false) {
                    $rootScope.emptySection = true;
                } else {
                    $rootScope.emptySection = false;
                }
            }
        }));
        //Listen for section state changes
        evts.push(eventsService.on('appState.sectionState.changed', function (e, args) {
            //section changed
            if (args.key === 'currentSection' && $scope.currentSection != args.value) {
                //before loading the main tree we need to ensure that the nav is ready
                navigationService.waitForNavReady().then(function () {
                    $scope.currentSection = args.value;
                    //load the tree
                    configureTreeAndLanguages();
                    $scope.treeApi.load({
                        section: $scope.currentSection,
                        customTreeParams: $scope.customTreeParams,
                        cacheKey: $scope.treeCacheKey
                    });
                });
            }
            //show/hide search results
            if (args.key === 'showSearchResults') {
                $scope.showSearchResults = args.value;
            }
        }));
        // Listen for language updates
        evts.push(eventsService.on('editors.languages.languageDeleted', function (e, args) {
            loadLanguages().then(function (languages) {
                $scope.languages = languages;
                var defaultCulture = $scope.languages[0].culture;
                if (args.language.culture === $scope.selectedLanguage.culture) {
                    $scope.selectedLanguage = defaultCulture;
                    if ($scope.languages.length > 1) {
                        $location.search('mculture', defaultCulture);
                    } else {
                        $location.search('mculture', null);
                    }
                    var currentEditorState = editorState.getCurrent();
                    if (currentEditorState && currentEditorState.path) {
                        $scope.treeApi.syncTree({
                            path: currentEditorState.path,
                            activate: true
                        });
                    }
                }
            });
        }));
        //Emitted when a language is created or an existing one saved/edited
        evts.push(eventsService.on('editors.languages.languageSaved', function (e, args) {
            if (args.isNew) {
                //A new language has been created - reload languages for tree
                loadLanguages().then(function (languages) {
                    $scope.languages = languages;
                });
            } else if (args.language.isDefault) {
                //A language was saved and was set to be the new default (refresh the tree, so its at the top)
                loadLanguages().then(function (languages) {
                    $scope.languages = languages;
                });
            }
        }));
        //when a user logs out or timesout
        evts.push(eventsService.on('app.notAuthenticated', function () {
            $scope.authenticated = false;
        }));
        //when the application is ready and the user is authorized, setup the data
        //this will occur anytime a new user logs in!
        evts.push(eventsService.on('app.ready', function (evt, data) {
            $scope.authenticated = true;
            ensureInit();
            ensureMainCulture();
        }));
        // event for infinite editors
        evts.push(eventsService.on('appState.editors.open', function (name, args) {
            $scope.infiniteMode = args && args.editors.length > 0 ? true : false;
        }));
        evts.push(eventsService.on('appState.editors.close', function (name, args) {
            $scope.infiniteMode = args && args.editors.length > 0 ? true : false;
        }));
        evts.push(eventsService.on('treeService.removeNode', function (e, args) {
            //check to see if the current page has been removed
            var currentEditorState = editorState.getCurrent();
            if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) {
                //current page is loaded, so navigate to root
                var section = appState.getSectionState('currentSection');
                $location.path('/' + section);
            }
        }));
        /**
   * For multi language sites, this ensures that mculture is set to either the last selected language or the default one
   */
        function ensureMainCulture() {
            if ($location.search().mculture) {
                return;
            }
            var language = lastLanguageOrDefault();
            if (!language) {
                return;
            }
            // trigger a language selection in the next digest cycle
            $timeout(function () {
                $scope.selectLanguage(language);
            });
        }
        /**
   * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down
   */
        function configureTreeAndLanguages() {
            //create the custom query string param for this tree, this is currently only relevant for content
            if ($scope.currentSection === 'content') {
                //must use $location here because $routeParams isn't available until after the route change
                var mainCulture = $location.search().mculture;
                //select the current language if set in the query string
                if (mainCulture && $scope.languages && $scope.languages.length > 1) {
                    var found = _.find($scope.languages, function (l) {
                        if (mainCulture === true) {
                            return false;
                        }
                        return l.culture.toLowerCase() === mainCulture.toLowerCase();
                    });
                    if (found) {
                        //set the route param
                        found.active = true;
                        $scope.selectedLanguage = found;
                    }
                }
                var queryParams = {};
                if ($scope.selectedLanguage && $scope.selectedLanguage.culture) {
                    queryParams['culture'] = $scope.selectedLanguage.culture;
                    if (!mainCulture) {
                        $location.search('mculture', $scope.selectedLanguage.culture);
                    }
                }
                var queryString = $.param(queryParams);    //create the query string from the params object
            }
            if (queryString) {
                $scope.customTreeParams = queryString;
                $scope.treeCacheKey = queryString;    // this tree uses caching but we need to change it's cache key per lang
            } else {
                $scope.treeCacheKey = '_';    // this tree uses caching, there's no lang selected so use the default
            }
        }
        /**
   * Called when the app is ready and sets up the navigation (should only be called once)
   */
        function ensureInit() {
            //only run once ever!
            if (isInit) {
                return;
            }
            isInit = true;
            var navInit = false;
            //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view,
            //* we listen for the first route change with a section to setup the navigation.
            //* we listen for all route changes to track the current section.
            $rootScope.$on('$routeChangeSuccess', function () {
                //only continue if there's a section available
                if ($routeParams.section) {
                    if (!navInit) {
                        navInit = true;
                        initNav();
                    }
                    //keep track of the current section when it changes
                    if ($scope.currentSection != $routeParams.section) {
                        appState.setSectionState('currentSection', $routeParams.section);
                    }
                }
            });
        }
        /**
   * This loads the language data, if the are no variant content types configured this will return no languages
   */
        function loadLanguages() {
            return contentResource.allowsCultureVariation().then(function (b) {
                if (b === true) {
                    return languageResource.getAll();
                } else {
                    return $q.when([]);    //resolve an empty collection
                }
            });
        }
        /**
   * Called once during init to initialize the navigation/tree/languages
   */
        function initNav() {
            // load languages
            loadLanguages().then(function (languages) {
                $scope.languages = languages;
                if ($scope.languages.length > 1) {
                    //if there's already one set, check if it exists
                    var language = null;
                    var mainCulture = $location.search().mculture;
                    if (mainCulture) {
                        language = _.find($scope.languages, function (l) {
                            return l.culture.toLowerCase() === mainCulture.toLowerCase();
                        });
                    }
                    if (!language) {
                        language = lastLanguageOrDefault();
                        if (language) {
                            $location.search('mculture', language.culture);
                        }
                    }
                }
                $scope.currentSection = $routeParams.section;
                configureTreeAndLanguages();
                //resolve the tree promise, set it's property values for loading the tree which will make the tree load
                treeInitPromise.resolve({
                    section: $scope.currentSection,
                    customTreeParams: $scope.customTreeParams,
                    cacheKey: $scope.treeCacheKey,
                    //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else
                    //like normal JS promises we could do resolve(...).then()
                    onLoaded: function onLoaded() {
                        //the nav is ready, let the app know
                        eventsService.emit('app.navigationReady', { treeApi: $scope.treeApi });
                    }
                });
            });
        }
        function lastLanguageOrDefault() {
            if (!$scope.languages || $scope.languages.length <= 1) {
                return null;
            }
            // see if we can find a culture in the cookie set when changing language
            var lastCulture = $cookies.get('UMB_MCULTURE');
            var language = lastCulture ? _.find($scope.languages, function (l) {
                return l.culture.toLowerCase() === lastCulture.toLowerCase();
            }) : null;
            if (!language) {
                // no luck, look for the default language
                language = _.find($scope.languages, function (l) {
                    return l.isDefault;
                });
            }
            return language;
        }
        function nodeExpandedHandler(args) {
            //store the reference to the expanded node path
            if (args.node) {
                treeService._trackExpandedPaths(args.node, expandedPaths);
            }
        }
        $scope.selectLanguage = function (language) {
            $location.search('mculture', language.culture);
            // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year)
            var expireDate = new Date();
            expireDate.setDate(expireDate.getDate() + 365);
            $cookies.put('UMB_MCULTURE', language.culture, {
                path: '/',
                expires: expireDate
            });
            // close the language selector
            $scope.page.languageSelectorIsOpen = false;
            configureTreeAndLanguages();
            //re-bind language to the query string and update the tree params
            //reload the tree with it's updated querystring args
            $scope.treeApi.load({
                section: $scope.currentSection,
                customTreeParams: $scope.customTreeParams,
                cacheKey: $scope.treeCacheKey
            }).then(function () {
                //re-sync to currently edited node
                var currNode = appState.getTreeState('selectedNode');
                //create the list of promises
                var promises = [];
                //starting with syncing to the currently selected node if there is one
                if (currNode) {
                    var path = treeService.getPath(currNode);
                    promises.push($scope.treeApi.syncTree({
                        path: path,
                        activate: true
                    }));
                }
                // TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding
                //for (var i = 0; i < expandedPaths.length; i++) {
                //    promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true }));
                //}
                //execute them sequentially
                // set selected language to active
                Utilities.forEach($scope.languages, function (language) {
                    language.active = false;
                });
                language.active = true;
                angularHelper.executeSequentialPromises(promises);
            });
        };
        //this reacts to the options item in the tree
        // TODO: migrate to nav service
        // TODO: is this used?
        $scope.searchShowMenu = function (ev, args) {
            //always skip default
            args.skipDefault = true;
            navigationService.showMenu(args);
        };
        // TODO: migrate to nav service
        // TODO: is this used?
        $scope.searchHide = function () {
            navigationService.hideSearch();
        };
        //the below assists with hiding/showing the tree
        var treeActive = false;
        //Sets a service variable as soon as the user hovers the navigation with the mouse
        //used by the leaveTree method to delay hiding
        $scope.enterTree = function (event) {
            treeActive = true;
        };
        // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
        $scope.leaveTree = function (event) {
            //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down
            if (!event) {
                return;
            }
            closeTree();
        };
        $scope.onOutsideClick = function () {
            closeTree();
        };
        function closeTree() {
            if (!appState.getGlobalState('touchDevice')) {
                treeActive = false;
                $timeout(function () {
                    if (!treeActive) {
                        navigationService.hideTree();
                    }
                }, 300);
            }
        }
        $scope.toggleLanguageSelector = function () {
            $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen;
        };
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    //register it
    angular.module('umbraco').controller('Umbraco.NavigationController', NavigationController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.MainController
 * @function
 * 
 * @description
 * The controller for the AuthorizeUpgrade login page
 * 
 */
    function AuthorizeUpgradeController($scope, $window) {
        $scope.loginAndRedirect = function (event) {
            var qry = $window.location.search.trimStart('?').split('&');
            var redir = _.find(qry, function (item) {
                return item.startsWith('redir=');
            });
            if (redir) {
                $window.location = decodeURIComponent(redir.split('=')[1]);
            } else {
                $window.location = '/';
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.AuthorizeUpgradeController', AuthorizeUpgradeController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.DashboardController
 * @function
 * 
 * @description
 * Controls the dashboards of the application
 * 
 */
    function DashboardController($scope, $q, $routeParams, $location, dashboardResource, localizationService) {
        var DASHBOARD_QUERY_PARAM = 'dashboard';
        $scope.page = {};
        $scope.page.nameLocked = true;
        $scope.page.loading = true;
        $scope.dashboard = {};
        var promises = [];
        promises.push(localizationService.localize('sections_' + $routeParams.section).then(function (name) {
            $scope.dashboard.name = name;
        }));
        promises.push(dashboardResource.getDashboard($routeParams.section).then(function (tabs) {
            $scope.dashboard.tabs = tabs;
            if ($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) {
                initActiveTab();
            }
        }));
        $q.all(promises).then(function () {
            $scope.page.loading = false;
        });
        $scope.changeTab = function (tab) {
            if ($scope.dashboard.tabs && $scope.dashboard.tabs.length > 0) {
                $scope.dashboard.tabs.forEach(function (tab) {
                    tab.active = false;
                });
            }
            tab.active = true;
            $location.search(DASHBOARD_QUERY_PARAM, tab.alias);
        };
        function initActiveTab() {
            // Check the query parameter for a dashboard alias
            var dashboardAlias = $location.search()[DASHBOARD_QUERY_PARAM];
            var dashboardIndex = $scope.dashboard.tabs.findIndex(function (tab) {
                return tab.alias === dashboardAlias;
            });
            var showDefaultDashboard = dashboardIndex === -1;
            // Set the first dashboard to active if there is no query parameter or we can't find a matching dashboard for the alias
            var activeIndex = showDefaultDashboard ? 0 : dashboardIndex;
            var tab = $scope.dashboard.tabs[activeIndex];
            tab.active = true;
            if (!showDefaultDashboard) {
                $location.search(DASHBOARD_QUERY_PARAM, tab.alias);
            }
        }
    }
    // Register it
    angular.module('umbraco').controller('Umbraco.DashboardController', DashboardController);
    'use strict';
    (function () {
        'use strict';
        function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter, editorState) {
            var vm = this;
            var evts = [];
            vm.title = '';
            vm.subtitle = 'Umbraco version' + ' ' + Umbraco.Sys.ServerVariables.application.version;
            vm.section = $routeParams.section;
            vm.tree = $routeParams.tree;
            vm.sectionName = '';
            vm.customDashboard = null;
            vm.tours = [];
            vm.closeDrawer = closeDrawer;
            vm.startTour = startTour;
            vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage;
            vm.showTourButton = showTourButton;
            vm.showDocTypeTour = false;
            vm.docTypeTours = [];
            vm.nodeName = '';
            function startTour(tour) {
                tourService.startTour(tour);
                closeDrawer();
            }
            function oninit() {
                // set title
                localizationService.localize('general_help').then(function (data) {
                    vm.title = data;
                });
                tourService.getGroupedTours().then(function (groupedTours) {
                    vm.tours = groupedTours;
                    getTourGroupCompletedPercentage();
                });
                // load custom help dashboard
                dashboardResource.getDashboard('user-help').then(function (dashboard) {
                    vm.customDashboard = dashboard;
                });
                if (!vm.section) {
                    vm.section = 'content';
                }
                setSectionName();
                userService.getCurrentUser().then(function (user) {
                    vm.userType = user.userType;
                    vm.userLang = user.locale;
                    vm.hasAccessToSettings = _.contains(user.allowedSections, 'settings');
                    evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
                        handleSectionChange();
                    }));
                    evts.push(eventsService.on('editorState.changed', function (e, args) {
                        setDocTypeTour(args.entity);
                    }));
                    findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
                });
                setDocTypeTour(editorState.getCurrent());
                // check if a tour is running - if it is open the matching group
                var currentTour = tourService.getCurrentTour();
                if (currentTour) {
                    openTourGroup(currentTour.alias);
                }
            }
            function closeDrawer() {
                appState.setDrawerState('showDrawer', false);
            }
            function handleSectionChange() {
                $timeout(function () {
                    if (vm.section !== $routeParams.section || vm.tree !== $routeParams.tree) {
                        vm.section = $routeParams.section;
                        vm.tree = $routeParams.tree;
                        setSectionName();
                        findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
                        setDocTypeTour();
                    }
                });
            }
            function findHelp(section, tree, usertype, userLang) {
                if (vm.hasAccessToSettings) {
                    helpService.getContextHelpForPage(section, tree).then(function (topics) {
                        vm.topics = topics;
                    });
                }
                var rq = {};
                rq.section = vm.section;
                rq.usertype = usertype;
                rq.lang = userLang;
                if ($routeParams.url) {
                    rq.path = decodeURIComponent($routeParams.url);
                    if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                        rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                    }
                    if (rq.path.indexOf('.aspx') > 0) {
                        rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                    }
                } else {
                    rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
                }
                if (vm.hasAccessToSettings) {
                    helpService.findVideos(rq).then(function (videos) {
                        vm.videos = videos;
                    });
                }
            }
            function setSectionName() {
                // Get section name
                var languageKey = 'sections_' + vm.section;
                localizationService.localize(languageKey).then(function (value) {
                    vm.sectionName = value;
                });
            }
            function showTourButton(index, tourGroup) {
                if (index !== 0) {
                    var prevTour = tourGroup.tours[index - 1];
                    if (prevTour.completed) {
                        return true;
                    }
                } else {
                    return true;
                }
            }
            function openTourGroup(tourAlias) {
                vm.tours.forEach(function (group) {
                    group.tours.forEach(function (tour) {
                        if (tour.alias === tourAlias) {
                            group.open = true;
                        }
                    });
                });
            }
            function getTourGroupCompletedPercentage() {
                // Finding out, how many tours are completed for the progress circle
                vm.tours.forEach(function (group) {
                    var completedTours = 0;
                    group.tours.forEach(function (tour) {
                        if (tour.completed) {
                            completedTours++;
                        }
                    });
                    group.completedPercentage = Math.round(completedTours / group.tours.length * 100);
                });
            }
            function setDocTypeTour(node) {
                vm.showDocTypeTour = false;
                vm.docTypeTours = [];
                vm.nodeName = '';
                if (vm.section === 'content' && vm.tree === 'content') {
                    if (node) {
                        tourService.getToursForDoctype(node.contentTypeAlias).then(function (data) {
                            if (data && data.length > 0) {
                                vm.docTypeTours = data;
                                var currentVariant = _.find(node.variants, function (x) {
                                    return x.active;
                                });
                                vm.nodeName = currentVariant.name;
                                vm.showDocTypeTour = true;
                            }
                        });
                    }
                }
            }
            evts.push(eventsService.on('appState.tour.complete', function (event, tour) {
                tourService.getGroupedTours().then(function (groupedTours) {
                    vm.tours = groupedTours;
                    openTourGroup(tour.alias);
                    getTourGroupCompletedPercentage();
                });
            }));
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
            oninit();
        }
        angular.module('umbraco').controller('Umbraco.Drawers.Help', HelpDrawerController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.BlockEditorController', function ($scope, localizationService, formHelper, overlayService) {
        var vm = this;
        vm.model = $scope.model;
        vm.tabs = [];
        localizationService.localizeMany([
            vm.model.createFlow ? 'general_cancel' : vm.model.liveEditing ? 'prompt_discardChanges' : 'general_close',
            vm.model.createFlow ? 'general_create' : vm.model.liveEditing ? 'buttons_confirmActionConfirm' : 'buttons_submitChanges'
        ]).then(function (data) {
            vm.closeLabel = data[0];
            vm.submitLabel = data[1];
        });
        if (vm.model.content && vm.model.content.variants) {
            var apps = vm.model.content.apps;
            // configure the content app based on settings
            var contentApp = apps.find(function (entry) {
                return entry.alias === 'umbContent';
            });
            if (contentApp) {
                if (vm.model.hideContent) {
                    apps.splice(apps.indexOf(contentApp), 1);
                }
                contentApp.active = vm.model.openSettings !== true;
            }
            if (vm.model.settings && vm.model.settings.variants) {
                var settingsApp = apps.find(function (entry) {
                    return entry.alias === 'settings';
                });
                if (settingsApp) {
                    settingsApp.active = vm.model.openSettings === true;
                }
            }
            vm.tabs = apps;
        }
        vm.submitAndClose = function () {
            if (vm.model && vm.model.submit) {
                // always keep server validations since this will be a nested editor and server validations are global
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: vm.blockForm,
                        keepServerValidation: true
                    })) {
                    vm.model.submit(vm.model);
                    vm.saveButtonState = 'success';
                } else {
                    vm.saveButtonState = 'error';
                }
            }
        };
        vm.close = function () {
            if (vm.model && vm.model.close) {
                // TODO: At this stage there could very well have been server errors that have been cleared
                // but if we 'close' we are basically cancelling the value changes which means we'd want to cancel
                // all of the server errors just cleared. It would be possible to do that but also quite annoying.
                // The rudimentary way would be to:
                // * Track all cleared server errors here by subscribing to the prefix validation of controls contained here
                // * If this is closed, re-add all of those server validation errors
                // A more robust way to do this would be to:
                // * Add functionality to the serverValidationManager whereby we can remove validation errors and it will
                //      maintain a copy of the original errors
                // * It would have a 'commit' method to commit the removed errors - which we would call in the formHelper.submitForm when it's successful
                // * It would have a 'rollback' method to reset the removed errors - which we would call here
                if (vm.model.createFlow === true || vm.blockForm.$dirty === true) {
                    var labels = vm.model.createFlow === true ? [
                        'blockEditor_confirmCancelBlockCreationHeadline',
                        'blockEditor_confirmCancelBlockCreationMessage'
                    ] : [
                        'prompt_discardChanges',
                        'blockEditor_blockHasChanges'
                    ];
                    localizationService.localizeMany(labels).then(function (localizations) {
                        var confirm = {
                            title: localizations[0],
                            view: 'default',
                            content: localizations[1],
                            submitButtonLabelKey: 'general_discard',
                            submitButtonStyle: 'danger',
                            closeButtonLabelKey: 'prompt_stay',
                            submit: function submit() {
                                overlayService.close();
                                vm.model.close(vm.model);
                            },
                            close: function close() {
                                overlayService.close();
                            }
                        };
                        overlayService.open(confirm);
                    });
                } else {
                    vm.model.close(vm.model);
                }
            }
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.BlockPickerController', function ($scope, localizationService) {
        var vm = this;
        vm.navigation = [];
        vm.filter = { searchTerm: '' };
        localizationService.localizeMany([
            'blockEditor_tabCreateEmpty',
            'blockEditor_tabClipboard'
        ]).then(function (data) {
            vm.navigation = [
                {
                    'alias': 'empty',
                    'name': data[0],
                    'icon': 'icon-add',
                    'view': ''
                },
                {
                    'alias': 'clipboard',
                    'name': data[1],
                    'icon': 'icon-paste-in',
                    'view': '',
                    'disabled': vm.model.clipboardItems.length === 0
                }
            ];
            if (vm.model.openClipboard === true) {
                vm.activeTab = vm.navigation[1];
            } else {
                vm.activeTab = vm.navigation[0];
            }
            vm.activeTab.active = true;
        });
        vm.onNavigationChanged = function (tab) {
            vm.activeTab.active = false;
            vm.activeTab = tab;
            vm.activeTab.active = true;
        };
        vm.clickClearClipboard = function () {
            vm.onNavigationChanged(vm.navigation[0]);
            vm.navigation[1].disabled = true;
            // disabled ws determined when creating the navigation, so we need to update it here.
            vm.model.clipboardItems = [];
            // This dialog is not connected via the clipboardService events, so we need to update manually.
            vm.model.clickClearClipboard();
        };
        vm.model = $scope.model;
        vm.selectItem = function (item, $event) {
            vm.model.selectedItem = item;
            vm.model.submit($scope.model, $event);
        };
        vm.close = function () {
            if ($scope.model && $scope.model.close) {
                $scope.model.close($scope.model);
            }
        };
    });
    'use strict';
    (function () {
        'use strict';
        function CompositionsController($scope, $location, $filter, $timeout, overlayService, localizationService) {
            var vm = this;
            var oldModel = null;
            vm.showConfirmSubmit = false;
            vm.loadingAlias = null;
            vm.isSelected = isSelected;
            vm.openContentType = openContentType;
            vm.selectCompositeContentType = selectCompositeContentType;
            vm.submit = submit;
            vm.close = close;
            function onInit() {
                /* make a copy of the init model so it is possible to roll 
      back the changes on cancel */
                oldModel = Utilities.copy($scope.model);
                if (!$scope.model.title) {
                    $scope.model.title = 'Compositions';
                }
                // Group the content types by their container paths
                vm.availableGroups = $filter('orderBy')(_.map(_.groupBy($scope.model.availableCompositeContentTypes, function (compositeContentType) {
                    compositeContentType.selected = isSelected(compositeContentType.contentType.alias);
                    return compositeContentType.contentType.metaData.containerPath;
                }), function (group) {
                    return {
                        containerPath: group[0].contentType.metaData.containerPath,
                        compositeContentTypes: group
                    };
                }), function (group) {
                    return group.containerPath.replace(/\//g, ' ');
                });
            }
            function isSelected(alias) {
                if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) {
                    return true;
                }
                return false;
            }
            function openContentType(contentType, section) {
                var url = (section === 'documentType' ? '/settings/documenttypes/edit/' : '/settings/mediaTypes/edit/') + contentType.id;
                $location.path(url);
            }
            function selectCompositeContentType(compositeContentType) {
                vm.loadingAlias = compositeContentType.contentType.alias;
                var contentType = compositeContentType.contentType;
                $scope.model.selectCompositeContentType(contentType).then(function (response) {
                    vm.loadingAlias = null;
                });
                // Check if the template is already selected.
                var index = $scope.model.contentType.compositeContentTypes.indexOf(contentType.alias);
                if (index === -1) {
                    $scope.model.contentType.compositeContentTypes.push(contentType.alias);
                } else {
                    $scope.model.contentType.compositeContentTypes.splice(index, 1);
                }
            }
            function submit() {
                if ($scope.model && $scope.model.submit) {
                    // check if any compositions has been removed
                    var compositionRemoved = false;
                    for (var i = 0; oldModel.compositeContentTypes.length > i; i++) {
                        var oldComposition = oldModel.compositeContentTypes[i];
                        if (_.contains($scope.model.compositeContentTypes, oldComposition) === false) {
                            compositionRemoved = true;
                        }
                    }
                    /* submit the form if there havne't been removed any composition
        or the confirm checkbox has been checked */
                    if (compositionRemoved) {
                        vm.allowSubmit = false;
                        localizationService.localize('general_remove').then(function (value) {
                            var dialog = {
                                view: 'views/common/infiniteeditors/compositions/overlays/confirmremove.html',
                                title: value,
                                submitButtonLabelKey: 'general_ok',
                                submitButtonStyle: 'danger',
                                closeButtonLabelKey: 'general_cancel',
                                submit: function submit(model) {
                                    $scope.model.submit($scope.model);
                                    overlayService.close();
                                },
                                close: function close() {
                                    overlayService.close();
                                }
                            };
                            overlayService.open(dialog);
                        });
                        return;
                    }
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close(oldModel);
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.CompositionsController', CompositionsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function CopyController($scope, localizationService, eventsService, entityHelper) {
            var vm = this;
            vm.labels = {};
            vm.hideSearch = hideSearch;
            vm.selectResult = selectResult;
            vm.onSearchResults = onSearchResults;
            vm.onToggle = toggleHandler;
            vm.submit = submit;
            vm.close = close;
            var dialogOptions = $scope.model;
            var node = dialogOptions.currentNode;
            $scope.model.relateToOriginal = true;
            $scope.model.includeDescendants = true;
            $scope.dialogTreeApi = {};
            vm.searchInfo = {
                searchFromId: null,
                searchFromName: null,
                showSearch: false,
                results: [],
                selectedSearchResults: []
            };
            // get entity type based on the section
            $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
            function onInit() {
                var labelKeys = ['general_copy'];
                localizationService.localizeMany(labelKeys).then(function (data) {
                    vm.labels.title = data[0];
                    setTitle(vm.labels.title);
                });
            }
            function setTitle(value) {
                if (!$scope.model.title) {
                    $scope.model.title = value;
                }
            }
            function nodeSelectHandler(args) {
                if (args && args.event) {
                    args.event.preventDefault();
                    args.event.stopPropagation();
                }
                //eventsService.emit("editors.content.copyController.select", args);
                if ($scope.model.target) {
                    //un-select if there's a current one selected
                    $scope.model.target.selected = false;
                }
                $scope.model.target = args.node;
                $scope.model.target.selected = true;
            }
            function nodeExpandedHandler(args) {
                // open mini list view for list views
                if (args.node.metaData.isContainer) {
                    openMiniListView(args.node);
                }
            }
            function hideSearch() {
                vm.searchInfo.showSearch = false;
                vm.searchInfo.searchFromId = null;
                vm.searchInfo.searchFromName = null;
                vm.searchInfo.results = [];
            }
            // method to select a search result
            function selectResult(evt, result) {
                result.selected = result.selected === true ? false : true;
                nodeSelectHandler({
                    event: evt,
                    node: result
                });
            }
            //callback when there are search results
            function onSearchResults(results) {
                vm.searchInfo.results = results;
                vm.searchInfo.showSearch = true;
            }
            $scope.onTreeInit = function () {
                $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
                $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
            };
            // Mini list view
            $scope.selectListViewNode = function (node) {
                node.selected = node.selected === true ? false : true;
                nodeSelectHandler({ node: node });
            };
            $scope.closeMiniListView = function () {
                $scope.miniListView = undefined;
            };
            function openMiniListView(node) {
                $scope.miniListView = node;
            }
            function submit() {
                if ($scope.model && $scope.model.submit) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
            function toggleHandler(type) {
                // If the relateToOriginal toggle is clicked
                if (type === 'relate') {
                    $scope.model.relateToOriginal = !$scope.model.relateToOriginal;
                }
                // If the includeDescendants toggle is clicked
                if (type === 'descendants') {
                    $scope.model.includeDescendants = !$scope.model.includeDescendants;
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.CopyController', CopyController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataTypeConfigurationPickerController
 * @function
 *
 * @description
 * The controller for the content type editor data type configuration picker dialog
 */
    (function () {
        'use strict';
        function DataTypeConfigurationPicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) {
            var vm = this;
            vm.configs = [];
            vm.loading = true;
            vm.newDataType = newDataType;
            vm.pickDataType = pickDataType;
            vm.close = close;
            function activate() {
                setTitle();
                load();
            }
            function setTitle() {
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectEditorConfiguration').then(function (data) {
                        $scope.model.title = data;
                    });
                }
            }
            function load() {
                dataTypeResource.getGroupedDataTypes().then(function (configs) {
                    var filteredConfigs = [];
                    Object.values(configs).forEach(function (configGroup) {
                        for (var i = 0; i < configGroup.length; i++) {
                            if (configGroup[i].alias === $scope.model.editor.alias) {
                                filteredConfigs.push(configGroup[i]);
                            }
                        }
                    });
                    vm.configs = filteredConfigs;
                    vm.loading = false;
                });
            }
            function newDataType() {
                var dataTypeSettings = {
                    propertyEditor: $scope.model.editor,
                    property: $scope.model.property,
                    contentTypeName: $scope.model.contentTypeName,
                    create: true,
                    view: 'views/common/infiniteeditors/datatypesettings/datatypesettings.html',
                    submit: function submit(model) {
                        contentTypeResource.getPropertyTypeScaffold(model.dataType.id).then(function (propertyType) {
                            $scope.model.submit(model.dataType, propertyType, true);
                            editorService.close();
                        });
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.open(dataTypeSettings);
            }
            function pickDataType(selectedConfig) {
                selectedConfig.loading = true;
                dataTypeResource.getById(selectedConfig.id).then(function (dataType) {
                    contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function (propertyType) {
                        selectedConfig.loading = false;
                        $scope.model.submit(dataType, propertyType, false);
                    });
                });
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Editors.DataTypeConfigurationPickerController', DataTypeConfigurationPicker);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataTypePickerController
 * @function
 *
 * @description
 * The controller for the content type editor data type picker dialog
 */
    (function () {
        'use strict';
        function DataTypePicker($scope, $filter, dataTypeResource, contentTypeResource, localizationService, editorService) {
            var vm = this;
            vm.showDataTypes = true;
            vm.dataTypes = [];
            vm.loading = true;
            vm.loadingConfigs = false;
            vm.searchTerm = '';
            vm.searchResult = null;
            vm.viewOptionsForEditor = viewOptionsForEditor;
            vm.pickDataType = pickDataType;
            vm.pickEditor = pickEditor;
            vm.close = close;
            vm.searchTermChanged = searchTermChanged;
            function activate() {
                setTitle();
                loadTypes();
            }
            function setTitle() {
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectEditor').then(function (data) {
                        $scope.model.title = data;
                    });
                }
            }
            function loadTypes() {
                dataTypeResource.getGroupedPropertyEditors().then(function (dataTypes) {
                    vm.dataTypes = dataTypes;
                    vm.loading = false;
                });
            }
            function loadConfigurations() {
                vm.loading = true;
                vm.loadingConfigs = true;
                dataTypeResource.getGroupedDataTypes().then(function (configs) {
                    vm.configs = configs;
                    vm.loading = false;
                    performeSearch();
                });
            }
            function searchTermChanged() {
                vm.showDataTypes = vm.searchTerm === '';
                if (vm.loadingConfigs !== true) {
                    loadConfigurations();
                } else {
                    performeSearch();
                }
            }
            function performeSearch() {
                if (vm.searchTerm) {
                    if (vm.configs) {
                        var regex = new RegExp(vm.searchTerm, 'i');
                        vm.searchResult = {
                            configs: filterCollection(vm.configs, regex),
                            dataTypes: filterCollection(vm.dataTypes, regex)
                        };
                    }
                } else {
                    vm.searchResult = null;
                }
            }
            function filterCollection(collection, regex) {
                return _.map(_.keys(collection), function (key) {
                    return {
                        group: key,
                        entries: $filter('filter')(collection[key], function (dataType) {
                            return regex.test(dataType.name) || regex.test(dataType.alias);
                        })
                    };
                });
            }
            function viewOptionsForEditor(editor) {
                var dataTypeConfigurationPicker = {
                    editor: editor,
                    property: $scope.model.property,
                    contentTypeName: $scope.model.contentTypeName,
                    view: 'views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.html',
                    size: 'small',
                    submit: function submit(dataType, propertyType, isNew) {
                        _submit(dataType, propertyType, isNew);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.open(dataTypeConfigurationPicker);
            }
            function pickDataType(selectedDataType) {
                selectedDataType.loading = true;
                dataTypeResource.getById(selectedDataType.id).then(function (dataType) {
                    contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function (propertyType) {
                        selectedDataType.loading = false;
                        _submit(dataType, propertyType, false);
                    });
                });
            }
            function pickEditor(propertyEditor) {
                var dataTypeSettings = {
                    propertyEditor: propertyEditor,
                    property: $scope.model.property,
                    contentTypeName: $scope.model.contentTypeName,
                    create: true,
                    view: 'views/common/infiniteeditors/datatypesettings/datatypesettings.html',
                    submit: function submit(model) {
                        contentTypeResource.getPropertyTypeScaffold(model.dataType.id).then(function (propertyType) {
                            _submit(model.dataType, propertyType, true);
                            editorService.close();
                        });
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.open(dataTypeSettings);
            }
            function _submit(dataType, propertyType, isNew) {
                // update property
                $scope.model.property.config = propertyType.config;
                $scope.model.property.editor = propertyType.editor;
                $scope.model.property.view = propertyType.view;
                $scope.model.property.dataTypeId = dataType.id;
                $scope.model.property.dataTypeIcon = dataType.icon;
                $scope.model.property.dataTypeName = dataType.name;
                $scope.model.updateSameDataTypes = isNew;
                $scope.model.submit($scope.model);
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Editors.DataTypePickerController', DataTypePicker);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataTypeSettingsController
 * @function
 *
 * @description
 * The controller for the content type editor data type settings dialog
 */
    (function () {
        'use strict';
        function DataTypeSettingsController($scope, dataTypeResource, dataTypeHelper, localizationService, notificationsService, overlayService, formHelper, eventsService) {
            var vm = this;
            vm.dataType = {};
            vm.loadingDataType = false;
            vm.saveButtonState = 'init';
            vm.close = close;
            vm.submit = submit;
            function onInit() {
                setTitle();
                if ($scope.model.create) {
                    createNewDataType();
                } else {
                    getDataType();
                }
            }
            function setTitle() {
                if (!$scope.model.title) {
                    localizationService.localize('contentTypeEditor_editorSettings').then(function (data) {
                        $scope.model.title = data;
                    });
                }
            }
            function createNewDataType() {
                vm.loadingDataType = true;
                var parentId = -1;
                var newDataType = {};
                dataTypeResource.getScaffold(parentId).then(function (dataType) {
                    newDataType = dataType;
                    // set alias
                    newDataType.selectedEditor = $scope.model.propertyEditor.alias;
                    // set name
                    var nameArray = [];
                    if ($scope.model.contentTypeName) {
                        nameArray.push($scope.model.contentTypeName);
                    }
                    if ($scope.model.property.label) {
                        nameArray.push($scope.model.property.label);
                    }
                    if ($scope.model.propertyEditor.name) {
                        nameArray.push($scope.model.propertyEditor.name);
                    }
                    // make name
                    newDataType.name = nameArray.join(' - ');
                    // get pre values
                    dataTypeResource.getPreValues(newDataType.selectedEditor).then(function (preValues) {
                        newDataType.preValues = dataTypeHelper.createPreValueProps(preValues);
                        vm.dataType = newDataType;
                        vm.loadingDataType = false;
                    });
                });
            }
            function getDataType() {
                vm.loadingDataType = true;
                dataTypeResource.getById($scope.model.id).then(function (dataType) {
                    dataType.preValues = dataTypeHelper.createPreValueProps(dataType.preValues);
                    vm.dataType = dataType;
                    vm.loadingDataType = false;
                });
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
            function submit() {
                if (!formHelper.submitForm({ scope: $scope })) {
                    return;
                }
                vm.saveButtonState = 'busy';
                var preValues = dataTypeHelper.createPreValueProps(vm.dataType.preValues);
                dataTypeResource.save(vm.dataType, preValues, $scope.model.create).then(function (newDataType) {
                    $scope.model.dataType = newDataType;
                    var args = { dataType: newDataType };
                    eventsService.emit('editors.dataTypeSettings.saved', args);
                    vm.saveButtonState = 'success';
                    if ($scope.model && $scope.model.submit) {
                        $scope.model.submit($scope.model);
                    }
                }, function (err) {
                    vm.saveButtonState = 'error';
                    if (err.status === 400) {
                        if (err.data && err.data.ModelState) {
                            formHelper.handleServerValidation(err.data.ModelState);
                            for (var e in err.data.ModelState) {
                                notificationsService.error('Validation', err.data.ModelState[e][0]);
                            }
                        }
                    }
                });
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.DataTypeSettingsController', DataTypeSettingsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function EmbedController($scope, $http, $sce, umbRequestHelper, localizationService) {
            var vm = this;
            var origWidth = 500;
            var origHeight = 300;
            vm.loading = false;
            vm.trustedPreview = null;
            $scope.model.embed = {
                url: '',
                width: 360,
                height: 240,
                constrain: true,
                preview: '',
                success: false,
                info: '',
                a11yInfo: '',
                supportsDimensions: false,
                originalWidth: 360,
                originalHeight: 240
            };
            if ($scope.model.modify) {
                Utilities.extend($scope.model.embed, $scope.model.modify);
                showPreview();
            }
            vm.toggleConstrain = toggleConstrain;
            vm.showPreview = showPreview;
            vm.changeSize = changeSize;
            vm.submit = submit;
            vm.close = close;
            function onInit() {
                if (!$scope.model.title) {
                    localizationService.localize('general_embed').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            }
            function showPreview() {
                if ($scope.model.embed.url) {
                    $scope.model.embed.show = true;
                    $scope.model.embed.info = '';
                    $scope.model.embed.a11yInfo = '';
                    $scope.model.embed.success = false;
                    vm.loading = true;
                    $http({
                        method: 'GET',
                        url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'),
                        params: {
                            url: $scope.model.embed.url,
                            width: $scope.model.embed.width,
                            height: $scope.model.embed.height
                        }
                    }).then(function (response) {
                        $scope.model.embed.preview = '';
                        switch (response.data.OEmbedStatus) {
                        case 0:
                            //not supported
                            $scope.model.embed.preview = '';
                            $scope.model.embed.info = 'Not supported';
                            $scope.model.embed.a11yInfo = $scope.model.embed.info;
                            $scope.model.embed.success = false;
                            $scope.model.embed.supportsDimensions = false;
                            vm.trustedPreview = null;
                            break;
                        case 1:
                            //error
                            $scope.model.embed.preview = '';
                            $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                            $scope.model.embed.a11yInfo = $scope.model.embed.info;
                            $scope.model.embed.success = false;
                            $scope.model.embed.supportsDimensions = false;
                            vm.trustedPreview = null;
                            break;
                        case 2:
                            $scope.model.embed.success = true;
                            $scope.model.embed.supportsDimensions = response.data.SupportsDimensions;
                            $scope.model.embed.preview = response.data.Markup;
                            $scope.model.embed.info = '';
                            $scope.model.embed.a11yInfo = 'Retrieved URL';
                            vm.trustedPreview = $sce.trustAsHtml(response.data.Markup);
                            break;
                        }
                        vm.loading = false;
                    }, function () {
                        $scope.model.embed.success = false;
                        $scope.model.embed.supportsDimensions = false;
                        $scope.model.embed.preview = '';
                        $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                        $scope.model.embed.a11yInfo = $scope.model.embed.info;
                        vm.loading = false;
                    });
                } else {
                    $scope.model.embed.supportsDimensions = false;
                    $scope.model.embed.preview = '';
                    $scope.model.embed.info = 'Please enter a URL';
                    $scope.model.embed.a11yInfo = $scope.model.embed.info;
                }
            }
            function changeSize(type) {
                var width = parseInt($scope.model.embed.width, 10);
                var height = parseInt($scope.model.embed.height, 10);
                var originalWidth = parseInt($scope.model.embed.originalWidth, 10);
                var originalHeight = parseInt($scope.model.embed.originalHeight, 10);
                var resize = originalWidth !== width || originalHeight !== height;
                if ($scope.model.embed.constrain) {
                    if (type === 'width') {
                        origHeight = Math.round(width / origWidth * height);
                        $scope.model.embed.height = origHeight;
                    } else {
                        origWidth = Math.round(height / origHeight * width);
                        $scope.model.embed.width = origWidth;
                    }
                }
                $scope.model.embed.originalWidth = $scope.model.embed.width;
                $scope.model.embed.originalHeight = $scope.model.embed.height;
                if ($scope.model.embed.url !== '' && resize) {
                    showPreview();
                }
            }
            function toggleConstrain() {
                $scope.model.embed.constrain = !$scope.model.embed.constrain;
            }
            function submit() {
                if ($scope.model && $scope.model.submit) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.EmbedController', EmbedController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.IconPickerController
 * @function
 *
 * @description
 * The controller for the content type editor icon picker
 */
    function IconPickerController($scope, localizationService, iconHelper) {
        var vm = this;
        vm.filter = { searchTerm: '' };
        vm.selectIcon = selectIcon;
        vm.selectColor = selectColor;
        vm.submit = submit;
        vm.close = close;
        vm.colors = [
            {
                name: 'Black',
                value: 'color-black',
                default: true
            },
            {
                name: 'Blue Grey',
                value: 'color-blue-grey'
            },
            {
                name: 'Grey',
                value: 'color-grey'
            },
            {
                name: 'Brown',
                value: 'color-brown'
            },
            {
                name: 'Blue',
                value: 'color-blue'
            },
            {
                name: 'Light Blue',
                value: 'color-light-blue'
            },
            {
                name: 'Indigo',
                value: 'color-indigo'
            },
            {
                name: 'Purple',
                value: 'color-purple'
            },
            {
                name: 'Deep Purple',
                value: 'color-deep-purple'
            },
            {
                name: 'Cyan',
                value: 'color-cyan'
            },
            {
                name: 'Green',
                value: 'color-green'
            },
            {
                name: 'Light Green',
                value: 'color-light-green'
            },
            {
                name: 'Lime',
                value: 'color-lime'
            },
            {
                name: 'Yellow',
                value: 'color-yellow'
            },
            {
                name: 'Amber',
                value: 'color-amber'
            },
            {
                name: 'Orange',
                value: 'color-orange'
            },
            {
                name: 'Deep Orange',
                value: 'color-deep-orange'
            },
            {
                name: 'Red',
                value: 'color-red'
            },
            {
                name: 'Pink',
                value: 'color-pink'
            }
        ];
        function onInit() {
            vm.loading = true;
            setTitle();
            iconHelper.getAllIcons().then(function (icons) {
                vm.icons = icons;
                // Get's legacy icons, removes duplicates then maps them to IconModel
                iconHelper.getIcons().then(function (icons) {
                    if (icons && icons.length > 0) {
                        var legacyIcons = icons.filter(function (icon) {
                            return !vm.icons.find(function (x) {
                                return x.name == icon;
                            });
                        }).map(function (icon) {
                            return {
                                name: icon,
                                svgString: null
                            };
                        });
                        vm.icons = legacyIcons.concat(vm.icons);
                    }
                    vm.loading = false;
                });
            });
            // set a default color if nothing is passed in
            vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors.find(function (x) {
                return x.default;
            });
            // if an icon is passed in - preselect it
            vm.icon = $scope.model.icon ? $scope.model.icon : undefined;
        }
        function setTitle() {
            if (!$scope.model.title) {
                localizationService.localize('defaultdialogs_selectIcon').then(function (data) {
                    $scope.model.title = data;
                });
            }
        }
        function selectIcon(icon, color) {
            $scope.model.icon = icon;
            $scope.model.color = color;
            submit();
        }
        function findColor(value) {
            return vm.colors.find(function (x) {
                return x.value === value;
            });
        }
        function selectColor(color) {
            var newColor = color || vm.colors.find(function (x) {
                return x.default;
            });
            $scope.model.color = newColor.value;
            vm.color = newColor;
        }
        function close() {
            if ($scope.model && $scope.model.close) {
                $scope.model.close();
            }
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.IconPickerController', IconPickerController);
    'use strict';
    (function () {
        'use strict';
        function InsertOverlayController($scope, localizationService, editorService) {
            var vm = this;
            vm.openMacroPicker = openMacroPicker;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openPartialOverlay = openPartialOverlay;
            vm.close = close;
            function onInit() {
                if (!$scope.model.title) {
                    localizationService.localize('template_insert').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                if (!$scope.model.subtitle) {
                    localizationService.localize('template_insertDesc').then(function (value) {
                        $scope.model.subtitle = value;
                    });
                }
            }
            function openMacroPicker() {
                var macroPicker = {
                    dialogData: {},
                    submit: function submit(model) {
                        $scope.model.insert = {
                            'type': 'macro',
                            'macroParams': model.macroParams,
                            'selectedMacro': model.selectedMacro
                        };
                        $scope.model.submit($scope.model);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.macroPicker(macroPicker);
            }
            function openPageFieldOverlay() {
                var insertFieldEditor = {
                    submit: function submit(model) {
                        $scope.model.insert = {
                            'type': 'umbracoField',
                            'umbracoField': model.umbracoField
                        };
                        $scope.model.submit($scope.model);
                        editorService.close();
                    },
                    close: function close(model) {
                        editorService.close();
                    }
                };
                editorService.insertField(insertFieldEditor);
            }
            function openDictionaryItemOverlay() {
                var labelKeys = [
                    'template_insertDictionaryItem',
                    'template_insertDictionaryItemDesc',
                    'emptyStates_emptyDictionaryTree'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    var title = values[0];
                    var subtitle = values[1];
                    var emptyStateMessage = values[2];
                    var dictionaryItemPicker = {
                        section: 'translation',
                        treeAlias: 'dictionary',
                        entityType: 'dictionary',
                        multiPicker: false,
                        title: title,
                        subtitle: subtitle,
                        emptyStateMessage: emptyStateMessage,
                        select: function select(node) {
                            $scope.model.insert = {
                                'type': 'dictionary',
                                'node': node
                            };
                            $scope.model.submit($scope.model);
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.treePicker(dictionaryItemPicker);
                });
            }
            function openPartialOverlay() {
                localizationService.localize('template_insertPartialView').then(function (value) {
                    var title = value;
                    var partialItemPicker = {
                        section: 'settings',
                        treeAlias: 'partialViews',
                        entityType: 'partialView',
                        multiPicker: false,
                        title: title,
                        filter: function filter(i) {
                            if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                                return true;
                            }
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            $scope.model.insert = {
                                'type': 'partial',
                                'node': node
                            };
                            $scope.model.submit($scope.model);
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.treePicker(partialItemPicker);
                });
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.InsertOverlay', InsertOverlayController);
    }());
    'use strict';
    (function () {
        'use strict';
        function InsertFieldController($scope, contentTypeResource, localizationService) {
            var vm = this;
            vm.field = null;
            vm.defaultValue = null;
            vm.recursive = false;
            vm.showDefaultValue = false;
            vm.generateOutputSample = generateOutputSample;
            vm.submit = submit;
            vm.close = close;
            function onInit() {
                var labelKeys = ['template_insertPageField'];
                // set default title
                if (!$scope.model.title) {
                    localizationService.localizeMany(labelKeys).then(function (data) {
                        $scope.model.title = data[0];
                    });
                }
                // Load all fields
                contentTypeResource.getAllPropertyTypeAliases().then(function (array) {
                    vm.properties = array;
                });
                // Load all standard fields
                contentTypeResource.getAllStandardFields().then(function (array) {
                    vm.standardFields = array;
                });
            }
            function generateOutputSample() {
                var fallback = null;
                if (vm.recursive !== false && vm.defaultValue !== null) {
                    fallback = 'Fallback.To(Fallback.Ancestors, Fallback.DefaultValue)';
                } else if (vm.recursive !== false) {
                    fallback = 'Fallback.ToAncestors';
                } else if (vm.defaultValue !== null) {
                    fallback = 'Fallback.ToDefaultValue';
                }
                var pageField = (vm.field !== null ? '@Model.Value("' + vm.field + '"' : '') + (fallback !== null ? ', fallback: ' + fallback : '') + (vm.defaultValue !== null ? ', defaultValue: new HtmlString("' + vm.defaultValue + '")' : '') + (vm.field ? ')' : '');
                $scope.model.umbracoField = pageField;
                return pageField;
            }
            function submit(model) {
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.InsertFieldController', InsertFieldController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ItemPickerController
 * @function
 *
 * @description
 * The controller for a reusable editor to pick items
 */
    function ItemPickerController($scope, localizationService) {
        var vm = this;
        vm.selectItem = selectItem;
        vm.submit = submit;
        vm.close = close;
        function onInit() {
            if (!$scope.model.title) {
                localizationService.localize('defaultdialogs_selectItem').then(function (value) {
                    $scope.model.title = value;
                });
            }
        }
        function selectItem(item) {
            $scope.model.selectedItem = item;
            submit($scope.model);
        }
        ;
        function submit(model) {
            if ($scope.model.submit) {
                $scope.model.submit(model);
            }
        }
        function close() {
            if ($scope.model.close) {
                $scope.model.close();
            }
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.ItemPicker', ItemPickerController);
    'use strict';
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.LinkPickerController', function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, editorService) {
        var vm = this;
        var dialogOptions = $scope.model;
        vm.submit = submit;
        vm.close = close;
        vm.toggleOpenInNewWindow = toggleOpenInNewWindow;
        vm.labels = {};
        localizationService.localizeMany(['defaultdialogs_openInNewWindow']).then(function (data) {
            vm.labels.openInNewWindow = data[0];
        });
        if (!$scope.model.title) {
            localizationService.localize('defaultdialogs_selectLink').then(function (value) {
                $scope.model.title = value;
            });
        }
        $scope.customTreeParams = dialogOptions.dataTypeKey ? 'dataTypeKey=' + dialogOptions.dataTypeKey : '';
        $scope.dialogTreeApi = {};
        $scope.model.target = {};
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            dataTypeKey: dialogOptions.dataTypeKey,
            results: [],
            selectedSearchResults: []
        };
        $scope.showTarget = $scope.model.hideTarget !== true;
        $scope.showAnchor = $scope.model.hideAnchor !== true;
        // this ensures that we only sync the tree once and only when it's ready
        var oneTimeTreeSync = {
            executed: false,
            treeReady: false,
            sync: function sync() {
                // don't run this if:
                // - it was already run once
                // - the tree isn't ready yet
                // - the model path hasn't been loaded yet
                if (this.executed || !this.treeReady || !($scope.model.target && $scope.model.target.path)) {
                    return;
                }
                this.executed = true;
                // sync the tree to the model path
                $scope.dialogTreeApi.syncTree({
                    path: $scope.model.target.path,
                    tree: 'content'
                });
            }
        };
        if (dialogOptions.currentTarget) {
            // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
            $scope.model.target = Utilities.copy(dialogOptions.currentTarget);
            // if we have a node ID, we fetch the current node to build the form data
            if ($scope.model.target.id || $scope.model.target.udi) {
                // will be either a udi or an int
                var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
                if ($scope.model.target.udi) {
                    // extract the entity type from the udi and set target.isMedia accordingly
                    var udi = udiParser.parse(id);
                    if (udi && udi.entityType === 'media') {
                        $scope.model.target.isMedia = true;
                    } else {
                        delete $scope.model.target.isMedia;
                    }
                }
                if ($scope.model.target.isMedia) {
                    mediaResource.getById(id).then(function (resp) {
                        $scope.model.target.url = resp.mediaLink;
                    });
                } else {
                    // get the content path
                    entityResource.getPath(id, 'Document').then(function (path) {
                        $scope.model.target.path = path;
                        oneTimeTreeSync.sync();
                    });
                    entityResource.getUrlAndAnchors(id).then(function (resp) {
                        $scope.anchorValues = resp.anchorValues;
                        $scope.model.target.url = resp.url;
                    });
                }
            } else if ($scope.model.target.url && $scope.model.target.url.length) {
                // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
                // only do the substring if there's a # or a ?
                var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/);
                if (indexOfAnchor > -1) {
                    // populate the anchor
                    $scope.model.target.anchor = $scope.model.target.url.substring(indexOfAnchor);
                    // then rewrite the model and populate the link
                    $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor);
                }
            }
            // need to translate the link target ("_blank" or "") into a boolean value for umb-checkbox
            vm.openInNewWindow = $scope.model.target.target === '_blank';
        } else if (dialogOptions.anchors) {
            $scope.anchorValues = dialogOptions.anchors;
        }
        function treeLoadedHandler(args) {
            oneTimeTreeSync.treeReady = true;
            oneTimeTreeSync.sync();
        }
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('dialogs.linkPicker.select', args);
            if ($scope.currentNode) {
                //un-select if there's a current one selected
                $scope.currentNode.selected = false;
            }
            $scope.currentNode = args.node;
            $scope.currentNode.selected = true;
            $scope.model.target.id = args.node.id;
            $scope.model.target.udi = args.node.udi;
            $scope.model.target.name = args.node.name;
            if (args.node.id < 0) {
                $scope.model.target.url = '/';
            } else {
                entityResource.getUrlAndAnchors(args.node.id).then(function (resp) {
                    $scope.anchorValues = resp.anchorValues;
                    $scope.model.target.url = resp.url;
                });
            }
            if (!Utilities.isUndefined($scope.model.target.isMedia)) {
                delete $scope.model.target.isMedia;
            }
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.switchToMediaPicker = function () {
            userService.getCurrentUser().then(function (userData) {
                var startNodeId, startNodeIsVirtual;
                if (dialogOptions.ignoreUserStartNodes === true) {
                    startNodeId = -1;
                    startNodeIsVirtual = true;
                } else {
                    startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                    startNodeIsVirtual = userData.startMediaIds.length !== 1;
                }
                var mediaPicker = {
                    startNodeId: startNodeId,
                    startNodeIsVirtual: startNodeIsVirtual,
                    dataTypeKey: dialogOptions.dataTypeKey,
                    submit: function submit(model) {
                        var media = model.selection[0];
                        $scope.model.target.id = media.id;
                        $scope.model.target.udi = media.udi;
                        $scope.model.target.isMedia = true;
                        $scope.model.target.name = media.name;
                        $scope.model.target.url = media.image;
                        editorService.close();
                        // make sure the content tree has nothing highlighted 
                        $scope.dialogTreeApi.syncTree({
                            path: '-1',
                            tree: 'content'
                        });
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.mediaPicker(mediaPicker);
            });
        };
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
        function toggleOpenInNewWindow(model, value) {
            $scope.model.target.target = model ? '_blank' : '';
        }
        function close() {
            if ($scope.model && $scope.model.close) {
                $scope.model.close();
            }
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MacroParameterPickerController 
 * @function
 *
 * @description
 * The controller for the content type editor macro parameter dialog
 */
    (function () {
        'use strict';
        function MacroParameterController($scope, $filter, macroResource, localizationService, editorService) {
            var vm = this;
            vm.searchTerm = '';
            vm.parameterEditors = [];
            vm.loading = false;
            vm.labels = {};
            vm.filterItems = filterItems;
            vm.showDetailsOverlay = showDetailsOverlay;
            vm.hideDetailsOverlay = hideDetailsOverlay;
            vm.pickParameterEditor = pickParameterEditor;
            vm.close = close;
            function init() {
                setTitle();
                getGroupedParameterEditors();
            }
            function setTitle() {
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectEditor').then(function (data) {
                        $scope.model.title = data;
                    });
                }
            }
            function getGroupedParameterEditors() {
                vm.loading = true;
                macroResource.getGroupedParameterEditors().then(function (data) {
                    vm.parameterEditors = data;
                    vm.loading = false;
                }, function () {
                    vm.loading = false;
                });
            }
            function filterItems() {
                // clear item details
                $scope.model.itemDetails = null;
                if (vm.searchTerm) {
                    var regex = new RegExp(vm.searchTerm, 'i');
                    var parameterEditors = filterCollection(vm.parameterEditors, regex);
                    var totalResults = _.reduce(_.pluck(parameterEditors, 'count'), function (m, n) {
                        return m + n;
                    }, 0);
                    vm.filterResult = {
                        parameterEditors: parameterEditors,
                        totalResults: totalResults
                    };
                } else {
                    vm.filterResult = null;
                }
            }
            function filterCollection(collection, regex) {
                return _.map(_.keys(collection), function (key) {
                    var filteredEditors = $filter('filter')(collection[key], function (editor) {
                        return regex.test(editor.name) || regex.test(editor.alias);
                    });
                    return {
                        group: key,
                        count: filteredEditors.length,
                        parameterEditors: filteredEditors
                    };
                });
            }
            function showDetailsOverlay(property) {
                var propertyDetails = {};
                propertyDetails.icon = property.icon;
                propertyDetails.title = property.name;
                $scope.model.itemDetails = propertyDetails;
            }
            function hideDetailsOverlay() {
                $scope.model.itemDetails = null;
            }
            function pickParameterEditor(selectedParameterEditor) {
                $scope.model.parameter.editor = selectedParameterEditor.alias;
                $scope.model.parameter.dataTypeName = selectedParameterEditor.name;
                $scope.model.parameter.dataTypeIcon = selectedParameterEditor.icon;
                $scope.model.submit($scope.model);
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.MacroParameterPickerController', MacroParameterController);
    }());
    'use strict';
    function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) {
        $scope.macros = [];
        $scope.a11yInfo = '';
        $scope.model.selectedMacro = null;
        $scope.model.macroParams = [];
        $scope.displayA11YMessageForFilter = displayA11YMessageForFilter;
        $scope.wizardStep = 'macroSelect';
        $scope.noMacroParams = false;
        $scope.model.searchTerm = '';
        function onInit() {
            if (!$scope.model.title) {
                localizationService.localize('defaultdialogs_selectMacro').then(function (value) {
                    $scope.model.title = value;
                });
            }
        }
        $scope.selectMacro = function (macro) {
            $scope.model.selectedMacro = macro;
            if ($scope.wizardStep === 'macroSelect') {
                editParams(true);
            } else {
                $scope.$broadcast('formSubmitting', { scope: $scope });
                $scope.model.submit($scope.model);
            }
        };
        $scope.close = function () {
            if ($scope.model.close) {
                $scope.model.close();
            }
        };
        /** changes the view to edit the params of the selected macro */
        /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/
        function editParams(insertIfNoParameters) {
            //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 
            insertIfNoParameters = typeof insertIfNoParameters !== 'undefined' ? insertIfNoParameters : true;
            //get the macro params if there are any
            macroResource.getMacroParameters($scope.model.selectedMacro.id).then(function (data) {
                //go to next page if there are params otherwise we can just exit
                if (!Utilities.isArray(data) || data.length === 0) {
                    if (insertIfNoParameters) {
                        $scope.model.submit($scope.model);
                    } else {
                        $scope.wizardStep = 'macroSelect';
                        displayA11yMessages($scope.macros);
                    }
                } else {
                    $scope.wizardStep = 'paramSelect';
                    $scope.model.macroParams = data;
                    //fill in the data if we are editing this macro
                    if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) {
                        _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) {
                            var prop = _.find($scope.model.macroParams, function (item) {
                                return item.alias == key;
                            });
                            if (prop) {
                                if (_.isString(val)) {
                                    //we need to unescape values as they have most likely been escaped while inserted
                                    val = _.unescape(val);
                                    //detect if it is a json string
                                    if (val.detectIsJson()) {
                                        try {
                                            //Parse it from json
                                            prop.value = Utilities.fromJson(val);
                                        } catch (e) {
                                            // not json
                                            prop.value = val;
                                        }
                                    } else {
                                        prop.value = val;
                                    }
                                } else {
                                    prop.value = val;
                                }
                            }
                        });
                    }
                }
            });
        }
        function displayA11yMessages(macros) {
            if ($scope.noMacroParams || !macros || macros.length === 0)
                localizationService.localize('general_searchNoResult').then(function (value) {
                    $scope.a11yInfo = value;
                });
            else if (macros) {
                if (macros.length === 1) {
                    localizationService.localize('treeSearch_searchResult').then(function (value) {
                        $scope.a11yInfo = '1 ' + value;
                    });
                } else {
                    localizationService.localize('treeSearch_searchResults').then(function (value) {
                        $scope.a11yInfo = macros.length + ' ' + value;
                    });
                }
            }
        }
        function displayA11YMessageForFilter() {
            var macros = _.filter($scope.macros, function (v) {
                return v.name.toLowerCase().includes($scope.model.searchTerm.toLowerCase());
            });
            displayA11yMessages(macros);
        }
        //here we check to see if we've been passed a selected macro and if so we'll set the
        //editor to start with parameter editing
        if ($scope.model.dialogData && $scope.model.dialogData.macroData) {
            $scope.wizardStep = 'paramSelect';
        }
        //get the macro list - pass in a filter if it is only for rte
        entityResource.getAll('Macro', $scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) {
            if (Utilities.isArray(data) && data.length == 0) {
                $scope.nomacros = true;
            }
            //if 'allowedMacros' is specified, we need to filter
            if (Utilities.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) {
                $scope.macros = _.filter(data, function (d) {
                    return _.contains($scope.model.dialogData.allowedMacros, d.alias);
                });
            } else {
                $scope.macros = data;
            }
            //check if there's a pre-selected macro and if it exists
            if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) {
                var found = _.find(data, function (item) {
                    return item.alias === $scope.model.dialogData.macroData.macroAlias;
                });
                if (found) {
                    //select the macro and go to next screen
                    $scope.model.selectedMacro = found;
                    editParams(true);
                    return;
                }
            }
            //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection)
            if ($scope.macros.length == 1) {
                $scope.model.selectedMacro = $scope.macros[0];
                editParams(false);
            } else {
                //we don't have a pre-selected macro so ensure the correct step is set
                $scope.wizardStep = 'macroSelect';
            }
            displayA11yMessages($scope.macros);
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Overlays.MacroPickerController', MacroPickerController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MediaEntryEditorController', function ($scope, localizationService, entityResource, editorService, overlayService, eventsService, mediaHelper) {
        var unsubscribe = [];
        var vm = this;
        vm.loading = true;
        vm.model = $scope.model;
        vm.mediaEntry = vm.model.mediaEntry;
        vm.currentCrop = null;
        vm.title = '';
        vm.focalPointChanged = focalPointChanged;
        vm.onImageLoaded = onImageLoaded;
        vm.openMedia = openMedia;
        vm.repickMedia = repickMedia;
        vm.selectCrop = selectCrop;
        vm.deselectCrop = deselectCrop;
        vm.resetCrop = resetCrop;
        vm.submitAndClose = submitAndClose;
        vm.close = close;
        function init() {
            localizationService.localizeMany([
                vm.model.createFlow ? 'general_cancel' : 'general_close',
                vm.model.createFlow ? 'general_create' : 'buttons_submitChanges'
            ]).then(function (data) {
                vm.closeLabel = data[0];
                vm.submitLabel = data[1];
            });
            updateMedia();
            unsubscribe.push(eventsService.on('editors.media.saved', function (name, args) {
                // if this media item uses the updated media type we want to reload the media file
                if (args && args.media && args.media.key === vm.mediaEntry.mediaKey) {
                    updateMedia();
                }
            }));
        }
        function updateMedia() {
            vm.loading = true;
            entityResource.getById(vm.mediaEntry.mediaKey, 'Media').then(function (mediaEntity) {
                vm.media = mediaEntity;
                vm.imageSrc = mediaHelper.resolveFileFromEntity(mediaEntity, true);
                vm.loading = false;
                vm.hasDimensions = false;
                vm.isCroppable = false;
                localizationService.localize('mediaPicker_editMediaEntryLabel', [
                    vm.media.name,
                    vm.model.documentName
                ]).then(function (data) {
                    vm.title = data;
                });
            }, function () {
                localizationService.localize('mediaPicker_deletedItem').then(function (localized) {
                    vm.media = {
                        name: localized,
                        icon: 'icon-picture',
                        trashed: true
                    };
                    vm.loading = false;
                    vm.hasDimensions = false;
                    vm.isCroppable = false;
                });
            });
        }
        function onImageLoaded(isCroppable, hasDimensions) {
            vm.isCroppable = isCroppable;
            vm.hasDimensions = hasDimensions;
        }
        function repickMedia() {
            vm.model.propertyEditor.changeMediaFor(vm.model.mediaEntry, onMediaReplaced);
        }
        function onMediaReplaced() {
            // mark we have changes:
            vm.imageCropperForm.$setDirty();
            // un-select crop:
            vm.currentCrop = null;
            //
            updateMedia();
        }
        function openMedia() {
            var mediaEditor = {
                id: vm.mediaEntry.mediaKey,
                submit: function submit() {
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.mediaEditor(mediaEditor);
        }
        function focalPointChanged(left, top) {
            //update the model focalpoint value
            vm.mediaEntry.focalPoint = {
                left: left,
                top: top
            };
            //set form to dirty to track changes
            setDirty();
        }
        function selectCrop(targetCrop) {
            vm.currentCrop = targetCrop;
            setDirty();    // TODO: start watchin values of crop, first when changed set to dirty.
        }
        function deselectCrop() {
            vm.currentCrop = null;
        }
        function resetCrop() {
            if (vm.currentCrop) {
                $scope.$evalAsync(function () {
                    vm.model.propertyEditor.resetCrop(vm.currentCrop);
                    vm.forceUpdateCrop = Math.random();
                });
            }
        }
        function setDirty() {
            vm.imageCropperForm.$setDirty();
        }
        function submitAndClose() {
            if (vm.model && vm.model.submit) {
                vm.model.submit(vm.model);
            }
        }
        function close() {
            if (vm.model && vm.model.close) {
                if (vm.model.createFlow === true || vm.imageCropperForm.$dirty === true) {
                    var labelKeys = vm.model.createFlow === true ? [
                        'mediaPicker_confirmCancelMediaEntryCreationHeadline',
                        'mediaPicker_confirmCancelMediaEntryCreationMessage'
                    ] : [
                        'prompt_discardChanges',
                        'mediaPicker_confirmCancelMediaEntryHasChanges'
                    ];
                    localizationService.localizeMany(labelKeys).then(function (localizations) {
                        var confirm = {
                            title: localizations[0],
                            view: 'default',
                            content: localizations[1],
                            submitButtonLabelKey: 'general_discard',
                            submitButtonStyle: 'danger',
                            closeButtonLabelKey: 'prompt_stay',
                            submit: function submit() {
                                overlayService.close();
                                vm.model.close(vm.model);
                            },
                            close: function close() {
                                overlayService.close();
                            }
                        };
                        overlayService.open(confirm);
                    });
                } else {
                    vm.model.close(vm.model);
                }
            }
        }
        init();
        $scope.$on('$destroy', function () {
            unsubscribe.forEach(function (x) {
                return x();
            });
        });
    });
    'use strict';
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.MediaPickerController', function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService, umbSessionStorage, notificationsService, clipboardService) {
        var vm = this;
        vm.submit = submit;
        vm.close = close;
        vm.toggle = toggle;
        vm.upload = upload;
        vm.dragLeave = dragLeave;
        vm.dragEnter = dragEnter;
        vm.onUploadComplete = onUploadComplete;
        vm.onFilesQueue = onFilesQueue;
        vm.changeSearch = changeSearch;
        vm.submitFolder = submitFolder;
        vm.enterSubmitFolder = enterSubmitFolder;
        vm.focalPointChanged = focalPointChanged;
        vm.changePagination = changePagination;
        vm.onNavigationChanged = onNavigationChanged;
        vm.clickClearClipboard = clickClearClipboard;
        vm.clickHandler = clickHandler;
        vm.clickItemName = clickItemName;
        vm.gotoFolder = gotoFolder;
        vm.toggleListView = toggleListView;
        vm.selectLayout = selectLayout;
        vm.showMediaList = false;
        vm.navigation = [];
        var dialogOptions = $scope.model;
        vm.clipboardItems = dialogOptions.clipboardItems;
        $scope.disableFolderSelect = dialogOptions.disableFolderSelect && dialogOptions.disableFolderSelect !== '0' ? true : false;
        $scope.disableFocalPoint = dialogOptions.disableFocalPoint && dialogOptions.disableFocalPoint !== '0' ? true : false;
        $scope.onlyImages = dialogOptions.onlyImages && dialogOptions.onlyImages !== '0' ? true : false;
        $scope.onlyFolders = dialogOptions.onlyFolders && dialogOptions.onlyFolders !== '0' ? true : false;
        $scope.showDetails = dialogOptions.showDetails && dialogOptions.showDetails !== '0' ? true : false;
        $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false;
        $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
        $scope.cropSize = dialogOptions.cropSize;
        $scope.lastOpenedNode = localStorageService.get('umbLastOpenedMediaNodeId');
        $scope.lockedFolder = true;
        $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false;
        $scope.filterOptions = { excludeSubFolders: umbSessionStorage.get('mediaPickerExcludeSubFolders') || false };
        var userStartNodes = [];
        var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
        var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
        if ($scope.onlyImages) {
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes);
        } else {
            // Use list of allowed file types if provided
            if (allowedUploadFiles !== '') {
                vm.acceptedFileTypes = allowedUploadFiles;
            } else {
                // If no allowed list, we pass in a disallowed list by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
        }
        vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
        $scope.model.selection = [];
        vm.acceptedMediatypes = [];
        mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) {
            vm.acceptedMediatypes = types;
        });
        var dataTypeKey = null;
        if ($scope.model && $scope.model.dataTypeKey) {
            dataTypeKey = $scope.model.dataTypeKey;
        }
        vm.searchOptions = {
            pageNumber: 1,
            pageSize: 100,
            totalItems: 0,
            totalPages: 0,
            filter: '',
            dataTypeKey: dataTypeKey
        };
        vm.layout = {
            layouts: [
                {
                    name: 'Grid',
                    icon: 'icon-thumbnails-small',
                    path: 'gridpath',
                    selected: true
                },
                {
                    name: 'List',
                    icon: 'icon-list',
                    path: 'listpath',
                    selected: true
                }
            ],
            activeLayout: {
                name: 'Grid',
                icon: 'icon-thumbnails-small',
                path: 'gridpath',
                selected: true
            }
        };
        // preload selected item
        $scope.target = null;
        if (dialogOptions.currentTarget) {
            $scope.target = dialogOptions.currentTarget;
        }
        function setTitle() {
            if (!$scope.model.title) {
                localizationService.localizeMany([
                    'defaultdialogs_selectMedia',
                    'mediaPicker_tabClipboard'
                ]).then(function (data) {
                    $scope.model.title = data[0];
                    vm.navigation = [{
                            'alias': 'empty',
                            'name': data[0],
                            'icon': 'icon-umb-media',
                            'active': true,
                            'view': ''
                        }];
                    if (vm.clipboardItems) {
                        vm.navigation.push({
                            'alias': 'clipboard',
                            'name': data[1],
                            'icon': 'icon-paste-in',
                            'view': '',
                            'disabled': vm.clipboardItems.length === 0
                        });
                    }
                    vm.activeTab = vm.navigation[0];
                });
            }
        }
        function onInit() {
            setTitle();
            userService.getCurrentUser().then(function (userData) {
                userStartNodes = userData.startMediaIds;
                if ($scope.startNodeId !== -1) {
                    entityResource.getById($scope.startNodeId, 'media').then(function (ent) {
                        $scope.startNodeId = ent.id;
                        run();
                    });
                } else {
                    run();
                }
            });
        }
        function run() {
            //default root item
            if (!$scope.target) {
                if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
                    entityResource.getById($scope.lastOpenedNode, 'media').then(ensureWithinStartNode, gotoStartNode);
                } else {
                    gotoStartNode();
                }
            } else {
                // if a target is specified, go look it up - generally this target will just contain ids not the actual full
                // media object so we need to look it up
                var originalTarget = $scope.target;
                var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
                var altText = $scope.target.altText;
                var caption = $scope.target.caption;
                // ID of a UDI or legacy int ID still could be null/undefinied here
                // As user may dragged in an image that has not been saved to media section yet
                if (id) {
                    entityResource.getById(id, 'Media').then(function (node) {
                        $scope.target = node;
                        // Moving directly to existing node's folder
                        gotoFolder({ id: node.parentId }).then(function () {
                            selectMedia(node);
                            $scope.target.url = mediaHelper.resolveFileFromEntity(node);
                            $scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true);
                            $scope.target.altText = altText;
                            $scope.target.caption = caption;
                            $scope.target.focalPoint = originalTarget.focalPoint;
                            $scope.target.coordinates = originalTarget.coordinates;
                            openDetailsDialog();
                        });
                    }, gotoStartNode);
                } else {
                    // No ID set - then this is going to be a tmpimg that has not been uploaded
                    // User editing this will want to be changing the ALT text
                    openDetailsDialog();
                }
            }
        }
        function upload(v) {
            var fileSelect = $('.umb-file-dropzone .file-select');
            if (fileSelect.length === 0) {
                localizationService.localize('media_uploadNotAllowed').then(function (message) {
                    notificationsService.warning(message);
                });
            } else {
                fileSelect.trigger('click');
            }
        }
        function dragLeave() {
            $scope.activeDrag = false;
        }
        function dragEnter() {
            $scope.activeDrag = true;
        }
        function submitFolder() {
            if ($scope.model.newFolderName) {
                $scope.model.creatingFolder = true;
                mediaResource.addFolder($scope.model.newFolderName, $scope.currentFolder.id).then(function (data) {
                    //we've added a new folder so lets clear the tree cache for that specific item
                    treeService.clearCache({
                        cacheKey: '__media',
                        //this is the main media tree cache key
                        childrenOf: data.parentId
                    });
                    $scope.model.creatingFolder = false;
                    gotoFolder(data);
                    $scope.model.showFolderInput = false;
                    $scope.model.newFolderName = '';
                });
            } else {
                $scope.model.showFolderInput = false;
            }
        }
        function enterSubmitFolder(event) {
            if (event.keyCode === 13) {
                submitFolder();
                event.stopPropagation();
            }
        }
        function gotoFolder(folder) {
            if (!$scope.multiPicker) {
                deselectAllMedia($scope.model.selection);
            }
            if (!folder) {
                folder = {
                    id: -1,
                    name: 'Media',
                    icon: 'icon-folder'
                };
            }
            if (folder.id > 0) {
                entityResource.getAncestors(folder.id, 'media', null, { dataTypeKey: dataTypeKey }).then(function (anc) {
                    $scope.path = _.filter(anc, function (f) {
                        return f.path.indexOf($scope.startNodeId) !== -1;
                    });
                });
            } else {
                $scope.path = [];
            }
            mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) {
                vm.acceptedMediatypes = types;
            });
            $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual || hasFolderAccess(folder) === false;
            $scope.currentFolder = folder;
            localStorageService.set('umbLastOpenedMediaNodeId', folder.id);
            return getChildren(folder.id);
        }
        function toggleListView() {
            vm.showMediaList = !vm.showMediaList;
        }
        function selectLayout(layout) {
            //this somehow doesn't set the 'active=true' property for the chosen layout
            vm.layout.activeLayout = layout;
            //workaround
            vm.layout.layouts.forEach(function (element) {
                return element.active = false;
            });
            layout.active = true;
            //set whether to toggle the list
            vm.showMediaList = layout.name === 'List';
        }
        function clickHandler(media, event, index) {
            if (media.isFolder) {
                if ($scope.disableFolderSelect) {
                    gotoFolder(media);
                } else {
                    selectMedia(media);
                }
            } else {
                if ($scope.showDetails) {
                    $scope.target = media;
                    // handle both entity and full media object
                    if (media.image) {
                        $scope.target.url = media.image;
                    } else {
                        $scope.target.url = mediaHelper.resolveFile(media);
                    }
                    openDetailsDialog();
                } else {
                    selectMedia(media);
                }
            }
        }
        function clickItemName(item, event, index) {
            if (item.isFolder) {
                gotoFolder(item);
            } else {
                clickHandler(item, event, index);
            }
        }
        ;
        function selectMedia(media) {
            if (!media.selectable) {
                return;
            }
            if (media.selected) {
                for (var i = 0; $scope.model.selection.length > i; i++) {
                    var imageInSelection = $scope.model.selection[i];
                    if (media.key === imageInSelection.key) {
                        media.selected = false;
                        $scope.model.selection.splice(i, 1);
                    }
                }
            } else {
                if (!$scope.multiPicker) {
                    deselectAllMedia($scope.model.selection);
                }
                eventsService.emit('dialogs.mediaPicker.select', media);
                media.selected = true;
                $scope.model.selection.push(media);
            }
        }
        function deselectAllMedia(medias) {
            for (var i = 0; i < medias.length; i++) {
                var media = medias[i];
                media.selected = false;
            }
            medias.length = 0;
        }
        function onUploadComplete(files) {
            gotoFolder($scope.currentFolder).then(function () {
                $timeout(function () {
                    if ($scope.multiPicker) {
                        var images = _.rest(_.sortBy($scope.images, 'id'), $scope.images.length - files.length);
                        images.forEach(function (image) {
                            return selectMedia(image);
                        });
                    } else {
                        var image = _.sortBy($scope.images, 'id')[$scope.images.length - 1];
                        clickHandler(image);
                    }
                });
            });
        }
        function onFilesQueue() {
            $scope.activeDrag = false;
        }
        function ensureWithinStartNode(node) {
            // make sure that last opened node is on the same path as start node
            var nodePath = node.path.split(',');
            // also make sure the node is not trashed
            if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) {
                gotoFolder({
                    id: $scope.lastOpenedNode || $scope.startNodeId,
                    name: 'Media',
                    icon: 'icon-folder',
                    path: node.path
                });
                return true;
            } else {
                gotoFolder({
                    id: $scope.startNodeId,
                    name: 'Media',
                    icon: 'icon-folder'
                });
                return false;
            }
        }
        function hasFolderAccess(node) {
            var nodePath = node.path ? node.path.split(',') : [node.id];
            for (var i = 0; i < nodePath.length; i++) {
                if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1)
                    return true;
            }
            return false;
        }
        function gotoStartNode() {
            gotoFolder({
                id: $scope.startNodeId,
                name: 'Media',
                icon: 'icon-folder'
            });
        }
        function openDetailsDialog() {
            var dialog = {
                size: 'small',
                cropSize: $scope.cropSize,
                target: $scope.target,
                disableFocalPoint: $scope.disableFocalPoint,
                submit: function submit() {
                    $scope.model.selection.push($scope.target);
                    $scope.model.submit($scope.model);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            localizationService.localize('defaultdialogs_editSelectedMedia').then(function (value) {
                dialog.title = value;
                editorService.mediaCropDetails(dialog);
            });
        }
        ;
        function onNavigationChanged(tab) {
            vm.activeTab.active = false;
            vm.activeTab = tab;
            vm.activeTab.active = true;
        }
        ;
        function clickClearClipboard() {
            vm.onNavigationChanged(vm.navigation[0]);
            vm.navigation[1].disabled = true;
            vm.clipboardItems = [];
            dialogOptions.clickClearClipboard();
        }
        ;
        var debounceSearchMedia = _.debounce(function () {
            $scope.$apply(function () {
                if (vm.searchOptions.filter) {
                    searchMedia();
                } else {
                    // reset pagination
                    vm.searchOptions = {
                        pageNumber: 1,
                        pageSize: 100,
                        totalItems: 0,
                        totalPages: 0,
                        filter: '',
                        dataTypeKey: dataTypeKey
                    };
                    getChildren($scope.currentFolder.id);
                }
            });
        }, 500);
        function changeSearch() {
            vm.loading = true;
            debounceSearchMedia();
        }
        function toggle() {
            umbSessionStorage.set('mediaPickerExcludeSubFolders', $scope.filterOptions.excludeSubFolders);
            // Make sure to activate the changeSearch function everytime the toggle is clicked
            changeSearch();
        }
        function changePagination(pageNumber) {
            vm.loading = true;
            vm.searchOptions.pageNumber = pageNumber;
            searchMedia();
        }
        ;
        function searchMedia() {
            vm.loading = true;
            entityResource.getPagedDescendants($scope.filterOptions.excludeSubFolders ? $scope.currentFolder.id : $scope.startNodeId, 'Media', vm.searchOptions).then(function (data) {
                // update image data to work with image grid
                if (data.items) {
                    var allowedTypes = dialogOptions.filter ? dialogOptions.filter.split(',') : null;
                    data.items.forEach(function (mediaItem) {
                        setMediaMetaData(mediaItem);
                        mediaItem.filtered = allowedTypes && allowedTypes.indexOf(mediaItem.metaData.ContentTypeAlias) < 0;
                    });
                }
                // update images
                $scope.images = data.items ? data.items : [];
                // update pagination
                if (data.pageNumber > 0)
                    vm.searchOptions.pageNumber = data.pageNumber;
                if (data.pageSize > 0)
                    vm.searchOptions.pageSize = data.pageSize;
                vm.searchOptions.totalItems = data.totalItems;
                vm.searchOptions.totalPages = data.totalPages;
                // set already selected medias to selected
                preSelectMedia();
                vm.loading = false;
            });
        }
        function setMediaMetaData(mediaItem) {
            // set thumbnail and src
            mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true);
            mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false);
            // set properties to match a media object
            if (mediaItem.metaData) {
                mediaItem.properties = [];
                if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) {
                    mediaItem.properties.push({
                        alias: 'umbracoWidth',
                        editor: mediaItem.metaData.umbracoWidth.PropertyEditorAlias,
                        value: mediaItem.metaData.umbracoWidth.Value
                    }, {
                        alias: 'umbracoHeight',
                        editor: mediaItem.metaData.umbracoHeight.PropertyEditorAlias,
                        value: mediaItem.metaData.umbracoHeight.Value
                    });
                }
                if (mediaItem.metaData.umbracoFile) {
                    // this is required for resolving files through the mediahelper
                    mediaItem.properties.push({
                        alias: 'umbracoFile',
                        editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias,
                        value: mediaItem.metaData.umbracoFile.Value
                    });
                }
                if (mediaItem.metaData.UpdateDate !== null) {
                    mediaItem.updateDate = mediaItem.metaData.UpdateDate;
                }
            }
        }
        function getChildren(id) {
            vm.loading = true;
            return entityResource.getChildren(id, 'Media', vm.searchOptions).then(function (data) {
                var allowedTypes = dialogOptions.filter ? dialogOptions.filter.split(',') : null;
                for (var i = 0; i < data.length; i++) {
                    setDefaultData(data[i]);
                    data[i].filtered = allowedTypes && allowedTypes.indexOf(data[i].metaData.ContentTypeAlias) < 0;
                }
                vm.searchOptions.filter = '';
                $scope.images = data ? data : [];
                // set already selected medias to selected
                preSelectMedia();
                vm.loading = false;
            });
        }
        function setDefaultData(item) {
            if (item.metaData.MediaPath !== null) {
                item.thumbnail = mediaHelper.resolveFileFromEntity(item, true);
                item.image = mediaHelper.resolveFileFromEntity(item, false);
            }
            if (item.metaData.UpdateDate !== null) {
                item.updateDate = item.metaData.UpdateDate;
            }
        }
        function preSelectMedia() {
            for (var folderIndex = 0; folderIndex < $scope.images.length; folderIndex++) {
                var folderImage = $scope.images[folderIndex];
                var imageIsSelected = false;
                if ($scope.model && Utilities.isArray($scope.model.selection)) {
                    for (var selectedIndex = 0; selectedIndex < $scope.model.selection.length; selectedIndex++) {
                        var selectedImage = $scope.model.selection[selectedIndex];
                        if (folderImage.key === selectedImage.key) {
                            imageIsSelected = true;
                        }
                    }
                }
                if (imageIsSelected) {
                    folderImage.selected = true;
                }
            }
        }
        /**
   * Called when the umbImageGravity component updates the focal point value
   * @param {any} left
   * @param {any} top
   */
        function focalPointChanged(left, top) {
            // update the model focalpoint value
            $scope.target.focalPoint = {
                left: left,
                top: top
            };
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        function close() {
            if ($scope.model && $scope.model.close) {
                $scope.model.close($scope.model);
            }
        }
        onInit();
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MediaCropDetailsController', function ($scope) {
        var vm = this;
        vm.submit = submit;
        vm.close = close;
        vm.hasCrops = cropSet() === true;
        vm.focalPointChanged = focalPointChanged;
        vm.disableFocalPoint = false;
        if (typeof $scope.model.disableFocalPoint === 'boolean') {
            vm.disableFocalPoint = $scope.model.disableFocalPoint;
        } else {
            vm.disableFocalPoint = $scope.model.disableFocalPoint !== undefined && $scope.model.disableFocalPoint !== '0' ? true : false;
        }
        if (!$scope.model.target.coordinates && !$scope.model.target.focalPoint) {
            $scope.model.target.focalPoint = {
                left: 0.5,
                top: 0.5
            };
        }
        if (!$scope.model.target.image) {
            $scope.model.target.image = $scope.model.target.url;
        }
        if (!$scope.model.target || $scope.model.target.id || $scope.model.target.url && $scope.model.target.url.toLowerCase().startsWith('blob:')) {
            vm.shouldShowUrl = false;
        } else {
            vm.shouldShowUrl = true;
        }
        /**
   * Called when the umbImageGravity component updates the focal point value
   * @param {any} left
   * @param {any} top
   */
        function focalPointChanged(left, top) {
            // update the model focalpoint value
            $scope.model.target.focalPoint = {
                left: left,
                top: top
            };
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        function close() {
            if ($scope.model && $scope.model.close) {
                $scope.model.close($scope.model);
            }
        }
        function cropSet() {
            var model = $scope.model;
            return (model.cropSize || {}).width !== undefined && (model.cropSize || {}).height !== undefined;
        }
    });
    'use strict';
    //used for the member picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.MemberGroupPickerController', function ($scope, eventsService, localizationService) {
        var vm = this;
        $scope.dialogTreeApi = {};
        $scope.multiPicker = $scope.model.multiPicker;
        vm.submit = submit;
        vm.close = close;
        function activate() {
            if (!$scope.model.title) {
                localizationService.localize('defaultdialogs_selectMemberGroup').then(function (value) {
                    $scope.model.title = value;
                });
            }
            if ($scope.multiPicker) {
                $scope.model.selectedMemberGroups = [];
            } else {
                $scope.model.selectedMemberGroup = '';
            }
        }
        function selectMemberGroup(id) {
            $scope.model.selectedMemberGroup = id;
        }
        function selectMemberGroups(id) {
            var index = $scope.model.selectedMemberGroups.indexOf(id);
            if (index === -1) {
                // If the id does not exists in the array then add it
                $scope.model.selectedMemberGroups.push(id);
            } else {
                // Otherwise we will remove it from the array instead
                $scope.model.selectedMemberGroups.splice(index, 1);
            }
        }
        /** Method used for selecting a node */
        function select(text, id) {
            if ($scope.model.multiPicker) {
                selectMemberGroups(id);
            } else {
                selectMemberGroup(id);
                $scope.model.submit($scope.model);
            }
        }
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            eventsService.emit('dialogs.memberGroupPicker.select', args);
            //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
            //from the server in this method.
            select(args.node.name, args.node.id);
            //toggle checked state
            args.node.selected = args.node.selected === true ? false : true;
        }
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        function close() {
            if ($scope.model && $scope.model.close) {
                $scope.model.close();
            }
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        activate();
    });
    'use strict';
    (function () {
        'use strict';
        function MoveController($scope, localizationService, entityHelper) {
            var vm = this;
            vm.hideSearch = hideSearch;
            vm.selectResult = selectResult;
            vm.onSearchResults = onSearchResults;
            vm.submit = submit;
            vm.close = close;
            var dialogOptions = $scope.model;
            var searchText = 'Search...';
            var node = dialogOptions.currentNode;
            $scope.model.relateToOriginal = true;
            $scope.dialogTreeApi = {};
            vm.searchInfo = {
                searchFromId: null,
                searchFromName: null,
                showSearch: false,
                results: [],
                selectedSearchResults: []
            };
            // get entity type based on the section
            $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
            function onInit() {
                if (!$scope.model.title) {
                    localizationService.localize('actions_move').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                localizationService.localize('general_search').then(function (value) {
                    searchText = value + '...';
                });
            }
            function nodeSelectHandler(args) {
                if (args && args.event) {
                    args.event.preventDefault();
                    args.event.stopPropagation();
                }
                //eventsService.emit("editors.content.copyController.select", args);
                if ($scope.model.target) {
                    //un-select if there's a current one selected
                    $scope.model.target.selected = false;
                }
                $scope.model.target = args.node;
                $scope.model.target.selected = true;
            }
            function nodeExpandedHandler(args) {
                // open mini list view for list views
                if (args.node.metaData.isContainer) {
                    openMiniListView(args.node);
                }
            }
            function hideSearch() {
                vm.searchInfo.showSearch = false;
                vm.searchInfo.searchFromId = null;
                vm.searchInfo.searchFromName = null;
                vm.searchInfo.results = [];
            }
            // method to select a search result
            function selectResult(evt, result) {
                result.selected = result.selected === true ? false : true;
                nodeSelectHandler({
                    event: evt,
                    node: result
                });
            }
            //callback when there are search results
            function onSearchResults(results) {
                vm.searchInfo.results = results;
                vm.searchInfo.showSearch = true;
            }
            $scope.onTreeInit = function () {
                $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
                $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
            };
            // Mini list view
            $scope.selectListViewNode = function (node) {
                node.selected = node.selected === true ? false : true;
                nodeSelectHandler({ node: node });
            };
            $scope.closeMiniListView = function () {
                $scope.miniListView = undefined;
            };
            function openMiniListView(node) {
                $scope.miniListView = node;
            }
            function submit() {
                if ($scope.model && $scope.model.submit) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.MoveController', MoveController);
    }());
    'use strict';
    (function () {
        'use strict';
        function NodePermissionsController($scope, localizationService) {
            var vm = this;
            vm.submit = submit;
            vm.close = close;
            function onInit() {
                // set default title
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_permissionsEdit').then(function (value) {
                        $scope.model.title = value + ' ' + $scope.model.node.name;
                    });
                }
            }
            function submit() {
                if ($scope.model.submit) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.NodePermissionsController', NodePermissionsController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.PropertySettingsController
 * @function
 *
 * @description
 * The controller for the content type editor property settings dialog
 */
    (function () {
        'use strict';
        function PropertySettingsEditor($scope, contentTypeResource, dataTypeResource, dataTypeHelper, formHelper, localizationService, userService, editorService) {
            var vm = this;
            vm.showValidationPattern = false;
            vm.focusOnPatternField = false;
            vm.focusOnMandatoryField = false;
            vm.selectedValidationType = null;
            vm.validationTypes = [];
            vm.labels = {};
            vm.changeValidationType = changeValidationType;
            vm.changeValidationPattern = changeValidationPattern;
            vm.openDataTypePicker = openDataTypePicker;
            vm.openDataTypeSettings = openDataTypeSettings;
            vm.submitOnEnter = submitOnEnter;
            vm.submit = submit;
            vm.close = close;
            vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
            vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants;
            vm.toggleValidation = toggleValidation;
            vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile;
            vm.toggleMemberCanEdit = toggleMemberCanEdit;
            vm.toggleIsSensitiveData = toggleIsSensitiveData;
            vm.toggleLabelOnTop = toggleLabelOnTop;
            function onInit() {
                userService.getCurrentUser().then(function (user) {
                    vm.showSensitiveData = user.userGroups.indexOf('sensitiveData') != -1;
                });
                //make the default the same as the content type
                if (!$scope.model.property.dataTypeId) {
                    $scope.model.property.allowCultureVariant = $scope.model.contentTypeAllowCultureVariant;
                }
                loadValidationTypes();
            }
            function loadValidationTypes() {
                var labels = [
                    'validation_validateAsEmail',
                    'validation_validateAsNumber',
                    'validation_validateAsUrl',
                    'validation_enterCustomValidation',
                    'validation_fieldIsMandatory',
                    'contentTypeEditor_displaySettingsLabelOnTop'
                ];
                localizationService.localizeMany(labels).then(function (data) {
                    vm.labels.validateAsEmail = data[0];
                    vm.labels.validateAsNumber = data[1];
                    vm.labels.validateAsUrl = data[2];
                    vm.labels.customValidation = data[3];
                    vm.labels.fieldIsMandatory = data[4];
                    vm.labels.displaySettingsLabelOnTop = data[5];
                    vm.validationTypes = [
                        {
                            'name': vm.labels.validateAsEmail,
                            'key': 'email',
                            'pattern': '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+',
                            'enableEditing': true
                        },
                        {
                            'name': vm.labels.validateAsNumber,
                            'key': 'number',
                            'pattern': '^[0-9]*$',
                            'enableEditing': true
                        },
                        {
                            'name': vm.labels.validateAsUrl,
                            'key': 'url',
                            'pattern': 'https?://[a-zA-Z0-9-.]+.[a-zA-Z]{2,}',
                            'enableEditing': true
                        },
                        {
                            'name': vm.labels.customValidation,
                            'key': 'custom',
                            'pattern': '',
                            'enableEditing': true
                        }
                    ];
                    matchValidationType();
                });
            }
            function changeValidationPattern() {
                matchValidationType();
            }
            function openDataTypePicker(property) {
                vm.focusOnMandatoryField = false;
                var dataTypePicker = {
                    property: $scope.model.property,
                    contentTypeName: $scope.model.contentTypeName,
                    view: 'views/common/infiniteeditors/datatypepicker/datatypepicker.html',
                    size: 'medium',
                    submit: function submit(model) {
                        $scope.model.updateSameDataTypes = model.updateSameDataTypes;
                        vm.focusOnMandatoryField = true;
                        // update property
                        property.config = model.property.config;
                        property.editor = model.property.editor;
                        property.view = model.property.view;
                        property.dataTypeId = model.property.dataTypeId;
                        property.dataTypeIcon = model.property.dataTypeIcon;
                        property.dataTypeName = model.property.dataTypeName;
                        editorService.close();
                    },
                    close: function close(model) {
                        editorService.close();
                    }
                };
                editorService.open(dataTypePicker);
            }
            function openDataTypeSettings(property) {
                vm.focusOnMandatoryField = false;
                var dataTypeSettings = {
                    view: 'views/common/infiniteeditors/datatypesettings/datatypesettings.html',
                    id: property.dataTypeId,
                    submit: function submit(model) {
                        contentTypeResource.getPropertyTypeScaffold(model.dataType.id).then(function (propertyType) {
                            // update editor
                            property.config = propertyType.config;
                            property.editor = propertyType.editor;
                            property.view = propertyType.view;
                            property.dataTypeId = model.dataType.id;
                            property.dataTypeIcon = model.dataType.icon;
                            property.dataTypeName = model.dataType.name;
                            // set flag to update same data types
                            $scope.model.updateSameDataTypes = true;
                            vm.focusOnMandatoryField = true;
                            editorService.close();
                        });
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.open(dataTypeSettings);
            }
            function submitOnEnter(event) {
                if (event && event.keyCode === 13) {
                    submit();
                }
            }
            function submit() {
                if ($scope.model.submit) {
                    if (formHelper.submitForm({ scope: $scope })) {
                        $scope.model.submit($scope.model);
                    }
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            function matchValidationType() {
                if ($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== '' && $scope.model.property.validation.pattern !== undefined) {
                    var match = false;
                    // find and show if a match from the list has been chosen
                    vm.validationTypes.forEach(function (validationType, index) {
                        if ($scope.model.property.validation.pattern === validationType.pattern) {
                            vm.selectedValidationType = vm.validationTypes[index];
                            vm.showValidationPattern = true;
                            match = true;
                        }
                    });
                    // if there is no match - choose the custom validation option.
                    if (!match) {
                        vm.validationTypes.forEach(function (validationType) {
                            if (validationType.key === 'custom') {
                                vm.selectedValidationType = validationType;
                                vm.showValidationPattern = true;
                            }
                        });
                    }
                }
            }
            function changeValidationType(selectedValidationType) {
                if (selectedValidationType) {
                    $scope.model.property.validation.pattern = selectedValidationType.pattern;
                    vm.showValidationPattern = true;
                    // set focus on textarea
                    if (selectedValidationType.key === 'custom') {
                        vm.focusOnPatternField = true;
                    }
                } else {
                    $scope.model.property.validation.pattern = '';
                    vm.showValidationPattern = false;
                }
            }
            function toggleValue(settingValue) {
                return !settingValue;
            }
            function toggleAllowCultureVariants() {
                $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant);
            }
            function toggleAllowSegmentVariants() {
                $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant);
            }
            function toggleValidation() {
                $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory);
            }
            function toggleShowOnMemberProfile() {
                $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile);
            }
            function toggleMemberCanEdit() {
                $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit);
            }
            function toggleIsSensitiveData() {
                $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData);
            }
            function toggleLabelOnTop() {
                $scope.model.property.labelOnTop = toggleValue($scope.model.property.labelOnTop);
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PropertySettingsController', PropertySettingsEditor);
    }());
    'use strict';
    (function () {
        'use strict';
        function QueryBuilderOverlayController($scope, templateQueryResource, localizationService, editorService) {
            var everything = '';
            var myWebsite = '';
            var ascendingTranslation = '';
            var descendingTranslation = '';
            var vm = this;
            vm.properties = [];
            vm.contentTypes = [];
            vm.conditions = [];
            vm.datePickerConfig = { dateFormat: 'Y-m-d' };
            vm.chooseSource = chooseSource;
            vm.getPropertyOperators = getPropertyOperators;
            vm.addFilter = addFilter;
            vm.trashFilter = trashFilter;
            vm.changeSortOrder = changeSortOrder;
            vm.setSortProperty = setSortProperty;
            vm.setContentType = setContentType;
            vm.setFilterProperty = setFilterProperty;
            vm.setFilterTerm = setFilterTerm;
            vm.changeConstraintValue = changeConstraintValue;
            vm.datePickerChange = datePickerChange;
            vm.submit = submit;
            vm.close = close;
            function onInit() {
                if (!$scope.model.title) {
                    localizationService.localize('template_queryBuilder').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                vm.query = {
                    contentType: { name: everything },
                    source: { name: myWebsite },
                    filters: [{
                            property: undefined,
                            operator: undefined
                        }],
                    sort: {
                        property: {
                            alias: '',
                            name: ''
                        },
                        direction: 'ascending',
                        //This is the value for sorting sent to server
                        translation: {
                            currentLabel: ascendingTranslation,
                            //This is the localized UI value in the the dialog
                            ascending: ascendingTranslation,
                            descending: descendingTranslation
                        }
                    }
                };
                templateQueryResource.getAllowedProperties().then(function (properties) {
                    vm.properties = properties;
                });
                templateQueryResource.getContentTypes().then(function (contentTypes) {
                    vm.contentTypes = contentTypes;
                });
                templateQueryResource.getFilterConditions().then(function (conditions) {
                    vm.conditions = conditions;
                });
                throttledFunc();
            }
            function chooseSource(query) {
                var contentPicker = {
                    submit: function submit(model) {
                        var selectedNodeId = model.selection[0].id;
                        var selectedNodeName = model.selection[0].name;
                        if (selectedNodeId > 0) {
                            query.source = {
                                id: selectedNodeId,
                                name: selectedNodeName
                            };
                        } else {
                            query.source.name = myWebsite;
                            delete query.source.id;
                        }
                        throttledFunc();
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.contentPicker(contentPicker);
            }
            function getPropertyOperators(property) {
                var conditions = _.filter(vm.conditions, function (condition) {
                    var index = condition.appliesTo.indexOf(property.type);
                    return index >= 0;
                });
                return conditions;
            }
            function addFilter(query) {
                query.filters.push({});
            }
            function trashFilter(query, filter) {
                for (var i = 0; i < query.filters.length; i++) {
                    if (query.filters[i] == filter) {
                        query.filters.splice(i, 1);
                    }
                }
                //if we remove the last one, add a new one to generate ui for it.
                if (query.filters.length == 0) {
                    query.filters.push({});
                }
            }
            function changeSortOrder(query) {
                if (query.sort.direction === 'ascending') {
                    query.sort.direction = 'descending';
                    query.sort.translation.currentLabel = query.sort.translation.descending;
                } else {
                    query.sort.direction = 'ascending';
                    query.sort.translation.currentLabel = query.sort.translation.ascending;
                }
                throttledFunc();
            }
            function setSortProperty(query, property) {
                query.sort.property = property;
                if (property.type === 'datetime') {
                    query.sort.direction = 'descending';
                    query.sort.translation.currentLabel = query.sort.translation.descending;
                } else {
                    query.sort.direction = 'ascending';
                    query.sort.translation.currentLabel = query.sort.translation.ascending;
                }
                throttledFunc();
            }
            function setContentType(contentType) {
                vm.query.contentType = contentType;
                throttledFunc();
            }
            function setFilterProperty(filter, property) {
                filter.property = property;
                filter.term = {};
                filter.constraintValue = '';
            }
            function setFilterTerm(filter, term) {
                filter.term = term;
                if (filter.constraintValue) {
                    throttledFunc();
                }
            }
            function changeConstraintValue() {
                throttledFunc();
            }
            function datePickerChange(date, filter) {
                var momentDate = moment(date);
                if (momentDate && momentDate.isValid()) {
                    filter.constraintValue = momentDate.format(vm.datePickerConfig.format);
                    throttledFunc();
                }
            }
            function submit(model) {
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            var throttledFunc = _.throttle(function () {
                templateQueryResource.postTemplateQuery(vm.query).then(function (response) {
                    $scope.model.result = response;
                });
            }, 200);
            localizationService.localizeMany([
                'template_allContent',
                'template_websiteRoot',
                'template_ascending',
                'template_descending'
            ]).then(function (res) {
                everything = res[0];
                myWebsite = res[1];
                ascendingTranslation = res[2];
                descendingTranslation = res[3];
                onInit();
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.QueryBuilderController', QueryBuilderOverlayController);
    }());
    'use strict';
    (function () {
        'use strict';
        function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService, notificationsService) {
            var vm = this;
            vm.rollback = rollback;
            vm.changeLanguage = changeLanguage;
            vm.changeVersion = changeVersion;
            vm.submit = submit;
            vm.close = close;
            vm.pinVersion = pinVersion;
            vm.goToPage = goToPage;
            vm.paginationCount = {
                from: 0,
                to: 0,
                total: 0
            };
            //////////
            function onInit() {
                vm.loading = true;
                vm.variantVersions = [];
                vm.diff = null;
                vm.currentVersion = null;
                vm.rollbackButtonDisabled = true;
                vm.labels = {};
                vm.pageSize = 15;
                vm.pageNumber = 1;
                vm.totalPages = 1;
                vm.totalItems = 0;
                // find the current version for invariant nodes
                if ($scope.model.node.variants.length === 1) {
                    vm.currentVersion = $scope.model.node.variants[0];
                }
                // find the current version for nodes with variants
                if ($scope.model.node.variants.length > 1) {
                    var active = _.find($scope.model.node.variants, function (v) {
                        return v.active;
                    });
                    // preselect the language in the dropdown
                    if (active) {
                        vm.selectedLanguage = active;
                        vm.currentVersion = active;
                    }
                }
                localizationService.localizeMany([
                    'actions_rollback',
                    'general_choose'
                ]).then(function (data) {
                    // set default title
                    if (!$scope.model.title) {
                        $scope.model.title = data[0];
                    }
                    vm.labels.choose = data[1];
                });
                // Load in diff library
                assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () {
                    getVersions().then(function () {
                        vm.loading = false;
                    });
                });
            }
            function changeLanguage(language) {
                vm.currentVersion = language;
                vm.pageNumber = 1;
                getVersions();
            }
            function changeVersion(version) {
                var canRollback = !version.currentDraftVersion && !version.currentPublishedVersion;
                if (canRollback === false) {
                    return;
                }
                if (vm.previousVersion && version && vm.previousVersion.versionId === version.versionId) {
                    vm.previousVersion = null;
                    vm.diff = null;
                    vm.rollbackButtonDisabled = true;
                    return;
                }
                if (version && version.versionId) {
                    vm.loadingDiff = true;
                    var culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
                    contentResource.getRollbackVersion(version.versionId, culture).then(function (data) {
                        vm.previousVersion = data;
                        vm.previousVersion.versionId = version.versionId;
                        vm.previousVersion.displayValue = version.displayValue + ' - ' + version.username;
                        createDiff(vm.currentVersion, vm.previousVersion);
                        vm.loadingDiff = false;
                        vm.rollbackButtonDisabled = false;
                    }, function () {
                        vm.loadingDiff = false;
                    });
                } else {
                    vm.diff = null;
                    vm.rollbackButtonDisabled = true;
                }
            }
            function getVersions() {
                var nodeId = $scope.model.node.id;
                var culture = vm.currentVersion.language ? vm.currentVersion.language.culture : null;
                return contentResource.getPagedContentVersions(nodeId, vm.pageNumber, vm.pageSize, culture).then(function (data) {
                    vm.totalPages = data.totalPages;
                    vm.totalItems = data.totalItems;
                    var possibleTotalItems = vm.pageNumber * vm.pageSize;
                    vm.paginationCount = {
                        from: vm.pageNumber * vm.pageSize - vm.pageSize + 1,
                        to: vm.totalItems < possibleTotalItems ? vm.totalItems : possibleTotalItems,
                        total: vm.totalItems
                    };
                    // get current backoffice user and format dates
                    userService.getCurrentUser().then(function (currentUser) {
                        vm.previousVersions = data.items.map(function (version) {
                            var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL');
                            version.displayValue = timestampFormatted;
                            return version;
                        });
                    });
                });
            }
            /**
     * This will load in a new version
     */
            function createDiff(currentVersion, previousVersion) {
                vm.diff = {};
                vm.diff.properties = [];
                // find diff in name
                vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name);
                // extract all properties from the tabs and create new object for the diff
                currentVersion.tabs.forEach(function (tab) {
                    tab.properties.forEach(function (property) {
                        var oldTabIndex = -1;
                        var oldTabPropertyIndex = -1;
                        var previousVersionTabs = previousVersion.tabs;
                        // find the property by alias, but only search until we find it
                        for (var oti = 0, length = previousVersionTabs.length; oti < length; oti++) {
                            var opi = previousVersionTabs[oti].properties.findIndex(function (p) {
                                return p.alias === property.alias;
                            });
                            if (opi !== -1) {
                                oldTabIndex = oti;
                                oldTabPropertyIndex = opi;
                                break;
                            }
                        }
                        if (oldTabIndex !== -1 && oldTabPropertyIndex !== -1) {
                            var oldProperty = previousVersion.tabs[oldTabIndex].properties[oldTabPropertyIndex];
                            // copy existing properties, so it doesn't manipulate existing properties on page
                            oldProperty = Utilities.copy(oldProperty);
                            property = Utilities.copy(property);
                            // we have to make properties storing values as object into strings (Grid, nested content, etc.)
                            if (property.value instanceof Object) {
                                property.value = JSON.stringify(property.value, null, 1);
                                property.isObject = true;
                            }
                            if (oldProperty.value instanceof Object) {
                                oldProperty.value = JSON.stringify(oldProperty.value, null, 1);
                                oldProperty.isObject = true;
                            }
                            // diff requires a string
                            property.value = property.value ? property.value + '' : '';
                            oldProperty.value = oldProperty.value ? oldProperty.value + '' : '';
                            var diffProperty = {
                                'alias': property.alias,
                                'label': property.label,
                                'diff': property.isObject ? JsDiff.diffJson(property.value, oldProperty.value) : JsDiff.diffWords(property.value, oldProperty.value),
                                'isObject': property.isObject || oldProperty.isObject
                            };
                            vm.diff.properties.push(diffProperty);
                        }
                    });
                });
            }
            function rollback() {
                vm.rollbackButtonState = 'busy';
                var nodeId = $scope.model.node.id;
                var versionId = vm.previousVersion.versionId;
                var culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
                return contentResource.rollback(nodeId, versionId, culture).then(function (data) {
                    vm.rollbackButtonState = 'success';
                    submit();
                }, function (error) {
                    vm.rollbackButtonState = 'error';
                });
            }
            function submit() {
                if ($scope.model.submit) {
                    $scope.model.submit($scope.model.submit);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            function pinVersion(version, event) {
                if (!version) {
                    return;
                }
                version.pinningState = 'busy';
                var nodeId = $scope.model.node.id;
                var versionId = version.versionId;
                var preventCleanup = !version.preventCleanup;
                contentResource.contentVersionPreventCleanup(nodeId, versionId, preventCleanup).then(function () {
                    version.pinningState = 'success';
                    version.preventCleanup = preventCleanup;
                }, function () {
                    version.pinningState = 'error';
                    var localizationKey = preventCleanup ? 'speechBubbles_preventCleanupEnableError' : 'speechBubbles_preventCleanupDisableError';
                    localizationService.localize(localizationKey).then(function (value) {
                        notificationsService.error(value);
                    });
                });
                event.stopPropagation();
            }
            function goToPage(pageNumber) {
                vm.pageNumber = pageNumber;
                getVersions();
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.RollbackController', RollbackController);
    }());
    'use strict';
    (function () {
        'use strict';
        function SectionPickerController($scope, sectionResource, localizationService) {
            var vm = this;
            vm.sections = [];
            vm.loading = false;
            vm.selectSection = selectSection;
            vm.submit = submit;
            vm.close = close;
            //////////
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectSections').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get sections
                sectionResource.getAllSections().then(function (sections) {
                    vm.sections = sections;
                    setSectionIcon(vm.sections);
                    if ($scope.model.selection && $scope.model.selection.length > 0) {
                        preSelect($scope.model.selection);
                    }
                    vm.loading = false;
                });
            }
            function preSelect(selection) {
                selection.forEach(function (selected) {
                    vm.sections.forEach(function (section) {
                        if (selected.alias === section.alias) {
                            section.selected = true;
                        }
                    });
                });
            }
            function selectSection(section) {
                if (!section.selected) {
                    section.selected = true;
                    $scope.model.selection.push(section);
                } else {
                    $scope.model.selection.forEach(function (selectedSection, index) {
                        if (selectedSection.alias === section.alias) {
                            section.selected = false;
                            $scope.model.selection.splice(index, 1);
                        }
                    });
                }
            }
            function setSectionIcon(sections) {
                sections.forEach(function (section) {
                    section.icon = 'icon-section';
                });
            }
            function submit(model) {
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.SectionPickerController', SectionPickerController);
    }());
    'use strict';
    (function () {
        'use strict';
        function TemplateSectionsController($scope, formHelper, localizationService) {
            var vm = this;
            vm.labels = {};
            vm.select = select;
            vm.submit = submit;
            vm.close = close;
            $scope.model.mandatoryRenderSection = false;
            function onInit() {
                if ($scope.model.hasMaster) {
                    $scope.model.insertType = 'addSection';
                } else {
                    $scope.model.insertType = 'renderBody';
                }
                var labelKeys = [
                    'template_insertSections',
                    'template_sectionMandatory'
                ];
                localizationService.localizeMany(labelKeys).then(function (data) {
                    vm.labels.title = data[0];
                    vm.labels.sectionMandatory = data[1];
                    setTitle(vm.labels.title);
                });
            }
            function setTitle(value) {
                if (!$scope.model.title) {
                    $scope.model.title = value;
                }
            }
            function select(type) {
                $scope.model.insertType = type;
            }
            function submit(model) {
                if (formHelper.submitForm({ scope: $scope })) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.TemplateSectionsController', TemplateSectionsController);
    }());
    'use strict';
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.TreePickerController', function ($scope, entityResource, eventsService, angularHelper, $timeout, localizationService, treeService, languageResource) {
        //used as the result selection
        $scope.model.selection = [];
        //the tree object when it loads
        var tree = null;
        // Search and listviews is only working for content, media and member section
        var searchableSections = [
            'content',
            'media',
            'member'
        ];
        // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths
        var expandedPaths = [];
        var vm = this;
        vm.treeReady = false;
        vm.dialogTreeApi = {};
        vm.initDialogTree = initDialogTree;
        vm.section = $scope.model.section;
        vm.treeAlias = $scope.model.treeAlias;
        vm.multiPicker = $scope.model.multiPicker;
        vm.hideHeader = typeof $scope.model.hideHeader === 'boolean' ? $scope.model.hideHeader : true;
        vm.dataTypeKey = $scope.model.dataTypeKey;
        vm.searchInfo = {
            searchFromId: $scope.model.startNodeId,
            searchFromName: null,
            showSearch: false,
            dataTypeKey: vm.dataTypeKey,
            results: [],
            selectedSearchResults: []
        };
        vm.startNodeId = $scope.model.startNodeId;
        //Used for toggling an empty-state message
        //Some trees can have no items (dictionary & forms email templates)
        vm.hasItems = true;
        vm.emptyStateMessage = $scope.model.emptyStateMessage;
        vm.languages = [];
        vm.selectedLanguage = {};
        vm.languageSelectorIsOpen = false;
        vm.showLanguageSelector = $scope.model.showLanguageSelector;
        // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
        vm.entityType = $scope.model.entityType ? $scope.model.entityType : 'Document';
        vm.enableSearh = searchableSections.indexOf(vm.section) !== -1;
        vm.toggleLanguageSelector = toggleLanguageSelector;
        vm.selectLanguage = selectLanguage;
        vm.onSearchResults = onSearchResults;
        vm.selectResult = selectResult;
        vm.hideSearch = hideSearch;
        vm.closeMiniListView = closeMiniListView;
        vm.selectListViewNode = selectListViewNode;
        vm.listViewItemsLoaded = listViewItemsLoaded;
        vm.submit = submit;
        vm.close = close;
        var currentNode = $scope.model.currentNode;
        var previouslyFocusedElement = null;
        function initDialogTree() {
            vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
            // TODO: Also deal with unexpanding!!
            vm.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
            vm.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        }
        /**
   * Performs the initialization of this component
   */
        function onInit() {
            if (vm.showLanguageSelector) {
                // load languages
                languageResource.getAll().then(function (languages) {
                    vm.languages = languages;
                    // set the default language
                    vm.languages.forEach(function (language) {
                        if (language.isDefault) {
                            vm.selectedLanguage = language;
                            vm.languageSelectorIsOpen = false;
                        }
                    });
                });
            }
            if (vm.treeAlias === 'content') {
                vm.entityType = 'Document';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectContent').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'documentTypes') {
                vm.entityType = 'DocumentType';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectContentType').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'member' || vm.section === 'member') {
                vm.entityType = 'Member';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectMember').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'memberTypes') {
                vm.entityType = 'MemberType';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectMemberType').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'media' || vm.section === 'media') {
                vm.entityType = 'Media';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectMedia').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'mediaTypes') {
                vm.entityType = 'MediaType';
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_selectMediaType').then(function (value) {
                        $scope.model.title = value;
                    });
                }
            } else if (vm.treeAlias === 'templates') {
                vm.entityType = 'Template';
            }
            // TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this
            if ($scope.model.minNumber) {
                $scope.model.minNumber = parseInt($scope.model.minNumber, 10);
            }
            if ($scope.model.maxNumber) {
                $scope.model.maxNumber = parseInt($scope.model.maxNumber, 10);
            }
            //if a alternative startnode is used, we need to check if it is a container
            if (vm.enableSearh && vm.startNodeId && vm.startNodeId !== -1 && vm.startNodeId !== '-1') {
                entityResource.getById(vm.startNodeId, vm.entityType).then(function (node) {
                    if (node.metaData.IsContainer) {
                        openMiniListView(node);
                    }
                    initTree();
                });
            } else {
                initTree();
            }
            //Configures filtering
            if ($scope.model.filter) {
                $scope.model.filterExclude = false;
                $scope.model.filterAdvanced = false;
                //used advanced filtering
                if (Utilities.isFunction($scope.model.filter)) {
                    $scope.model.filterAdvanced = true;
                } else if (Utilities.isObject($scope.model.filter)) {
                    $scope.model.filterAdvanced = true;
                } else {
                    if ($scope.model.filter.startsWith('!')) {
                        $scope.model.filterExclude = true;
                        $scope.model.filter = $scope.model.filter.substring(1);
                    }
                    //used advanced filtering
                    if ($scope.model.filter.startsWith('{')) {
                        $scope.model.filterAdvanced = true;
                        if ($scope.model.filterByMetadata && !Utilities.isFunction($scope.model.filter)) {
                            var filter = Utilities.fromJson($scope.model.filter);
                            $scope.model.filter = function (node) {
                                return _.isMatch(node.metaData, filter);
                            };
                        } else {
                            //convert to object
                            $scope.model.filter = Utilities.fromJson($scope.model.filter);
                        }
                    }
                }
            }
            vm.filter = {
                filterAdvanced: $scope.model.filterAdvanced,
                filterExclude: $scope.model.filterExclude,
                filter: $scope.model.filter
            };
        }
        /**
   * Updates the tree's query parameters
   */
        function initTree() {
            //create the custom query string param for this tree
            var queryParams = {};
            if (vm.startNodeId) {
                queryParams['startNodeId'] = $scope.model.startNodeId;
            }
            if (vm.selectedLanguage && vm.selectedLanguage.id) {
                queryParams['culture'] = vm.selectedLanguage.culture;
            }
            if (vm.dataTypeKey) {
                queryParams['dataTypeKey'] = vm.dataTypeKey;
            }
            var queryString = $.param(queryParams);
            //create the query string from the params object
            if (!queryString) {
                vm.customTreeParams = $scope.model.customTreeParams;
            } else {
                vm.customTreeParams = queryString;
                if ($scope.model.customTreeParams) {
                    vm.customTreeParams += '&' + $scope.model.customTreeParams;
                }
            }
            vm.treeReady = true;
        }
        function selectLanguage(language) {
            vm.selectedLanguage = language;
            // close the language selector
            vm.languageSelectorIsOpen = false;
            initTree();
            //this will reset the tree params and the tree directive will pick up the changes in a $watch
            //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it
            $timeout(function () {
                //reload the tree with it's updated querystring args
                vm.dialogTreeApi.load(vm.section).then(function () {
                    //create the list of promises
                    var promises = [];
                    for (var i = 0; i < expandedPaths.length; i++) {
                        promises.push(vm.dialogTreeApi.syncTree({
                            path: expandedPaths[i],
                            activate: false
                        }));
                    }
                    //execute them sequentially
                    angularHelper.executeSequentialPromises(promises);
                });
            });
        }
        ;
        function toggleLanguageSelector() {
            vm.languageSelectorIsOpen = !vm.languageSelectorIsOpen;
        }
        ;
        function nodeExpandedHandler(args) {
            //store the reference to the expanded node path
            if (args.node) {
                treeService._trackExpandedPaths(args.node, expandedPaths);
            }
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
            if (Utilities.isArray(args.children)) {
                //iterate children
                args.children.forEach(function (child) {
                    //now we need to look in the already selected search results and
                    // toggle the check boxes for those ones that are listed
                    var exists = vm.searchInfo.selectedSearchResults.find(function (selected) {
                        return child.id === selected.id;
                    });
                    if (exists) {
                        child.selected = true;
                    }
                });
                //check filter
                performFiltering(args.children);
            }
        }
        //gets the tree object when it loads
        function treeLoadedHandler(args) {
            //args.tree contains children (args.tree.root.children)
            vm.hasItems = args.tree.root.children.length > 0;
            tree = args.tree;
            var nodeHasPath = currentNode && currentNode.path;
            var startNodeNotDefined = !vm.startNodeId;
            if (startNodeNotDefined && nodeHasPath) {
                vm.dialogTreeApi.syncTree({
                    path: currentNode.path,
                    activate: true
                });
            }
        }
        //wires up selection
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if (args.node.metaData.isSearchResult) {
                //check if the item selected was a search result from a list view
                //unselect
                select(args.node.name, args.node.id);
                //remove it from the list view children
                var listView = args.node.parent();
                listView.children = _.reject(listView.children, function (child) {
                    return child.id == args.node.id;
                });
                //remove it from the custom tracked search result list
                vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, function (i) {
                    return i.id == args.node.id;
                });
            } else {
                eventsService.emit('dialogs.treePickerController.select', args);
                if (args.node.filtered) {
                    return;
                }
                //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                //from the server in this method.
                if ($scope.model.select) {
                    $scope.model.select(args.node);
                } else {
                    select(args.node.name, args.node.id);
                    //toggle checked state
                    args.node.selected = args.node.selected === true ? false : true;
                }
            }
        }
        /** Method used for selecting a node */
        function select(text, id, entity) {
            //if we get the root, we just return a constructed entity, no need for server data
            if (id < 0) {
                var rootNode = {
                    alias: null,
                    icon: 'icon-folder',
                    id: id,
                    name: text
                };
                if (vm.multiPicker) {
                    if (entity) {
                        multiSelectItem(entity);
                    } else {
                        multiSelectItem(rootNode);
                    }
                } else {
                    $scope.model.selection.push(rootNode);
                    $scope.model.submit($scope.model);
                }
            } else {
                if (vm.multiPicker) {
                    if (entity) {
                        multiSelectItem(entity);
                    } else {
                        //otherwise we have to get it from the server
                        entityResource.getById(id, vm.entityType).then(function (ent) {
                            multiSelectItem(ent);
                        });
                    }
                } else {
                    hideSearch();
                    //if an entity has been passed in, use it
                    if (entity) {
                        $scope.model.selection.push(entity);
                        $scope.model.submit($scope.model);
                    } else {
                        //otherwise we have to get it from the server
                        entityResource.getById(id, vm.entityType).then(function (ent) {
                            $scope.model.selection.push(ent);
                            $scope.model.submit($scope.model);
                        });
                    }
                }
            }
        }
        function multiSelectItem(item) {
            var found = false;
            var foundIndex = 0;
            if ($scope.model.selection.length > 0) {
                for (var i = 0; $scope.model.selection.length > i; i++) {
                    var selectedItem = $scope.model.selection[i];
                    if (selectedItem.id === parseInt(item.id)) {
                        found = true;
                        foundIndex = i;
                    }
                }
            }
            if (found) {
                $scope.model.selection.splice(foundIndex, 1);
            } else {
                $scope.model.selection.push(item);
            }
        }
        function performFiltering(nodes) {
            if (!$scope.model.filter) {
                return;
            }
            //remove any list view search nodes from being filtered since these are special nodes that always must
            // be allowed to be clicked on
            nodes = _.filter(nodes, function (n) {
                return !Utilities.isObject(n.metaData.listViewNode);
            });
            if ($scope.model.filterAdvanced) {
                //filter either based on a method or an object
                var filtered = Utilities.isFunction($scope.model.filter) ? _.filter(nodes, $scope.model.filter) : _.where(nodes, $scope.model.filter);
                filtered.forEach(function (value) {
                    value.filtered = true;
                    if ($scope.model.filterCssClass) {
                        if (!value.cssClasses) {
                            value.cssClasses = [];
                        }
                        value.cssClasses.push($scope.model.filterCssClass);
                        value.title = $scope.model.filterTitle;
                    }
                });
            } else {
                var a = $scope.model.filter.toLowerCase().replace(/\s/g, '').split(',');
                nodes.forEach(function (value) {
                    var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
                    if (!$scope.model.filterExclude && !found || $scope.model.filterExclude && found) {
                        value.filtered = true;
                        if ($scope.model.filterCssClass) {
                            if (!value.cssClasses) {
                                value.cssClasses = [];
                            }
                            value.cssClasses.push($scope.model.filterCssClass);
                            value.title = $scope.model.filterTitle;
                        }
                    }
                });
            }
        }
        function openMiniListView(node) {
            previouslyFocusedElement = document.activeElement;
            vm.miniListView = node;
        }
        function multiSubmit(result) {
            entityResource.getByIds(result, vm.entityType).then(function (ents) {
                $scope.submit(ents);
            });
        }
        /** method to select a search result */
        function selectResult(evt, result) {
            if (result.filtered) {
                return;
            }
            result.selected = result.selected === true ? false : true;
            //since result = an entity, we'll pass it in so we don't have to go back to the server
            select(result.name, result.id, result);
            //add/remove to our custom tracked list of selected search results
            if (result.selected) {
                vm.searchInfo.selectedSearchResults.push(result);
            } else {
                vm.searchInfo.selectedSearchResults = _.reject(vm.searchInfo.selectedSearchResults, function (i) {
                    return i.id == result.id;
                });
            }
            //ensure the tree node in the tree is checked/unchecked if it already exists there
            if (tree) {
                var found = treeService.getDescendantNode(tree.root, result.id);
                if (found) {
                    found.selected = result.selected;
                }
            }
        }
        function hideSearch() {
            //Traverse the entire displayed tree and update each node to sync with the selected search results
            if (tree) {
                //we need to ensure that any currently displayed nodes that get selected
                // from the search get updated to have a check box!
                var checkChildren = function checkChildren(children) {
                    children.forEach(function (child) {
                        //check if the id is in the selection, if so ensure it's flagged as selected
                        var exists = vm.searchInfo.selectedSearchResults.find(function (selected) {
                            return child.id === selected.id;
                        });
                        //if the curr node exists in selected search results, ensure it's checked
                        if (exists) {
                            child.selected = true;
                        }    //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
                        else if (child.metaData.isSearchResult) {
                            //if this tree node is under a list view it means that the node was added
                            // to the tree dynamically under the list view that was searched, so we actually want to remove
                            // it all together from the tree
                            var listView = child.parent();
                            listView.children = _.reject(listView.children, function (c) {
                                return c.id == child.id;
                            });
                        }
                        //check if the current node is a list view and if so, check if there's any new results
                        // that need to be added as child nodes to it based on search results selected
                        if (child.metaData.isContainer) {
                            child.cssClasses = _.reject(child.cssClasses, function (c) {
                                return c === 'tree-node-slide-up-hide-active';
                            });
                            var listViewResults = vm.searchInfo.selectedSearchResults.filter(function (i) {
                                return i.parentId === child.id;
                            });
                            listViewResults.forEach(function (item) {
                                if (!child.children)
                                    return;
                                var childExists = child.children.find(function (c) {
                                    return c.id === item.id;
                                });
                                if (!childExists) {
                                    var _parent = child;
                                    child.children.unshift({
                                        id: item.id,
                                        name: item.name,
                                        cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                        level: child.level + 1,
                                        metaData: { isSearchResult: true },
                                        hasChildren: false,
                                        parent: function parent() {
                                            return _parent;
                                        }
                                    });
                                }
                            });
                        }
                        //recurse
                        if (child.children && child.children.length > 0) {
                            checkChildren(child.children);
                        }
                    });
                };
                checkChildren(tree.root.children);
            }
            vm.searchInfo.showSearch = false;
            vm.searchInfo.searchFromId = vm.startNodeId;
            vm.searchInfo.searchFromName = null;
            vm.searchInfo.results = [];
        }
        function onSearchResults(results) {
            //filter all items - this will mark an item as filtered
            performFiltering(results);
            //now actually remove all filtered items so they are not even displayed
            results = results.filter(function (item) {
                return !item.filtered;
            });
            vm.searchInfo.results = results;
            //sync with the curr selected results
            vm.searchInfo.results.forEach(function (result) {
                var exists = $scope.model.selection.find(function (item) {
                    return result.id === item.id;
                });
                if (exists) {
                    result.selected = true;
                }
            });
            vm.searchInfo.showSearch = true;
        }
        function selectListViewNode(node) {
            select(node.name, node.id);
            //toggle checked state
            node.selected = node.selected === true ? false : true;
        }
        function closeMiniListView() {
            vm.miniListView = undefined;
            if (previouslyFocusedElement) {
                $timeout(function () {
                    previouslyFocusedElement.focus();
                    previouslyFocusedElement = null;
                });
            }
        }
        function listViewItemsLoaded(items) {
            var selectedIds = $scope.model.selection.map(function (x) {
                return x.id;
            });
            items.forEach(function (item) {
                return item.selected = selectedIds.includes(item.id);
            });
        }
        function submit(model) {
            if ($scope.model.submit) {
                $scope.model.submit(model);
            }
        }
        function close() {
            if ($scope.model.close) {
                $scope.model.close();
            }
        }
        //initialize
        onInit();
    });
    'use strict';
    (function () {
        'use strict';
        function UserGroupPickerController($scope, userGroupsResource, localizationService) {
            var vm = this;
            vm.userGroups = [];
            vm.loading = false;
            vm.selectUserGroup = selectUserGroup;
            vm.submit = submit;
            vm.close = close;
            //////////
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    localizationService.localize('user_selectUserGroups').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get venues
                userGroupsResource.getUserGroups().then(function (userGroups) {
                    vm.userGroups = userGroups;
                    if ($scope.model.selection && $scope.model.selection.length > 0) {
                        preSelect($scope.model.selection);
                    }
                    vm.loading = false;
                });
            }
            function preSelect(selection) {
                selection.forEach(function (selected) {
                    vm.userGroups.forEach(function (userGroup) {
                        if (selected.id === userGroup.id) {
                            userGroup.selected = true;
                        }
                    });
                });
            }
            function selectUserGroup(userGroup) {
                if (!userGroup.selected) {
                    userGroup.selected = true;
                    $scope.model.selection.push(userGroup);
                } else {
                    $scope.model.selection.forEach(function (selectedUserGroup, index) {
                        if (selectedUserGroup.id === userGroup.id) {
                            userGroup.selected = false;
                            $scope.model.selection.splice(index, 1);
                        }
                    });
                }
            }
            function submit(model) {
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.UserGroupPickerController', UserGroupPickerController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UserPickerController($scope, entityResource, localizationService, eventsService) {
            var vm = this;
            vm.users = [];
            vm.loading = false;
            vm.usersOptions = {};
            vm.selectUser = selectUser;
            vm.changePageNumber = changePageNumber;
            vm.submit = submit;
            vm.close = close;
            vm.multiPicker = $scope.model.multiPicker === false ? false : true;
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    var labelKey = vm.multiPicker ? 'defaultdialogs_selectUsers' : 'defaultdialogs_selectUser';
                    localizationService.localize(labelKey).then(function (value) {
                        $scope.model.title = value;
                    });
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get users
                getUsers();
            }
            function preSelect(selection, users) {
                Utilities.forEach(selection, function (selected) {
                    Utilities.forEach(users, function (user) {
                        if (selected.id === user.id) {
                            user.selected = true;
                        }
                    });
                });
            }
            function selectUser(user) {
                if (!user.selected) {
                    user.selected = true;
                    $scope.model.selection.push(user);
                } else {
                    if (user.selected) {
                        Utilities.forEach($scope.model.selection, function (selectedUser, index) {
                            if (selectedUser.id === user.id) {
                                user.selected = false;
                                $scope.model.selection.splice(index, 1);
                            }
                        });
                    } else {
                        if (!vm.multiPicker) {
                            deselectAllUsers($scope.model.selection);
                        }
                        eventsService.emit('dialogs.userPicker.select', user);
                        user.selected = true;
                        $scope.model.selection.push(user);
                    }
                }
                if (!vm.multiPicker) {
                    submit($scope.model);
                }
            }
            function deselectAllUsers(users) {
                for (var i = 0; i < users.length; i++) {
                    var user = users[i];
                    user.selected = false;
                }
                users.length = 0;
            }
            function getUsers() {
                vm.loading = true;
                // Get users
                entityResource.getAll('User').then(function (data) {
                    vm.users = data;
                    preSelect($scope.model.selection, vm.users);
                    vm.loading = false;
                });
            }
            function changePageNumber(pageNumber) {
                vm.usersOptions.pageNumber = pageNumber;
                getUsers();
            }
            function submit(model) {
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.UserPickerController', UserPickerController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.LegacyController
 * @function
 * 
 * @description
 * A controller to control the legacy iframe injection
 * 
*/
    function LegacyController($scope, $routeParams, $element) {
        var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, ''));
        //split into path and query
        var urlParts = url.split('?');
        var extIndex = urlParts[0].lastIndexOf('.');
        var ext = extIndex === -1 ? '' : urlParts[0].substr(extIndex);
        //path cannot be a js file
        if (ext !== '.js' || ext === '') {
            //path cannot contain any of these chars
            var toClean = '*(){}[];:<>\\|\'"';
            for (var i = 0; i < toClean.length; i++) {
                var reg = new RegExp('\\' + toClean[i], 'g');
                urlParts[0] = urlParts[0].replace(reg, '');
            }
            //join cleaned path and query back together
            url = urlParts[0] + (urlParts.length === 1 ? '' : '?' + urlParts[1]);
            $scope.legacyPath = url;
        } else {
            throw 'Invalid url';
        }
    }
    angular.module('umbraco').controller('Umbraco.LegacyController', LegacyController);
    'use strict';
    /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
    angular.module('umbraco').controller('Umbraco.LoginController', function (eventsService, $scope, userService, $location, $rootScope) {
        userService._showLoginDialog();
        var evtOn = eventsService.on('app.ready', function (evt, data) {
            $scope.avatar = 'assets/img/application/logo.png';
            var path = '/';
            //check if there's a returnPath query string, if so redirect to it
            var locationObj = $location.search();
            if (locationObj.returnPath) {
                path = decodeURIComponent(locationObj.returnPath);
            }
            $location.url(path);
        });
        $scope.$on('$destroy', function () {
            eventsService.unsubscribe(evtOn);
        });
    });
    'use strict';
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Notifications.ConfirmRouteChangeController', function ($scope, $location, $log, notificationsService, navigationService) {
        $scope.discard = function (not) {
            // allow for a callback for discard click
            if (not.args.onDiscard) {
                not.args.onDiscard();
                return;
            }
            // when no callback is added run the normal functionality of the discard button
            not.args.listener();
            navigationService.clearSearch();
            //we need to break the path up into path and query
            var parts = not.args.path.split('?');
            var query = {};
            if (parts.length > 1) {
                _.each(parts[1].split('&'), function (q) {
                    var keyVal = q.split('=');
                    query[keyVal[0]] = keyVal[1];
                });
            }
            $location.path(parts[0]).search(query);
            notificationsService.remove(not);
        };
        $scope.stay = function (not) {
            notificationsService.remove(not);
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Notifications.ConfirmUnpublishController', function ($scope, notificationsService, eventsService) {
        $scope.confirm = function (not, action) {
            eventsService.emit('content.confirmUnpublish', action);
            notificationsService.remove(not);
        };
    });
    'use strict';
    function ItemPickerOverlay($scope, localizationService) {
        $scope.filter = { searchTerm: '' };
        function onInit() {
            $scope.model.hideSubmitButton = true;
            if (!$scope.model.title) {
                localizationService.localize('defaultdialogs_selectItem').then(function (value) {
                    $scope.model.title = value;
                });
            }
            if (!$scope.model.orderBy) {
                $scope.model.orderBy = 'name';
            }
        }
        $scope.selectItem = function (item) {
            $scope.model.selectedItem = item;
            $scope.submitForm($scope.model);
        };
        $scope.tooltip = {
            show: false,
            event: null
        };
        $scope.showTooltip = function (item, $event) {
            if (!item.tooltip) {
                return;
            }
            $scope.tooltip = {
                show: true,
                event: $event,
                text: item.tooltip
            };
        };
        $scope.hideTooltip = function () {
            $scope.tooltip = {
                show: false,
                event: null,
                text: null
            };
        };
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Overlays.ItemPickerOverlay', ItemPickerOverlay);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Overlays.UserController', function ($scope, $location, $timeout, dashboardResource, userService, historyService, eventsService, externalLoginInfo, externalLoginInfoService, authResource, currentUserResource, formHelper, localizationService) {
        $scope.history = historyService.getCurrent();
        //$scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
        $scope.showPasswordFields = false;
        $scope.changePasswordButtonState = 'init';
        $scope.model.title = 'user.name';
        //$scope.model.subtitle = "Umbraco version" + " " + $scope.version;
        /*
  if(!$scope.model.title) {
      localizationService.localize("general_user").then(function(value){
          $scope.model.title = value;
      });
  }
  */
        // Set flag if any have deny local login, in which case we must disable all password functionality
        $scope.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
        // Only include login providers that have editable options
        $scope.externalLoginProviders = externalLoginInfoService.getLoginProvidersWithOptions();
        $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
        var evts = [];
        evts.push(eventsService.on('historyService.add', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.remove', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.removeAll', function (e, args) {
            $scope.history = [];
        }));
        $scope.logout = function () {
            //Add event listener for when there are pending changes on an editor which means our route was not successful
            var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) {
                //one time listener, remove the event
                pendingChangeEvent();
                $scope.model.close();
            });
            //perform the path change, if it is successful then the promise will resolve otherwise it will fail
            $scope.model.close();
            $location.path('/logout').search('');
        };
        $scope.gotoHistory = function (link) {
            $location.path(link);
            $scope.model.close();
        };
        /*
  //Manually update the remaining timeout seconds
  function updateTimeout() {
      $timeout(function () {
          if ($scope.remainingAuthSeconds > 0) {
              $scope.remainingAuthSeconds--;
              $scope.$digest();
              //recurse
              updateTimeout();
          }
        }, 1000, false); // 1 second, do NOT execute a global digest
  }
  */
        function updateUserInfo() {
            //get the user
            userService.getCurrentUser().then(function (user) {
                $scope.user = user;
                if ($scope.user) {
                    $scope.model.title = user.name;
                    $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                    $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1;
                    //set the timer
                    //updateTimeout();
                    authResource.getCurrentUserLinkedLogins().then(function (logins) {
                        //reset all to be un-linked
                        $scope.externalLoginProviders.forEach(function (provider) {
                            return provider.linkedProviderKey = undefined;
                        });
                        //set the linked logins
                        for (var login in logins) {
                            var found = _.find($scope.externalLoginProviders, function (i) {
                                return i.authType == login;
                            });
                            if (found) {
                                found.linkedProviderKey = logins[login];
                            }
                        }
                    });
                }
            });
        }
        $scope.linkProvider = function (e) {
            e.target.submit();
        };
        $scope.unlink = function (e, loginProvider, providerKey) {
            var result = confirm('Are you sure you want to unlink this account?');
            if (!result) {
                e.preventDefault();
                return;
            }
            authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
                updateUserInfo();
            });
        };
        updateUserInfo();
        //remove all event handlers
        $scope.$on('$destroy', function () {
            for (var e = 0; e < evts.length; e++) {
                evts[e]();
            }
        });
        /* ---------- UPDATE PASSWORD ---------- */
        //create the initial model for change password
        $scope.changePasswordModel = {
            config: {},
            value: {}
        };
        //go get the config for the membership provider and add it to the model
        authResource.getMembershipProviderConfig().then(function (data) {
            $scope.changePasswordModel.config = data;
            //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
            //this will ensure the oldPassword is shown so they can change it
            // disable reset password functionality beacuse it does not make sense inside the backoffice
            $scope.changePasswordModel.config.hasPassword = true;
            $scope.changePasswordModel.config.disableToggle = true;
            $scope.changePasswordModel.config.enableReset = false;
        });
        $scope.changePassword = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.changePasswordButtonState = 'busy';
                currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) {
                    //reset old data 
                    clearPasswordFields();
                    //if the password has been reset, then update our model
                    if (data.value) {
                        $scope.changePasswordModel.value.generatedPassword = data.value;
                    }
                    formHelper.resetForm({ scope: $scope });
                    $scope.changePasswordButtonState = 'success';
                    $timeout(function () {
                        $scope.togglePasswordFields();
                    }, 2000);
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    formHelper.handleError(err);
                    $scope.changePasswordButtonState = 'error';
                });
            }
        };
        $scope.togglePasswordFields = function () {
            clearPasswordFields();
            $scope.showPasswordFields = !$scope.showPasswordFields;
        };
        function clearPasswordFields() {
            $scope.changePasswordModel.value.oldPassword = '';
            $scope.changePasswordModel.value.newPassword = '';
            $scope.changePasswordModel.value.confirm = '';
        }
        dashboardResource.getDashboard('user-dialog').then(function (dashboard) {
            $scope.dashboard = dashboard;
        });
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Overlays.YsodController', function ($scope, localizationService) {
        function onInit() {
            if (!$scope.model.title) {
                localizationService.localize('errors_receivedErrorFromServer').then(function (value) {
                    $scope.model.title = value;
                });
            }
            if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) {
                //trim whitespace
                $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim();
            }
            if ($scope.model.error && $scope.model.error.data) {
                $scope.model.error.data.InnerExceptions = [];
                var ex = $scope.model.error.data.InnerException;
                while (ex) {
                    if (ex.StackTrace) {
                        ex.StackTrace = ex.StackTrace.trim();
                    }
                    $scope.model.error.data.InnerExceptions.push(ex);
                    ex = ex.InnerException;
                }
            }
        }
        onInit();
    });
    'use strict';
    (function () {
        'use strict';
        function ConfirmController($scope, userService) {
            var vm = this;
            vm.userEmailAddress = '';
            userService.getCurrentUser().then(function (user) {
                vm.userEmailAddress = user.email;
            });
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbEmailMarketing.ConfirmController', ConfirmController);
    }());
    'use strict';
    (function () {
        'use strict';
        function EmailsController($scope, userService) {
            var vm = this;
            vm.optIn = function () {
                // Get the current user in backoffice
                userService.getCurrentUser().then(function (user) {
                    // Send this user along to opt in
                    // It's a fire & forget - not sure we need to check the response
                    userService.addUserToEmailMarketing(user);
                });
                $scope.model.nextStep();
            };
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbEmailMarketing.EmailsController', EmailsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function NodeNameController($scope) {
            var vm = this;
            var element = $($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateContent.NodeNameController', NodeNameController);
    }());
    'use strict';
    (function () {
        'use strict';
        function DocTypeNameController($scope) {
            var vm = this;
            var element = $($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home page') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.DocTypeNameController', DocTypeNameController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PropertyNameController($scope) {
            var vm = this;
            var element = $($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'welcome text') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.PropertyNameController', PropertyNameController);
    }());
    'use strict';
    (function () {
        'use strict';
        function TabNameController($scope) {
            var vm = this;
            var element = $($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.TabNameController', TabNameController);
    }());
    'use strict';
    (function () {
        'use strict';
        function FolderNameController($scope) {
            var vm = this;
            var element = $($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'my images') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.FolderNameController', FolderNameController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UploadImagesController($scope, editorState, mediaResource) {
            var vm = this;
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                vm.error = false;
                vm.buttonState = 'busy';
                var currentNode = editorState.getCurrent();
                // make sure we have uploaded at least one image
                mediaResource.getChildren(currentNode.id).then(function (data) {
                    var children = data;
                    if (children.items && children.items.length > 0) {
                        $scope.model.nextStep();
                    } else {
                        vm.error = true;
                    }
                    vm.buttonState = 'init';
                });
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.UploadImagesController', UploadImagesController);
    }());
    'use strict';
    (function () {
        'use strict';
        function TemplatesTreeController($scope) {
            var eventElement = $($scope.model.currentStep.eventElement);
            function onInit() {
                // check if tree is already open - if it is - go to next step
                if (eventElement.hasClass('icon-navigation-down')) {
                    $scope.model.nextStep();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroRenderInTemplate.TemplatesTreeController', TemplatesTreeController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ContentAppContentController($scope, $timeout, serverValidationManager) {
            //the contentApp's viewModel is actually the index of the variant being edited, not the variant itself.
            //if we make the viewModel the variant itself, we end up with a circular reference in the models which isn't ideal
            // (i.e. variant.apps[contentApp].viewModel = variant)
            //so instead since we already have access to the content, we can just get the variant directly by the index.
            var unbindLanguageWatcher = function unbindLanguageWatcher() {
            };
            var unbindSegmentWatcher = function unbindSegmentWatcher() {
            };
            var timeout = null;
            var vm = this;
            vm.loading = true;
            function onInit() {
                serverValidationManager.notify();
                vm.loading = false;
                timeout = null;
                // ensure timeout is set to null, so we know that its not running anymore.
                //if this variant has a culture/language assigned, then we need to watch it since it will change
                //if the language drop down changes and we need to re-init
                if ($scope.variantContent) {
                    if ($scope.variantContent.language) {
                        unbindLanguageWatcher = $scope.$watch(function () {
                            return $scope.variantContent.language.culture;
                        }, function (newVal, oldVal) {
                            if (newVal !== oldVal) {
                                requestUpdate();
                            }
                        });
                    }
                    unbindSegmentWatcher = $scope.$watch(function () {
                        return $scope.variantContent.segment;
                    }, function (newVal, oldVal) {
                        if (newVal !== oldVal) {
                            requestUpdate();
                        }
                    });
                }
            }
            function requestUpdate() {
                if (timeout === null) {
                    vm.loading = true;
                    // TODO: Can we minimize the flicker?
                    timeout = $timeout(function () {
                        onInit();
                    }, 100);
                }
            }
            onInit();
            $scope.$on('$destroy', function () {
                unbindLanguageWatcher();
                unbindSegmentWatcher();
                $timeout.cancel(timeout);
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.Apps.ContentController', ContentAppContentController);
    }());
    'use strict';
    (function () {
        'use strict';
        function AssignDomainController($scope, localizationService, languageResource, contentResource, navigationService, notificationsService) {
            var vm = this;
            vm.closeDialog = closeDialog;
            vm.addDomain = addDomain;
            vm.removeDomain = removeDomain;
            vm.save = save;
            vm.languages = [];
            vm.domains = [];
            vm.language = null;
            function activate() {
                vm.loading = true;
                languageResource.getAll().then(function (langs) {
                    vm.languages = langs;
                    var defLang = langs.filter(function (l) {
                        return l.isDefault;
                    });
                    if (defLang.length > 0) {
                        vm.defaultLanguage = defLang[0];
                    } else {
                        vm.defaultLanguage = langs[0];
                    }
                    getCultureAndDomains().then(function () {
                        vm.loading = false;
                    });
                });
                localizationService.localize('assignDomain_inherit').then(function (value) {
                    vm.inherit = value;
                });
            }
            function getCultureAndDomains() {
                return contentResource.getCultureAndDomains($scope.currentNode.id).then(function (data) {
                    if (data.language !== 'undefined') {
                        var lang = vm.languages.filter(function (l) {
                            return matchLanguageById(l, data.language);
                        });
                        if (lang.length > 0) {
                            vm.language = lang[0];
                        }
                    }
                    vm.domains = data.domains.map(function (d) {
                        var matchedLangs = vm.languages.filter(function (lng) {
                            return matchLanguageById(lng, d.lang);
                        });
                        return {
                            name: d.name,
                            lang: matchedLangs.length > 0 ? matchedLangs[0] : vm.defaultLanguage
                        };
                    });
                });
            }
            function matchLanguageById(language, id) {
                var langId = parseInt(language.id);
                var comparisonId = parseInt(id);
                return langId === comparisonId;
            }
            function closeDialog() {
                navigationService.hideDialog();
            }
            function addDomain() {
                vm.domains.push({
                    name: '',
                    lang: vm.defaultLanguage
                });
            }
            function removeDomain(index) {
                vm.domains.splice(index, 1);
            }
            function save() {
                vm.error = null;
                vm.submitButtonState = 'busy';
                if (vm.domainForm.$valid) {
                    // clear validation messages
                    vm.domains.forEach(function (domain) {
                        domain.duplicate = null;
                        domain.other = null;
                    });
                    var data = {
                        nodeId: $scope.currentNode.id,
                        domains: vm.domains.map(function (d) {
                            return {
                                name: d.name,
                                lang: d.lang.id
                            };
                        }),
                        language: vm.language != null ? vm.language.id : 0
                    };
                    contentResource.saveLanguageAndDomains(data).then(function (response) {
                        // validation is interesting. Check if response is valid
                        if (response.valid) {
                            vm.submitButtonState = 'success';
                            localizationService.localize('speechBubbles_editCulturesAndHostnamesSaved').then(function (value) {
                                notificationsService.success(value);
                            });
                            closeDialog();    // show validation messages for each domain
                        } else {
                            response.domains.forEach(function (validation) {
                                vm.domains.forEach(function (domain) {
                                    if (validation.name === domain.name) {
                                        domain.duplicate = validation.duplicate;
                                        domain.other = validation.other;
                                    }
                                });
                            });
                            vm.submitButtonState = 'error';
                            localizationService.localize('speechBubbles_editCulturesAndHostnamesError').then(function (value) {
                                notificationsService.error(value);
                            });
                        }
                    }, function (e) {
                        vm.error = e;
                        vm.submitButtonState = 'error';
                    });
                } else {
                    vm.submitButtonState = 'error';
                }
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.AssignDomainController', AssignDomainController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.Content.CopyController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        $scope.relateToOriginal = true;
        $scope.recursive = true;
        $scope.dialogTreeApi = {};
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        $scope.toggle = toggleHandler;
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.source = _.clone($scope.currentNode);
        function treeLoadedHandler(args) {
            if ($scope.source && $scope.source.path) {
                $scope.dialogTreeApi.syncTree({
                    path: $scope.source.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.content.copyController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        function toggleHandler(type) {
            // If the relateToOriginal toggle is clicked
            if (type === 'relate') {
                if ($scope.relateToOriginal) {
                    $scope.relateToOriginal = false;
                    return;
                }
                $scope.relateToOriginal = true;
            }
            // If the recurvise toggle is clicked
            if (type === 'recursive') {
                if ($scope.recursive) {
                    $scope.recursive = false;
                    return;
                }
                $scope.recursive = true;
            }
        }
        $scope.closeDialog = function () {
            navigationService.hideDialog();
        };
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            contentResource.copy({
                parentId: $scope.target.id,
                id: $scope.source.id,
                relateToOriginal: $scope.relateToOriginal,
                recursive: $scope.recursive
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.CreateController
 * @function
 *
 * @description
 * The controller for the content creation dialog
 */
    function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper, $location, navigationService, blueprintConfig, authResource, contentResource, $q) {
        var mainCulture = $routeParams.mculture ? $routeParams.mculture : null;
        function initialize() {
            $scope.loading = true;
            $scope.allowedTypes = null;
            var getAllowedTypes = contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
                $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
            });
            var getCurrentUser = authResource.getCurrentUser().then(function (currentUser) {
                $scope.hasSettingsAccess = currentUser.allowedSections.indexOf('settings') > -1;
                if ($scope.hasSettingsAccess) {
                    if ($scope.currentNode.id > -1) {
                        return contentResource.getById($scope.currentNode.id).then(function (data) {
                            $scope.contentTypeId = data.contentTypeId;
                        });
                    }
                }
            });
            $q.all([
                getAllowedTypes,
                getCurrentUser
            ]).then(function () {
                if ($scope.hasSettingsAccess === true && $scope.allowedTypes.length === 0) {
                    return contentTypeResource.getCount().then(function (count) {
                        $scope.countTypes = count;
                    });
                }
            }).then(function () {
                $scope.loading = false;
            });
            $scope.selectContentType = true;
            $scope.selectBlueprint = false;
            $scope.allowBlank = blueprintConfig.allowBlank;
        }
        function close() {
            navigationService.hideMenu();
        }
        function createBlank(docType) {
            $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype', docType.alias).search('create', 'true')    /* when we create a new node we want to make sure it uses the same
    language as what is selected in the tree */.search('cculture', mainCulture)    /* when we create a new node we must make sure that any previously
    opened segments is reset */.search('csegment', null)    /* when we create a new node we must make sure that any previously
    used blueprint is reset */.search('blueprintId', null);
            close();
        }
        function createOrSelectBlueprintIfAny(docType) {
            // map the blueprints into a collection that's sortable in the view
            var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) {
                return {
                    id: pair[0],
                    name: pair[1]
                };
            });
            $scope.docType = docType;
            if (blueprints.length) {
                if (blueprintConfig.skipSelect) {
                    createFromBlueprint(blueprints[0].id);
                } else {
                    $scope.selectContentType = false;
                    $scope.selectBlueprint = true;
                    $scope.selectableBlueprints = blueprints;
                }
            } else {
                createBlank(docType);
            }
        }
        function createFromBlueprint(blueprintId) {
            $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype', $scope.docType.alias).search('create', 'true').search('blueprintId', blueprintId);
            close();
        }
        $scope.close = function () {
            close();
        };
        $scope.closeDialog = function (showMenu) {
            navigationService.hideDialog(showMenu);
        };
        $scope.createContentType = function () {
            $location.path('/settings/documenttypes/edit/-1').search('create', 'true');
            close();
        };
        $scope.editContentType = function () {
            $location.path('/settings/documenttypes/edit/' + $scope.contentTypeId).search('view', 'permissions');
            close();
        };
        $scope.createBlank = createBlank;
        $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
        $scope.createFromBlueprint = createFromBlueprint;
        // the current node changes behind the scenes when the context menu is clicked without closing
        // the default menu first, so we must watch the current node and re-initialize accordingly
        var unbindModelWatcher = $scope.$watch('currentNode', initialize);
        $scope.$on('$destroy', function () {
            unbindModelWatcher();
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.CreateController', contentCreateController);
    angular.module('umbraco').value('blueprintConfig', {
        skipSelect: false,
        allowBlank: true
    });
    'use strict';
    (function () {
        function CreateBlueprintController($scope, contentResource, navigationService, localizationService, formHelper, contentEditingHelper) {
            $scope.message = { name: $scope.currentNode.name };
            localizationService.localize('blueprints_createBlueprintFrom', [$scope.message.name]).then(function (localizedVal) {
                $scope.title = localizedVal;
            });
            $scope.cancel = function () {
                navigationService.hideMenu();
            };
            $scope.create = function () {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: this.blueprintForm
                    })) {
                    contentResource.createBlueprintFromContent($scope.currentNode.id, $scope.message.name).then(function (data) {
                        formHelper.resetForm({ scope: $scope });
                        navigationService.hideMenu();
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            hasErrors: true
                        });
                        contentEditingHelper.handleSaveError({ err: err });
                    });
                }
            };
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.CreateBlueprintController', CreateBlueprintController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.DeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function ContentDeleteController($scope, $timeout, contentResource, treeService, navigationService, editorState, $location, overlayService, languageResource) {
        /**
   * Used to toggle UI elements during delete operations
   * @param {any} isDeleting
   */
        function toggleDeleting(isDeleting) {
            $scope.currentNode.loading = isDeleting;
            $scope.busy = isDeleting;
        }
        $scope.performDelete = function () {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            toggleDeleting(true);
            contentResource.deleteById($scope.currentNode.id).then(function () {
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                treeService.removeNode($scope.currentNode);
                toggleDeleting(false);
                if (rootNode) {
                    //ensure the recycle bin has child nodes now            
                    var recycleBin = treeService.getDescendantNode(rootNode, -20);
                    if (recycleBin) {
                        recycleBin.hasChildren = true;
                        //reload the recycle bin if it's already expanded so the deleted item is shown
                        if (recycleBin.expanded) {
                            treeService.loadNodeChildren({
                                node: recycleBin,
                                section: 'content'
                            });
                        }
                    }
                }
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                    //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                    var location = '/content';
                    if ($scope.currentNode.parentId.toString() === '-20')
                        location = '/content/content/recyclebin';
                    else if ($scope.currentNode.parentId.toString() !== '-1')
                        location = '/content/content/edit/' + $scope.currentNode.parentId;
                    $location.path(location);
                }
                $scope.success = true;
            }, function (err) {
                toggleDeleting(false);
                //check if response is ysod
                if (err.status && err.status >= 500) {
                    // TODO: All YSOD handling should be done with an interceptor
                    overlayService.ysod(err);
                    navigationService.hideDialog();
                }
                if (err.data && err.data.notifications && err.data.notifications.length > 0) {
                    navigationService.hideDialog();
                }
            });
        };
        $scope.cancel = function () {
            toggleDeleting(false);
            $scope.close();
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.DeleteController', ContentDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EditController
 * @function
 * 
 * @description
 * The controller for the content editor
 */
    function ContentEditController($scope, $routeParams, contentResource) {
        var infiniteMode = $scope.model && $scope.model.infiniteMode;
        function scaffoldEmpty() {
            return contentResource.getScaffold($routeParams.id, $routeParams.doctype);
        }
        function scaffoldInfiniteEmpty() {
            return contentResource.getScaffold($scope.model.parentId, $scope.model.documentTypeAlias);
        }
        function scaffoldBlueprint() {
            return contentResource.getBlueprintScaffold($routeParams.id, $routeParams.blueprintId);
        }
        $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id;
        $scope.saveMethod = contentResource.save;
        $scope.getMethod = contentResource.getById;
        $scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : infiniteMode ? scaffoldInfiniteEmpty : scaffoldEmpty;
        $scope.page = $routeParams.page;
        $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create;
        //load the default culture selected in the main tree if any
        $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
        $scope.segment = $routeParams.csegment ? $routeParams.csegment : null;
        //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered.
        //This is so we can listen to changes on the cculture parameter since that will not cause a route change
        //and then we can pass in the updated culture to the editor.
        //This will also execute when we are redirecting from creating an item to a newly created item since that
        //will not cause a route change and so we can update the isNew and contentId flags accordingly.
        $scope.$on('$routeUpdate', function (event, next) {
            $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture;
            $scope.segment = next.params.csegment ? next.params.csegment : null;
            $scope.isNew = next.params.create === 'true';
            $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.EditController', ContentEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EmptyRecycleBinController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) {
        $scope.busy = false;
        $scope.performDelete = function () {
            //(used in the UI)
            $scope.busy = true;
            $scope.currentNode.loading = true;
            contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                $scope.busy = false;
                $scope.currentNode.loading = false;
                treeService.removeChildNodes($scope.currentNode);
                navigationService.hideMenu();
                //reload the current view
                $route.reload();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.EmptyRecycleBinController', ContentEmptyRecycleBinController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.Content.MoveController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        $scope.dialogTreeApi = {};
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.source = _.clone($scope.currentNode);
        function treeLoadedHandler(args) {
            if ($scope.source && $scope.source.path) {
                $scope.dialogTreeApi.syncTree({
                    path: $scope.source.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.content.moveController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            contentResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currently edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    'use strict';
    (function () {
        function CreateNotifyController($scope, contentResource, navigationService, localizationService) {
            var vm = this;
            vm.notifyOptions = [];
            vm.save = save;
            vm.cancel = cancel;
            vm.notificationChanged = notificationChanged;
            vm.message = { name: $scope.currentNode.name };
            vm.labels = {};
            function onInit() {
                vm.loading = true;
                contentResource.getNotifySettingsById($scope.currentNode.id).then(function (options) {
                    vm.loading = false;
                    vm.notifyOptions = options;
                });
                localizationService.localize('notifications_editNotifications', [$scope.currentNode.name]).then(function (value) {
                    vm.labels.headline = value;
                });
            }
            function cancel() {
                navigationService.hideMenu();
            }
            ;
            function save(notifyOptions) {
                vm.saveState = 'busy';
                vm.saveError = false;
                vm.saveSuccces = false;
                var selectedString = [];
                notifyOptions.forEach(function (option) {
                    if (option.checked === true && option.notifyCode) {
                        selectedString.push(option.notifyCode);
                    }
                });
                contentResource.setNotifySettingsById($scope.currentNode.id, selectedString).then(function () {
                    vm.saveState = 'success';
                    vm.saveSuccces = true;
                }, function (error) {
                    vm.saveState = 'error';
                    vm.saveError = error;
                });
            }
            function notificationChanged(item) {
                vm.canSave = true;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.CreateNotifyController', CreateNotifyController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ContentProtectController($scope, $q, contentResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) {
            var vm = this;
            var id = $scope.currentNode.id;
            vm.loading = false;
            vm.buttonState = 'init';
            vm.isValid = isValid;
            vm.next = next;
            vm.save = save;
            vm.close = close;
            vm.toggle = toggle;
            vm.pickLoginPage = pickLoginPage;
            vm.pickErrorPage = pickErrorPage;
            vm.pickGroup = pickGroup;
            vm.removeGroup = removeGroup;
            vm.pickMember = pickMember;
            vm.removeMember = removeMember;
            vm.removeProtection = removeProtection;
            vm.removeProtectionConfirm = removeProtectionConfirm;
            vm.type = null;
            vm.step = null;
            function onInit() {
                vm.loading = true;
                // get the current public access protection
                contentResource.getPublicAccess(id).then(function (publicAccess) {
                    vm.loading = false;
                    // init the current settings for public access (if any)
                    vm.loginPage = publicAccess.loginPage;
                    vm.errorPage = publicAccess.errorPage;
                    vm.groups = publicAccess.groups || [];
                    vm.members = publicAccess.members || [];
                    vm.canRemove = true;
                    if (vm.members.length) {
                        vm.type = 'member';
                        next();
                    } else if (vm.groups.length) {
                        vm.type = 'group';
                        next();
                    } else {
                        vm.canRemove = false;
                    }
                });
            }
            function next() {
                if (vm.type === 'group') {
                    vm.loading = true;
                    // get all existing member groups for lookup upon selection
                    // NOTE: if/when member groups support infinite editing, we can't rely on using a cached lookup list of valid groups anymore
                    memberGroupResource.getGroups().then(function (groups) {
                        vm.step = vm.type;
                        vm.allGroups = groups;
                        vm.hasGroups = groups.length > 0;
                        vm.loading = false;
                    });
                } else {
                    vm.step = vm.type;
                }
            }
            function isValid() {
                if (!vm.type) {
                    return false;
                }
                if (!vm.protectForm.$valid) {
                    return false;
                }
                if (!vm.loginPage || !vm.errorPage) {
                    return false;
                }
                if (vm.type === 'group') {
                    return vm.groups && vm.groups.length > 0;
                }
                if (vm.type === 'member') {
                    return vm.members && vm.members.length > 0;
                }
                return true;
            }
            function save() {
                vm.buttonState = 'busy';
                var groups = _.map(vm.groups, function (group) {
                    return encodeURIComponent(group.name);
                });
                var usernames = _.map(vm.members, function (member) {
                    return member.username;
                });
                contentResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then(function () {
                    localizationService.localize('publicAccess_paIsProtected', [$scope.currentNode.name]).then(function (value) {
                        vm.success = { message: value };
                    });
                    navigationService.syncTree({
                        tree: 'content',
                        path: $scope.currentNode.path,
                        forceReload: true
                    });
                    $scope.dialog.confirmDiscardChanges = true;
                }, function (error) {
                    vm.error = error;
                    vm.buttonState = 'error';
                });
            }
            function close() {
                // ensure that we haven't set a locked state on the dialog before closing it
                navigationService.allowHideDialog(true);
                navigationService.hideDialog();
            }
            function toggle(group) {
                group.selected = !group.selected;
                $scope.dialog.confirmDiscardChanges = true;
            }
            function pickGroup() {
                navigationService.allowHideDialog(false);
                editorService.memberGroupPicker({
                    multiPicker: true,
                    submit: function submit(model) {
                        var selectedGroupIds = model.selectedMemberGroups ? model.selectedMemberGroups : [model.selectedMemberGroup];
                        _.each(selectedGroupIds, function (groupId) {
                            // find the group in the lookup list and add it if it isn't already
                            var group = _.find(vm.allGroups, function (g) {
                                return g.id === parseInt(groupId);
                            });
                            if (group && !_.find(vm.groups, function (g) {
                                    return g.id === group.id;
                                })) {
                                vm.groups.push(group);
                            }
                        });
                        editorService.close();
                        navigationService.allowHideDialog(true);
                        $scope.dialog.confirmDiscardChanges = true;
                    },
                    close: function close() {
                        editorService.close();
                        navigationService.allowHideDialog(true);
                    }
                });
            }
            function removeGroup(group) {
                vm.groups = _.reject(vm.groups, function (g) {
                    return g.id === group.id;
                });
                $scope.dialog.confirmDiscardChanges = true;
            }
            function pickMember() {
                navigationService.allowHideDialog(false);
                // TODO: once editorService has a memberPicker method, use that instead
                editorService.treePicker({
                    multiPicker: true,
                    entityType: 'Member',
                    section: 'member',
                    treeAlias: 'member',
                    filter: function filter(i) {
                        return i.metaData.isContainer;
                    },
                    filterCssClass: 'not-allowed',
                    submit: function submit(model) {
                        if (model.selection && model.selection.length) {
                            var promises = [];
                            // get the selected member usernames
                            _.each(model.selection, function (member) {
                                // TODO:
                                // as-is we need to fetch all the picked members one at a time to get their usernames.
                                // when editorService has a memberPicker method, see if this can't be avoided - otherwise
                                // add a memberResource.getByKeys() method to do all this in one request
                                promises.push(memberResource.getByKey(member.key).then(function (newMember) {
                                    if (!_.find(vm.members, function (currentMember) {
                                            return currentMember.username === newMember.username;
                                        })) {
                                        vm.members.push(newMember);
                                    }
                                }));
                            });
                            editorService.close();
                            navigationService.allowHideDialog(true);
                            // wait for all the member lookups to complete 
                            vm.loading = true;
                            $q.all(promises).then(function () {
                                vm.loading = false;
                            });
                            $scope.dialog.confirmDiscardChanges = true;
                        }
                    },
                    close: function close() {
                        editorService.close();
                        navigationService.allowHideDialog(true);
                    }
                });
            }
            function removeMember(member) {
                vm.members = _.without(vm.members, member);
            }
            function pickLoginPage() {
                pickPage(vm.loginPage);
            }
            function pickErrorPage() {
                pickPage(vm.errorPage);
            }
            function pickPage(page) {
                navigationService.allowHideDialog(false);
                editorService.contentPicker({
                    submit: function submit(model) {
                        if (page === vm.loginPage) {
                            vm.loginPage = model.selection[0];
                        } else {
                            vm.errorPage = model.selection[0];
                        }
                        editorService.close();
                        navigationService.allowHideDialog(true);
                        $scope.dialog.confirmDiscardChanges = true;
                    },
                    close: function close() {
                        editorService.close();
                        navigationService.allowHideDialog(true);
                    }
                });
            }
            function removeProtection() {
                vm.removing = true;
            }
            function removeProtectionConfirm() {
                vm.buttonState = 'busy';
                contentResource.removePublicAccess(id).then(function () {
                    localizationService.localize('publicAccess_paIsRemoved', [$scope.currentNode.name]).then(function (value) {
                        vm.success = { message: value };
                    });
                    navigationService.syncTree({
                        tree: 'content',
                        path: $scope.currentNode.path,
                        forceReload: true
                    });
                }, function (error) {
                    vm.error = error;
                    vm.buttonState = 'error';
                });
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.ProtectController', ContentProtectController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.RecycleBinController
 * @function
 * 
 * @description
 * Controls the recycle bin for content
 * 
 */
    function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) {
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.page = {};
        $scope.page.name = 'Recycle Bin';
        $scope.page.nameLocked = true;
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.listViewPath = null;
        $routeParams.id = '-20';
        contentResource.getRecycleBin().then(function (result) {
            $scope.content = result;
        });
        // sync tree node
        navigationService.syncTree({
            tree: 'content',
            path: [
                '-1',
                $routeParams.id
            ],
            forceReload: false
        });
        localizePageName();
        function localizePageName() {
            var pageName = 'general_recycleBin';
            localizationService.localize(pageName).then(function (value) {
                $scope.page.name = value;
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.RecycleBinController', ContentRecycleBinController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.Content.RestoreController', function ($scope, relationResource, contentResource, entityResource, navigationService, appState, treeService, userService, localizationService) {
        $scope.source = _.clone($scope.currentNode);
        $scope.error = null;
        $scope.loading = true;
        $scope.moving = false;
        $scope.success = false;
        $scope.dialogTreeApi = {};
        $scope.searchInfo = {
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.labels = {};
        localizationService.localizeMany(['treeHeaders_content']).then(function (data) {
            $scope.labels.treeRoot = data[0];
        });
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
        relationResource.getByChildId($scope.source.id, 'relateParentDocumentOnDelete').then(function (data) {
            $scope.loading = false;
            if (!data.length) {
                $scope.moving = true;
                return;
            }
            $scope.relation = data[0];
            if ($scope.relation.parentId === -1) {
                $scope.target = {
                    id: -1,
                    name: $scope.labels.treeRoot
                };
            } else {
                $scope.loading = true;
                entityResource.getById($scope.relation.parentId, 'Document').then(function (data) {
                    $scope.loading = false;
                    $scope.target = data;
                    // make sure the target item isn't in the recycle bin
                    if ($scope.target.path.indexOf('-20') !== -1) {
                        $scope.moving = true;
                        $scope.target = null;
                    }
                }, function (err) {
                    $scope.loading = false;
                    $scope.error = err;
                });
            }
        }, function (err) {
            $scope.loading = false;
            $scope.error = err;
        });
        $scope.restore = function () {
            $scope.loading = true;
            // this code was copied from `content.move.controller.js`
            contentResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.loading = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.loading = false;
                $scope.error = err;
            });
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    (function () {
        'use strict';
        function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper, navigationService, overlayService) {
            var vm = this;
            var currentForm;
            vm.availableUserGroups = [];
            vm.selectedUserGroups = [];
            vm.removedUserGroups = [];
            vm.viewState = 'manageGroups';
            vm.labels = {};
            vm.initialState = {};
            vm.setViewSate = setViewSate;
            vm.editPermissions = editPermissions;
            vm.setPermissions = setPermissions;
            vm.save = save;
            vm.removePermissions = removePermissions;
            vm.cancelManagePermissions = cancelManagePermissions;
            vm.closeDialog = closeDialog;
            vm.discardChanges = discardChanges;
            function onInit() {
                vm.loading = true;
                contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) {
                    initData(userGroups);
                    vm.loading = false;
                    currentForm = angularHelper.getCurrentForm($scope);
                });
            }
            /**
    * This will initialize the data and set the correct selectedUserGroups based on the default permissions and explicit permissions assigned
    * @param {any} userGroups
    */
            function initData(userGroups) {
                //reset this
                vm.selectedUserGroups = [];
                vm.availableUserGroups = userGroups;
                vm.availableUserGroups.forEach(function (group) {
                    if (group.permissions) {
                        //if there's explicit permissions assigned than it's selected
                        assignGroupPermissions(group);
                    }
                });
                vm.initialState = angular.copy(userGroups);
            }
            function resetData() {
                vm.selectedUserGroups = [];
                vm.availableUserGroups = angular.copy(vm.initialState);
                vm.availableUserGroups.forEach(function (group) {
                    if (group.permissions) {
                        //if there's explicit permissions assigned than it's selected
                        group.selected = false;
                        assignGroupPermissions(group);
                    }
                });
                currentForm = angularHelper.getCurrentForm($scope);
            }
            function setViewSate(state) {
                vm.viewState = state;
            }
            function editPermissions(group) {
                vm.selectedUserGroup = group;
                if (!vm.selectedUserGroup.permissions) {
                    //if no permissions are explicitly set this means we need to show the defaults
                    vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions;
                }
                localizationService.localize('defaultdialogs_permissionsSetForGroup', [
                    $scope.currentNode.name,
                    vm.selectedUserGroup.name
                ]).then(function (value) {
                    vm.labels.permissionsSetForGroup = value;
                });
                setViewSate('managePermissions');
                // hide dropdown
                vm.groupsDropdownOpen = false;
            }
            function assignGroupPermissions(group) {
                // clear allowed permissions before we make the list so we don't have duplicates
                group.allowedPermissions = [];
                // get list of checked permissions
                Object.values(group.permissions).forEach(function (permissionGroup) {
                    permissionGroup.forEach(function (permission) {
                        if (permission.checked) {
                            //the `allowedPermissions` is what will get sent up to the server for saving
                            group.allowedPermissions.push(permission);
                        }
                    });
                });
                if (!group.selected) {
                    // set to selected so we can remove from the dropdown easily
                    group.selected = true;
                    vm.selectedUserGroups.push(group);
                    //remove from the removed groups if it's been re-added
                    vm.removedUserGroups = _.reject(vm.removedUserGroups, function (g) {
                        return g.id == group.id;
                    });
                }
            }
            function setPermissions(group) {
                assignGroupPermissions(group);
                setViewSate('manageGroups');
                $scope.dialog.confirmDiscardChanges = true;
            }
            /**
     * This essentially resets the permissions for a group for this content item, it will remove it from the selected list
     * @param {any} index
     */
            function removePermissions(index) {
                // remove as selected so we can select it from the dropdown again
                var group = vm.selectedUserGroups[index];
                group.selected = false;
                //reset assigned permissions - so it will default back to default permissions
                group.permissions = [];
                group.allowedPermissions = [];
                vm.selectedUserGroups.splice(index, 1);
                //track it in the removed so this gets pushed to the server
                vm.removedUserGroups.push(group);
            }
            function cancelManagePermissions() {
                setViewSate('manageGroups');
                resetData();
            }
            function formatSaveModel(permissionsSave, groupCollection) {
                groupCollection.forEach(function (g) {
                    permissionsSave[g.id] = [];
                    g.allowedPermissions.forEach(function (p) {
                        permissionsSave[g.id].push(p.permissionCode);
                    });
                });
            }
            function save() {
                vm.saveState = 'busy';
                vm.saveError = false;
                vm.saveSuccces = false;
                //this is a dictionary that we need to populate
                var permissionsSave = {};
                //format the selectedUserGroups, then the removedUserGroups since we want to pass data from both collections up
                formatSaveModel(permissionsSave, vm.selectedUserGroups);
                formatSaveModel(permissionsSave, vm.removedUserGroups);
                var saveModel = {
                    contentId: $scope.currentNode.id,
                    permissions: permissionsSave
                };
                contentResource.savePermissions(saveModel).then(function (userGroups) {
                    //re-assign model from server since it could have changed
                    initData(userGroups);
                    // clear dirty state on the form so we don't see the discard changes notification
                    // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed
                    $timeout(function () {
                        if (currentForm) {
                            currentForm.$dirty = false;
                        }
                    });
                    $scope.dialog.confirmDiscardChanges = false;
                    vm.saveState = 'success';
                    vm.saveSuccces = true;
                }, function (error) {
                    vm.saveState = 'error';
                    vm.saveError = error;
                });
            }
            function closeDialog() {
                // check if form has been changed. If it has show discard changes notification
                if (currentForm && currentForm.$dirty) {
                    localizationService.localizeMany([
                        'prompt_unsavedChanges',
                        'prompt_unsavedChangesWarning',
                        'prompt_discardChanges',
                        'prompt_stay'
                    ]).then(function (values) {
                        var overlay = {
                            'view': 'default',
                            'title': values[0],
                            'content': values[1],
                            'disableBackdropClick': true,
                            'disableEscKey': true,
                            'submitButtonLabel': values[2],
                            'closeButtonLabel': values[3],
                            submit: function submit() {
                                overlayService.close();
                                navigationService.hideDialog();
                            },
                            close: function close() {
                                overlayService.close();
                            }
                        };
                        overlayService.open(overlay);
                    });
                } else {
                    navigationService.hideDialog();
                }
            }
            function discardChanges() {
                navigationService.hideDialog();
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.RightsController', ContentRightsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService, eventsService) {
            var vm = this;
            var id = $scope.currentNode.id;
            vm.loading = false;
            vm.children = [];
            vm.saveButtonState = 'init';
            vm.sortOrder = {};
            vm.sortableOptions = {
                axis: 'y',
                containment: 'parent',
                distance: 10,
                tolerance: 'pointer',
                opacity: 0.7,
                scroll: true,
                cursor: 'move',
                helper: fixSortableHelper,
                update: function update() {
                    // clear the sort order when drag and drop is used
                    vm.sortOrder.column = '';
                    vm.sortOrder.reverse = false;
                }
            };
            vm.save = save;
            vm.sort = sort;
            vm.close = close;
            function onInit() {
                vm.loading = true;
                contentResource.getChildren(id, { cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture }).then(function (data) {
                    vm.children = data.items;
                    vm.loading = false;
                });
            }
            function save() {
                vm.saveButtonState = 'busy';
                var args = {
                    parentId: id,
                    sortedIds: _.map(vm.children, function (child) {
                        return child.id;
                    })
                };
                contentResource.sort(args).then(function () {
                    navigationService.syncTree({
                        tree: 'content',
                        path: $scope.currentNode.path,
                        forceReload: true
                    }).then(function () {
                        return navigationService.reloadNode($scope.currentNode);
                    });
                    eventsService.emit('sortCompleted', { id: id });
                    vm.saveButtonState = 'success';
                }, function (error) {
                    vm.error = error;
                    vm.saveButtonState = 'error';
                });
            }
            function fixSortableHelper(e, ui) {
                // keep the correct width of each table cell when sorting
                ui.children().each(function () {
                    $(this).width($(this).width());
                });
                return ui;
            }
            function sort(column) {
                // reverse if it is already ordered by that column
                if (vm.sortOrder.column === column) {
                    vm.sortOrder.reverse = !vm.sortOrder.reverse;
                } else {
                    vm.sortOrder.column = column;
                    vm.sortOrder.reverse = false;
                }
                vm.children = $filter('orderBy')(vm.children, vm.sortOrder.column, vm.sortOrder.reverse);
            }
            function close() {
                navigationService.hideDialog();
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.SortController', ContentSortController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PublishController($scope, localizationService, contentEditingHelper) {
            var vm = this;
            vm.loading = true;
            vm.isNew = true;
            vm.changeSelection = changeSelection;
            /** 
     * Returns true if publish meets the requirements of mandatory languages 
     * */
            function canPublish() {
                var hasSomethingToPublish = false;
                vm.variants.forEach(function (variant) {
                    // if varaint is mandatory and not already published:
                    if (variant.publish === false && notPublishedMandatoryFilter(variant)) {
                        return false;
                    }
                    if (variant.publish === true) {
                        hasSomethingToPublish = true;
                    }
                });
                return hasSomethingToPublish;
            }
            function changeSelection(variant) {
                // update submit button state:
                $scope.model.disableSubmitButton = !canPublish();
                //need to set the Save state to same as publish.
                variant.save = variant.publish;
            }
            function hasAnyDataFilter(variant) {
                // if we have a name, then we have data.
                if (variant.name != null && variant.name.length > 0) {
                    return true;
                }
                if (variant.isDirty === true) {
                    return true;
                }
                variant.tabs.forEach(function (tab) {
                    tab.properties.forEach(function (property) {
                        if (property.value != null && property.value.length > 0) {
                            return true;
                        }
                    });
                });
                return false;
            }
            /**
     * determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
     *  * it's editor is in a $dirty state
     *  * it has pending saves
     *  * it is unpublished
     * @param {*} variant 
     */
            function dirtyVariantFilter(variant) {
                return variant.isDirty || variant.state === 'Draft' || variant.state === 'PublishedPendingChanges';
            }
            /**
     * determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
     *  * variant is active
     *  * it's editor is in a $dirty state
     *  * it has pending saves
     *  * it is unpublished
     * @param {*} variant 
     */
            function publishableVariantFilter(variant) {
                return variant.active || variant.isDirty || variant.state === 'Draft' || variant.state === 'PublishedPendingChanges';
            }
            function notPublishedMandatoryFilter(variant) {
                return variant.state !== 'Published' && variant.state !== 'PublishedPendingChanges' && variant.isMandatory === true;
            }
            /**
     * determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
     *  * has a mandatory language
     *  * without having a segment, segments cant be mandatory at current state of code.
     * @param {*} variant 
     */
            function isMandatoryFilter(variant) {
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            /**
     * determine a variant is needed, but not already a choice.
     *  * publishable — aka. displayed as a publish option.
     *  * published — its already published and everything is then fine.
     *  * mandatory — this is needed, and thats why we highlight it.
     * @param {*} variant 
     */
            function notPublishableButMandatoryFilter(variant) {
                return !publishableVariantFilter(variant) && variant.state !== 'Published' && variant.isMandatory === true;
            }
            function onInit() {
                vm.variants = $scope.model.variants;
                // If we have a variant that's not in the state of NotCreated, 
                // then we know we have data and it's not a new content node.
                vm.isNew = vm.variants.some(function (variant) {
                    return variant.state === 'NotCreated';
                });
                vm.variants.forEach(function (variant) {
                    // reset to not be published
                    variant.publish = variant.save = false;
                    variant.isMandatory = isMandatoryFilter(variant);
                    // if this is a new node and we have data on this variant.
                    if (vm.isNew === true && hasAnyDataFilter(variant)) {
                        variant.save = true;
                    }
                });
                vm.availableVariants = vm.variants.filter(publishableVariantFilter);
                vm.missingMandatoryVariants = vm.variants.filter(notPublishableButMandatoryFilter);
                // if any active varaiant that is available for publish, we set it to be published:
                vm.availableVariants.forEach(function (v) {
                    if (v.active) {
                        v.save = v.publish = true;
                    }
                });
                if (vm.availableVariants.length !== 0) {
                    vm.availableVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.availableVariants);
                }
                $scope.model.disableSubmitButton = !canPublish();
                var localizeKey = vm.missingMandatoryVariants.length > 0 ? 'content_notReadyToPublish' : !$scope.model.title ? 'content_readyToPublish' : '';
                if (localizeKey) {
                    localizationService.localize(localizeKey).then(function (value) {
                        $scope.model.title = value;
                        vm.loading = false;
                    });
                } else {
                    vm.loading = false;
                }
            }
            onInit();
            //when this dialog is closed, reset all 'publish' flags
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.publish = variant.save = false;
                });
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.PublishController', PublishController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PublishDescendantsController($scope, localizationService, contentEditingHelper) {
            var vm = this;
            vm.includeUnpublished = $scope.model.includeUnpublished || false;
            vm.changeSelection = changeSelection;
            vm.toggleIncludeUnpublished = toggleIncludeUnpublished;
            function onInit() {
                vm.variants = $scope.model.variants;
                vm.displayVariants = vm.variants.slice(0);
                // shallow copy, we don't want to share the array-object (because we will be performing a sort method) but each entry should be shared (because we need validation and notifications).
                vm.labels = {};
                // get localized texts for use in directives
                if (!$scope.model.title) {
                    localizationService.localize('buttons_publishDescendants').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                if (!vm.labels.includeUnpublished) {
                    localizationService.localize('content_includeUnpublished').then(function (value) {
                        vm.labels.includeUnpublished = value;
                    });
                }
                if (!vm.labels.includeUnpublished) {
                    localizationService.localize('content_includeUnpublished').then(function (value) {
                        vm.labels.includeUnpublished = value;
                    });
                }
                vm.variants.forEach(function (variant) {
                    variant.isMandatory = isMandatoryFilter(variant);
                });
                if (vm.variants.length > 1) {
                    vm.displayVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.displayVariants);
                    var active = vm.variants.find(function (v) {
                        return v.active;
                    });
                    if (active) {
                        //ensure that the current one is selected
                        active.publish = active.save = true;
                    }
                    $scope.model.disableSubmitButton = !canPublish();
                } else {
                    // localize help text for invariant content
                    vm.labels.help = {
                        'key': 'content_publishDescendantsHelp',
                        'tokens': [vm.variants[0].name]
                    };
                }
            }
            function toggleIncludeUnpublished() {
                vm.includeUnpublished = !vm.includeUnpublished;
                // make sure this value is pushed back to the scope
                $scope.model.includeUnpublished = vm.includeUnpublished;
            }
            /** Returns true if publishing is possible based on if there are un-published mandatory languages */
            function canPublish() {
                var selected = [];
                vm.variants.forEach(function (variant) {
                    var published = !(variant.state === 'NotCreated' || variant.state === 'Draft');
                    if (variant.segment == null && variant.language && variant.language.isMandatory && !published && !variant.publish) {
                        //if a mandatory variant isn't published 
                        //and not flagged for saving
                        //then we cannot continue
                        // TODO: Show a message when this occurs
                        return false;
                    }
                    if (variant.publish) {
                        selected.push(variant.publish);
                    }
                });
                return selected.length > 0;
            }
            function changeSelection(variant) {
                $scope.model.disableSubmitButton = !canPublish();
                //need to set the Save state to true if publish is true
                variant.save = variant.publish;
            }
            function isMandatoryFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * has a mandatory language
                // * without having a segment, segments cant be mandatory at current state of code.
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            //when this dialog is closed, reset all 'publish' flags
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.publish = variant.save = false;
                });
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.PublishDescendantsController', PublishDescendantsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function SaveContentController($scope, localizationService, contentEditingHelper) {
            var vm = this;
            vm.loading = true;
            vm.hasPristineVariants = false;
            vm.isNew = true;
            vm.changeSelection = changeSelection;
            function changeSelection(variant) {
                var firstSelected = _.find(vm.variants, function (v) {
                    return v.save;
                });
                $scope.model.disableSubmitButton = !firstSelected;    //disable submit button if there is none selected
            }
            function saveableVariantFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as save-able) if it's
                // * the active one
                // * it's editor is in a $dirty state
                return variant.active || variant.isDirty;
            }
            function isMandatoryFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * has a mandatory language
                // * without having a segment, segments cant be mandatory at current state of code.
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            function hasAnyData(variant) {
                if (variant.name == null || variant.name.length === 0) {
                    return false;
                }
                var result = variant.isDirty != null;
                if (result)
                    return true;
                for (var t = 0; t < variant.tabs.length; t++) {
                    for (var p = 0; p < variant.tabs[t].properties.length; p++) {
                        var property = variant.tabs[t].properties[p];
                        if (property.culture == null)
                            continue;
                        result = result || property.value != null && property.value.length > 0;
                        if (result)
                            return true;
                    }
                }
                return result;
            }
            function onInit() {
                vm.variants = $scope.model.variants;
                vm.availableVariants = vm.variants.filter(saveableVariantFilter);
                vm.isNew = vm.variants.some(function (variant) {
                    return variant.state === 'NotCreated';
                });
                if (!$scope.model.title) {
                    localizationService.localize('content_readyToSave').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                vm.variants.forEach(function (variant) {
                    //reset state:
                    variant.save = variant.publish = false;
                    variant.isMandatory = isMandatoryFilter(variant);
                    if (vm.isNew && hasAnyData(variant)) {
                        variant.save = true;
                    }
                });
                if (vm.variants.length !== 0) {
                    //ensure that the current one is selected
                    var active = vm.variants.find(function (v) {
                        return v.active;
                    });
                    if (active) {
                        active.save = true;
                    }
                    vm.availableVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.availableVariants);
                } else {
                    //disable save button if we have nothing to save
                    $scope.model.disableSubmitButton = true;
                }
                vm.loading = false;
            }
            onInit();
            //when this dialog is closed, reset all 'save' flags
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.save = false;
                });
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.SaveContentController', SaveContentController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ScheduleContentController($scope, $timeout, localizationService, dateHelper, userService, contentEditingHelper) {
            var vm = this;
            vm.datePickerSetup = datePickerSetup;
            vm.datePickerChange = datePickerChange;
            vm.datePickerShow = datePickerShow;
            vm.datePickerClose = datePickerClose;
            vm.clearPublishDate = clearPublishDate;
            vm.clearUnpublishDate = clearUnpublishDate;
            vm.dirtyVariantFilter = dirtyVariantFilter;
            vm.changeSelection = changeSelection;
            vm.firstSelectedDates = {};
            vm.currentUser = null;
            //used to track the original values so if the user doesn't save the schedule and they close the dialog we reset the dates back to what they were.
            var origDates = [];
            function onInit() {
                vm.variants = $scope.model.variants;
                vm.displayVariants = vm.variants.slice(0);
                // shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications).
                if (!$scope.model.title) {
                    localizationService.localize('general_scheduledPublishing').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                vm.variants.forEach(function (variant) {
                    origDates.push({
                        releaseDate: variant.releaseDate,
                        expireDate: variant.expireDate
                    });
                    variant.isMandatory = isMandatoryFilter(variant);
                });
                // Check for variants: if a node is invariant it will still have the default language in variants
                // so we have to check for length > 1
                if (vm.variants.length > 1) {
                    vm.displayVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.displayVariants);
                    vm.variants.forEach(function (v) {
                        if (v.active) {
                            v.save = true;
                        }
                    });
                    $scope.model.disableSubmitButton = !canSchedule();
                }
                // get current backoffice user and format dates
                userService.getCurrentUser().then(function (currentUser) {
                    vm.currentUser = currentUser;
                    vm.variants.forEach(function (variant) {
                        // prevent selecting publish/unpublish date before today
                        var now = new Date();
                        var nowFormatted = moment(now).format('YYYY-MM-DD HH:mm');
                        var datePickerConfig = {
                            enableTime: true,
                            dateFormat: 'Y-m-d H:i',
                            time_24hr: true,
                            minDate: nowFormatted,
                            defaultDate: nowFormatted
                        };
                        variant.datePickerConfig = datePickerConfig;
                        // format all dates to local
                        if (variant.releaseDate || variant.expireDate) {
                            formatDatesToLocal(variant);
                        }
                    });
                });
            }
            /**
     * Callback when date is set up
     * @param {any} variant
     * @param {any} type publish or unpublish 
     * @param {any} datePickerInstance The date picker instance
     */
            function datePickerSetup(variant, type, datePickerInstance) {
                // store a date picker instance for publish and unpublish picker
                // so we can change the settings independently.
                if (type === 'publish') {
                    variant.releaseDatePickerInstance = datePickerInstance;
                } else if (type === 'unpublish') {
                    variant.expireDatePickerInstance = datePickerInstance;
                }
                $scope.model.disableSubmitButton = !canSchedule();
            }
            ;
            /**
     * Callback when date picker date changes
     * @param {any} variant 
     * @param {any} dateStr Date string from the date picker
     * @param {any} type publish or unpublish
     */
            function datePickerChange(variant, dateStr, type) {
                if (type === 'publish') {
                    setPublishDate(variant, dateStr);
                } else if (type === 'unpublish') {
                    setUnpublishDate(variant, dateStr);
                }
                $scope.model.disableSubmitButton = !canSchedule();
            }
            /**
     * Add flag when a date picker opens is we can prevent the overlay from closing
     * @param {any} variant 
     * @param {any} type publish or unpublish
     */
            function datePickerShow(variant, type) {
                var activeDatePickerInstance;
                if (type === 'publish') {
                    variant.releaseDatePickerOpen = true;
                    activeDatePickerInstance = variant.releaseDatePickerInstance;
                } else if (type === 'unpublish') {
                    variant.expireDatePickerOpen = true;
                    activeDatePickerInstance = variant.expireDatePickerInstance;
                }
                // Prevent enter key in time fields from submitting the overlay before the associated input gets the updated time
                if (activeDatePickerInstance && !activeDatePickerInstance.hourElement.hasAttribute('overlay-submit-on-enter')) {
                    activeDatePickerInstance.hourElement.setAttribute('overlay-submit-on-enter', 'false');
                }
                if (activeDatePickerInstance && !activeDatePickerInstance.minuteElement.hasAttribute('overlay-submit-on-enter')) {
                    activeDatePickerInstance.minuteElement.setAttribute('overlay-submit-on-enter', 'false');
                }
                checkForBackdropClick();
                $scope.model.disableSubmitButton = !canSchedule();
            }
            /**
     * Remove flag when a date picker closes so the overlay can be closed again
     * @param {any} variant 
     * @param {any} type publish or unpublish
     */
            function datePickerClose(variant, type) {
                $timeout(function () {
                    if (type === 'publish') {
                        variant.releaseDatePickerOpen = false;
                    } else if (type === 'unpublish') {
                        variant.expireDatePickerOpen = false;
                    }
                    checkForBackdropClick();
                    $scope.model.disableSubmitButton = !canSchedule();
                }, 200);
            }
            /**
     * Prevent the overlay from closing if any date pickers are open
     */
            function checkForBackdropClick() {
                var open = vm.variants.find(function (variant) {
                    return variant.releaseDatePickerOpen || variant.expireDatePickerOpen;
                });
                if (open) {
                    $scope.model.disableBackdropClick = true;
                } else {
                    $scope.model.disableBackdropClick = false;
                }
            }
            /**
     * Sets the selected publish date
     * @param {any} variant 
     * @param {any} date The selected date
     */
            function setPublishDate(variant, date) {
                if (!date) {
                    return;
                }
                //The date being passed in here is the user's local date/time that they have selected
                //we need to convert this date back to the server date on the model.
                var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                // update publish value
                variant.releaseDate = serverTime;
                // make sure dates are formatted to the user's locale
                formatDatesToLocal(variant);
                // make sure the unpublish date can't be before the publish date
                variant.expireDatePickerInstance.set('minDate', moment(variant.releaseDate).format('YYYY-MM-DD HH:mm'));
            }
            /**
     * Sets the selected unpublish date
     * @param {any} variant 
     * @param {any} date The selected date
     */
            function setUnpublishDate(variant, date) {
                if (!date) {
                    return;
                }
                //The date being passed in here is the user's local date/time that they have selected
                //we need to convert this date back to the server date on the model.
                var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                // update publish value
                variant.expireDate = serverTime;
                // make sure dates are formatted to the user's locale
                formatDatesToLocal(variant);
                // make sure the publish date can't be after the publish date
                variant.releaseDatePickerInstance.set('maxDate', moment(variant.expireDate).format('YYYY-MM-DD HH:mm'));
            }
            /**
     * Clears the publish date
     * @param {any} variant 
     */
            function clearPublishDate(variant) {
                if (variant && variant.releaseDate) {
                    variant.releaseDate = null;
                    // we don't have a publish date anymore so we can clear the min date for unpublish
                    var now = new Date();
                    var nowFormatted = moment(now).format('YYYY-MM-DD HH:mm');
                    variant.expireDatePickerInstance.set('minDate', nowFormatted);
                }
                $scope.model.disableSubmitButton = !canSchedule();
            }
            /**
     * Clears the unpublish date
     * @param {any} variant 
     */
            function clearUnpublishDate(variant) {
                if (variant && variant.expireDate) {
                    variant.expireDate = null;
                    // we don't have a unpublish date anymore so we can clear the max date for publish
                    variant.releaseDatePickerInstance.set('maxDate', null);
                }
                $scope.model.disableSubmitButton = !canSchedule();
            }
            /**
     * Formates the selected dates to fit the user culture
     * @param {any} variant 
     */
            function formatDatesToLocal(variant) {
                if (variant && variant.releaseDate) {
                    variant.releaseDateFormatted = dateHelper.getLocalDate(variant.releaseDate, vm.currentUser.locale, 'MMM Do YYYY, HH:mm');
                }
                if (variant && variant.expireDate) {
                    variant.expireDateFormatted = dateHelper.getLocalDate(variant.expireDate, vm.currentUser.locale, 'MMM Do YYYY, HH:mm');
                }
            }
            /**
     * Called when new variants are selected or deselected
     * @param {any} variant 
     */
            function changeSelection(variant) {
                $scope.model.disableSubmitButton = !canSchedule();
                //need to set the Save state to true if publish is true
                variant.save = variant.save;
            }
            function dirtyVariantFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * the active one
                // * it's editor is in a $dirty state
                // * it has pending saves
                // * it is unpublished
                // * it is in NotCreated state
                return variant.active || variant.isDirty || variant.state === 'Draft' || variant.state === 'PublishedPendingChanges' || variant.state === 'NotCreated';
            }
            function isMandatoryFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * has a mandatory language
                // * without having a segment, segments cant be mandatory at current state of code.
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            /** Returns true if publishing is possible based on if there are un-published mandatory languages */
            function canSchedule() {
                // sched is enabled if
                //  1) when mandatory langs are not published AND all mandatory langs are selected AND all mandatory langs have a release date
                //  2) OR all mandatory langs are published
                //  3) OR all mandatory langs are are scheduled for publishing
                //  4) OR there has been a persisted schedule for a variant and it has now been changed
                var selectedWithDates = [];
                for (var i = 0; i < vm.variants.length; i++) {
                    var variant = vm.variants[i];
                    //if the sched dates for this variant have been removed then we must allow the schedule button to be used to save the changes
                    var schedCleared = origDates[i].releaseDate && origDates[i].releaseDate !== variant.releaseDate || origDates[i].expireDate && origDates[i].expireDate !== variant.expireDate;
                    if (schedCleared) {
                        return true;
                    }
                    var isMandatory = variant.segment == null && variant.language && variant.language.isMandatory;
                    //if this variant will show up in the publish-able list
                    var publishable = dirtyVariantFilter(variant);
                    var published = !(variant.state === 'NotCreated' || variant.state === 'Draft');
                    var isScheduledPublished = variant.releaseDate;
                    if (isMandatory && !published && !isScheduledPublished && (!publishable || !variant.save)) {
                        //if a mandatory variant isn't published or scheduled published
                        //and it's not publishable or not selected to be published
                        //then we cannot continue
                        // TODO: Show a message when this occurs
                        return false;
                    }
                    if (variant.save && (variant.releaseDate || variant.expireDate)) {
                        selectedWithDates.push(variant.save);
                    }
                }
                return selectedWithDates.length > 0;
            }
            onInit();
            //when this dialog is closed, clean up
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.save = false;
                    // remove properties only needed for this dialog
                    delete variant.releaseDateFormatted;
                    delete variant.expireDateFormatted;
                    delete variant.datePickerConfig;
                    delete variant.releaseDatePickerInstance;
                    delete variant.expireDatePickerInstance;
                    delete variant.releaseDatePickerOpen;
                    delete variant.expireDatePickerOpen;
                });
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.ScheduleContentController', ScheduleContentController);
    }());
    'use strict';
    (function () {
        'use strict';
        function SendToPublishController($scope, localizationService, contentEditingHelper) {
            var vm = this;
            vm.loading = true;
            vm.changeSelection = changeSelection;
            function onInit() {
                vm.variants = $scope.model.variants;
                // set dialog title
                if (!$scope.model.title) {
                    localizationService.localize('content_sendForApproval').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                vm.variants.forEach(function (variant) {
                    variant.isMandatory = isMandatoryFilter(variant);
                });
                vm.availableVariants = vm.variants.filter(publishableVariantFilter);
                if (vm.availableVariants.length !== 0) {
                    vm.availableVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.availableVariants);
                    vm.availableVariants.forEach(function (v) {
                        if (v.active) {
                            v.save = true;
                        }
                    });
                } else {
                    //disable save button if we have nothing to save
                    $scope.model.disableSubmitButton = true;
                }
                vm.loading = false;
            }
            function changeSelection() {
                var firstSelected = vm.variants.find(function (v) {
                    return v.save;
                });
                $scope.model.disableSubmitButton = !firstSelected;    //disable submit button if there is none selected
            }
            function isMandatoryFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * has a mandatory language
                // * without having a segment, segments cant be mandatory at current state of code.
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            function publishableVariantFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * variant is active
                // * it's editor is in a $dirty state
                // * it has pending saves
                // * it is unpublished
                return variant.active || variant.isDirty || variant.state === 'Draft' || variant.state === 'PublishedPendingChanges';
            }
            //when this dialog is closed, reset all 'save' flags
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.save = false;
                });
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.SendToPublishController', SendToPublishController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UnpublishController($scope, localizationService, contentEditingHelper) {
            var vm = this;
            var autoSelectedVariants = [];
            vm.changeSelection = changeSelection;
            function onInit() {
                vm.variants = $scope.model.variants;
                vm.unpublishableVariants = vm.variants.filter(publishedVariantFilter);
                // set dialog title
                if (!$scope.model.title) {
                    localizationService.localize('content_unpublish').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                vm.variants.forEach(function (variant) {
                    variant.isMandatory = isMandatoryFilter(variant);
                });
                // node has variants
                if (vm.variants.length !== 1) {
                    vm.unpublishableVariants = contentEditingHelper.getSortedVariantsAndSegments(vm.unpublishableVariants);
                    var active = vm.variants.find(function (v) {
                        return v.active;
                    });
                    if (active && publishedVariantFilter(active)) {
                        //ensure that the current one is selected
                        active.save = true;
                    }
                    // autoselect other variants if needed
                    changeSelection(active);
                }
            }
            function changeSelection(selectedVariant) {
                // if a mandatory variant is selected we want to select all other variants, we cant have anything published if a mandatory variants gets unpublished.
                // and disable selection for the others
                if (selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) {
                    vm.variants.forEach(function (variant) {
                        if (!variant.save) {
                            // keep track of the variants we automaically select
                            // so we can remove the selection again
                            autoSelectedVariants.push(variant);
                            variant.save = true;
                        }
                        variant.disabled = true;
                    });
                    // make sure the mandatory isn't disabled so we can deselect again
                    selectedVariant.disabled = false;
                }
                // if a mandatory variant is deselected we want to deselet all the variants
                // that was automatically selected so it goes back to the state before the mandatory language was selected.
                // We also want to enable all checkboxes again
                if (!selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) {
                    vm.variants.forEach(function (variant) {
                        // check if variant was auto selected, then deselect
                        var autoSelected = autoSelectedVariants.find(function (x) {
                            return x.culture === variant.culture;
                        });
                        if (autoSelected) {
                            variant.save = false;
                        }
                        variant.disabled = false;
                    });
                    autoSelectedVariants = [];
                }
                // disable submit button if nothing is selected
                var firstSelected = vm.variants.find(function (v) {
                    return v.save;
                });
                $scope.model.disableSubmitButton = !firstSelected;    //disable submit button if there is none selected
            }
            function isMandatoryFilter(variant) {
                //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's
                // * has a mandatory language
                // * without having a segment, segments cant be mandatory at current state of code.
                return variant.language && variant.language.isMandatory === true && variant.segment == null;
            }
            function publishedVariantFilter(variant) {
                //determine a variant is 'published' (meaning it will show up as able unpublish)
                // * it has been published
                // * it has been published with pending changes
                return variant.state === 'Published' || variant.state === 'PublishedPendingChanges';
            }
            //when this dialog is closed, remove all unpublish and disabled flags
            $scope.$on('$destroy', function () {
                vm.variants.forEach(function (variant) {
                    variant.save = variant.disabled = false;
                });
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.UnpublishController', UnpublishController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentBlueprint.CreateController
 * @function
 *
 * @description
 * The controller for creating content blueprints
 */
    function ContentBlueprintCreateController($scope, $location, contentTypeResource, navigationService, appState) {
        var vm = this;
        var node = $scope.currentNode;
        var section = appState.getSectionState('currentSection');
        vm.createBlueprint = createBlueprint;
        vm.close = close;
        function onInit() {
            vm.loading = true;
            contentTypeResource.getAll().then(function (documentTypes) {
                vm.documentTypes = documentTypes;
                vm.loading = false;
            });
        }
        function createBlueprint(documentType) {
            $location.path('/' + section + '/contentBlueprints/edit/' + node.id).search('create', 'true').search('doctype', documentType.alias);
            navigationService.hideMenu();
        }
        function close() {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.CreateController', ContentBlueprintCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentBlueprint.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content blueprints
 */
    function ContentBlueprintDeleteController($scope, contentResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentResource.deleteBlueprint($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.DeleteController', ContentBlueprintDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EditController
 * @function
 * 
 * @description
 * The controller for the content editor
 */
    function ContentBlueprintEditController($scope, $routeParams, contentResource) {
        function getScaffold() {
            return contentResource.getScaffold(-1, $routeParams.doctype).then(function (scaffold) {
                return initialize(scaffold);
            });
        }
        function getBlueprintById(id) {
            return contentResource.getBlueprintById(id).then(function (blueprint) {
                return initialize(blueprint);
            });
        }
        function initialize(content) {
            if (content.apps && content.apps.length) {
                var contentApp = _.find(content.apps, function (app) {
                    return app.alias === 'umbContent';
                });
                if (contentApp) {
                    content.apps = [contentApp];
                } else {
                    content.apps = [];
                }
            }
            content.allowPreview = false;
            content.allowedActions = [
                'A',
                'S',
                'C'
            ];
            return content;
        }
        $scope.contentId = $routeParams.id;
        $scope.isNew = $routeParams.id === '-1';
        $scope.saveMethod = contentResource.saveBlueprint;
        $scope.getMethod = getBlueprintById;
        $scope.getScaffoldMethod = getScaffold;
        //load the default culture selected in the main tree if any
        $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
        $scope.segment = $routeParams.csegment ? $routeParams.csegment : null;
        //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered.
        //This is so we can listen to changes on the cculture parameter since that will not cause a route change
        //and then we can pass in the updated culture to the editor.
        //This will also execute when we are redirecting from creating an item to a newly created item since that
        //will not cause a route change and so we can update the isNew and contentId flags accordingly.
        $scope.$on('$routeUpdate', function (event, next) {
            $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture;
            $scope.segment = next.params.csegment ? next.params.csegment : null;
            $scope.isNew = $routeParams.id === '-1';
            $scope.contentId = $routeParams.id;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.EditController', ContentBlueprintEditController);
    'use strict';
    (function () {
        'use strict';
        function RedirectUrlsController($scope, $q, redirectUrlsResource, notificationsService, localizationService, eventsService, overlayService) {
            // TODO: search by url or url part
            // TODO: search by domain
            // TODO: display domain in dashboard results?
            // used to cancel any request in progress if another one needs to take it's place
            var vm = this;
            var canceler = null;
            vm.dashboard = {
                searchTerm: '',
                loading: false,
                urlTrackerDisabled: false,
                userIsAdmin: false
            };
            vm.pagination = {
                pageIndex: 0,
                pageNumber: 1,
                totalPages: 1,
                pageSize: 20
            };
            vm.goToPage = goToPage;
            vm.search = search;
            vm.removeRedirect = removeRedirect;
            vm.disableUrlTracker = disableUrlTracker;
            vm.enableUrlTracker = enableUrlTracker;
            vm.filter = filter;
            vm.checkEnabled = checkEnabled;
            function activate() {
                vm.checkEnabled().then(function () {
                    vm.search();
                });
            }
            function checkEnabled() {
                vm.dashboard.loading = true;
                return redirectUrlsResource.getEnableState().then(function (response) {
                    vm.dashboard.urlTrackerDisabled = response.enabled !== true;
                    vm.dashboard.userIsAdmin = response.userIsAdmin;
                    vm.dashboard.loading = false;
                });
            }
            function goToPage(pageNumber) {
                vm.pagination.pageIndex = pageNumber - 1;
                vm.pagination.pageNumber = pageNumber;
                vm.search();
            }
            function search() {
                vm.dashboard.loading = true;
                var searchTerm = vm.dashboard.searchTerm;
                if (searchTerm === undefined) {
                    searchTerm = '';
                }
                redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function (response) {
                    vm.redirectUrls = response.searchResults;
                    // update pagination
                    vm.pagination.pageIndex = response.currentPage;
                    vm.pagination.pageNumber = response.currentPage + 1;
                    vm.pagination.totalPages = response.pageCount;
                    vm.dashboard.loading = false;
                });
            }
            function disableUrlTracker(event) {
                var dialog = {
                    view: 'views/dashboard/content/overlays/disable.html',
                    submitButtonLabel: 'Disable',
                    submitButtonLabelKey: 'actions_disable',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        performDisable();
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                localizationService.localize('redirectUrls_disableUrlTracker').then(function (value) {
                    dialog.title = value;
                    overlayService.open(dialog);
                });
                event.preventDefault();
                event.stopPropagation();
            }
            function removeRedirect(redirect, event) {
                var dialog = {
                    view: 'views/dashboard/content/overlays/delete.html',
                    redirect: redirect,
                    submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        performDelete(model.redirect);
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                localizationService.localize('general_delete').then(function (value) {
                    dialog.title = value;
                    overlayService.open(dialog);
                });
                event.preventDefault();
                event.stopPropagation();
            }
            function performDisable() {
                redirectUrlsResource.toggleUrlTracker(true).then(function () {
                    activate();
                    localizationService.localize('redirectUrls_disabledConfirm').then(function (value) {
                        notificationsService.success(value);
                    });
                }, function (error) {
                    localizationService.localize('redirectUrls_disableError').then(function (value) {
                        notificationsService.warning(value);
                    });
                });
            }
            function performDelete(redirect) {
                redirect.deleteButtonState = 'busy';
                redirectUrlsResource.deleteRedirectUrl(redirect.redirectId).then(function () {
                    // emit event
                    var args = { redirect: redirect };
                    eventsService.emit('editors.redirects.redirectDeleted', args);
                    // remove from list
                    var index = vm.redirectUrls.indexOf(redirect);
                    vm.redirectUrls.splice(index, 1);
                    localizationService.localize('redirectUrls_redirectRemoved').then(function (value) {
                        notificationsService.success(value);
                    });
                    // check if new redirects needs to be loaded
                    if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) {
                        // if we are not on the first page - get records from the previous
                        if (vm.pagination.pageIndex > 0) {
                            vm.pagination.pageIndex = vm.pagination.pageIndex - 1;
                            vm.pagination.pageNumber = vm.pagination.pageNumber - 1;
                        }
                        search();
                    }
                }, function (error) {
                    redirect.deleteButtonState = 'error';
                    localizationService.localize('redirectUrls_redirectRemoveError').then(function (value) {
                        notificationsService.error(value);
                    });
                });
            }
            function enableUrlTracker() {
                redirectUrlsResource.toggleUrlTracker(false).then(function () {
                    activate();
                    localizationService.localize('redirectUrls_enabledConfirm').then(function (value) {
                        notificationsService.success(value);
                    });
                }, function (error) {
                    localizationService.localize('redirectUrls_enableError').then(function (value) {
                        notificationsService.warning(value);
                    });
                });
            }
            var filterDebounced = _.debounce(function (e) {
                $scope.$apply(function () {
                    //a canceler exists, so perform the cancelation operation and reset
                    if (canceler) {
                        canceler.resolve();
                        canceler = $q.defer();
                    } else {
                        canceler = $q.defer();
                    }
                    vm.search();
                });
            }, 200);
            function filter() {
                vm.dashboard.loading = true;
                filterDebounced();
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Dashboard.RedirectUrlsController', RedirectUrlsController);
    }());
    'use strict';
    function startUpVideosDashboardController($scope, dashboardResource) {
        $scope.videos = [];
        $scope.init = function (url) {
            dashboardResource.getRemoteXmlData('COM', url).then(function (data) {
                var feed = $(data.data);
                $('item', feed).each(function (i, item) {
                    var video = {};
                    video.thumbnail = $(item).find('thumbnail').attr('url');
                    video.title = $('title', item).text();
                    video.link = $('guid', item).text();
                    $scope.videos.push(video);
                });
            }, function (exception) {
                console.error('ex from remote data', exception);
            });
        };
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartupVideosController', startUpVideosDashboardController);
    function startUpDynamicContentController($q, $timeout, $scope, dashboardResource, assetsService, tourService, eventsService) {
        var vm = this;
        var evts = [];
        vm.loading = true;
        vm.showDefault = false;
        vm.startTour = startTour;
        function onInit() {
            // load tours
            tourService.getGroupedTours().then(function (groupedTours) {
                vm.tours = groupedTours;
            });
        }
        function startTour(tour) {
            tourService.startTour(tour);
        }
        // default dashboard content
        vm.defaultDashboard = {
            infoBoxes: [
                {
                    title: 'Documentation',
                    description: 'Find the answers to your Umbraco questions',
                    url: 'https://our.umbraco.com/documentation/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=documentation/'
                },
                {
                    title: 'Community',
                    description: 'Find the answers or ask your Umbraco questions',
                    url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=our_forum'
                },
                {
                    title: 'Umbraco.tv',
                    description: 'Tutorial videos (some are free, some are on subscription)',
                    url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=tutorial_videos'
                },
                {
                    title: 'Training',
                    description: 'Real-life training and official Umbraco certifications',
                    url: 'https://umbraco.com/training/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=training'
                }
            ],
            articles: [
                {
                    title: 'Umbraco.TV - Learn from the source!',
                    description: 'Umbraco.TV will help you go from zero to Umbraco hero at a pace that suits you. Our easy to follow online training videos will give you the fundamental knowledge to start building awesome Umbraco websites.',
                    img: 'views/dashboard/default/umbracotv.png',
                    url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=tv',
                    altText: 'Umbraco.TV - Hours of Umbraco Video Tutorials',
                    buttonText: 'Visit Umbraco.TV'
                },
                {
                    title: 'Our Umbraco - The Friendliest Community',
                    description: 'Our Umbraco - the official community site is your one stop for everything Umbraco. Whether you need a question answered or looking for cool plugins, the world\'s best and friendliest community is just a click away.',
                    img: 'views/dashboard/default/ourumbraco.png',
                    url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=our',
                    altText: 'Our Umbraco',
                    buttonText: 'Visit Our Umbraco'
                }
            ]
        };
        evts.push(eventsService.on('appState.tour.complete', function (name, completedTour) {
            $timeout(function () {
                Utilities.forEach(vm.tours, function (tourGroup) {
                    Utilities.forEach(tourGroup, function (tour) {
                        if (tour.alias === completedTour.alias) {
                            tour.completed = true;
                        }
                    });
                });
            });
        }));
        //proxy remote css through the local server
        assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl('content'), $scope);
        dashboardResource.getRemoteDashboardContent('content').then(function (data) {
            vm.loading = false;
            //test if we have received valid data
            //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code
            if (data && data.sections) {
                vm.dashboard = data;
            } else {
                vm.showDefault = true;
            }
        }, function (exception) {
            console.error(exception);
            vm.loading = false;
            vm.showDefault = true;
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartUpDynamicContentController', startUpDynamicContentController);
    function FormsController($scope, $cookies, packageResource, localizationService) {
        var vm = this;
        var labels = {};
        var labelKeys = [
            'packager_installStateDownloading',
            'packager_installStateImporting',
            'packager_installStateInstalling',
            'packager_installStateRestarting',
            'packager_installStateComplete'
        ];
        localizationService.localizeMany(labelKeys).then(function (values) {
            labels.installStateDownloading = values[0];
            labels.installStateImporting = values[1];
            labels.installStateInstalling = values[2];
            labels.installStateRestarting = values[3];
            labels.installStateComplete = values[4];
        });
        vm.installForms = function () {
            vm.state = labels.installStateDownloading;
            packageResource.fetch('CD44CF39-3D71-4C19-B6EE-948E1FAF0525').then(function (pack) {
                vm.state = labels.installStateImporting;
                return packageResource.import(pack);
            }, vm.error).then(function (pack) {
                vm.state = labels.installStateInstalling;
                return packageResource.installFiles(pack);
            }, vm.error).then(function (pack) {
                vm.state = labels.installStateRestarting;
                return packageResource.installData(pack);
            }, vm.error).then(function (pack) {
                vm.state = labels.installStateComplete;
                return packageResource.cleanUp(pack);
            }, vm.error).then(vm.complete, vm.error);
        };
        vm.complete = function (result) {
            window.location.href + '?init=true';
            $cookies.putObject('umbPackageInstallId', result.packageGuid);
            window.location.reload(true);
        };
        vm.error = function (err) {
            vm.state = undefined;
            vm.error = err;
            // This will return a rejection meaning that the promise change above will stop
            return $q.reject();
        };
        function Video_player(videoId) {
            // Get dom elements
            this.container = document.getElementById(videoId);
            // Create controls
            this.controls = document.createElement('div');
            this.controls.className = 'video-controls';
            this.seek_bar = document.createElement('input');
            this.seek_bar.className = 'seek-bar';
            this.seek_bar.type = 'range';
            this.seek_bar.setAttribute('value', '0');
            this.loader = document.createElement('div');
            this.loader.className = 'loader';
            this.progress_bar = document.createElement('span');
            this.progress_bar.className = 'progress-bar';
            // Insert controls
            this.controls.appendChild(this.seek_bar);
            this.container.appendChild(this.controls);
            this.controls.appendChild(this.loader);
            this.loader.appendChild(this.progress_bar);
        }
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.FormsDashboardController', FormsController);
    function startupLatestEditsController($scope) {
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartupLatestEditsController', startupLatestEditsController);
    function MediaFolderBrowserDashboardController($scope, $routeParams, $location, contentTypeResource, userService) {
        var currentUser = {};
        userService.getCurrentUser().then(function (user) {
            currentUser = user;
            // check if the user has access to the root which they will require to see this dashboard
            if (currentUser.startMediaIds.indexOf(-1) >= 0) {
                //get the system media listview
                contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
                    $scope.fakeProperty = {
                        alias: 'contents',
                        config: dt.config,
                        description: '',
                        editor: dt.editor,
                        hideLabel: true,
                        id: 1,
                        label: 'Contents:',
                        validation: {
                            mandatory: false,
                            pattern: null
                        },
                        value: '',
                        view: dt.view
                    };
                    // tell the list view to list content at root
                    $routeParams.id = -1;
                });
            } else if (currentUser.startMediaIds.length > 0) {
                // redirect to start node
                $location.path('/media/media/edit/' + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0]));
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.MediaFolderBrowserDashboardController', MediaFolderBrowserDashboardController);
    'use strict';
    function ExamineManagementController($http, $q, $timeout, umbRequestHelper, localizationService, overlayService, editorService) {
        var vm = this;
        vm.indexerDetails = [];
        vm.searcherDetails = [];
        vm.loading = true;
        vm.viewState = 'list';
        vm.selectedIndex = null;
        vm.selectedSearcher = null;
        vm.searchResults = null;
        vm.showSearchResultFields = [];
        vm.showSelectFieldsDialog = showSelectFieldsDialog;
        vm.showSearchResultDialog = showSearchResultDialog;
        vm.showIndexInfo = showIndexInfo;
        vm.showSearcherInfo = showSearcherInfo;
        vm.search = search;
        vm.toggle = toggle;
        vm.rebuildIndex = rebuildIndex;
        vm.setViewState = setViewState;
        vm.nextSearchResultPage = nextSearchResultPage;
        vm.prevSearchResultPage = prevSearchResultPage;
        vm.goToPageSearchResultPage = goToPageSearchResultPage;
        vm.goToResult = goToResult;
        vm.infoOverlay = null;
        function showSelectFieldsDialog() {
            if (vm.searchResults) {
                // build list of available fields
                var availableFields = [];
                vm.searchResults.results.map(function (r) {
                    return Object.keys(r.values).map(function (key) {
                        if (availableFields.indexOf(key) == -1 && key != '__NodeId' && key != 'nodeName') {
                            availableFields.push(key);
                        }
                    });
                });
                availableFields.sort();
                editorService.open({
                    title: 'Fields',
                    availableFields: availableFields,
                    fieldIsSelected: function fieldIsSelected(key) {
                        return vm.showSearchResultFields.indexOf(key) > -1;
                    },
                    toggleField: vm.toggleField,
                    size: 'small',
                    view: 'views/dashboard/settings/examinemanagementfields.html',
                    close: function close() {
                        editorService.close();
                    }
                });
            }
        }
        function showSearchResultDialog(values) {
            if (vm.searchResults) {
                localizationService.localize('examineManagement_fieldValues').then(function (value) {
                    editorService.open({
                        title: value,
                        searchResultValues: values,
                        size: 'medium',
                        view: 'views/dashboard/settings/examinemanagementresults.html',
                        close: function close() {
                            editorService.close();
                        }
                    });
                });
            }
        }
        vm.toggleField = function (key) {
            if (vm.showSearchResultFields.indexOf(key) > -1) {
                vm.showSearchResultFields = vm.showSearchResultFields.filter(function (field) {
                    return field != key;
                });
            } else {
                vm.showSearchResultFields.push(key);
            }
            vm.showSearchResultFields.sort();
        };
        function nextSearchResultPage(pageNumber) {
            search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
        }
        function prevSearchResultPage(pageNumber) {
            search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
        }
        function goToPageSearchResultPage(pageNumber) {
            search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
        }
        function goToResult(result, event) {
            if (!result.editUrl) {
                return;
            }
            // targeting a new tab/window?
            if (event.ctrlKey || event.shiftKey || event.metaKey || // apple
                event.button && event.button === 1    // middle click, >IE9 + everyone else
) {
                // yes, let the link open itself
                return;
            }
            var editor = {
                id: result.editId,
                submit: function submit(model) {
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            switch (result.editSection) {
            case 'content':
                editorService.contentEditor(editor);
                break;
            case 'media':
                editorService.mediaEditor(editor);
                break;
            case 'member':
                editorService.memberEditor(editor);
                break;
            }
            event.stopPropagation();
            event.preventDefault();
        }
        function setViewState(state) {
            vm.searchResults = null;
            vm.viewState = state;
        }
        function showIndexInfo(index) {
            vm.selectedIndex = index;
            setViewState('index-details');
        }
        function showSearcherInfo(searcher) {
            vm.selectedSearcher = searcher;
            setViewState('searcher-details');
        }
        function checkProcessing(index, checkActionName) {
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', checkActionName, { indexName: index.name })), 'Failed to check index processing').then(function (data) {
                if (data !== null && data !== 'null') {
                    //copy all resulting properties
                    for (var k in data) {
                        index[k] = data[k];
                    }
                    index.isProcessing = false;
                } else {
                    $timeout(function () {
                        //don't continue if we've tried 100 times
                        if (index.processingAttempts < 100) {
                            checkProcessing(index, checkActionName);
                            //add an attempt
                            index.processingAttempts++;
                        } else {
                            //we've exceeded 100 attempts, stop processing
                            index.isProcessing = false;
                        }
                    }, 1000);
                }
            });
        }
        function search(searcher, e, pageNumber) {
            //deal with accepting pressing the enter key
            if (e && e.keyCode !== 13) {
                return;
            }
            if (!searcher) {
                throw 'searcher parameter is required';
            }
            searcher.isProcessing = true;
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearchResults', {
                searcherName: searcher.name,
                query: encodeURIComponent(vm.searchText),
                pageIndex: pageNumber ? pageNumber - 1 : 0
            })), 'Failed to search').then(function (searchResults) {
                searcher.isProcessing = false;
                vm.searchResults = searchResults;
                vm.searchResults.pageNumber = pageNumber ? pageNumber : 1;
                //20 is page size
                vm.searchResults.totalPages = Math.ceil(vm.searchResults.totalRecords / 20);
                // add URLs to edit well known entities
                _.each(vm.searchResults.results, function (result) {
                    var section = result.values['__IndexType'][0];
                    switch (section) {
                    case 'content':
                    case 'media':
                        result.editUrl = '/' + section + '/' + section + '/edit/' + result.values['__NodeId'][0];
                        result.editId = result.values['__NodeId'][0];
                        result.editSection = section;
                        break;
                    case 'member':
                        result.editUrl = '/member/member/edit/' + result.values['__Key'][0];
                        result.editId = result.values['__Key'][0];
                        result.editSection = section;
                        break;
                    }
                });
            });
        }
        function toggle(provider, propName) {
            if (provider[propName] !== undefined) {
                provider[propName] = !provider[propName];
            } else {
                provider[propName] = true;
            }
        }
        function rebuildIndex(index, event) {
            var dialog = {
                view: 'views/dashboard/settings/overlays/examinemanagement.rebuild.html',
                index: index,
                submitButtonLabelKey: 'general_ok',
                submitButtonStyle: 'danger',
                submit: function submit(model) {
                    performRebuild(model.index);
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('examineManagement_rebuildIndex').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
            event.preventDefault();
            event.stopPropagation();
        }
        function performRebuild(index) {
            index.isProcessing = true;
            index.processingAttempts = 0;
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostRebuildIndex', { indexName: index.name })), 'Failed to rebuild index').then(function () {
                // rebuilding has started, nothing is returned accept a 200 status code.
                // lets poll to see if it is done.
                $timeout(function () {
                    checkProcessing(index, 'PostCheckRebuildIndex'), 1000;
                });
            });
        }
        function init() {
            // go get the data
            //combine two promises and execute when they are both done
            $q.all([
                //get the indexer details
                umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetIndexerDetails')), 'Failed to retrieve indexer details').then(function (data) {
                    vm.indexerDetails = data;
                }),
                //get the searcher details
                umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearcherDetails')), 'Failed to retrieve searcher details').then(function (data) {
                    vm.searcherDetails = data;
                })
            ]).then(function () {
                vm.loading = false;
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.ExamineManagementController', ExamineManagementController);
    'use strict';
    (function () {
        'use strict';
        function HealthCheckController(healthCheckResource) {
            var SUCCESS = 0;
            var WARNING = 1;
            var ERROR = 2;
            var INFO = 3;
            var vm = this;
            vm.viewState = 'list';
            vm.groups = [];
            vm.selectedGroup = {};
            vm.getStatus = getStatus;
            vm.executeAction = executeAction;
            vm.checkAllGroups = checkAllGroups;
            vm.checkAllInGroup = checkAllInGroup;
            vm.openGroup = openGroup;
            vm.setViewState = setViewState;
            vm.parseRegex = parseRegex;
            // Get a (grouped) list of all health checks
            healthCheckResource.getAllChecks().then(function (response) {
                vm.groups = response;
            });
            function setGroupGlobalResultType(group) {
                var totalSuccess = 0;
                var totalError = 0;
                var totalWarning = 0;
                var totalInfo = 0;
                // count total number of statusses
                Utilities.forEach(group.checks, function (check) {
                    if (check.status) {
                        check.status.forEach(function (status) {
                            switch (status.resultType) {
                            case SUCCESS:
                                totalSuccess = totalSuccess + 1;
                                break;
                            case WARNING:
                                totalWarning = totalWarning + 1;
                                break;
                            case ERROR:
                                totalError = totalError + 1;
                                break;
                            case INFO:
                                totalInfo = totalInfo + 1;
                                break;
                            }
                        });
                    }
                });
                group.totalSuccess = totalSuccess;
                group.totalError = totalError;
                group.totalWarning = totalWarning;
                group.totalInfo = totalInfo;
            }
            // Get the status of an individual check
            function getStatus(check) {
                check.loading = true;
                check.status = null;
                healthCheckResource.getStatus(check.id).then(function (response) {
                    check.loading = false;
                    check.status = response;
                });
            }
            function executeAction(check, index, action) {
                check.loading = true;
                healthCheckResource.executeAction(action).then(function (response) {
                    check.status[index] = response;
                    check.loading = false;
                });
            }
            function checkAllGroups(groups) {
                // set number of checks which has been executed
                for (var i = 0; i < groups.length; i++) {
                    var group = groups[i];
                    checkAllInGroup(group, group.checks);
                }
                vm.groups = groups;
            }
            function checkAllInGroup(group, checks) {
                group.checkCounter = 0;
                group.loading = true;
                if (checks) {
                    checks.forEach(function (check) {
                        check.loading = true;
                        healthCheckResource.getStatus(check.id).then(function (response) {
                            check.status = response;
                            group.checkCounter = group.checkCounter + 1;
                            check.loading = false;
                            // when all checks are done, set global group result
                            if (group.checkCounter === checks.length) {
                                setGroupGlobalResultType(group);
                                group.loading = false;
                            }
                        });
                    });
                }
            }
            function openGroup(group) {
                vm.selectedGroup = group;
                vm.viewState = 'details';
            }
            function setViewState(state) {
                vm.viewState = state;
                if (state === 'list') {
                    for (var i = 0; i < vm.groups.length; i++) {
                        var group = vm.groups[i];
                        setGroupGlobalResultType(group);
                    }
                }
            }
            function parseRegex(regexAsString) {
                return new RegExp(regexAsString);
            }
        }
        angular.module('umbraco').controller('Umbraco.Dashboard.HealthCheckController', HealthCheckController);
    }());
    'use strict';
    function modelsBuilderManagementController($scope, $http, umbRequestHelper, modelsBuilderManagementResource) {
        var vm = this;
        vm.reload = reload;
        vm.generate = generate;
        vm.dashboard = null;
        function generate() {
            vm.generating = true;
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('modelsBuilderBaseUrl', 'BuildModels')), 'Failed to generate.').then(function (result) {
                vm.generating = false;
                vm.dashboard = result;
            });
        }
        function reload() {
            vm.loading = true;
            modelsBuilderManagementResource.getDashboard().then(function (result) {
                vm.dashboard = result;
                vm.loading = false;
            });
        }
        function init() {
            vm.loading = true;
            modelsBuilderManagementResource.getDashboard().then(function (result) {
                vm.dashboard = result;
                vm.loading = false;
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.ModelsBuilderManagementController', modelsBuilderManagementController);
    'use strict';
    function nuCacheController($scope, $http, umbRequestHelper, localizationService, overlayService) {
        var vm = this;
        vm.collect = collect;
        vm.reload = reload;
        vm.verify = verify;
        vm.rebuild = rebuild;
        function reload(event) {
            if (vm.working)
                return;
            var dialog = {
                view: 'views/dashboard/settings/overlays/nucache.reload.html',
                submitButtonLabelKey: 'general_ok',
                submit: function submit(model) {
                    performReload();
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('general_reload').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
            event.preventDefault();
            event.stopPropagation();
        }
        function collect() {
            if (vm.working)
                return;
            vm.working = true;
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('nuCacheStatusBaseUrl', 'Collect')), 'Failed to verify the cache.').then(function (result) {
                vm.working = false;
                vm.status = result;
            });
        }
        function verify() {
            if (vm.working)
                return;
            vm.working = true;
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('nuCacheStatusBaseUrl', 'GetStatus')), 'Failed to verify the cache.').then(function (result) {
                vm.working = false;
                vm.status = result;
            });
        }
        function rebuild(event) {
            if (vm.working)
                return;
            var dialog = {
                view: 'views/dashboard/settings/overlays/nucache.rebuild.html',
                submitButtonLabelKey: 'general_ok',
                submit: function submit(model) {
                    performRebuild();
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('general_rebuild').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
            event.preventDefault();
            event.stopPropagation();
        }
        function performReload() {
            vm.working = true;
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('nuCacheStatusBaseUrl', 'ReloadCache')), 'Failed to trigger a cache reload').then(function (result) {
                vm.working = false;
            });
        }
        function performRebuild() {
            vm.working = true;
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('nuCacheStatusBaseUrl', 'RebuildDbCache')), 'Failed to rebuild the cache.').then(function (result) {
                vm.working = false;
                vm.status = result;
            });
        }
        function init() {
            vm.working = false;
            verify();
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.NuCacheController', nuCacheController);
    'use strict';
    function ProfilerController($scope, $cookies, $http, umbRequestHelper) {
        var vm = this;
        vm.loading = true;
        vm.toggle = toggle;
        function toggle() {
            if (vm.alwaysOn === true) {
                $cookies.remove('UMB-DEBUG', { path: '/' });
                vm.alwaysOn = false;
            } else {
                $cookies.put('UMB-DEBUG', 'true', {
                    path: '/',
                    expires: 'Tue, 01 Jan 2100 00:00:01 GMT'
                });
                vm.alwaysOn = true;
            }
        }
        function init() {
            vm.alwaysOn = $cookies.get('UMB-DEBUG') === 'true';
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('webProfilingBaseUrl', 'GetStatus')), 'Failed to retrieve status for web profiling').then(function (status) {
                vm.loading = false;
                vm.profilerEnabled = status.Enabled;
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.ProfilerController', ProfilerController);
    'use strict';
    function publishedStatusController($scope, $http, umbRequestHelper) {
        var vm = this;
        // note: must defined base url in BackOfficeController
        umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('publishedStatusBaseUrl', 'GetPublishedStatusUrl')), 'Failed to get published status url').then(function (result) {
            //result = 'views/dashboard/developer/nucache.html'
            vm.includeUrl = result;
        });
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.PublishedStatusController', publishedStatusController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataType.CreateController
 * @function
 *
 * @description
 * The controller for the data type creation dialog
 */
    function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.currentNode;
        var section = appState.getSectionState('currentSection');
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createFolderForm
                })) {
                dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'datatypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm
                    });
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm,
                        hasErrors: true
                    });    // TODO: Handle errors
                });
            }
            ;
        };
        $scope.createDataType = function () {
            $location.search('create', null);
            $location.path('/' + section + '/datatypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
        $scope.close = function () {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.CreateController', DataTypeCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService, localizationService) {
        var vm = this;
        vm.hasReferences = false;
        vm.references = [];
        vm.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            dataTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        vm.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        vm.cancel = function () {
            navigationService.hideDialog();
        };
        vm.onReferenceClicked = function (event) {
            if (event.metaKey !== true) {
                navigationService.hideDialog();
            }
        };
        vm.labels = {};
        localizationService.localize('editdatatype_acceptDeleteConsequence', [$scope.currentNode.name]).then(function (data) {
            vm.labels.deleteConfirm = data;
        });
        var init = function init() {
            if ($scope.currentNode.nodeType === 'dataTypes') {
                vm.loading = true;
                dataTypeResource.getReferences($scope.currentNode.id).then(function (data) {
                    vm.loading = false;
                    vm.references = data;
                    vm.hasReferences = data.documentTypes.length > 0 || data.mediaTypes.length > 0 || data.memberTypes.length > 0;
                });
            }
        };
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.DeleteController', DataTypeDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataType.EditController
 * @function
 *
 * @description
 * The controller for the content editor
 */
    function DataTypeEditController($scope, $routeParams, appState, navigationService, dataTypeResource, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService, localizationService) {
        var evts = [];
        var vm = this;
        vm.header = {};
        vm.header.editorfor = 'visuallyHiddenTexts_newDataType';
        vm.header.setPageTitle = true;
        //setup scope vars
        vm.page = {};
        vm.page.loading = false;
        vm.page.menu = {};
        vm.page.menu.currentSection = appState.getSectionState('currentSection');
        vm.page.menu.currentNode = null;
        //set up the standard data type props
        vm.properties = {
            selectedEditor: {
                alias: 'selectedEditor',
                description: 'Select a property editor',
                label: 'Property editor'
            }
        };
        //setup the pre-values as props
        vm.preValues = [];
        //method used to configure the pre-values when we retrieve them from the server
        function createPreValueProps(preVals) {
            vm.preValues = dataTypeHelper.createPreValueProps(preVals);
        }
        function setHeaderNameState(content) {
            if (content.isSystem == 1) {
                vm.page.nameLocked = true;
            }
        }
        function loadDataType() {
            vm.page.loading = true;
            vm.showIdentifier = true;
            //we are editing so get the content item from the server
            dataTypeResource.getById($routeParams.id).then(function (data) {
                vm.preValuesLoaded = true;
                vm.content = data;
                createPreValueProps(vm.content.preValues);
                setHeaderNameState(vm.content);
                //share state
                editorState.set(vm.content);
                //in one particular special case, after we've created a new item we redirect back to the edit
                // route but there might be server validation errors in the collection which we need to display
                // after the redirect, so we will bind all subscriptions which will show the server validation errors
                // if there are any and then clear them so the collection no longer persists them.
                serverValidationManager.notifyAndClearAllSubscriptions();
                navigationService.syncTree({
                    tree: 'datatypes',
                    path: data.path
                }).then(function (syncArgs) {
                    vm.page.menu.currentNode = syncArgs.node;
                });
                vm.page.loading = false;
            });
        }
        function saveDataType() {
            if (formHelper.submitForm({ scope: $scope })) {
                vm.page.saveButtonState = 'busy';
                dataTypeResource.save(vm.content, vm.preValues, $routeParams.create).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data,
                        rebindCallback: function rebindCallback() {
                            createPreValueProps(data.preValues);
                        }
                    });
                    setHeaderNameState(vm.content);
                    //share state
                    editorState.set(vm.content);
                    navigationService.syncTree({
                        tree: 'datatypes',
                        path: data.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                    vm.page.saveButtonState = 'success';
                    dataTypeHelper.rebindChangedProperties(vm.content, data);
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    //NOTE: in the case of data type values we are setting the orig/new props
                    // to be the same thing since that only really matters for content/media.
                    contentEditingHelper.handleSaveError({ err: err });
                    vm.page.saveButtonState = 'error';
                    //share state
                    editorState.set(vm.content);
                });
            }
        }
        ;
        vm.save = saveDataType;
        evts.push(eventsService.on('app.refreshEditor', function (name, error) {
            loadDataType();
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
        function init() {
            $scope.$watch('vm.content.selectedEditor', function (newVal, oldVal) {
                //when the value changes, we need to dynamically load in the new editor
                if (newVal && newVal != oldVal && (oldVal || $routeParams.create)) {
                    //we are editing so get the content item from the server
                    var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
                    dataTypeResource.getPreValues(newVal, currDataTypeId).then(function (data) {
                        vm.preValuesLoaded = true;
                        vm.content.preValues = data;
                        createPreValueProps(vm.content.preValues);
                        setHeaderNameState(vm.content);
                        //share state
                        editorState.set(vm.content);
                    });
                }
            });
            if ($routeParams.create) {
                vm.page.loading = true;
                vm.showIdentifier = false;
                //we are creating so get an empty data type item
                dataTypeResource.getScaffold($routeParams.id).then(function (data) {
                    vm.preValuesLoaded = true;
                    vm.content = data;
                    setHeaderNameState(vm.content);
                    //set a shared state
                    editorState.set(vm.content);
                    vm.page.loading = false;
                });
            } else {
                loadDataType();
            }
            var labelKeys = [
                'general_settings',
                'general_info'
            ];
            localizationService.localizeMany(labelKeys).then(function (values) {
                vm.page.navigation = [
                    {
                        'name': values[0],
                        'alias': 'settings',
                        'icon': 'icon-settings',
                        'view': 'views/datatypes/views/datatype.settings.html',
                        'active': true
                    },
                    {
                        'name': values[1],
                        'alias': 'info',
                        'icon': 'icon-info',
                        'view': 'views/datatypes/views/datatype.info.html'
                    }
                ];
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.EditController', DataTypeEditController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.DataType.MoveController', function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            dataTypeResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'dataTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'dataTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataType.InfoController
 * @function
 *
 * @description
 * The controller for the info view of the datatype editor
 */
    function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout, editorService) {
        var vm = this;
        var evts = [];
        var referencesLoaded = false;
        vm.references = {};
        vm.hasReferences = false;
        vm.view = {};
        vm.view.loading = true;
        vm.openDocumentType = openDocumentType;
        vm.openMediaType = openMediaType;
        vm.openMemberType = openMemberType;
        /** Loads in the data type references one time */
        function loadRelations() {
            if (!referencesLoaded) {
                referencesLoaded = true;
                dataTypeResource.getReferences($routeParams.id).then(function (data) {
                    vm.view.loading = false;
                    vm.references = data;
                    vm.hasReferences = data.documentTypes.length > 0 || data.mediaTypes.length > 0 || data.memberTypes.length > 0;
                });
            }
        }
        function openDocumentType(id, event) {
            open(id, event, 'documentType');
        }
        function openMediaType(id, event) {
            open(id, event, 'mediaType');
        }
        function openMemberType(id, event) {
            open(id, event, 'memberType');
        }
        function open(id, event, type) {
            // targeting a new tab/window?
            if (event.ctrlKey || event.shiftKey || event.metaKey || // apple
                event.button && event.button === 1    // middle click, >IE9 + everyone else
) {
                // yes, let the link open itself
                return;
            }
            event.stopPropagation();
            event.preventDefault();
            var editor = {
                id: id,
                submit: function submit(model) {
                    editorService.close();
                    vm.view.loading = true;
                    referencesLoaded = false;
                    loadRelations();
                },
                close: function close() {
                    editorService.close();
                }
            };
            switch (type) {
            case 'documentType':
                editorService.documentTypeEditor(editor);
                break;
            case 'mediaType':
                editorService.mediaTypeEditor(editor);
                break;
            case 'memberType':
                editorService.memberTypeEditor(editor);
                break;
            }
        }
        // load data type references when the references tab is activated
        evts.push(eventsService.on('app.tabChange', function (event, args) {
            $timeout(function () {
                if (args.alias === 'info') {
                    loadRelations();
                }
            });
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.InfoController', DataTypeInfoController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.CreateController
 * @function
 * 
 * @description
 * The controller for creating dictionary items
 */
    function DictionaryCreateController($scope, $location, dictionaryResource, navigationService, notificationsService, formHelper, appState) {
        var vm = this;
        vm.itemKey = '';
        vm.createItem = createItem;
        vm.close = close;
        $scope.$emit('$changeTitle', '');
        function close() {
            navigationService.hideDialog();
        }
        function createItem() {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createDictionaryForm
                })) {
                var node = $scope.currentNode;
                dictionaryResource.create(node.id, vm.itemKey).then(function (data) {
                    navigationService.hideMenu();
                    // set new item as active in tree
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'dictionary',
                        path: currPath + ',' + data,
                        forceReload: true,
                        activate: true
                    });
                    // reset form state
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createDictionaryForm
                    });
                    // navigate to edit view
                    var currentSection = appState.getSectionState('currentSection');
                    $location.path('/' + currentSection + '/dictionary/edit/' + data);
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createDictionaryForm,
                        hasErrors: true
                    });
                    if (err.data && err.data.message) {
                        notificationsService.error(err.data.message);
                        navigationService.hideMenu();
                    }
                });
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.CreateController', DictionaryCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.DeleteController
 * @function
 * 
 * @description
 * The controller for deleting dictionary items
 */
    function DictionaryDeleteController($scope, $location, dictionaryResource, treeService, navigationService, appState) {
        var vm = this;
        function cancel() {
            navigationService.hideDialog();
        }
        function performDelete() {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            dictionaryResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                // get the parent id 
                var parentId = $scope.currentNode.parentId;
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
                var currentSection = appState.getSectionState('currentSection');
                if (parentId !== '-1') {
                    // set the view of the parent item
                    $location.path('/' + currentSection + '/dictionary/edit/' + parentId);
                } else {
                    // we have no parent, so redirect to section
                    $location.path('/' + currentSection + '/');
                }
            });
        }
        vm.cancel = cancel;
        vm.performDelete = performDelete;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.DeleteController', DictionaryDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.EditController
 * @function
 * 
 * @description
 * The controller for editing dictionary items
 */
    function DictionaryEditController($scope, $routeParams, $location, dictionaryResource, navigationService, appState, editorState, contentEditingHelper, formHelper, notificationsService, localizationService) {
        var vm = this;
        //setup scope vars
        vm.nameDirty = false;
        vm.header = {};
        vm.header.editorfor = 'template_insertDictionaryItem';
        vm.header.setPageTitle = true;
        vm.page = {};
        vm.page.loading = false;
        vm.page.nameLocked = false;
        vm.page.menu = {};
        vm.page.menu.currentSection = appState.getSectionState('currentSection');
        vm.page.menu.currentNode = null;
        vm.description = '';
        vm.showBackButton = true;
        vm.maxlength = 1000;
        vm.save = saveDictionary;
        vm.back = back;
        vm.change = change;
        function loadDictionary() {
            vm.page.loading = true;
            //we are editing so get the content item from the server
            dictionaryResource.getById($routeParams.id).then(function (data) {
                bindDictionary(data);
                vm.page.loading = false;
            });
        }
        function createTranslationProperty(translation) {
            return {
                alias: translation.isoCode,
                label: translation.displayName,
                hideLabel: false
            };
        }
        function bindDictionary(data) {
            localizationService.localize('dictionaryItem_description').then(function (value) {
                vm.description = value.replace('%0%', data.name);
            });
            // create data for  umb-property displaying
            for (var i = 0; i < data.translations.length; i++) {
                data.translations[i].property = createTranslationProperty(data.translations[i]);
                change(data.translations[i]);
            }
            contentEditingHelper.handleSuccessfulSave({
                scope: $scope,
                savedContent: data
            });
            // set content
            vm.content = data;
            //share state
            editorState.set(vm.content);
            navigationService.syncTree({
                tree: 'dictionary',
                path: data.path,
                forceReload: true
            }).then(function (syncArgs) {
                vm.page.menu.currentNode = syncArgs.node;
            });
        }
        function onInit() {
            loadDictionary();
        }
        function saveDictionary() {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                vm.page.saveButtonState = 'busy';
                dictionaryResource.save(vm.content, vm.nameDirty).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    bindDictionary(data);
                    vm.page.saveButtonState = 'success';
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({ err: err });
                    notificationsService.error(err.data.message);
                    vm.page.saveButtonState = 'error';
                });
            }
        }
        function back() {
            $location.path(vm.page.menu.currentSection + '/dictionary/list');
        }
        function change(translation) {
            if (translation.translation) {
                var charsCount = translation.translation.length;
                translation.nearMaxLimit = charsCount > Math.max(vm.maxlength * 0.8, vm.maxlength - 50);
            }
        }
        $scope.$watch('vm.content.name', function (newVal, oldVal) {
            //when the value changes, we need to set the name dirty
            if (newVal && newVal !== oldVal && typeof oldVal !== 'undefined') {
                vm.nameDirty = true;
            }
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.EditController', DictionaryEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.ListController
 * @function
 * 
 * @description
 * The controller for listting dictionary items
 */
    function DictionaryListController($scope, $location, dictionaryResource, localizationService, appState, navigationService) {
        var vm = this;
        vm.title = 'Dictionary overview';
        vm.loading = false;
        vm.items = [];
        function loadList() {
            vm.loading = true;
            dictionaryResource.getList().then(function (data) {
                vm.items = data;
                vm.items.forEach(function (item) {
                    item.style = { 'paddingLeft': item.level * 10 };
                });
                vm.loading = false;
            });
        }
        function clickItem(id) {
            var currentSection = appState.getSectionState('currentSection');
            $location.path('/' + currentSection + '/dictionary/edit/' + id);
        }
        function createNewItem() {
            var rootNode = appState.getTreeState('currentRootNode').root;
            //We need to load the menu first before we can access the menu actions.
            navigationService.showMenu({ node: rootNode }).then(function () {
                var action = appState.getMenuState('menuActions').find(function (item) {
                    return item.alias === 'create';
                });
                navigationService.executeMenuAction(action, rootNode, appState.getSectionState('currentSection'));
            });
        }
        vm.clickItem = clickItem;
        vm.createNewItem = createNewItem;
        function onInit() {
            localizationService.localize('dictionaryItem_overviewTitle').then(function (value) {
                vm.title = value;
            });
            loadList();
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.ListController', DictionaryListController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CopyController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            contentTypeResource.copy({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'documentTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'documentTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.CreateController
 * @function
 *
 * @description
 * The controller for the doc type creation dialog
 */
    function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState) {
        $scope.model = {
            allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container',
            folderName: '',
            creatingFolder: false
        };
        var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
        $scope.model.disableTemplates = disableTemplates;
        var node = $scope.currentNode;
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createFolderForm
                })) {
                contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'documenttypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm
                    });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm,
                        hasErrors: true
                    });
                    $scope.error = err;
                });
            }
        };
        function createDocType(config) {
            $location.search('create', null);
            $location.search('notemplate', null);
            $location.search('iscomposition', null);
            $location.search('iselement', null);
            $location.search('icon', null);
            var icon = null;
            if (config.icon != undefined && config.icon != null) {
                icon = config.icon;
                if (config.color) {
                    icon += ' ' + config.color;
                }
            }
            $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true').search('notemplate', config.notemplate ? 'true' : null).search('iscomposition', config.iscomposition ? 'true' : null).search('iselement', config.iselement ? 'true' : null).search('icon', icon);
            navigationService.hideMenu();
        }
        // Disabling logic for creating document type with template if disableTemplates is set to true
        if (!disableTemplates) {
            $scope.createDocType = function (icon) {
                createDocType({ icon: icon });
            };
        }
        $scope.createComponent = function (icon) {
            createDocType({
                notemplate: true,
                icon: icon
            });
        };
        $scope.createComposition = function (icon) {
            createDocType({
                notemplate: true,
                iscomposition: true,
                iselement: true,
                icon: icon
            });
        };
        $scope.createElement = function (icon) {
            createDocType({
                notemplate: true,
                iselement: true,
                icon: icon
            });
        };
        $scope.close = function () {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CreateController', DocumentTypesCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
    function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService, localizationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
        $scope.labels = {};
        localizationService.format([
            'contentTypeEditor_yesDelete',
            'contentTypeEditor_andAllDocuments'
        ], '%0% ' + $scope.currentNode.name + ' %1%').then(function (data) {
            $scope.labels.deleteConfirm = data;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.DeleteController', DocumentTypesDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.EditController
 * @function
 *
 * @description
 * The controller for the content type editor
 */
    (function () {
        'use strict';
        function DocumentTypesEditController($scope, $routeParams, $q, contentTypeResource, editorState, contentEditingHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, localizationService, overlayHelper, eventsService, angularHelper, editorService) {
            var vm = this;
            var evts = [];
            var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
            var documentTypeId = $routeParams.id;
            var create = $routeParams.create;
            var noTemplate = $routeParams.notemplate;
            var isElement = $routeParams.iselement;
            var icon = $routeParams.icon;
            var allowVaryByCulture = $routeParams.culturevary;
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var documentTypeIcon = '';
            vm.save = save;
            vm.close = close;
            vm.currentNode = null;
            vm.contentType = {};
            vm.header = {};
            vm.header.editorfor = 'content_documentType';
            vm.header.setPageTitle = true;
            vm.labels = {};
            vm.submitButtonKey = 'buttons_save';
            vm.generateModelsKey = 'buttons_saveAndGenerateModels';
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.page.navigation = [];
            var labelKeys = [
                'general_design',
                'general_listView',
                'general_rights',
                'treeHeaders_templates',
                'main_sections',
                'shortcuts_navigateSections',
                'shortcuts_addTab',
                'shortcuts_addGroup',
                'shortcuts_addProperty',
                'defaultdialogs_selectEditor',
                'shortcuts_editDataType',
                'shortcuts_toggleListView',
                'shortcuts_toggleAllowAsRoot',
                'shortcuts_addChildNode',
                'shortcuts_addTemplate',
                'shortcuts_toggleAllowCultureVariants'
            ];
            onInit();
            function onInit() {
                // get init values from model when in infinite mode
                if (infiniteMode) {
                    documentTypeId = $scope.model.id;
                    create = $scope.model.create;
                    if (create && !documentTypeId)
                        documentTypeId = -1;
                    noTemplate = $scope.model.notemplate || $scope.model.noTemplate;
                    isElement = $scope.model.isElement;
                    icon = $scope.model.icon;
                    allowVaryByCulture = $scope.model.allowVaryByCulture;
                    vm.submitButtonKey = 'buttons_saveAndClose';
                    vm.generateModelsKey = 'buttons_generateModelsAndClose';
                }
            }
            localizationService.localizeMany(labelKeys).then(function (values) {
                // navigation
                vm.labels.design = values[0];
                vm.labels.listview = values[1];
                vm.labels.permissions = values[2];
                vm.labels.templates = values[3];
                // keyboard shortcuts
                vm.labels.sections = values[4];
                vm.labels.navigateSections = values[5];
                vm.labels.addTab = values[6];
                vm.labels.addGroup = values[7];
                vm.labels.addProperty = values[8];
                vm.labels.addEditor = values[9];
                vm.labels.editDataType = values[10];
                vm.labels.toggleListView = values[11];
                vm.labels.allowAsRoot = values[12];
                vm.labels.addChildNode = values[13];
                vm.labels.addTemplate = values[14];
                vm.labels.allowCultureVariants = values[15];
                vm.page.keyboardShortcutsOverview = [
                    {
                        'name': vm.labels.sections,
                        'shortcuts': [{
                                'description': vm.labels.navigateSections,
                                'keys': [
                                    { 'key': '1' },
                                    { 'key': '4' }
                                ],
                                'keyRange': true
                            }]
                    },
                    {
                        'name': vm.labels.design,
                        'shortcuts': [
                            {
                                'description': vm.labels.addTab,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'a' }
                                ]
                            },
                            {
                                'description': vm.labels.addGroup,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'g' }
                                ]
                            },
                            {
                                'description': vm.labels.addProperty,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'p' }
                                ]
                            },
                            {
                                'description': vm.labels.addEditor,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'e' }
                                ]
                            },
                            {
                                'description': vm.labels.editDataType,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'd' }
                                ]
                            }
                        ]
                    },
                    {
                        'name': vm.labels.listview,
                        'shortcuts': [{
                                'description': vm.labels.toggleListView,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'l' }
                                ]
                            }]
                    },
                    {
                        'name': vm.labels.permissions,
                        'shortcuts': [
                            {
                                'description': vm.labels.allowAsRoot,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'r' }
                                ]
                            },
                            {
                                'description': vm.labels.addChildNode,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'c' }
                                ]
                            },
                            {
                                'description': vm.labels.allowCultureVariants,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'v' }
                                ]
                            }
                        ]
                    },
                    {
                        'name': vm.labels.templates,
                        'shortcuts': [{
                                'description': vm.labels.addTemplate,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 't' }
                                ]
                            }]
                    }
                ];
            });
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        alias: 'save',
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: vm.submitButtonKey,
                        letter: 'S',
                        handler: function handler() {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            alias: 'saveAndGenerateModels',
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: vm.generateModelsKey,
                            letter: 'G',
                            handler: function handler() {
                                vm.page.saveButtonState = 'busy';
                                saveInternal().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        // generateModels() returns the dashboard content
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if (create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                contentTypeResource.getScaffold(documentTypeId).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                loadDocumentType();
            }
            function loadDocumentType() {
                vm.page.loading = true;
                contentTypeResource.getById(documentTypeId).then(function (dt) {
                    init(dt);
                    // we don't need to sync the tree in infinite mode
                    if (!infiniteMode) {
                        syncTreeNode(vm.contentType, dt.path, true);
                    }
                    vm.page.loading = false;
                });
            }
            function loadButtons() {
                vm.page.navigation = vm.contentType.apps;
                if (disableTemplates === true) {
                    Utilities.forEach(vm.contentType.apps, function (app, index) {
                        if (app.alias === 'templates') {
                            vm.page.navigation.splice(index, 1);
                        }
                    });
                }
                initializeActiveNavigationPanel();
            }
            function initializeActiveNavigationPanel() {
                // Initialise first loaded panel based on page route paramater
                // i.e. ?view=design|listview|permissions
                var initialViewSetFromRouteParams = false;
                var view = $routeParams.view;
                if (view) {
                    for (var i = 0; i < vm.page.navigation.length; i++) {
                        if (vm.page.navigation[i].alias.localeCompare(view, undefined, { sensitivity: 'accent' }) === 0) {
                            vm.page.navigation[i].active = true;
                            initialViewSetFromRouteParams = true;
                            break;
                        }
                    }
                }
                if (initialViewSetFromRouteParams === false) {
                    vm.page.navigation[0].active = true;
                }
            }
            /* ---------- SAVE ---------- */
            function save() {
                saveInternal().then(Utilities.noop, Utilities.noop);
            }
            /** This internal save method performs the actual saving and returns a promise, not to be bound to any buttons but used by other bound methods */
            function saveInternal() {
                // only save if there are no dialogs open
                if (overlayHelper.getNumberOfOverlays() === 0 && (editorService.getNumberOfEditors() === 0 || infiniteMode)) {
                    vm.page.saveButtonState = 'busy';
                    // reformat allowed content types to array if id's
                    vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                    //if this is a new item and it's creating a template, ensure that the template alias is synced correctly
                    syncTemplateAlias(vm.contentType);
                    return contentEditingHelper.contentEditorPerformSave({
                        saveMethod: contentTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        infiniteMode: infiniteMode,
                        rebindCallback: function rebindCallback(_, savedContentType) {
                            // we need to rebind... the IDs that have been created!
                            contentTypeHelper.rebindSavedContentType(vm.contentType, savedContentType);
                        }
                    }).then(function (data) {
                        // allow UI to access server validation state
                        vm.contentType.ModelState = data.ModelState;
                        //success
                        // we don't need to sync the tree in infinite mode
                        if (!infiniteMode) {
                            syncTreeNode(vm.contentType, data.path);
                        }
                        // emit event
                        var args = { documentType: vm.contentType };
                        eventsService.emit('editors.documentType.saved', args);
                        if (documentTypeIcon !== vm.contentType.icon) {
                            eventsService.emit('editors.tree.icon.changed', args);
                        }
                        vm.page.saveButtonState = 'success';
                        if (infiniteMode && $scope.model.submit) {
                            $scope.model.documentTypeAlias = vm.contentType.alias;
                            $scope.model.documentTypeKey = vm.contentType.key;
                            $scope.model.submit($scope.model);
                        }
                        return $q.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            // allow UI to access server validation state
                            vm.contentType.ModelState = err.data.ModelState;
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        return $q.reject(err);
                    });
                } else {
                    return $q.reject();
                }
            }
            function init(contentType) {
                // insert template on new doc types
                if (!noTemplate && contentType.id === 0) {
                    contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate);
                    contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
                }
                // set isElement checkbox by default
                if (isElement) {
                    contentType.isElement = true;
                }
                // set icon if one is provided
                if (icon !== null && icon !== undefined) {
                    contentType.icon = icon;
                }
                // set vary by culture checkbox by default
                if (allowVaryByCulture) {
                    contentType.allowCultureVariant = true;
                }
                // convert icons for content type
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
                documentTypeIcon = contentType.icon;
                loadButtons();
            }
            /** Syncs the template alias for new doc types before saving if a template is to be created */
            function syncTemplateAlias(contentType) {
                if (!noTemplate && contentType.id === 0) {
                    //sync default template that had the placeholder flag
                    if (contentType.defaultTemplate !== null && contentType.defaultTemplate.placeholder) {
                        contentType.defaultTemplate.name = contentType.name;
                        contentType.defaultTemplate.alias = contentType.alias;
                    }
                    //sync allowed templates that had the placeholder flag
                    contentType.allowedTemplates.forEach(function (allowedTemplate) {
                        if (allowedTemplate.placeholder) {
                            allowedTemplate.name = contentType.name;
                            allowedTemplate.alias = contentType.alias;
                        }
                    });
                }
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                var args = {
                    tree: 'documenttypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                };
                navigationService.syncTree(args).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close($scope.model);
                }
            }
            evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                loadDocumentType();
            }));
            evts.push(eventsService.on('editors.documentType.reload', function (name, args) {
                if (args && args.node && vm.contentType.id === args.node.id) {
                    loadDocumentType();
                }
            }));
            evts.push(eventsService.on('editors.documentType.saved', function (name, args) {
                if (args.documentType.allowedTemplates.length > 0) {
                    navigationService.hasTree('templates').then(function (treeExists) {
                        if (treeExists) {
                            navigationService.syncTree({
                                tree: 'templates',
                                path: [],
                                forceReload: true
                            }).then(function (syncArgs) {
                                navigationService.reloadNode(syncArgs.node);
                            });
                        }
                    });
                }
            }));
            evts.push(eventsService.on('editors.groupsBuilder.changed', function (name, args) {
                angularHelper.getCurrentForm($scope).$setDirty();
            }));
            //ensure to unregister from all events!
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
            // #3368 - changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty 
            $scope.$watch('vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.allowCultureVariant + vm.contentType.isElement + ' + 'vm.contentType.allowedTemplates.length + vm.contentType.isContainer + vm.contentType.compositeContentTypes.length', function (newVal, oldVal) {
                if (oldVal === undefined) {
                    // still initializing, ignore
                    return;
                }
                angularHelper.getCurrentForm($scope).$setDirty();
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.EditController', DocumentTypesEditController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.ExportController', function ($scope, contentTypeResource, navigationService) {
        $scope.export = function () {
            contentTypeResource.export($scope.currentNode.id);
            navigationService.hideMenu();
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.ImportController', function ($scope, contentTypeResource, navigationService, Upload, umbRequestHelper) {
        var vm = this;
        vm.serverErrorMessage = '';
        vm.state = 'upload';
        vm.model = {};
        vm.uploadStatus = '';
        vm.cancelButtonLabel = 'cancel';
        $scope.handleFiles = function (files, event, invalidFiles) {
            if (files && files.length > 0) {
                $scope.upload(files[0]);
            }
        };
        $scope.upload = function (file) {
            Upload.upload({
                url: umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'Upload'),
                fields: {},
                file: file
            }).success(function (data, status, headers, config) {
                if (data.notifications && data.notifications.length > 0) {
                    // set error status on file
                    vm.uploadStatus = 'error';
                    // Throw message back to user with the cause of the error
                    vm.serverErrorMessage = data.notifications[0].message;
                } else {
                    // set done status on file
                    vm.uploadStatus = 'done';
                    vm.model = data;
                    vm.state = 'confirm';
                }
            }).error(function (evt, status, headers, config) {
                // set status done
                $scope.uploadStatus = 'error';
                // If file not found, server will return a 404 and display this message
                if (status === 404) {
                    $scope.serverErrorMessage = 'File not found';
                } else if (status == 400) {
                    //it's a validation error
                    $scope.serverErrorMessage = evt.message;
                } else {
                    //it's an unhandled error
                    //if the service returns a detailed error
                    if (evt.InnerException) {
                        $scope.serverErrorMessage = evt.InnerException.ExceptionMessage;
                        //Check if its the common "too large file" exception
                        if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                            $scope.serverErrorMessage = 'File too large to upload';
                        }
                    } else if (evt.Message) {
                        $scope.serverErrorMessage = evt.Message;
                    }
                }
            });
        };
        $scope.import = function () {
            contentTypeResource.import(vm.model.tempFileName);
            vm.state = 'done';
            vm.cancelButtonLabel = 'general_close';
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.MoveController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            contentTypeResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'documentTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'documentTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.ContentTypeContainers.RenameController', function ($scope, $injector, navigationService, notificationsService, localizationService) {
        var notificationHeader;
        function reportSuccessAndClose(treeName) {
            var lastComma = $scope.currentNode.path.lastIndexOf(','), path = lastComma === -1 ? $scope.currentNode.path : $scope.currentNode.path.substring(0, lastComma - 1);
            navigationService.syncTree({
                tree: treeName,
                path: path,
                forceReload: true,
                activate: true
            });
            localizationService.localize('renamecontainer_folderWasRenamed', [
                $scope.currentNode.name,
                $scope.model.folderName
            ]).then(function (msg) {
                notificationsService.showNotification({
                    type: 0,
                    header: notificationHeader,
                    message: msg
                });
            });
            navigationService.hideMenu();
        }
        localizationService.localize('renamecontainer_renamed').then(function (s) {
            notificationHeader = s;
        });
        $scope.model = { folderName: $scope.currentNode.name };
        $scope.renameContainer = function (resourceKey, treeName) {
            var resource = $injector.get(resourceKey);
            resource.renameContainer($scope.currentNode.id, $scope.model.folderName).then(function () {
                reportSuccessAndClose(treeName);
            }, function (err) {
                $scope.error = err;
            });
        };
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.PropertyController
 * @function
 *
 * @description
 * The controller for the content type editor property dialog
 */
    (function () {
        'use strict';
        function PermissionsController($scope, $timeout, contentTypeResource, iconHelper, contentTypeHelper, editorService) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            vm.contentTypes = [];
            vm.selectedChildren = [];
            vm.showAllowSegmentationOption = Umbraco.Sys.ServerVariables.umbracoSettings.showAllowSegmentationForDocumentTypes || false;
            vm.addChild = addChild;
            vm.removeChild = removeChild;
            vm.sortChildren = sortChildren;
            vm.toggleAllowAsRoot = toggleAllowAsRoot;
            vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
            vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants;
            vm.canToggleIsElement = false;
            vm.toggleIsElement = toggleIsElement;
            vm.toggleHistoryCleanupPreventCleanup = toggleHistoryCleanupPreventCleanup;
            /* ---------- INIT ---------- */
            init();
            function init() {
                contentTypeResource.getAll().then(function (contentTypes) {
                    vm.contentTypes = contentTypes.filter(function (x) {
                        return !x.isElement;
                    });
                    // convert legacy icons
                    iconHelper.formatContentTypeIcons(vm.contentTypes);
                    vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, contentTypes);
                    if ($scope.model.id === 0) {
                        contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                    }
                });
                // Can only switch to an element type if there are no content nodes already created from the type.
                if ($scope.model.id > 0 && !$scope.model.isElement) {
                    contentTypeResource.hasContentNodes($scope.model.id).then(function (result) {
                        vm.canToggleIsElement = !result;
                    });
                } else {
                    vm.canToggleIsElement = true;
                }
                if (!$scope.model.historyCleanup) {
                    $scope.model.historyCleanup = {};
                }
            }
            function addChild($event) {
                var editor = {
                    multiPicker: true,
                    filterCssClass: 'not-allowed not-published',
                    filter: function filter(item) {
                        return !vm.contentTypes.some(function (x) {
                            return x.udi == item.udi;
                        }) || vm.selectedChildren.some(function (x) {
                            return x.udi === item.udi;
                        });
                    },
                    submit: function submit(model) {
                        model.selection.forEach(function (item) {
                            return contentTypeResource.getById(item.id).then(function (contentType) {
                                vm.selectedChildren.push(contentType);
                                $scope.model.allowedContentTypes.push(item.id);
                            });
                        });
                        editorService.close();
                    },
                    close: function close() {
                        return editorService.close();
                    }
                };
                editorService.contentTypePicker(editor);
            }
            function removeChild(selectedChild, index) {
                // remove from vm
                vm.selectedChildren.splice(index, 1);
                // remove from content type model
                var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
            }
            function sortChildren() {
                // we need to wait until the next digest cycle for vm.selectedChildren to be updated
                $timeout(function () {
                    return $scope.model.allowedContentTypes = vm.selectedChildren.map(function (x) {
                        return x.id;
                    });
                });
            }
            // note: 'safe toggling' here ie handling cases where the value is undefined, etc
            function toggleAllowAsRoot() {
                $scope.model.allowAsRoot = $scope.model.allowAsRoot ? false : true;
            }
            function toggleAllowCultureVariants() {
                $scope.model.allowCultureVariant = $scope.model.allowCultureVariant ? false : true;
            }
            function toggleAllowSegmentVariants() {
                $scope.model.allowSegmentVariant = $scope.model.allowSegmentVariant ? false : true;
            }
            function toggleIsElement() {
                $scope.model.isElement = $scope.model.isElement ? false : true;
            }
            function toggleHistoryCleanupPreventCleanup() {
                $scope.model.historyCleanup.preventCleanup = $scope.model.historyCleanup.preventCleanup ? false : true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentType.PermissionsController', PermissionsController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.TemplatesController
 * @function
 *
 * @description
 * The controller for the content type editor templates sub view
 */
    (function () {
        'use strict';
        function TemplatesController($scope, entityResource, contentTypeHelper, contentTypeResource, editorService, $routeParams) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            vm.availableTemplates = [];
            vm.canCreateTemplate = false;
            vm.updateTemplatePlaceholder = false;
            vm.loadingTemplates = false;
            vm.isElement = $scope.model.isElement;
            vm.createTemplate = createTemplate;
            vm.openTemplatePicker = openTemplatePicker;
            /* ---------- INIT ---------- */
            function onInit() {
                vm.loadingTemplates = true;
                entityResource.getAll('Template').then(function (templates) {
                    vm.availableTemplates = templates;
                    // update placeholder template information on new doc types
                    if (!$routeParams.notemplate && $scope.model.id === 0) {
                        vm.updateTemplatePlaceholder = true;
                        vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates);
                    }
                    vm.loadingTemplates = false;
                    checkIfTemplateExists();
                });
            }
            function createTemplate() {
                vm.createTemplateButtonState = 'busy';
                contentTypeResource.createDefaultTemplate($scope.model.id).then(function (savedTemplate) {
                    // add icon
                    savedTemplate.icon = 'icon-layout';
                    vm.availableTemplates.push(savedTemplate);
                    vm.canCreateTemplate = false;
                    $scope.model.allowedTemplates.push(savedTemplate);
                    if ($scope.model.defaultTemplate === null) {
                        $scope.model.defaultTemplate = savedTemplate;
                    }
                    vm.createTemplateButtonState = 'success';
                }, function () {
                    vm.createTemplateButtonState = 'error';
                });
            }
            ;
            function checkIfTemplateExists() {
                if ($scope.model.id === 0) {
                    return;
                }
                var existingTemplate = vm.availableTemplates.find(function (availableTemplate) {
                    return availableTemplate.name === $scope.model.name || availableTemplate.placeholder;
                });
                vm.canCreateTemplate = existingTemplate ? false : true;
            }
            function openTemplatePicker() {
                var editor = {
                    title: 'Choose template',
                    filterCssClass: 'not-allowed',
                    multiPicker: true,
                    filter: function filter(item) {
                        return !vm.availableTemplates.some(function (template) {
                            return template.id == item.id;
                        }) || $scope.model.allowedTemplates.some(function (template) {
                            return template.id == item.id;
                        });
                    },
                    submit: function submit(model) {
                        model.selection.forEach(function (item) {
                            $scope.model.allowedTemplates.push(item);
                        });
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.templatePicker(editor);
            }
            var unbindWatcher = $scope.$watch('model.isElement', function (newValue, oldValue) {
                vm.isElement = newValue;
            });
            $scope.$on('$destroy', function () {
                unbindWatcher();
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentType.TemplatesController', TemplatesController);
    }());
    'use strict';
    function _slicedToArray(arr, i) {
        return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
    }
    function _nonIterableRest() {
        throw new TypeError('Invalid attempt to destructure non-iterable instance');
    }
    function _iterableToArrayLimit(arr, i) {
        if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === '[object Arguments]')) {
            return;
        }
        var _arr = [];
        var _n = true;
        var _d = false;
        var _e = undefined;
        try {
            for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                _arr.push(_s.value);
                if (i && _arr.length === i)
                    break;
            }
        } catch (err) {
            _d = true;
            _e = err;
        } finally {
            try {
                if (!_n && _i['return'] != null)
                    _i['return']();
            } finally {
                if (_d)
                    throw _e;
            }
        }
        return _arr;
    }
    function _arrayWithHoles(arr) {
        if (Array.isArray(arr))
            return arr;
    }
    (function () {
        'use strict';
        function LanguagesEditController($scope, $q, $timeout, $location, $routeParams, overlayService, navigationService, notificationsService, localizationService, languageResource, contentEditingHelper, formHelper, eventsService) {
            var vm = this;
            vm.page = {};
            vm.showBackButton = true;
            vm.language = {};
            vm.availableCultures = null;
            vm.breadcrumbs = [];
            vm.labels = {};
            vm.initIsDefault = false;
            vm.showDefaultLanguageInfo = false;
            vm.save = save;
            vm.back = back;
            vm.goToPage = goToPage;
            vm.toggleMandatory = toggleMandatory;
            vm.toggleDefault = toggleDefault;
            var currCulture = null;
            function init() {
                // localize labels
                var labelKeys = [
                    'treeHeaders_languages',
                    'languages_mandatoryLanguage',
                    'languages_mandatoryLanguageHelp',
                    'languages_defaultLanguage',
                    'languages_defaultLanguageHelp',
                    'languages_addLanguage',
                    'languages_noFallbackLanguageOption',
                    'languages_fallbackLanguageDescription',
                    'languages_fallbackLanguage',
                    'defaultdialogs_confirmSure',
                    'defaultdialogs_editlanguage'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.languages = values[0];
                    vm.labels.mandatoryLanguage = values[1];
                    vm.labels.mandatoryLanguageHelp = values[2];
                    vm.labels.defaultLanguage = values[3];
                    vm.labels.defaultLanguageHelp = values[4];
                    vm.labels.addLanguage = values[5];
                    vm.labels.noFallbackLanguageOption = values[6];
                    vm.labels.areYouSure = values[9];
                    vm.labels.editLanguage = values[10];
                    $scope.properties = {
                        fallbackLanguage: {
                            alias: 'fallbackLanguage',
                            description: values[7],
                            label: values[8]
                        }
                    };
                    if ($routeParams.create) {
                        vm.page.name = vm.labels.addLanguage;
                        $scope.$emit('$changeTitle', vm.labels.addLanguage);
                    }
                });
                vm.loading = true;
                var promises = [];
                //load all culture/languages
                promises.push(languageResource.getCultures().then(function (culturesDictionary) {
                    var cultures = [];
                    Object.entries(culturesDictionary).forEach(function (_ref) {
                        var _ref2 = _slicedToArray(_ref, 2), key = _ref2[0], value = _ref2[1];
                        cultures.push({
                            name: key,
                            displayName: value
                        });
                    });
                    vm.availableCultures = cultures;
                }));
                //load all possible fallback languages
                promises.push(languageResource.getAll().then(function (languages) {
                    vm.availableLanguages = languages.filter(function (l) {
                        return $routeParams.id != l.id;
                    });
                    vm.loading = false;
                }));
                if (!$routeParams.create) {
                    promises.push(languageResource.getById($routeParams.id).then(function (lang) {
                        vm.language = lang;
                        vm.page.name = vm.language.name;
                        $scope.$emit('$changeTitle', vm.labels.editLanguage + ': ' + vm.page.name);
                        /* we need to store the initial default state so we can disable the toggle if it is the default.
          we need to prevent from not having a default language. */
                        vm.initIsDefault = Utilities.copy(vm.language.isDefault);
                        makeBreadcrumbs();
                        //store to check if we are changing the lang culture
                        currCulture = vm.language.culture;
                    }));
                }
                $q.all(promises, function () {
                    vm.loading = false;
                });
                $timeout(function () {
                    navigationService.syncTree({
                        tree: 'languages',
                        path: '-1'
                    });
                });
            }
            function save() {
                if (formHelper.submitForm({ scope: $scope })) {
                    vm.page.saveButtonState = 'busy';
                    //check if the culture is being changed
                    if (currCulture && vm.language.culture !== currCulture) {
                        var changeCultureAlert = {
                            title: vm.labels.areYouSure,
                            view: 'views/languages/overlays/change.html',
                            submitButtonLabelKey: 'general_continue',
                            submit: function submit(model) {
                                saveLanguage();
                                overlayService.close();
                            },
                            close: function close() {
                                overlayService.close();
                                vm.page.saveButtonState = 'init';
                            }
                        };
                        overlayService.open(changeCultureAlert);
                    } else {
                        saveLanguage();
                    }
                }
            }
            function saveLanguage() {
                languageResource.save(vm.language).then(function (lang) {
                    formHelper.resetForm({ scope: $scope });
                    vm.language = lang;
                    vm.page.saveButtonState = 'success';
                    localizationService.localize('speechBubbles_languageSaved').then(function (value) {
                        notificationsService.success(value);
                    });
                    // emit event when language is created or updated/saved
                    var args = {
                        language: lang,
                        isNew: $routeParams.create ? true : false
                    };
                    eventsService.emit('editors.languages.languageSaved', args);
                    back();
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    formHelper.handleError(err);
                });
            }
            function back() {
                $location.path('settings/languages/overview');
            }
            function goToPage(ancestor) {
                $location.path(ancestor.path);
            }
            function toggleMandatory() {
                vm.language.isMandatory = !vm.language.isMandatory;
            }
            function toggleDefault() {
                // it shouldn't be possible to uncheck the default language
                if (vm.initIsDefault) {
                    return;
                }
                vm.language.isDefault = !vm.language.isDefault;
                if (vm.language.isDefault) {
                    vm.showDefaultLanguageInfo = true;
                } else {
                    vm.showDefaultLanguageInfo = false;
                }
            }
            function makeBreadcrumbs() {
                vm.breadcrumbs = [
                    {
                        'name': vm.labels.languages,
                        'path': '/settings/languages/overview'
                    },
                    { 'name': vm.language.name }
                ];
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Languages.EditController', LanguagesEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function LanguagesOverviewController($location, $timeout, navigationService, localizationService, languageResource, eventsService, overlayService, $scope) {
            var vm = this;
            vm.page = {};
            vm.languages = [];
            vm.labels = {};
            vm.addLanguage = addLanguage;
            vm.editLanguage = editLanguage;
            vm.deleteLanguage = deleteLanguage;
            vm.getLanguageById = function (id) {
                for (var i = 0; i < vm.languages.length; i++) {
                    if (vm.languages[i].id === id) {
                        return vm.languages[i];
                    }
                }
                return null;
            };
            function init() {
                vm.loading = true;
                // localize labels
                var labelKeys = [
                    'treeHeaders_languages',
                    'general_mandatory',
                    'general_default',
                    'languages_fallsbackToLabel'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.languages = values[0];
                    vm.labels.mandatory = values[1];
                    vm.labels.general = values[2];
                    vm.labels.fallsbackTo = values[3];
                    // set page name
                    vm.page.name = vm.labels.languages;
                    $scope.$emit('$changeTitle', vm.labels.languages);
                });
                languageResource.getAll().then(function (languages) {
                    vm.languages = languages;
                    vm.loading = false;
                });
                $timeout(function () {
                    navigationService.syncTree({
                        tree: 'languages',
                        path: '-1'
                    });
                });
            }
            function addLanguage() {
                $location.search('create', null);
                $location.path('/settings/languages/edit/-1').search('create', 'true');
            }
            function editLanguage(language) {
                $location.search('create', null);
                $location.path('/settings/languages/edit/' + language.id);
            }
            function deleteLanguage(language, event) {
                var dialog = {
                    view: 'views/languages/overlays/delete.html',
                    language: language,
                    submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        performDelete(model.language);
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                localizationService.localize('general_delete').then(function (value) {
                    dialog.title = value;
                    overlayService.open(dialog);
                });
                event.preventDefault();
                event.stopPropagation();
            }
            function performDelete(language) {
                language.deleteButtonState = 'busy';
                languageResource.deleteById(language.id).then(function () {
                    // emit event
                    var args = { language: language };
                    eventsService.emit('editors.languages.languageDeleted', args);
                    // remove from list
                    var index = vm.languages.indexOf(language);
                    vm.languages.splice(index, 1);
                }, function (err) {
                    language.deleteButtonState = 'error';
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Languages.OverviewController', LanguagesOverviewController);
    }());
    'use strict';
    function _slicedToArray(arr, i) {
        return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
    }
    function _nonIterableRest() {
        throw new TypeError('Invalid attempt to destructure non-iterable instance');
    }
    function _iterableToArrayLimit(arr, i) {
        if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === '[object Arguments]')) {
            return;
        }
        var _arr = [];
        var _n = true;
        var _d = false;
        var _e = undefined;
        try {
            for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                _arr.push(_s.value);
                if (i && _arr.length === i)
                    break;
            }
        } catch (err) {
            _d = true;
            _e = err;
        } finally {
            try {
                if (!_n && _i['return'] != null)
                    _i['return']();
            } finally {
                if (_d)
                    throw _e;
            }
        }
        return _arr;
    }
    function _arrayWithHoles(arr) {
        if (Array.isArray(arr))
            return arr;
    }
    (function () {
        'use strict';
        function LogViewerOverviewController($q, $location, $timeout, logViewerResource, navigationService) {
            var vm = this;
            vm.loading = false;
            vm.canLoadLogs = false;
            vm.searches = [];
            vm.numberOfErrors = 0;
            vm.commonLogMessages = [];
            vm.commonLogMessagesCount = 10;
            vm.dateRangeLabel = '';
            vm.config = {
                enableTime: false,
                dateFormat: 'Y-m-d',
                time_24hr: false,
                mode: 'range',
                maxDate: 'today',
                conjunction: ' to '
            };
            // ChartJS Options - for count/overview of log distribution
            vm.logTypeLabels = [
                'Debug',
                'Info',
                'Warning',
                'Error',
                'Fatal'
            ];
            vm.logTypeData = [
                0,
                0,
                0,
                0,
                0
            ];
            vm.logTypeColors = [
                '#2e8aea',
                '#2bc37c',
                '#ff9412',
                '#d42054',
                '#343434'
            ];
            vm.chartOptions = {
                legend: {
                    display: true,
                    position: 'left'
                }
            };
            // Functions
            vm.searchLogQuery = searchLogQuery;
            vm.findMessageTemplate = findMessageTemplate;
            vm.searchErrors = searchErrors;
            vm.showMore = showMore;
            vm.dateRangeChange = dateRangeChange;
            var querystring = $location.search();
            if (querystring.startDate) {
                vm.startDate = querystring.startDate;
                vm.dateRangeLabel = getDateRangeLabel('Selected Time Period');
            } else {
                vm.startDate = new Date(Date.now());
                vm.startDate.setDate(vm.startDate.getDate() - 1);
                vm.startDate = vm.startDate.toIsoDateString();
                vm.dateRangeLabel = getDateRangeLabel('Today');
            }
            if (querystring.endDate) {
                vm.endDate = querystring.endDate;
                if (querystring.endDate === querystring.startDate) {
                    vm.dateRangeLabel = getDateRangeLabel('Selected Date');
                }
            } else {
                vm.endDate = new Date(Date.now()).toIsoDateString();
            }
            vm.period = [
                vm.startDate,
                vm.endDate
            ];
            function preFlightCheck() {
                vm.loading = true;
                // Do our pre-flight check (to see if we can view logs)
                // IE the log file is NOT too big such as 1GB & crash the site
                logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function (result) {
                    vm.loading = false;
                    vm.canLoadLogs = result;
                    if (result) {
                        // Can view logs - so initialize
                        init();
                    }
                });
            }
            function showMore() {
                vm.commonLogMessagesCount += 10;
            }
            function init() {
                vm.loading = true;
                var savedSearches = logViewerResource.getSavedSearches().then(function (data) {
                    vm.searches = data;
                }, // Fallback to some defaults if error from API response
                function () {
                    vm.searches = [
                        {
                            'name': 'Find all logs where the Level is NOT Verbose and NOT Debug',
                            'query': 'Not(@Level=\'Verbose\') and Not(@Level=\'Debug\')'
                        },
                        {
                            'name': 'Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)',
                            'query': 'Has(@Exception)'
                        },
                        {
                            'name': 'Find all logs that have the property \'Duration\'',
                            'query': 'Has(Duration)'
                        },
                        {
                            'name': 'Find all logs that have the property \'Duration\' and the duration is greater than 1000ms',
                            'query': 'Has(Duration) and Duration > 1000'
                        },
                        {
                            'name': 'Find all logs that are from the namespace \'Umbraco.Core\'',
                            'query': 'StartsWith(SourceContext, \'Umbraco.Core\')'
                        },
                        {
                            'name': 'Find all logs that use a specific log message template',
                            'query': '@MessageTemplate = \'[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)\''
                        }
                    ];
                });
                var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) {
                    vm.numberOfErrors = data;
                });
                var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) {
                    vm.logTypeData = [];
                    var _loop = function _loop() {
                        var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), key = _Object$entries$_i[0], value = _Object$entries$_i[1];
                        var index = vm.logTypeLabels.findIndex(function (x) {
                            return key.startsWith(x);
                        });
                        if (index > -1) {
                            vm.logTypeData[index] = value;
                        }
                    };
                    for (var _i = 0, _Object$entries = Object.entries(data); _i < _Object$entries.length; _i++) {
                        _loop();
                    }
                });
                var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function (data) {
                    vm.commonLogMessages = data;
                });
                var logLevel = logViewerResource.getLogLevel().then(function (data) {
                    vm.logLevel = data;
                    var index = vm.logTypeLabels.findIndex(function (x) {
                        return vm.logLevel.startsWith(x);
                    });
                    vm.logLevelColor = index > -1 ? vm.logTypeColors[index] : '#000';
                });
                // Set loading indicator to false when these 3 queries complete
                $q.all([
                    savedSearches,
                    numOfErrors,
                    logCounts,
                    commonMsgs,
                    logLevel
                ]).then(function () {
                    vm.loading = false;
                });
                $timeout(function () {
                    navigationService.syncTree({
                        tree: 'logViewer',
                        path: '-1'
                    });
                });
            }
            preFlightCheck();
            function searchLogQuery(logQuery) {
                $location.path('/settings/logViewer/search').search({
                    lq: logQuery,
                    startDate: vm.startDate,
                    endDate: vm.endDate
                });
            }
            function findMessageTemplate(template) {
                var logQuery = '@MessageTemplate=\'' + template.MessageTemplate + '\'';
                searchLogQuery(logQuery);
            }
            function getDateRangeLabel(suffix) {
                return 'Log Overview for ' + suffix;
            }
            function searchErrors() {
                var logQuery = '@Level=\'Fatal\' or @Level=\'Error\' or Has(@Exception)';
                searchLogQuery(logQuery);
            }
            function dateRangeChange(selectedDates, dateStr, instance) {
                if (selectedDates.length > 0) {
                    // Update view by re-requesting route with updated querystring.
                    // By doing this we make sure the URL matches the selected time period, aiding sharing the link.
                    // Also resolves a minor layout issue where the " to " conjunction between the selected dates
                    // is collapsed to a comma.
                    var startDate = selectedDates[0].toIsoDateString();
                    var endDate = selectedDates[selectedDates.length - 1].toIsoDateString();
                    // Take the last date as end
                    // Check if date range has changed
                    if (startDate === vm.period[0] && endDate === vm.period[1]) {
                        // Same date range
                        return;
                    }
                    $location.path('/settings/logViewer/overview').search({
                        startDate: startDate,
                        endDate: endDate
                    });
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.LogViewer.OverviewController', LogViewerOverviewController);
    }());
    'use strict';
    (function () {
        'use strict';
        function LogViewerSearchController($location, $timeout, logViewerResource, overlayService, localizationService) {
            var vm = this;
            vm.loading = false;
            vm.logsLoading = false;
            vm.showBackButton = true;
            vm.page = {};
            // this array is also used to map the logTypeColor param onto the log items
            // in setLogTypeColors()
            vm.logLevels = [
                {
                    name: 'Verbose',
                    logTypeColor: 'gray'
                },
                {
                    name: 'Debug',
                    logTypeColor: 'info'
                },
                {
                    name: 'Information',
                    logTypeColor: 'success'
                },
                {
                    name: 'Warning',
                    logTypeColor: 'warning'
                },
                {
                    name: 'Error',
                    logTypeColor: 'danger'
                },
                {
                    name: 'Fatal',
                    logTypeColor: 'dark'
                }
            ];
            vm.polling = {
                enabled: false,
                interval: 0,
                promise: null,
                defaultButton: {
                    labelKey: 'logViewer_polling',
                    handler: function handler() {
                        if (vm.polling.enabled) {
                            vm.polling.enabled = false;
                            vm.polling.interval = 0;
                            vm.polling.defaultButton.icon = null;
                            vm.polling.defaultButton.labelKey = 'logViewer_polling';
                        } else {
                            vm.polling.subButtons[0].handler();
                        }
                    }
                },
                subButtons: [
                    {
                        labelKey: 'logViewer_every2',
                        handler: function handler() {
                            enablePolling(2);
                        }
                    },
                    {
                        labelKey: 'logViewer_every5',
                        handler: function handler() {
                            enablePolling(5);
                        }
                    },
                    {
                        labelKey: 'logViewer_every10',
                        handler: function handler() {
                            enablePolling(10);
                        }
                    },
                    {
                        labelKey: 'logViewer_every20',
                        handler: function handler() {
                            enablePolling(20);
                        }
                    },
                    {
                        labelKey: 'logViewer_every30',
                        handler: function handler() {
                            enablePolling(30);
                        }
                    }
                ]
            };
            function enablePolling(interval) {
                vm.polling.enabled = true;
                vm.polling.interval = interval;
                vm.polling.defaultButton.icon = 'icon-axis-rotation fa-spin';
                vm.polling.defaultButton.labelKey = 'logViewer_pollingEvery' + interval;
                if (vm.polling.promise) {
                    $timeout.cancel(vm.polling.promise);
                }
                vm.polling.promise = poll(interval);
            }
            function poll(interval) {
                vm.polling.promise = $timeout(function () {
                    getLogs(true, true);
                    if (vm.polling.enabled && vm.polling.interval > 0) {
                        poll(vm.polling.interval);
                    }
                }, interval * 1000);
            }
            vm.searches = [];
            vm.logItems = {};
            vm.logOptions = {};
            vm.logOptions.orderDirection = 'Descending';
            vm.fromDatePickerConfig = {
                pickTime: true,
                useSeconds: false,
                useCurrent: false,
                format: 'YYYY-MM-DD HH:mm',
                icons: {
                    time: 'icon-time',
                    date: 'icon-calendar',
                    up: 'icon-chevron-up',
                    down: 'icon-chevron-down'
                }
            };
            vm.toDatePickerConfig = {
                pickTime: true,
                useSeconds: false,
                format: 'YYYY-MM-DD HH:mm',
                icons: {
                    time: 'icon-time',
                    date: 'icon-calendar',
                    up: 'icon-chevron-up',
                    down: 'icon-chevron-down'
                }
            };
            //Functions
            vm.getLogs = getLogs;
            vm.changePageNumber = changePageNumber;
            vm.search = search;
            vm.getFilterName = getFilterName;
            vm.setLogLevelFilter = setLogLevelFilter;
            vm.selectAllLogLevelFilters = selectAllLogLevelFilters;
            vm.deselectAllLogLevelFilters = deselectAllLogLevelFilters;
            vm.toggleOrderBy = toggleOrderBy;
            vm.selectSearch = selectSearch;
            vm.resetSearch = resetSearch;
            vm.findItem = findItem;
            vm.checkForSavedSearch = checkForSavedSearch;
            vm.addToSavedSearches = addToSavedSearches;
            vm.deleteSavedSearch = deleteSavedSearch;
            vm.back = back;
            function init() {
                //If we have a Querystring set for lq (log query)
                //Then update vm.logOptions.filterExpression
                var querystring = $location.search();
                if (querystring.lq) {
                    vm.logOptions.filterExpression = querystring.lq;
                }
                if (querystring.startDate) {
                    vm.logOptions.startDate = querystring.startDate;
                }
                if (querystring.endDate) {
                    vm.logOptions.endDate = querystring.endDate;
                }
                vm.loading = true;
                logViewerResource.getSavedSearches().then(function (data) {
                    vm.searches = data;
                    vm.loading = false;
                }, // fallback to some defaults if error from API response
                function () {
                    vm.searches = [
                        {
                            'name': 'Find all logs where the Level is NOT Verbose and NOT Debug',
                            'query': 'Not(@Level=\'Verbose\') and Not(@Level=\'Debug\')'
                        },
                        {
                            'name': 'Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)',
                            'query': 'Has(@Exception)'
                        },
                        {
                            'name': 'Find all logs that have the property \'Duration\'',
                            'query': 'Has(Duration)'
                        },
                        {
                            'name': 'Find all logs that have the property \'Duration\' and the duration is greater than 1000ms',
                            'query': 'Has(Duration) and Duration > 1000'
                        },
                        {
                            'name': 'Find all logs that are from the namespace \'Umbraco.Core\'',
                            'query': 'StartsWith(SourceContext, \'Umbraco.Core\')'
                        },
                        {
                            'name': 'Find all logs that use a specific log message template',
                            'query': '@MessageTemplate = \'[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)\''
                        }
                    ];
                });
                //Get all logs on init load
                getLogs();
            }
            function search() {
                //Update the querystring lq (log query)
                $location.search('lq', vm.logOptions.filterExpression);
                //Reset pagenumber back to 1
                vm.logOptions.pageNumber = 1;
                getLogs();
            }
            function changePageNumber(pageNumber) {
                vm.logOptions.pageNumber = pageNumber;
                getLogs();
            }
            function getLogs(hideLoadingIndicator, keepOpenItems) {
                vm.logsLoading = !hideLoadingIndicator;
                logViewerResource.getLogs(vm.logOptions).then(function (data) {
                    if (keepOpenItems) {
                        var openItemTimestamps = vm.logItems.items.filter(function (item) {
                            return item.open;
                        }).map(function (item) {
                            return item.Timestamp;
                        });
                        data.items = data.items.map(function (item) {
                            item.open = openItemTimestamps.indexOf(item.Timestamp) > -1;
                            return item;
                        });
                    }
                    vm.logItems = data;
                    vm.logsLoading = false;
                    setLogTypeColor(vm.logItems.items);
                }, function (err) {
                    vm.logsLoading = false;
                });
            }
            function setLogTypeColor(logItems) {
                logItems.forEach(function (logItem) {
                    return logItem.logTypeColor = vm.logLevels.find(function (x) {
                        return x.name === logItem.Level;
                    }).logTypeColor;
                });
            }
            function getFilterName(array) {
                var name = 'All';
                var found = false;
                array.forEach(function (item) {
                    if (item.selected) {
                        if (!found) {
                            name = item.name;
                            found = true;
                        } else {
                            name = name + ', ' + item.name;
                        }
                    }
                });
                return name;
            }
            function setLogLevelFilter(logLevel) {
                if (!vm.logOptions.logLevels) {
                    vm.logOptions.logLevels = [];
                }
                if (logLevel.selected) {
                    vm.logOptions.logLevels.push(logLevel.name);
                } else {
                    var index = vm.logOptions.logLevels.indexOf(logLevel.name);
                    vm.logOptions.logLevels.splice(index, 1);
                }
                getLogs();
            }
            function updateAllLogLevelFilterCheckboxes(bool) {
                vm.logLevels.forEach(function (logLevel) {
                    return logLevel.selected = bool;
                });
            }
            function selectAllLogLevelFilters() {
                vm.logOptions.logLevels = vm.logLevels.map(function (logLevel) {
                    return logLevel.name;
                });
                updateAllLogLevelFilterCheckboxes(true);
                getLogs();
            }
            function deselectAllLogLevelFilters() {
                vm.logOptions.logLevels = [];
                updateAllLogLevelFilterCheckboxes(false);
                getLogs();
            }
            function toggleOrderBy() {
                vm.logOptions.orderDirection = vm.logOptions.orderDirection === 'Descending' ? 'Ascending' : 'Descending';
                getLogs();
            }
            function selectSearch(searchItem) {
                //Update search box input
                vm.logOptions.filterExpression = searchItem.query;
                vm.dropdownOpen = false;
                search();
            }
            function resetSearch() {
                vm.logOptions.filterExpression = '';
                search();
            }
            function findItem(key, value) {
                if (isNaN(value)) {
                    vm.logOptions.filterExpression = key + '=\'' + value + '\'';
                } else {
                    vm.logOptions.filterExpression = key + '=' + value;
                }
                search();
            }
            //Return a bool to toggle display of the star/fav
            function checkForSavedSearch() {
                //Check if we have a value in
                if (!vm.logOptions.filterExpression) {
                    return false;
                } else {
                    //Check what we have searched for is not an existing saved search
                    var findQuery = _.findWhere(vm.searches, { query: vm.logOptions.filterExpression });
                    return !findQuery ? true : false;
                }
            }
            function addToSavedSearches() {
                var overlay = {
                    title: 'Save Search',
                    closeButtonLabel: 'Cancel',
                    submitButtonLabel: 'Save Search',
                    disableSubmitButton: true,
                    view: 'logviewersearch',
                    query: vm.logOptions.filterExpression,
                    submit: function submit(model) {
                        //Resource call with two params (name & query)
                        //API that opens the JSON and adds it to the bottom
                        logViewerResource.postSavedSearch(model.queryName, model.query).then(function (data) {
                            vm.searches = data;
                            overlayService.close();
                        });
                    },
                    close: function close() {
                        return overlayService.close();
                    }
                };
                var labelKeys = [
                    'general_cancel',
                    'logViewer_saveSearch',
                    'logViewer_saveSearchDescription'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    overlay.title = values[1];
                    overlay.subtitle = values[2], overlay.submitButtonLabel = values[1], overlay.closeButtonLabel = values[0], overlayService.open(overlay);
                });
            }
            function deleteSavedSearch(searchItem) {
                var overlay = {
                    title: 'Delete Saved Search',
                    closeButtonLabel: 'Cancel',
                    submitButtonLabel: 'Delete Saved Search',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        //Resource call with two params (name & query)
                        //API that opens the JSON and adds it to the bottom
                        logViewerResource.deleteSavedSearch(searchItem.name, searchItem.query).then(function (data) {
                            vm.searches = data;
                            overlayService.close();
                        });
                    },
                    close: function close() {
                        return overlayService.close();
                    }
                };
                var labelKeys = [
                    'general_cancel',
                    'defaultdialogs_confirmdelete',
                    'logViewer_deleteSavedSearch'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    overlay.title = values[2];
                    overlay.subtitle = values[1];
                    overlay.submitButtonLabel = values[2];
                    overlay.closeButtonLabel = values[0];
                    overlayService.open(overlay);
                });
            }
            function back() {
                $location.path('settings/logViewer/overview').search('lq', null);
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.LogViewer.SearchController', LogViewerSearchController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ParameterEditorController($scope, formHelper, editorService) {
            var vm = this;
            vm.submit = submit;
            vm.close = close;
            vm.openMacroParameterPicker = openMacroParameterPicker;
            function openMacroParameterPicker(parameter) {
                vm.focusOnMandatoryField = false;
                var overlay = {
                    parameter: $scope.model.parameter,
                    view: 'views/common/infiniteeditors/macroparameterpicker/macroparameterpicker.html',
                    size: 'small',
                    submit: function submit(model) {
                        vm.focusOnMandatoryField = true;
                        // update property
                        parameter.editor = model.parameter.editor;
                        editorService.close();
                    },
                    close: function close(model) {
                        editorService.close();
                    }
                };
                editorService.open(overlay);
            }
            function submit() {
                if ($scope.model && $scope.model.submit && formHelper.submitForm({ scope: $scope })) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.ParameterEditorController', ParameterEditorController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Macros.CreateController
 * @function
 * 
 * @description
 * The controller for creating macro items
 */
    function MacrosCreateController($scope, $location, macroResource, navigationService, notificationsService, formHelper, appState) {
        var vm = this;
        vm.itemKey = '';
        function createItem() {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createMacroForm
                })) {
                var node = $scope.currentNode;
                macroResource.createMacro(vm.itemKey).then(function (data) {
                    navigationService.hideMenu();
                    // set new item as active in tree
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'macros',
                        path: currPath + ',' + data,
                        forceReload: true,
                        activate: true
                    });
                    // reset form state
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createMacroForm
                    });
                    // navigate to edit view
                    var currentSection = appState.getSectionState('currentSection');
                    $location.path('/' + currentSection + '/macros/edit/' + data);
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createMacroForm,
                        hasErrors: true
                    });
                    if (err.data && err.data.message) {
                        notificationsService.error(err.data.message);
                        navigationService.hideMenu();
                    }
                });
            }
        }
        $scope.close = function () {
            navigationService.hideDialog(true);
        };
        vm.createItem = createItem;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Macros.CreateController', MacrosCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Macros.DeleteController
 * @function
 * 
 * @description
 * The controller for deleting macro items
 */
    function MacrosDeleteController($scope, macroResource, navigationService, treeService) {
        var vm = this;
        vm.name = $scope.currentNode.name;
        function performDelete() {
            $scope.currentNode.loading = true;
            macroResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        }
        function cancel() {
            navigationService.hideDialog();
        }
        vm.performDelete = performDelete;
        vm.cancel = cancel;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Macros.DeleteController', MacrosDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Macros.EditController
 * @function
 *
 * @description
 * The controller for editing macros.
 */
    function MacrosEditController($scope, $q, $routeParams, macroResource, editorState, navigationService, formHelper, contentEditingHelper, localizationService, angularHelper) {
        var vm = this;
        vm.promises = {};
        vm.header = {};
        vm.header.editorfor = 'general_macro';
        vm.header.setPageTitle = true;
        vm.page = {};
        vm.page.loading = false;
        vm.page.saveButtonState = 'init';
        vm.page.menu = {};
        vm.labels = {};
        function toggleValue(key) {
            vm.macro[key] = !vm.macro[key];
        }
        vm.toggle = toggleValue;
        function saveMacro() {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                vm.page.saveButtonState = 'busy';
                macroResource.saveMacro(vm.macro).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    bindMacro(data);
                    vm.page.saveButtonState = 'success';
                }, function (error) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({ err: error });
                    vm.page.saveButtonState = 'error';
                });
            }
        }
        vm.save = saveMacro;
        function setFormDirty() {
            var currentForm = angularHelper.getCurrentForm($scope);
            if (currentForm) {
                currentForm.$setDirty();
            }
        }
        vm.setDirty = setFormDirty;
        function getPartialViews() {
            var deferred = $q.defer();
            macroResource.getPartialViews().then(function (data) {
                deferred.resolve(data);
            }, function () {
                deferred.reject();
            });
            return deferred.promise;
        }
        function getMacro() {
            var deferred = $q.defer();
            macroResource.getById($routeParams.id).then(function (data) {
                deferred.resolve(data);
            }, function () {
                deferred.reject();
            });
            return deferred.promise;
        }
        function bindMacro(data) {
            vm.macro = data;
            if (vm.macro && vm.macro.view) {
                vm.macro.node = {
                    icon: 'icon-article',
                    name: vm.macro.view
                };
            }
            editorState.set(vm.macro);
            navigationService.syncTree({
                tree: 'macros',
                path: vm.macro.path,
                forceReload: true
            }).then(function (syncArgs) {
                vm.page.menu.currentNode = syncArgs.node;
            });
        }
        function init() {
            vm.page.loading = true;
            vm.promises['partialViews'] = getPartialViews();
            vm.promises['macro'] = getMacro();
            vm.views = [];
            vm.node = null;
            $q.all(vm.promises).then(function (values) {
                var keys = Object.keys(values);
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i];
                    if (key === 'partialViews') {
                        vm.views = values[key];
                    }
                    if (key === 'macro') {
                        bindMacro(values[key]);
                    }
                }
                vm.page.loading = false;
            });
            var labelKeys = [
                'general_settings',
                'macro_parameters'
            ];
            localizationService.localizeMany(labelKeys).then(function (values) {
                // navigation
                vm.labels.settings = values[0];
                vm.labels.parameters = values[1];
                vm.page.navigation = [
                    {
                        'name': vm.labels.settings,
                        'alias': 'settings',
                        'icon': 'icon-settings',
                        'view': 'views/macros/views/settings.html',
                        'active': true
                    },
                    {
                        'name': vm.labels.parameters,
                        'alias': 'parameters',
                        'icon': 'icon-list',
                        'view': 'views/macros/views/parameters.html'
                    }
                ];
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Macros.EditController', MacrosEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Macros.ParametersController
 * @function
 *
 * @description
 * The controller for editing macros parameters
 */
    function MacrosParametersController($scope, $q, editorService, localizationService, macroResource) {
        var vm = this;
        vm.add = add;
        vm.edit = edit;
        vm.remove = remove;
        vm.labels = {};
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> div.control-group',
            handle: '.handle',
            tolerance: 'pointer',
            update: function update(e, ui) {
                setDirty();
            }
        };
        function remove(parameter, evt) {
            evt.preventDefault();
            $scope.model.macro.parameters = _.without($scope.model.macro.parameters, parameter);
            setDirty();
        }
        function add(evt) {
            evt.preventDefault();
            openOverlay({}, vm.labels.addParameter, function (newParameter) {
                if (!$scope.model.macro.parameters) {
                    $scope.model.macro.parameters = [];
                }
                $scope.model.macro.parameters.push(newParameter);
                setDirty();
            });
        }
        function edit(parameter, evt) {
            evt.preventDefault();
            var promises = [getParameterEditorByAlias(parameter.editor)];
            $q.all(promises).then(function (values) {
                parameter.dataTypeName = values[0].name;
                openOverlay(parameter, vm.labels.editParameter, function (newParameter) {
                    parameter.key = newParameter.key;
                    parameter.label = newParameter.label;
                    parameter.editor = newParameter.editor;
                    setDirty();
                });
            });
        }
        function getParameterEditorByAlias(alias) {
            var deferred = $q.defer();
            macroResource.getParameterEditorByAlias(alias).then(function (data) {
                deferred.resolve(data);
            }, function () {
                deferred.reject();
            });
            return deferred.promise;
        }
        function openOverlay(parameter, title, onSubmit) {
            var ruleDialog = {
                title: title,
                parameter: _.clone(parameter),
                view: 'views/macros/infiniteeditors/parameter.html',
                size: 'small',
                submit: function submit(model) {
                    onSubmit(model.parameter);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.open(ruleDialog);
        }
        function setDirty() {
            $scope.model.setDirty();
        }
        function init() {
            localizationService.localizeMany([
                'macro_addParameter',
                'macro_editParameter'
            ]).then(function (data) {
                vm.labels.addParameter = data[0];
                vm.labels.editParameter = data[1];
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Macros.ParametersController', MacrosParametersController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Macros.SettingsController
 * @function
 *
 * @description
 * The controller for editing macros settings
 */
    function MacrosSettingsController($scope, editorService, localizationService) {
        var vm = this;
        //vm.openViewPicker = openViewPicker;
        //vm.removeMacroView = removeMacroView;
        $scope.model.openViewPicker = openViewPicker;
        $scope.model.removeMacroView = removeMacroView;
        var labels = {};
        localizationService.localizeMany(['macro_selectViewFile']).then(function (data) {
            labels.selectViewFile = data[0];
        });
        function openViewPicker() {
            var controlPicker = {
                title: labels.selectViewFile,
                section: 'settings',
                treeAlias: 'partialViewMacros',
                entityType: 'partialView',
                onlyInitialized: false,
                filter: function filter(i) {
                    if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                        return true;
                    }
                },
                filterCssClass: 'not-allowed',
                select: function select(node) {
                    var id = decodeURIComponent(node.id.replace(/\+/g, ' '));
                    //vm.macro.view = id;
                    $scope.model.macro.view = '~/Views/MacroPartials/' + id;
                    $scope.model.macro.node = {
                        icon: node.icon,
                        name: $scope.model.macro.view
                    };
                    //$scope.model.submit($scope.model); 
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.treePicker(controlPicker);
        }
        function removeMacroView() {
            //vm.macro.view = null;
            $scope.model.macro.node = null;
            $scope.model.macro.view = null;
        }
        function init() {
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Macros.SettingsController', MacrosSettingsController);
    'use strict';
    (function () {
        'use strict';
        function MediaAppContentController($scope, $filter, contentEditingHelper, contentTypeHelper) {
            var vm = this;
            vm.tabs = [];
            vm.activeTabAlias = null;
            vm.setActiveTab = setActiveTab;
            $scope.$watchCollection('content.tabs', function (newValue) {
                contentTypeHelper.defineParentAliasOnGroups(newValue);
                contentTypeHelper.relocateDisorientedGroups(newValue);
                vm.tabs = $filter('filter')(newValue, function (tab) {
                    return tab.type === 1;
                });
                if (vm.tabs.length > 0) {
                    // if we have tabs and some groups that doesn't belong to a tab we need to render those on an "Other" tab.
                    contentEditingHelper.registerGenericTab(newValue);
                    setActiveTab(vm.tabs[0]);
                }
            });
            function setActiveTab(tab) {
                vm.activeTabAlias = tab.alias;
                vm.tabs.forEach(function (tab) {
                    return tab.active = false;
                });
                tab.active = true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Media.Apps.ContentController', MediaAppContentController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.CreateController
 * @function
 * 
 * @description
 * The controller for the media creation dialog
 */
    function mediaCreateController($scope, $location, mediaTypeResource, iconHelper, navigationService, authResource, mediaResource) {
        function initialize() {
            $scope.allowedTypes = null;
            mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
                $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
            });
            if ($scope.currentNode.id > -1) {
                authResource.getCurrentUser().then(function (currentUser) {
                    if (currentUser.allowedSections.indexOf('settings') > -1) {
                        $scope.hasSettingsAccess = true;
                        mediaResource.getById($scope.currentNode.id).then(function (data) {
                            $scope.mediaTypeId = data.contentType.id;
                        });
                    }
                });
            }
        }
        $scope.createMediaItem = function (docType) {
            $location.path('/media/media/edit/' + $scope.currentNode.id).search('doctype', docType.alias).search('create', 'true');
            navigationService.hideMenu();
        };
        $scope.close = function () {
            navigationService.hideMenu();
        };
        $scope.closeDialog = function () {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        };
        // the current node changes behind the scenes when the context menu is clicked without closing 
        // the default menu first, so we must watch the current node and re-initialize accordingly
        var unbindModelWatcher = $scope.$watch('currentNode', initialize);
        $scope.$on('$destroy', function () {
            unbindModelWatcher();
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.CreateController', mediaCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, overlayService) {
        $scope.performDelete = function () {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            mediaResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                treeService.removeNode($scope.currentNode);
                if (rootNode) {
                    //ensure the recycle bin has child nodes now            
                    var recycleBin = treeService.getDescendantNode(rootNode, -21);
                    if (recycleBin) {
                        recycleBin.hasChildren = true;
                        //reload the recycle bin if it's already expanded so the deleted item is shown
                        if (recycleBin.expanded) {
                            treeService.loadNodeChildren({
                                node: recycleBin,
                                section: 'media'
                            });
                        }
                    }
                }
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                    //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                    var location = '/media';
                    if ($scope.currentNode.parentId.toString() === '-21')
                        location = '/media/media/recyclebin';
                    else if ($scope.currentNode.parentId.toString() !== '-1')
                        location = '/media/media/edit/' + $scope.currentNode.parentId;
                    $location.path(location);
                }
                $scope.success = true;
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.busy = false;
                //check if response is ysod
                if (err.status && err.status >= 500) {
                    // TODO: All YSOD handling should be done with an interceptor
                    overlayService.ysod(err);
                }
            });
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.DeleteController', MediaDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.EditController
 * @function
 *
 * @description
 * The controller for the media editor
 */
    function mediaEditController($scope, $routeParams, $location, $http, $q, appState, mediaResource, entityResource, navigationService, notificationsService, localizationService, serverValidationManager, contentEditingHelper, fileManager, formHelper, editorState, umbRequestHelper, eventsService) {
        var evts = [];
        var nodeId = null;
        var create = false;
        var infiniteMode = $scope.model && $scope.model.infiniteMode;
        // when opening the editor through infinite editing get the
        // node id from the model instead of the route param
        if (infiniteMode && $scope.model.id) {
            nodeId = $scope.model.id;
        } else {
            nodeId = $routeParams.id;
        }
        // when opening the editor through infinite editing get the
        // create option from the model instead of the route param
        if (infiniteMode) {
            create = $scope.model.create;
        } else {
            create = $routeParams.create;
        }
        //setup scope vars
        $scope.currentSection = appState.getSectionState('currentSection');
        $scope.currentNode = null;
        //the editors affiliated node
        $scope.header = {};
        $scope.header.setPageTitle = $scope.currentSection === 'media';
        $scope.page = {};
        $scope.page.loading = false;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        //the editors affiliated node
        $scope.page.listViewPath = null;
        $scope.page.saveButtonState = 'init';
        $scope.page.submitButtonLabelKey = 'buttons_save';
        $scope.app = null;
        if (create) {
            $scope.page.loading = true;
            mediaResource.getScaffold(nodeId, $routeParams.doctype).then(function (data) {
                $scope.content = data;
                init();
                $scope.page.loading = false;
            }, function () {
                $scope.page.loading = false;
            });
        } else {
            $scope.page.loading = true;
            loadMedia().then(function () {
                $scope.page.loading = false;
            }, function () {
                $scope.page.loading = false;
            });
        }
        function init() {
            var content = $scope.content;
            // we need to check whether an app is present in the current data, if not we will present the default app.
            var isAppPresent = false;
            // on first init, we dont have any apps. but if we are re-initializing, we do, but ...
            if ($scope.app) {
                // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
                content.apps.forEach(function (app) {
                    if (app === $scope.app) {
                        isAppPresent = true;
                    }
                });
                // if we did reload our DocType, but still have the same app we will try to find it by the alias.
                if (isAppPresent === false) {
                    content.apps.forEach(function (app) {
                        if (app.alias === $scope.app.alias) {
                            isAppPresent = true;
                            app.active = true;
                            $scope.appChanged(app);
                        }
                    });
                }
            }
            // if we still dont have a app, lets show the first one:
            if (isAppPresent === false) {
                content.apps[0].active = true;
                $scope.appChanged(content.apps[0]);
            }
            editorState.set($scope.content);
            bindEvents();
        }
        function bindEvents() {
            //bindEvents can be called more than once and we don't want to have multiple bound events
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
            evts.push(eventsService.on('editors.mediaType.saved', function (name, args) {
                // if this media item uses the updated media type we need to reload the media item
                if (args && args.mediaType && args.mediaType.key === $scope.content.contentType.key) {
                    $scope.page.loading = true;
                    loadMedia().then(function () {
                        $scope.page.loading = false;
                    }, function () {
                        $scope.page.loading = false;
                    });
                }
            }));
        }
        $scope.page.submitButtonLabelKey = 'buttons_save';
        /** Syncs the content item to it's tree node - this occurs on first load and after saving */
        function syncTreeNode(content, path, initialLoad) {
            if (infiniteMode) {
                return;
            }
            if (!$scope.content.isChildOfListView) {
                navigationService.syncTree({
                    tree: 'media',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    $scope.page.menu.currentNode = syncArgs.node;
                });
            } else if (initialLoad === true) {
                //it's a child item, just sync the ui node to the parent
                navigationService.syncTree({
                    tree: 'media',
                    path: path.substring(0, path.lastIndexOf(',')).split(','),
                    forceReload: initialLoad !== true
                });
                //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
                // from the server so that we can load in the actions menu.
                umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) {
                    $scope.page.menu.currentNode = node;
                });
            }
        }
        /** Just shows a simple notification that there are client side validation issues to be fixed */
        function showValidationNotification() {
            //TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants
            //need to show a notification else it's not clear there was an error.
            localizationService.localizeMany([
                'speechBubbles_validationFailedHeader',
                'speechBubbles_validationFailedMessage'
            ]).then(function (data) {
                notificationsService.error(data[0], data[1]);
            });
        }
        $scope.save = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.page.saveButtonState = 'busy';
                mediaResource.save($scope.content, create, fileManager.getFiles()).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    // close the editor if it's infinite mode
                    // submit function manages rebinding changes
                    if (infiniteMode && $scope.model.submit) {
                        $scope.model.mediaNode = $scope.content;
                        $scope.model.submit($scope.model);
                    } else {
                        // if not infinite mode, rebind changed props etc
                        contentEditingHelper.handleSuccessfulSave({
                            scope: $scope,
                            savedContent: data,
                            rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                        });
                        editorState.set($scope.content);
                        syncTreeNode($scope.content, data.path);
                        $scope.page.saveButtonState = 'success';
                        init();
                    }
                    eventsService.emit('editors.media.saved', { media: data });
                    return data;
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({
                        err: err,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                    });
                    editorState.set($scope.content);
                    $scope.page.saveButtonState = 'error';
                });
            } else {
                showValidationNotification();
            }
        };
        function loadMedia() {
            return mediaResource.getById(nodeId).then(function (data) {
                $scope.content = data;
                if (data.isChildOfListView && data.trashed === false) {
                    $scope.page.listViewPath = $routeParams.page ? '/media/media/edit/' + data.parentId + '?page=' + $routeParams.page : '/media/media/edit/' + data.parentId;
                }
                editorState.set($scope.content);
                //in one particular special case, after we've created a new item we redirect back to the edit
                // route but there might be server validation errors in the collection which we need to display
                // after the redirect, so we will bind all subscriptions which will show the server validation errors
                // if there are any and then clear them so the collection no longer persists them.
                serverValidationManager.notifyAndClearAllSubscriptions();
                if (!infiniteMode) {
                    syncTreeNode($scope.content, data.path, true);
                }
                if ($scope.content.parentId && $scope.content.parentId !== -1 && $scope.content.parentId !== -21) {
                    //We fetch all ancestors of the node to generate the footer breadcrump navigation
                    entityResource.getAncestors(nodeId, 'media').then(function (anc) {
                        $scope.ancestors = anc;
                    });
                }
                init();
                $scope.page.loading = false;
                $q.resolve($scope.content);
            }, function (error) {
                $scope.page.loading = false;
                $q.reject(error);
            });
        }
        $scope.close = function () {
            if ($scope.model.close) {
                $scope.model.close($scope.model);
            }
        };
        $scope.appChanged = function (app) {
            $scope.app = app;
            // setup infinite mode
            if (infiniteMode) {
                $scope.page.submitButtonLabelKey = 'buttons_saveAndClose';
            }
        };
        $scope.showBack = function () {
            return !infiniteMode && !!$scope.page.listViewPath;
        };
        /** Callback for when user clicks the back-icon */
        $scope.onBack = function () {
            if ($scope.page.listViewPath) {
                $location.path($scope.page.listViewPath.split('?')[0]);
            }
        };
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.EditController', mediaEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.EmptyRecycleBinController
 * @function
 * 
 * @description
 * The controller for deleting media
 */
    function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) {
        $scope.busy = false;
        $scope.performDelete = function () {
            //(used in the UI)
            $scope.busy = true;
            $scope.currentNode.loading = true;
            mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                $scope.busy = false;
                $scope.currentNode.loading = false;
                treeService.removeChildNodes($scope.currentNode);
                navigationService.hideMenu();
                //reload the current view
                $route.reload();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.EmptyRecycleBinController', MediaEmptyRecycleBinController);
    'use strict';
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.Media.MoveController', function ($scope, userService, eventsService, mediaResource, appState, treeService, navigationService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startMediaIds.length > 0 && userData.startMediaIds.indexOf(-1) == -1;
        });
        function treeLoadedHandler(args) {
            if ($scope.source && $scope.source.path) {
                $scope.dialogTreeApi.syncTree({
                    path: $scope.source.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.media.moveController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.move = function () {
            $scope.busy = true;
            mediaResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.busy = false;
                $scope.error = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'media',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'media',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.busy = false;
                $scope.success = false;
                $scope.error = err;
            });
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.MediaRecycleBinController
 * @function
 * 
 * @description
 * Controls the recycle bin for media
 * 
 */
    function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) {
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.page = {};
        $scope.page.name = 'Recycle Bin';
        $scope.page.nameLocked = true;
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.listViewPath = null;
        $routeParams.id = '-21';
        mediaResource.getRecycleBin().then(function (result) {
            $scope.content = result;
        });
        // sync tree node
        navigationService.syncTree({
            tree: 'media',
            path: [
                '-1',
                $routeParams.id
            ],
            forceReload: false
        });
        localizePageName();
        function localizePageName() {
            var pageName = 'general_recycleBin';
            localizationService.localize(pageName).then(function (value) {
                $scope.page.name = value;
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.RecycleBinController', MediaRecycleBinController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.Media.RestoreController', function ($scope, relationResource, mediaResource, entityResource, navigationService, appState, treeService, userService, localizationService) {
        $scope.source = _.clone($scope.currentNode);
        $scope.error = null;
        $scope.loading = true;
        $scope.moving = false;
        $scope.success = false;
        $scope.dialogTreeApi = {};
        $scope.searchInfo = {
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.labels = {};
        localizationService.localizeMany(['treeHeaders_media']).then(function (data) {
            $scope.labels.treeRoot = data[0];
        });
        function nodeSelectHandler(args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler({
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
            $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
        };
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({ node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
        relationResource.getByChildId($scope.source.id, 'relateParentMediaFolderOnDelete').then(function (data) {
            $scope.loading = false;
            if (!data.length) {
                $scope.moving = true;
                return;
            }
            $scope.relation = data[0];
            if ($scope.relation.parentId === -1) {
                $scope.target = {
                    id: -1,
                    name: $scope.labels.treeRoot
                };
            } else {
                $scope.loading = true;
                entityResource.getById($scope.relation.parentId, 'media').then(function (data) {
                    $scope.loading = false;
                    $scope.target = data;
                    // make sure the target item isn't in the recycle bin
                    if ($scope.target.path.indexOf('-21') !== -1) {
                        $scope.moving = true;
                        $scope.target = null;
                    }
                }, function (err) {
                    $scope.loading = false;
                    $scope.error = err;
                });
            }
        }, function (err) {
            $scope.loading = false;
            $scope.error = err;
        });
        $scope.restore = function () {
            $scope.loading = true;
            // this code was copied from `content.move.controller.js`
            mediaResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.loading = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved media item - but don't activate the node,
                //then sync to the currenlty edited media item (note: this might not be the media item that was moved!!)
                navigationService.syncTree({
                    tree: 'media',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'media',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.loading = false;
                $scope.error = err;
            });
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    (function () {
        'use strict';
        function MediaSortController($scope, $filter, mediaResource, navigationService, eventsService) {
            var vm = this;
            var id = $scope.currentNode.id;
            vm.loading = false;
            vm.children = [];
            vm.saveButtonState = 'init';
            vm.sortOrder = {};
            vm.sortableOptions = {
                axis: 'y',
                containment: 'parent',
                distance: 10,
                tolerance: 'pointer',
                opacity: 0.7,
                scroll: true,
                cursor: 'move',
                helper: fixSortableHelper,
                update: function update() {
                    // clear the sort order when drag and drop is used
                    vm.sortOrder.column = '';
                    vm.sortOrder.reverse = false;
                }
            };
            vm.save = save;
            vm.sort = sort;
            vm.close = close;
            function onInit() {
                vm.loading = true;
                mediaResource.getChildren(id).then(function (data) {
                    vm.children = data.items;
                    vm.loading = false;
                });
            }
            function save() {
                vm.saveButtonState = 'busy';
                var args = {
                    parentId: id,
                    sortedIds: _.map(vm.children, function (child) {
                        return child.id;
                    })
                };
                mediaResource.sort(args).then(function () {
                    navigationService.syncTree({
                        tree: 'media',
                        path: $scope.currentNode.path,
                        forceReload: true
                    }).then(function () {
                        return navigationService.reloadNode($scope.currentNode);
                    });
                    eventsService.emit('sortCompleted', { id: id });
                    vm.saveButtonState = 'success';
                }, function (error) {
                    vm.error = error;
                    vm.saveButtonState = 'error';
                });
            }
            function fixSortableHelper(e, ui) {
                // keep the correct width of each table cell when sorting
                ui.children().each(function () {
                    $(this).width($(this).width());
                });
                return ui;
            }
            function sort(column) {
                // reverse if it is already ordered by that column
                if (vm.sortOrder.column === column) {
                    vm.sortOrder.reverse = !vm.sortOrder.reverse;
                } else {
                    vm.sortOrder.column = column;
                    vm.sortOrder.reverse = false;
                }
                vm.children = $filter('orderBy')(vm.children, vm.sortOrder.column, vm.sortOrder.reverse);
            }
            function close() {
                navigationService.hideDialog();
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Media.SortController', MediaSortController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CopyController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            mediaTypeResource.copy({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'mediaTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'mediaTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.CreateController
 * @function
 *
 * @description
 * The controller for the media type creation dialog
 */
    function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.currentNode;
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createFolderForm
                })) {
                mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'mediatypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm
                    });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createFolderForm,
                        hasErrors: true
                    });
                    $scope.error = err;
                });
            }
            ;
        };
        $scope.createMediaType = function () {
            $location.search('create', null);
            $location.path('/settings/mediatypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
        $scope.close = function () {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CreateController', MediaTypesCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.DeleteController
 * @function
 *
 * @description
 * The controller for the media type delete dialog
 */
    function MediaTypesDeleteController($scope, mediaTypeResource, treeService, navigationService, localizationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            mediaTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
        $scope.labels = {};
        localizationService.format([
            'contentTypeEditor_yesDelete',
            'contentTypeEditor_andAllMediaItems'
        ], '%0% ' + $scope.currentNode.name + ' %1%').then(function (data) {
            $scope.labels.deleteConfirm = data;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.DeleteController', MediaTypesDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.EditController
 * @function
 *
 * @description
 * The controller for the media type editor
 */
    (function () {
        'use strict';
        function MediaTypesEditController($scope, $routeParams, $q, mediaTypeResource, editorState, contentEditingHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, localizationService, overlayHelper, eventsService, angularHelper) {
            var vm = this;
            var evts = [];
            var mediaTypeId = $routeParams.id;
            var create = $routeParams.create;
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var mediaTypeIcon = '';
            vm.save = save;
            vm.close = close;
            vm.currentNode = null;
            vm.header = {};
            vm.header.editorfor = 'content_mediatype';
            vm.header.setPageTitle = true;
            vm.contentType = {};
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.labels = {};
            vm.saveButtonKey = 'buttons_save';
            vm.generateModelsKey = 'buttons_saveAndGenerateModels';
            onInit();
            function onInit() {
                // get init values from model when in infinite mode
                if (infiniteMode) {
                    mediaTypeId = $scope.model.id;
                    create = $scope.model.create;
                    vm.saveButtonKey = 'buttons_saveAndClose';
                    vm.generateModelsKey = 'buttons_generateModelsAndClose';
                }
            }
            var labelKeys = [
                'general_design',
                'general_listView',
                'general_rights',
                'main_sections',
                'shortcuts_navigateSections',
                'shortcuts_addGroup',
                'shortcuts_addProperty',
                'shortcuts_addEditor',
                'shortcuts_editDataType',
                'shortcuts_toggleListView',
                'shortcuts_toggleAllowAsRoot',
                'shortcuts_addChildNode'
            ];
            localizationService.localizeMany(labelKeys).then(function (values) {
                // navigation
                vm.labels.design = values[0];
                vm.labels.listview = values[1];
                vm.labels.permissions = values[2];
                // keyboard shortcuts
                vm.labels.sections = values[3];
                vm.labels.navigateSections = values[4];
                vm.labels.addGroup = values[5];
                vm.labels.addProperty = values[6];
                vm.labels.addEditor = values[7];
                vm.labels.editDataType = values[8];
                vm.labels.toggleListView = values[9];
                vm.labels.allowAsRoot = values[10];
                vm.labels.addChildNode = values[11];
                vm.page.navigation = [
                    {
                        'name': vm.labels.design,
                        'alias': 'design',
                        'icon': 'icon-document-dashed-line',
                        'view': 'views/mediatypes/views/design/design.html'
                    },
                    {
                        'name': vm.labels.listview,
                        'alias': 'listView',
                        'icon': 'icon-list',
                        'view': 'views/mediatypes/views/listview/listview.html'
                    },
                    {
                        'name': vm.labels.permissions,
                        'alias': 'permissions',
                        'icon': 'icon-keychain',
                        'view': 'views/mediatypes/views/permissions/permissions.html'
                    }
                ];
                vm.page.keyboardShortcutsOverview = [
                    {
                        'name': vm.labels.sections,
                        'shortcuts': [{
                                'description': vm.labels.navigateSections,
                                'keys': [
                                    { 'key': '1' },
                                    { 'key': '3' }
                                ],
                                'keyRange': true
                            }]
                    },
                    {
                        'name': vm.labels.design,
                        'shortcuts': [
                            {
                                'description': vm.labels.addGroup,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'g' }
                                ]
                            },
                            {
                                'description': vm.labels.addProperty,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'p' }
                                ]
                            },
                            {
                                'description': vm.labels.addEditor,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'e' }
                                ]
                            },
                            {
                                'description': vm.labels.editDataType,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'd' }
                                ]
                            }
                        ]
                    },
                    {
                        'name': vm.labels.listview,
                        'shortcuts': [{
                                'description': vm.labels.toggleListView,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'l' }
                                ]
                            }]
                    },
                    {
                        'name': vm.labels.permissions,
                        'shortcuts': [
                            {
                                'description': vm.labels.allowAsRoot,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'r' }
                                ]
                            },
                            {
                                'description': vm.labels.addChildNode,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'c' }
                                ]
                            }
                        ]
                    }
                ];
                initializeActiveNavigationPanel();
            });
            function initializeActiveNavigationPanel() {
                // Initialise first loaded page based on page route paramater
                // i.e. ?view=design|listview|permissions
                var initialViewSetFromRouteParams = false;
                var view = $routeParams.view;
                if (view) {
                    var viewPath = 'views/mediatypes/views/' + view + '/' + view + '.html';
                    for (var i = 0; i < vm.page.navigation.length; i++) {
                        if (vm.page.navigation[i].view === viewPath) {
                            vm.page.navigation[i].active = true;
                            initialViewSetFromRouteParams = true;
                            break;
                        }
                    }
                }
                if (initialViewSetFromRouteParams === false) {
                    vm.page.navigation[0].active = true;
                }
            }
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: vm.saveButtonKey,
                        letter: 'S',
                        handler: function handler() {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: vm.generateModelsKey,
                            letter: 'G',
                            handler: function handler() {
                                vm.page.saveButtonState = 'busy';
                                vm.save().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if (create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                mediaTypeResource.getScaffold(mediaTypeId).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                loadMediaType();
            }
            function loadMediaType() {
                vm.page.loading = true;
                mediaTypeResource.getById(mediaTypeId).then(function (dt) {
                    init(dt);
                    if (!infiniteMode) {
                        syncTreeNode(vm.contentType, dt.path, true);
                    }
                    vm.page.loading = false;
                });
            }
            /* ---------- SAVE ---------- */
            function save() {
                // only save if there is no overlays open
                if (overlayHelper.getNumberOfOverlays() === 0) {
                    var deferred = $q.defer();
                    vm.page.saveButtonState = 'busy';
                    // reformat allowed content types to array if id's
                    vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                    contentEditingHelper.contentEditorPerformSave({
                        saveMethod: mediaTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        rebindCallback: function rebindCallback(_, savedContentType) {
                            // we need to rebind... the IDs that have been created!
                            contentTypeHelper.rebindSavedContentType(vm.contentType, savedContentType);
                        }
                    }).then(function (data) {
                        //success
                        if (!infiniteMode) {
                            syncTreeNode(vm.contentType, data.path);
                        }
                        // emit event
                        var args = { mediaType: vm.contentType };
                        eventsService.emit('editors.mediaType.saved', args);
                        if (mediaTypeIcon !== vm.contentType.icon) {
                            eventsService.emit('editors.tree.icon.changed', args);
                        }
                        vm.page.saveButtonState = 'success';
                        if (infiniteMode && $scope.model.submit) {
                            $scope.model.submit();
                        }
                        deferred.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        deferred.reject(err);
                    });
                    return deferred.promise;
                }
            }
            function init(contentType) {
                // convert icons for content type
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
                mediaTypeIcon = contentType.icon;
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                navigationService.syncTree({
                    tree: 'mediatypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
            function close() {
                if (infiniteMode && $scope.model.close) {
                    $scope.model.close();
                }
            }
            evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                loadMediaType();
            }));
            evts.push(eventsService.on('editors.groupsBuilder.changed', function (name, args) {
                angularHelper.getCurrentForm($scope).$setDirty();
            }));
            //ensure to unregister from all events!
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
            // changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty 
            $scope.$watch('vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.isContainer + vm.contentType.compositeContentTypes.length', function (newVal, oldVal) {
                if (oldVal === undefined) {
                    // still initializing, ignore
                    return;
                }
                angularHelper.getCurrentForm($scope).$setDirty();
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.EditController', MediaTypesEditController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.MoveController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            mediaTypeResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'mediaTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'mediaTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    (function () {
        'use strict';
        function PermissionsController($scope, $timeout, mediaTypeResource, iconHelper, contentTypeHelper, editorService) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            vm.mediaTypes = [];
            vm.selectedChildren = [];
            vm.addChild = addChild;
            vm.removeChild = removeChild;
            vm.sortChildren = sortChildren;
            vm.toggle = toggle;
            /* ---------- INIT ---------- */
            init();
            function init() {
                mediaTypeResource.getAll().then(function (mediaTypes) {
                    vm.mediaTypes = mediaTypes;
                    // convert legacy icons
                    iconHelper.formatContentTypeIcons(vm.mediaTypes);
                    vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes);
                    if ($scope.model.id === 0) {
                        contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                    }
                });
            }
            function addChild($event) {
                var editor = {
                    multiPicker: true,
                    filterCssClass: 'not-allowed not-published',
                    filter: function filter(item) {
                        return !vm.mediaTypes.some(function (x) {
                            return x.udi == item.udi;
                        }) || vm.selectedChildren.some(function (x) {
                            return x.udi === item.udi;
                        });
                    },
                    submit: function submit(model) {
                        model.selection.forEach(function (item) {
                            return mediaTypeResource.getById(item.id).then(function (contentType) {
                                vm.selectedChildren.push(contentType);
                                $scope.model.allowedContentTypes.push(item.id);
                            });
                        });
                        editorService.close();
                    },
                    close: function close() {
                        return editorService.close();
                    }
                };
                editorService.mediaTypePicker(editor);
            }
            function removeChild(selectedChild, index) {
                // remove from vm
                vm.selectedChildren.splice(index, 1);
                // remove from content type model
                var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
            }
            function sortChildren() {
                // we need to wait until the next digest cycle for vm.selectedChildren to be updated
                $timeout(function () {
                    return $scope.model.allowedContentTypes = vm.selectedChildren.map(function (x) {
                        return x.id;
                    });
                });
            }
            /**
     * Toggle the $scope.model.allowAsRoot value to either true or false
     */
            function toggle() {
                if ($scope.model.allowAsRoot) {
                    $scope.model.allowAsRoot = false;
                    return;
                }
                $scope.model.allowAsRoot = true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.MediaType.PermissionsController', PermissionsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function MemberAppContentController($scope, $filter, contentEditingHelper, contentTypeHelper) {
            var vm = this;
            vm.tabs = [];
            vm.activeTabAlias = null;
            vm.setActiveTab = setActiveTab;
            $scope.$watchCollection('content.tabs', function (newValue) {
                contentTypeHelper.defineParentAliasOnGroups(newValue);
                contentTypeHelper.relocateDisorientedGroups(newValue);
                vm.tabs = $filter('filter')(newValue, function (tab) {
                    return tab.type === 1;
                });
                if (vm.tabs.length > 0) {
                    // if we have tabs and some groups that doesn't belong to a tab we need to render those on an "Other" tab.
                    contentEditingHelper.registerGenericTab(newValue);
                    setActiveTab(vm.tabs[0]);
                }
            });
            function setActiveTab(tab) {
                vm.activeTabAlias = tab.alias;
                vm.tabs.forEach(function (tab) {
                    return tab.active = false;
                });
                tab.active = true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Member.Apps.ContentController', MemberAppContentController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.CreateController
 * @function
 * 
 * @description
 * The controller for the member creation dialog
 */
    function memberCreateController($scope, memberTypeResource, iconHelper, navigationService, $location) {
        memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
            $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
        });
        $scope.close = function () {
            var showMenu = true;
            navigationService.hideDialog(showMenu);
        };
        $scope.createMemberType = function (memberType) {
            $location.path('/member/member/edit/' + $scope.currentNode.id).search('doctype', memberType.alias).search('create', 'true');
            navigationService.hideNavigation();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.CreateController', memberCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
    function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            memberResource.deleteByKey($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                treeService.removeNode($scope.currentNode);
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.key == $scope.currentNode.id) {
                    $location.path('/member/member/list/' + ($routeParams.listName ? $routeParams.listName : 'all-members'));
                }
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.DeleteController', MemberDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.EditController
 * @function
 *
 * @description
 * The controller for the member editor
 */
    function MemberEditController($scope, $routeParams, $location, $http, $q, appState, memberResource, entityResource, navigationService, notificationsService, localizationService, serverValidationManager, contentEditingHelper, fileManager, formHelper, editorState, umbRequestHelper, eventsService) {
        var evts = [];
        var infiniteMode = $scope.model && $scope.model.infiniteMode;
        var id = infiniteMode ? $scope.model.id : $routeParams.id;
        var create = infiniteMode ? $scope.model.create : $routeParams.create;
        var listName = infiniteMode ? $scope.model.listname : $routeParams.listName;
        var docType = infiniteMode ? $scope.model.doctype : $routeParams.doctype;
        $scope.header = {};
        $scope.header.editorfor = 'visuallyHiddenTexts_newMember';
        $scope.header.setPageTitle = true;
        //setup scope vars
        $scope.page = {};
        $scope.page.loading = true;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        //the editors affiliated node
        $scope.page.nameLocked = false;
        $scope.page.saveButtonState = 'init';
        $scope.page.exportButton = 'init';
        //build a path to sync the tree with
        function buildTreePath(data) {
            return listName ? '-1,' + listName : '-1';
        }
        if (create) {
            //if there is no doc type specified then we are going to assume that
            // we are not using the umbraco membership provider
            if (docType) {
                //we are creating so get an empty member item
                memberResource.getScaffold(docType).then(function (data) {
                    $scope.content = data;
                    init();
                    $scope.page.loading = false;
                });
            } else {
                memberResource.getScaffold().then(function (data) {
                    $scope.content = data;
                    init();
                    $scope.page.loading = false;
                });
            }
        } else {
            $scope.page.loading = true;
            loadMember().then(function () {
                $scope.page.loading = false;
            });
        }
        function init() {
            var content = $scope.content;
            // we need to check wether an app is present in the current data, if not we will present the default app.
            var isAppPresent = false;
            // on first init, we dont have any apps. but if we are re-initializing, we do, but ...
            if ($scope.app) {
                // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
                _.forEach(content.apps, function (app) {
                    if (app === $scope.app) {
                        isAppPresent = true;
                    }
                });
                // if we did reload our DocType, but still have the same app we will try to find it by the alias.
                if (isAppPresent === false) {
                    _.forEach(content.apps, function (app) {
                        if (app.alias === $scope.app.alias) {
                            isAppPresent = true;
                            app.active = true;
                            $scope.appChanged(app);
                        }
                    });
                }
            }
            // if we still dont have a app, lets show the first one:
            if (isAppPresent === false) {
                content.apps[0].active = true;
                $scope.appChanged(content.apps[0]);
            }
            if (content.membershipScenario === 0) {
                $scope.page.nameLocked = true;
            }
            editorState.set($scope.content);
            bindEvents();
        }
        function bindEvents() {
            //bindEvents can be called more than once and we don't want to have multiple bound events
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
            evts.push(eventsService.on('editors.memberType.saved', function (name, args) {
                // if this member item uses the updated member type we need to reload the member item
                if (args && args.memberType && args.memberType.key.replace(/-/g, '') === $scope.content.contentType.key) {
                    $scope.page.loading = true;
                    loadMember().then(function () {
                        $scope.page.loading = false;
                    });
                }
            }));
        }
        /** Syncs the content item to it's tree node - this occurs on first load and after saving */
        function syncTreeNode(content, path, initialLoad) {
            if (infiniteMode) {
                return;
            }
            if (!$scope.content.isChildOfListView) {
                navigationService.syncTree({
                    tree: 'member',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    $scope.page.menu.currentNode = syncArgs.node;
                });
            } else if (initialLoad === true) {
                //it's a child item, just sync the ui node to the parent
                navigationService.syncTree({
                    tree: 'member',
                    path: path.substring(0, path.lastIndexOf(',')).split(','),
                    forceReload: initialLoad !== true
                });
                //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
                // from the server so that we can load in the actions menu.
                umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) {
                    $scope.page.menu.currentNode = node;
                });
            }
        }
        /** Just shows a simple notification that there are client side validation issues to be fixed */
        function showValidationNotification() {
            //TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants
            //need to show a notification else it's not clear there was an error.
            localizationService.localizeMany([
                'speechBubbles_validationFailedHeader',
                'speechBubbles_validationFailedMessage'
            ]).then(function (data) {
                notificationsService.error(data[0], data[1]);
            });
        }
        $scope.save = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.page.saveButtonState = 'busy';
                //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
                var passwordProp = _.find($scope.content.membershipProperties, function (e) {
                    return e.alias === '_umb_password';
                });
                if (passwordProp && passwordProp.value && typeof passwordProp.value.reset !== 'undefined' && !passwordProp.value.reset) {
                    //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
                    passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
                }
                memberResource.save($scope.content, create, fileManager.getFiles()).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    // close the editor if it's infinite mode
                    // submit function manages rebinding changes
                    if (infiniteMode && $scope.model.submit) {
                        $scope.model.memberNode = $scope.content;
                        $scope.model.submit($scope.model);
                    } else {
                        // if not infinite mode, rebind changed props etc
                        contentEditingHelper.handleSuccessfulSave({
                            scope: $scope,
                            savedContent: data,
                            //specify a custom id to redirect to since we want to use the GUID
                            redirectId: data.key,
                            rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                        });
                        editorState.set($scope.content);
                        var path = buildTreePath(data);
                        navigationService.syncTree({
                            tree: 'member',
                            path: path.split(',')
                        });
                        //syncTreeNode($scope.content, data.path);
                        $scope.page.saveButtonState = 'success';
                        init();
                    }
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({
                        err: err,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                    });
                    editorState.set($scope.content);
                    $scope.page.saveButtonState = 'error';
                });
            } else {
                showValidationNotification();
            }
        };
        function loadMember() {
            var deferred = $q.defer();
            //so, we usually reference all editors with the Int ID, but with members we have
            //a different pattern, adding a route-redirect here to handle this just in case.
            //(isNumber doesnt work here since its seen as a string)
            //The reason this might be an INT is due to the routing used for the member list view
            //but this is now configured to use the key, so this is just a fail safe
            if (id && id.length < 9) {
                entityResource.getById(id, 'Member').then(function (entity) {
                    $location.path('/member/member/edit/' + entity.key);
                    deferred.resolve($scope.content);
                }, function () {
                    deferred.reject();
                });
            } else {
                //we are editing so get the content item from the server
                memberResource.getByKey(id).then(function (data) {
                    $scope.content = data;
                    if (!infiniteMode) {
                        var path = buildTreePath(data);
                        navigationService.syncTree({
                            tree: 'member',
                            path: path.split(','),
                            forceReload: true
                        });    //syncTreeNode($scope.content, data.path, true);
                    }
                    //it's the initial load of the editor, we need to get the tree node
                    // from the server so that we can load in the actions menu.
                    umbRequestHelper.resourcePromise($http.get(data.treeNodeUrl), 'Failed to retrieve data for child node ' + data.key).then(function (node) {
                        $scope.page.menu.currentNode = node;
                    });
                    //in one particular special case, after we've created a new item we redirect back to the edit
                    // route but there might be server validation errors in the collection which we need to display
                    // after the redirect, so we will bind all subscriptions which will show the server validation errors
                    // if there are any and then clear them so the collection no longer persists them.
                    serverValidationManager.notifyAndClearAllSubscriptions();
                    init();
                    $scope.page.loading = false;
                    deferred.resolve($scope.content);
                }, function () {
                    deferred.reject();
                });
            }
            return deferred.promise;
        }
        $scope.appChanged = function (app) {
            $scope.app = app;
            // setup infinite mode
            if (infiniteMode) {
                $scope.page.submitButtonLabelKey = 'buttons_saveAndClose';
            }
        };
        $scope.showBack = function () {
            return !infiniteMode && !!listName;
        };
        /** Callback for when user clicks the back-icon */
        $scope.onBack = function () {
            $location.path('/member/member/list/' + listName);
            $location.search('listName', null);
            if ($routeParams.page) {
                $location.search('page', $routeParams.page);
            }
        };
        $scope.export = function () {
            var memberKey = $scope.content.key;
            memberResource.exportMemberData(memberKey);
        };
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.EditController', MemberEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.ListController
 * @function
 *
 * @description
 * The controller for the member list view
 */
    function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) {
        //setup scope vars
        $scope.currentSection = appState.getSectionState('currentSection');
        $scope.currentNode = null;
        //the editors affiliated node
        $scope.page = {};
        $scope.page.lockedName = true;
        $scope.page.loading = true;
        //we are editing so get the content item from the server
        memberResource.getListNode($routeParams.id).then(function (data) {
            $scope.content = data;
            //translate "All Members"
            if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(' ', '').toLowerCase() == 'allmembers') {
                localizationService.localize('member_allMembers').then(function (value) {
                    $scope.content.name = value;
                });
            }
            editorState.set($scope.content);
            navigationService.syncTree({
                tree: 'member',
                path: data.path.split(',')
            }).then(function (syncArgs) {
                $scope.currentNode = syncArgs.node;
            });
            //in one particular special case, after we've created a new item we redirect back to the edit
            // route but there might be server validation errors in the collection which we need to display
            // after the redirect, so we will bind all subscriptions which will show the server validation errors
            // if there are any and then clear them so the collection no longer persists them.
            serverValidationManager.notifyAndClearAllSubscriptions();
            $scope.page.loading = false;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.ListController', MemberListController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberGroups.DeleteController
 * @function
 *
 * @description
 * The controller for deleting member groups
 */
    function MemberGroupsDeleteController($scope, memberGroupResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            memberGroupResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberGroups.DeleteController', MemberGroupsDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberGroups.EditController
 * @function
 *
 * @description
 * The controller for the member group editor
 */
    function MemberGroupsEditController($scope, $routeParams, appState, navigationService, memberGroupResource, contentEditingHelper, formHelper, editorState, eventsService) {
        //setup scope vars
        $scope.page = {};
        $scope.page.loading = false;
        $scope.header = {};
        $scope.header.editorfor = 'content_membergroup';
        $scope.header.setPageTitle = true;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        var evts = [];
        if ($routeParams.create) {
            $scope.page.loading = true;
            //we are creating so get an empty member group item
            memberGroupResource.getScaffold().then(function (data) {
                $scope.content = data;
                //set a shared state
                editorState.set($scope.content);
                $scope.page.loading = false;
            });
        } else {
            loadMemberGroup();
        }
        function loadMemberGroup() {
            $scope.page.loading = true;
            //we are editing so get the content item from the server
            memberGroupResource.getById($routeParams.id).then(function (data) {
                $scope.content = data;
                //share state
                editorState.set($scope.content);
                navigationService.syncTree({
                    tree: 'memberGroups',
                    path: data.path
                }).then(function (syncArgs) {
                    $scope.page.menu.currentNode = syncArgs.node;
                });
                $scope.page.loading = false;
            });
        }
        $scope.save = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.page.saveButtonState = 'busy';
                memberGroupResource.save($scope.content, $scope.preValues, $routeParams.create).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data
                    });
                    //share state
                    editorState.set($scope.content);
                    navigationService.syncTree({
                        tree: 'memberGroups',
                        path: data.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        $scope.page.menu.currentNode = syncArgs.node;
                    });
                    $scope.page.saveButtonState = 'success';
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({ err: err });
                    $scope.page.saveButtonState = 'error';
                    //share state
                    editorState.set($scope.content);
                });
            }
        };
        evts.push(eventsService.on('app.refreshEditor', function (name, error) {
            loadMemberGroup();
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberGroups.EditController', MemberGroupsEditController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.CopyController', function ($scope, memberTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        $scope.dialogTreeApi = {};
        $scope.source = _.clone($scope.currentNode);
        function nodeSelectHandler(args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            memberTypeResource.copy({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'memberTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'memberTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
            });
        };
        $scope.onTreeInit = function () {
            $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
        };
        $scope.close = function () {
            navigationService.hideDialog();
        };
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberTypes.CreateController
 * @function
 *
 * @description
 * The controller for the member type creation dialog
 */
    function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.currentNode;
        var section = appState.getSectionState('currentSection');
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createFolderForm
                })) {
                memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'membertypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: this.createFolderForm
                    });
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: this.createFolderForm,
                        hasErrors: true
                    });    // TODO: Handle errors
                });
            }
            ;
        };
        $scope.createMemberType = function () {
            $location.search('create', null);
            $location.path('/' + section + '/membertypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.CreateController', MemberTypesCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberTypes.DeleteController
 * @function
 *
 * @description
 * The controller for deleting member types
 */
    function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService, localizationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            memberTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
        $scope.labels = {};
        localizationService.format([
            'contentTypeEditor_yesDelete',
            'contentTypeEditor_andAllMembers'
        ], '%0% ' + $scope.currentNode.name + ' %1%').then(function (data) {
            $scope.labels.deleteConfirm = data;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.DeleteController', MemberTypesDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberTypes.EditController
 * @function
 *
 * @description
 * The controller for the member type editor
 */
    (function () {
        'use strict';
        function MemberTypesEditController($scope, $routeParams, $q, memberTypeResource, editorState, iconHelper, navigationService, contentEditingHelper, notificationsService, localizationService, overlayHelper, contentTypeHelper, angularHelper, eventsService) {
            var evts = [];
            var vm = this;
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var memberTypeId = $routeParams.id;
            var create = $routeParams.create;
            var memberTypeIcon = '';
            vm.save = save;
            vm.close = close;
            vm.editorfor = 'visuallyHiddenTexts_newMember';
            vm.header = {};
            vm.header.editorfor = 'content_membergroup';
            vm.header.setPageTitle = true;
            vm.currentNode = null;
            vm.contentType = {};
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.labels = {};
            vm.saveButtonKey = 'buttons_save';
            vm.generateModelsKey = 'buttons_saveAndGenerateModels';
            onInit();
            function onInit() {
                // get init values from model when in infinite mode
                if (infiniteMode) {
                    memberTypeId = $scope.model.id;
                    create = $scope.model.create;
                    vm.saveButtonKey = 'buttons_saveAndClose';
                    vm.generateModelsKey = 'buttons_generateModelsAndClose';
                }
            }
            var labelKeys = [
                'general_design',
                'shortcuts_shortcut',
                'shortcuts_addGroup',
                'shortcuts_addProperty',
                'shortcuts_addEditor',
                'shortcuts_editDataType'
            ];
            localizationService.localizeMany(labelKeys).then(function (values) {
                vm.labels.design = values[0];
                vm.labels.shortcut = values[1];
                vm.labels.addGroup = values[2];
                vm.labels.addProperty = values[3];
                vm.labels.addEditor = values[4];
                vm.labels.editDataType = values[5];
                vm.page.navigation = [{
                        'name': vm.labels.design,
                        'icon': 'icon-document-dashed-line',
                        'view': 'views/membertypes/views/design/design.html',
                        'active': true
                    }];
                vm.page.keyboardShortcutsOverview = [{
                        'name': vm.labels.shortcut,
                        'shortcuts': [
                            {
                                'description': vm.labels.addGroup,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'g' }
                                ]
                            },
                            {
                                'description': vm.labels.addProperty,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'p' }
                                ]
                            },
                            {
                                'description': vm.labels.addEditor,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'e' }
                                ]
                            },
                            {
                                'description': vm.labels.editDataType,
                                'keys': [
                                    { 'key': 'alt' },
                                    { 'key': 'shift' },
                                    { 'key': 'd' }
                                ]
                            }
                        ]
                    }];
            });
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: vm.saveButtonKey,
                        letter: 'S',
                        handler: function handler() {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: infiniteMode ? 'buttons_generateModelsAndClose' : 'buttons_saveAndGenerateModels',
                            letter: 'G',
                            handler: function handler() {
                                vm.page.saveButtonState = 'busy';
                                vm.save().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if (create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                memberTypeResource.getScaffold(memberTypeId).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                loadMemberType();
            }
            function loadMemberType() {
                vm.page.loading = true;
                memberTypeResource.getById(memberTypeId).then(function (dt) {
                    init(dt);
                    if (!infiniteMode) {
                        syncTreeNode(vm.contentType, dt.path, true);
                    }
                    vm.page.loading = false;
                });
            }
            /* ---------- SAVE ---------- */
            function save() {
                // only save if there is no overlays open
                if (overlayHelper.getNumberOfOverlays() === 0) {
                    var deferred = $q.defer();
                    vm.page.saveButtonState = 'busy';
                    contentEditingHelper.contentEditorPerformSave({
                        saveMethod: memberTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        rebindCallback: function rebindCallback(_, savedContentType) {
                            // we need to rebind... the IDs that have been created!
                            contentTypeHelper.rebindSavedContentType(vm.contentType, savedContentType);
                        }
                    }).then(function (data) {
                        //success
                        if (!infiniteMode) {
                            syncTreeNode(vm.contentType, data.path);
                        }
                        // emit event
                        var args = { memberType: vm.contentType };
                        eventsService.emit('editors.memberType.saved', args);
                        if (memberTypeIcon !== vm.contentType.icon) {
                            eventsService.emit('editors.tree.icon.changed', args);
                        }
                        vm.page.saveButtonState = 'success';
                        if (infiniteMode && $scope.model.submit) {
                            $scope.model.submit();
                        }
                        deferred.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        deferred.reject(err);
                    });
                    return deferred.promise;
                }
            }
            function init(contentType) {
                // convert legacy icons
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
                memberTypeIcon = contentType.icon;
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                navigationService.syncTree({
                    tree: 'membertypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
            function close() {
                if (infiniteMode && $scope.model.close) {
                    $scope.model.close();
                }
            }
            evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                loadMemberType();
            }));
            evts.push(eventsService.on('editors.groupsBuilder.changed', function (name, args) {
                angularHelper.getCurrentForm($scope).$setDirty();
            }));
            //ensure to unregister from all events!
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.EditController', MemberTypesEditController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.MoveController', function ($scope) {
    });
    'use strict';
    (function () {
        'use strict';
        function EditController($scope, $location, $routeParams, umbRequestHelper, entityResource, packageResource, editorService, formHelper, localizationService) {
            var vm = this;
            var packageId = $routeParams.id;
            var create = $routeParams.create;
            vm.showBackButton = true;
            // open all expansion panels
            vm.propertiesOpen = true;
            vm.contentOpen = true;
            vm.filesOpen = true;
            vm.actionsOpen = true;
            vm.loading = true;
            vm.back = back;
            vm.createOrUpdatePackage = createOrUpdatePackage;
            vm.removeContentItem = removeContentItem;
            vm.openContentPicker = openContentPicker;
            vm.openFilePicker = openFilePicker;
            vm.removeFile = removeFile;
            vm.openViewPicker = openViewPicker;
            vm.removePackageView = removePackageView;
            vm.downloadFile = downloadFile;
            vm.contributorsEditor = null;
            vm.selectDocumentType = selectDocumentType;
            vm.selectTemplate = selectTemplate;
            vm.selectStyleSheet = selectStyleSheet;
            vm.selectMacro = selectMacro;
            vm.selectLanguage = selectLanguage;
            vm.selectDictionaryItem = selectDictionaryItem;
            vm.selectDataType = selectDataType;
            vm.labels = {};
            vm.versionRegex = /^(\d+\.)(\d+\.)(\*|\d+)$/;
            vm.aceOption = {
                mode: 'xml',
                theme: 'chrome',
                showPrintMargin: false,
                advanced: {
                    fontSize: '14px',
                    enableSnippets: true,
                    enableBasicAutocompletion: true,
                    enableLiveAutocompletion: false
                },
                onLoad: function onLoad(_editor) {
                    vm.editor = _editor;
                    vm.editor.setValue(vm.package.actions);
                }
            };
            function onInit() {
                if (create) {
                    // Pre populate package with some values
                    packageResource.getEmpty().then(function (scaffold) {
                        vm.package = scaffold;
                        loadResources();
                        buildContributorsEditor(vm.package);
                        vm.loading = false;
                    });
                    localizationService.localizeMany([
                        'general_create',
                        'packager_includeAllChildNodes'
                    ]).then(function (values) {
                        vm.labels.button = values[0];
                        vm.labels.includeAllChildNodes = values[1];
                    });
                } else {
                    // Load package
                    packageResource.getCreatedById(packageId).then(function (createdPackage) {
                        vm.package = createdPackage;
                        loadResources();
                        buildContributorsEditor(vm.package);
                        vm.loading = false;
                        // Get render model for content node
                        if (vm.package.contentNodeId) {
                            entityResource.getById(vm.package.contentNodeId, 'Document').then(function (entity) {
                                vm.contentNodeDisplayModel = entity;
                            });
                        }
                    });
                    localizationService.localizeMany([
                        'buttons_save',
                        'packager_includeAllChildNodes'
                    ]).then(function (values) {
                        vm.labels.button = values[0];
                        vm.labels.includeAllChildNodes = values[1];
                    });
                }
            }
            function loadResources() {
                // Get all document types
                entityResource.getAll('DocumentType').then(function (documentTypes) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    documentTypes.forEach(function (documentType) {
                        documentType.id = documentType.id.toString();
                        documentType.selected = vm.package.documentTypes.indexOf(documentType.id) !== -1;
                    });
                    vm.documentTypes = documentTypes;
                });
                // Get all templates
                entityResource.getAll('Template').then(function (templates) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    templates.forEach(function (template) {
                        template.id = template.id.toString();
                        template.selected = vm.package.templates.indexOf(template.id) >= 0;
                    });
                    vm.templates = templates;
                });
                // Get all stylesheets
                entityResource.getAll('Stylesheet').then(function (stylesheets) {
                    stylesheets.forEach(function (stylesheet) {
                        stylesheet.selected = vm.package.stylesheets.indexOf(stylesheet.name) >= 0;
                    });
                    vm.stylesheets = stylesheets;
                });
                // Get all macros
                entityResource.getAll('Macro').then(function (macros) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    macros.forEach(function (macro) {
                        macro.id = macro.id.toString();
                        macro.selected = vm.package.macros.indexOf(macro.id) !== -1;
                    });
                    vm.macros = macros;
                });
                // Get all languages
                entityResource.getAll('Language').then(function (languages) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    languages.forEach(function (language) {
                        language.id = language.id.toString();
                        language.selected = vm.package.languages.indexOf(language.id) !== -1;
                    });
                    vm.languages = languages;
                });
                // Get all dictionary items
                entityResource.getAll('DictionaryItem').then(function (dictionaryItems) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    dictionaryItems.forEach(function (dictionaryItem) {
                        dictionaryItem.id = dictionaryItem.id.toString();
                        dictionaryItem.selected = vm.package.dictionaryItems.indexOf(dictionaryItem.id) !== -1;
                    });
                    vm.dictionaryItems = dictionaryItems;
                });
                // Get all data types
                entityResource.getAll('DataType').then(function (dataTypes) {
                    // a package stores the id as a string so we 
                    // need to convert all ids to string for comparison
                    dataTypes.forEach(function (dataType) {
                        dataType.id = dataType.id.toString();
                        dataType.selected = vm.package.dataTypes.indexOf(dataType.id) !== -1;
                    });
                    vm.dataTypes = dataTypes;
                });
            }
            function downloadFile(id) {
                var url = umbRequestHelper.getApiUrl('packageApiBaseUrl', 'DownloadCreatedPackage', { id: id });
                umbRequestHelper.downloadFile(url).then(function () {
                });
            }
            function back() {
                $location.path('packages/packages/created').search('create', null).search('packageId', null);
            }
            function createOrUpdatePackage(editPackageForm) {
                var contributors = vm.contributorsEditor.value.map(function (o) {
                    return o.value;
                });
                vm.package.contributors = contributors;
                if (formHelper.submitForm({
                        formCtrl: editPackageForm,
                        scope: $scope
                    })) {
                    vm.buttonState = 'busy';
                    packageResource.savePackage(vm.package).then(function (updatedPackage) {
                        vm.package = updatedPackage;
                        vm.buttonState = 'success';
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: editPackageForm
                        });
                        if (create) {
                            //if we are creating, then redirect to the correct url and reload
                            $location.path('packages/packages/edit/' + vm.package.id).search('create', null);
                            //don't add a browser history for this
                            $location.replace();
                        }
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: editPackageForm,
                            hasErrors: true
                        });
                        formHelper.handleError(err);
                        vm.buttonState = 'error';
                    });
                }
            }
            function removeContentItem() {
                vm.package.contentNodeId = null;
            }
            function openContentPicker() {
                var contentPicker = {
                    submit: function submit(model) {
                        if (model.selection && model.selection.length > 0) {
                            vm.package.contentNodeId = model.selection[0].id.toString();
                            vm.contentNodeDisplayModel = model.selection[0];
                        }
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.contentPicker(contentPicker);
            }
            function openFilePicker() {
                var selection = Utilities.copy(vm.package.files);
                var filePicker = {
                    title: 'Select files',
                    section: 'settings',
                    treeAlias: 'files',
                    entityType: 'file',
                    multiPicker: true,
                    isDialog: true,
                    select: function select(node) {
                        node.selected = !node.selected;
                        var id = decodeURIComponent(node.id.replace(/\+/g, ' '));
                        var index = selection.indexOf(id);
                        if (node.selected) {
                            if (index === -1) {
                                selection.push(id);
                            }
                        } else {
                            selection.splice(index, 1);
                        }
                    },
                    submit: function submit() {
                        vm.package.files = selection;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(filePicker);
            }
            function removeFile(index) {
                vm.package.files.splice(index, 1);
            }
            function openViewPicker() {
                var controlPicker = {
                    title: 'Select view',
                    section: 'settings',
                    treeAlias: 'files',
                    entityType: 'file',
                    onlyInitialized: false,
                    filter: function filter(i) {
                        if (i.name.indexOf('.html') === -1 && i.name.indexOf('.htm') === -1) {
                            return true;
                        }
                    },
                    filterCssClass: 'not-allowed',
                    select: function select(node) {
                        var id = decodeURIComponent(node.id.replace(/\+/g, ' '));
                        vm.package.packageView = id;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(controlPicker);
            }
            function removePackageView() {
                vm.package.packageView = null;
            }
            function selectDocumentType(doctype) {
                // Check if the document type is already selected.
                var index = vm.package.documentTypes.indexOf(doctype.id);
                if (index === -1) {
                    vm.package.documentTypes.push(doctype.id);
                } else {
                    vm.package.documentTypes.splice(index, 1);
                }
            }
            function selectTemplate(template) {
                // Check if the template is already selected.
                var index = vm.package.templates.indexOf(template.id);
                if (index === -1) {
                    vm.package.templates.push(template.id);
                } else {
                    vm.package.templates.splice(index, 1);
                }
            }
            function selectStyleSheet(stylesheet) {
                // Check if the style sheet is already selected.
                var index = vm.package.stylesheets.indexOf(stylesheet.name);
                if (index === -1) {
                    vm.package.stylesheets.push(stylesheet.name);
                } else {
                    vm.package.stylesheets.splice(index, 1);
                }
            }
            function selectMacro(macro) {
                // Check if the macro is already selected.
                var index = vm.package.macros.indexOf(macro.id);
                if (index === -1) {
                    vm.package.macros.push(macro.id);
                } else {
                    vm.package.macros.splice(index, 1);
                }
            }
            function selectLanguage(language) {
                // Check if the language is already selected.
                var index = vm.package.languages.indexOf(language.id);
                if (index === -1) {
                    vm.package.languages.push(language.id);
                } else {
                    vm.package.languages.splice(index, 1);
                }
            }
            function selectDictionaryItem(dictionaryItem) {
                // Check if the dictionary item is already selected.
                var index = vm.package.dictionaryItems.indexOf(dictionaryItem.id);
                if (index === -1) {
                    vm.package.dictionaryItems.push(dictionaryItem.id);
                } else {
                    vm.package.dictionaryItems.splice(index, 1);
                }
            }
            function selectDataType(dataType) {
                // Check if the dictionary item is already selected.
                var index = vm.package.dataTypes.indexOf(dataType.id);
                if (index === -1) {
                    vm.package.dataTypes.push(dataType.id);
                } else {
                    vm.package.dataTypes.splice(index, 1);
                }
            }
            function buildContributorsEditor(pkg) {
                vm.contributorsEditor = {
                    alias: 'contributors',
                    editor: 'Umbraco.MultipleTextstring',
                    label: 'Contributors',
                    description: '',
                    hideLabel: true,
                    view: 'views/propertyeditors/multipletextbox/multipletextbox.html',
                    value: getVals(pkg.contributors),
                    validation: {
                        mandatory: false,
                        pattern: null
                    },
                    config: {
                        min: 0,
                        max: 0
                    }
                };
            }
            function getVals(array) {
                var vals = [];
                for (var i = 0; i < array.length; i++) {
                    vals.push({ value: array[i] });
                }
                return vals;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.EditController', EditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function OptionsController($scope, $location, $routeParams, packageResource, umbRequestHelper) {
            var vm = this;
            vm.showBackButton = true;
            vm.loading = true;
            vm.back = back;
            var packageId = $routeParams.id;
            function onInit() {
                packageResource.getInstalledById(packageId).then(function (pck) {
                    vm.package = pck;
                    //set the $scope too, packages can then access this if they wanted from their own scope or parent scope
                    $scope.package = pck;
                    vm.loading = false;
                    //make sure the packageView is formatted as a virtual path
                    pck.packageView = pck.packageView.startsWith('~/') ? pck.packageView : pck.packageView.startsWith('/') ? '~' + pck.packageView : '~/' + pck.packageView;
                    pck.packageView = umbRequestHelper.convertVirtualToAbsolutePath(pck.packageView);
                });
            }
            function back() {
                $location.path('packages/packages/installed').search('packageId', null);
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.OptionsController', OptionsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PackagesOverviewController($scope, $location, $routeParams, localizationService, localStorageService) {
            //Hack!
            // if there is a local storage value for packageInstallData then we need to redirect there,
            // the issue is that we still have webforms and we cannot go to a hash location and then window.reload
            // because it will double load it.
            // we will refresh and then navigate there.
            var packageInstallData = localStorageService.get('packageInstallData');
            var packageUri = $routeParams.method;
            if (packageInstallData) {
                localStorageService.remove('packageInstallData');
                if (packageInstallData.postInstallationPath) {
                    //navigate to the custom installer screen if set
                    $location.path(packageInstallData.postInstallationPath).search('packageId', packageInstallData.id);
                    return;
                }
                //if it is "installed" then set the uri/path to that
                if (packageInstallData === 'installed') {
                    packageUri = 'installed';
                }
            }
            var vm = this;
            vm.page = {};
            vm.page.labels = {};
            vm.page.name = '';
            vm.page.navigation = [];
            onInit();
            function onInit() {
                loadNavigation();
                setPageName();
            }
            function loadNavigation() {
                var labels = [
                    'sections_packages',
                    'packager_installed',
                    'packager_installLocal',
                    'packager_created'
                ];
                localizationService.localizeMany(labels).then(function (data) {
                    vm.page.labels.packages = data[0];
                    vm.page.labels.installed = data[1];
                    vm.page.labels.install = data[2];
                    vm.page.labels.created = data[3];
                    vm.page.navigation = [
                        {
                            'name': vm.page.labels.packages,
                            'icon': 'icon-cloud',
                            'view': 'views/packages/views/repo.html',
                            'active': !packageUri || packageUri === 'repo',
                            'alias': 'umbPackages',
                            'action': function action() {
                                $location.path('/packages/packages/repo');
                            }
                        },
                        {
                            'name': vm.page.labels.installed,
                            'icon': 'icon-box',
                            'view': 'views/packages/views/installed.html',
                            'active': packageUri === 'installed',
                            'alias': 'umbInstalled',
                            'action': function action() {
                                $location.path('/packages/packages/installed');
                            }
                        },
                        {
                            'name': vm.page.labels.install,
                            'icon': 'icon-cloud-upload',
                            'view': 'views/packages/views/install-local.html',
                            'active': packageUri === 'local',
                            'alias': 'umbInstallLocal',
                            'action': function action() {
                                $location.path('/packages/packages/local');
                            }
                        },
                        {
                            'name': vm.page.labels.created,
                            'icon': 'icon-files',
                            'view': 'views/packages/views/created.html',
                            'active': packageUri === 'created',
                            'alias': 'umbCreatedPackages',
                            'action': function action() {
                                $location.path('/packages/packages/created');
                            }
                        }
                    ];
                });
            }
            function setPageName() {
                localizationService.localize('sections_packages').then(function (data) {
                    vm.page.name = data;
                });
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.OverviewController', PackagesOverviewController);
    }());
    'use strict';
    (function () {
        'use strict';
        function CreatedController($timeout, $location, packageResource, localizationService, overlayService) {
            var vm = this;
            vm.deleteCreatedPackage = deleteCreatedPackage;
            vm.goToPackage = goToPackage;
            vm.createPackage = createPackage;
            function onInit() {
                vm.createdPackages = [];
                packageResource.getAllCreated().then(function (createdPackages) {
                    vm.createdPackages = createdPackages;
                }, Utilities.noop);
            }
            function deleteCreatedPackage(event, index, createdPackage) {
                event.stopPropagation();
                event.preventDefault();
                var dialog = {
                    view: 'views/packages/overlays/delete.html',
                    package: createdPackage,
                    submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        performDelete(index, createdPackage);
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                var keys = [
                    'general_delete',
                    'defaultdialogs_confirmdelete'
                ];
                localizationService.localizeMany(keys).then(function (values) {
                    dialog.title = values[0];
                    dialog.content = values[1];
                    overlayService.open(dialog);
                });
            }
            function performDelete(index, createdPackage) {
                createdPackage.deleteButtonState = 'busy';
                packageResource.deleteCreatedPackage(createdPackage.id).then(function () {
                    vm.createdPackages.splice(index, 1);
                }, function (err) {
                    createdPackage.deleteButtonState = 'error';
                });
            }
            function goToPackage(createdPackage) {
                $location.path('packages/packages/edit/' + createdPackage.id);
            }
            function createPackage() {
                $location.search('create', null);
                $location.path('packages/packages/edit/-1').search('create', 'true');
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.CreatedController', CreatedController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PackagesInstallLocalController($scope, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService, $q) {
            var vm = this;
            vm.state = 'upload';
            vm.localPackage = {};
            vm.installPackage = installPackage;
            vm.installState = {
                status: '',
                progress: 0
            };
            vm.installCompleted = false;
            vm.zipFile = {
                uploadStatus: 'idle',
                uploadProgress: 0,
                serverErrorMessage: null
            };
            $scope.handleFiles = function (files, event, invalidFiles) {
                if (files) {
                    for (var i = 0; i < files.length; i++) {
                        upload(files[i]);
                    }
                }
            };
            var labels = {};
            var labelKeys = [
                'packager_installStateImporting',
                'packager_installStateInstalling',
                'packager_installStateRestarting',
                'packager_installStateComplete',
                'packager_installStateCompleted'
            ];
            localizationService.localizeMany(labelKeys).then(function (values) {
                labels.installStateImporting = values[0];
                labels.installStateInstalling = values[1];
                labels.installStateRestarting = values[2];
                labels.installStateComplete = values[3];
                labels.installStateCompleted = values[4];
            });
            function upload(file) {
                Upload.upload({
                    url: umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'UploadLocalPackage'),
                    fields: {},
                    file: file
                }).progress(function (evt) {
                    // hack: in some browsers the progress event is called after success
                    // this prevents the UI from going back to a uploading state
                    if (vm.zipFile.uploadStatus !== 'done' && vm.zipFile.uploadStatus !== 'error') {
                        // set view state to uploading
                        vm.state = 'uploading';
                        // calculate progress in percentage
                        var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                        // set percentage property on file
                        vm.zipFile.uploadProgress = progressPercentage;
                        // set uploading status on file
                        vm.zipFile.uploadStatus = 'uploading';
                    }
                }).success(function (data, status, headers, config) {
                    if (data.notifications && data.notifications.length > 0) {
                        // set error status on file
                        vm.zipFile.uploadStatus = 'error';
                        // Throw message back to user with the cause of the error
                        vm.zipFile.serverErrorMessage = data.notifications[0].message;
                    } else {
                        // set done status on file
                        vm.zipFile.uploadStatus = 'done';
                        loadPackage();
                        vm.zipFile.uploadProgress = 100;
                        vm.localPackage = data;
                    }
                }).error(function (evt, status, headers, config) {
                    // set status done
                    vm.zipFile.uploadStatus = 'error';
                    // If file not found, server will return a 404 and display this message
                    if (status === 404) {
                        vm.zipFile.serverErrorMessage = 'File not found';
                    } else if (status == 400) {
                        //it's a validation error
                        vm.zipFile.serverErrorMessage = evt.message;
                    } else {
                        //it's an unhandled error
                        //if the service returns a detailed error
                        if (evt.InnerException) {
                            vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                            //Check if its the common "too large file" exception
                            if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                vm.zipFile.serverErrorMessage = 'File too large to upload';
                            }
                        } else if (evt.Message) {
                            vm.zipFile.serverErrorMessage = evt.Message;
                        }
                    }
                });
            }
            function loadPackage() {
                if (vm.zipFile.uploadStatus === 'done') {
                    vm.state = 'packageDetails';
                }
            }
            function installPackage() {
                vm.installState.status = labels.installStateImporting;
                vm.installState.progress = '0';
                packageResource.import(vm.localPackage).then(function (pack) {
                    vm.installState.progress = '25';
                    vm.installState.status = labels.installStateInstalling;
                    return packageResource.installFiles(pack);
                }, installError).then(function (pack) {
                    vm.installState.status = labels.installStateRestarting;
                    vm.installState.progress = '50';
                    var deferred = $q.defer();
                    //check if the app domain is restarted ever 2 seconds
                    var count = 0;
                    function checkRestart() {
                        $timeout(function () {
                            packageResource.checkRestart(pack).then(function (d) {
                                count++;
                                //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                if (d.isRestarting && count < 10) {
                                    checkRestart();
                                } else {
                                    //it's restarted!
                                    deferred.resolve(d);
                                }
                            }, installError);
                        }, 2000);
                    }
                    checkRestart();
                    return deferred.promise;
                }, installError).then(function (pack) {
                    vm.installState.status = labels.installStateInstalling;
                    vm.installState.progress = '75';
                    return packageResource.installData(pack);
                }, installError).then(function (pack) {
                    vm.installState.status = labels.installStateComplete;
                    vm.installState.progress = '100';
                    return packageResource.cleanUp(pack);
                }, installError).then(function (result) {
                    //Put the package data in local storage so we can use after reloading
                    localStorageService.set('packageInstallData', result);
                    vm.installState.status = labels.installStateCompleted;
                    vm.installCompleted = true;
                }, installError);
            }
            function installError() {
                //This will return a rejection meaning that the promise change above will stop
                return $q.reject();
            }
            vm.reloadPage = function () {
                //reload on next digest (after cookie)
                $timeout(function () {
                    $window.location.reload(true);
                });
            };
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.InstallLocalController', PackagesInstallLocalController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) {
            var vm = this;
            vm.confirmUninstall = confirmUninstall;
            vm.uninstallPackage = uninstallPackage;
            vm.packageOptions = packageOptions;
            vm.state = 'list';
            vm.installState = { status: '' };
            vm.package = {};
            var labels = {};
            function init() {
                packageResource.getInstalled().then(function (packs) {
                    vm.installedPackages = packs;
                });
                vm.installState.status = '';
                vm.state = 'list';
                var labelKeys = [
                    'packager_installStateUninstalling',
                    'packager_installStateComplete'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    labels.installStateUninstalling = values[0];
                    labels.installStateComplete = values[1];
                });
            }
            function packageOptions(pck) {
                $location.path('packages/packages/options/' + pck.id).search('packageId', null);    //ensure the installId flag is gone, it's only available on first install
            }
            function confirmUninstall(pck) {
                vm.state = 'packageDetails';
                vm.package = pck;
            }
            function uninstallPackage(installedPackage) {
                vm.installState.status = labels.installStateUninstalling;
                vm.installState.progress = '0';
                packageResource.uninstall(installedPackage.id).then(function () {
                    if (installedPackage.files.length > 0) {
                        vm.installState.status = labels.installStateComplete;
                        vm.installState.progress = '100';
                        //set this flag so that on refresh it shows the installed packages list
                        localStorageService.set('packageInstallData', 'installed');
                        //reload on next digest (after cookie)
                        $timeout(function () {
                            $window.location.reload(true);
                        });
                    } else {
                        init();
                    }
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.InstalledController', PackagesInstalledController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PackagesRepoController($scope, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) {
            var vm = this;
            vm.packageViewState = 'packageList';
            vm.categories = [];
            vm.loading = true;
            vm.pagination = {
                pageNumber: 1,
                totalPages: 10,
                pageSize: 24
            };
            vm.searchQuery = '';
            vm.installState = {
                status: '',
                progress: 0,
                type: 'ok'
            };
            vm.selectCategory = selectCategory;
            vm.showPackageDetails = showPackageDetails;
            vm.setPackageViewState = setPackageViewState;
            vm.nextPage = nextPage;
            vm.prevPage = prevPage;
            vm.goToPage = goToPage;
            vm.installPackage = installPackage;
            vm.downloadPackage = downloadPackage;
            vm.openLightbox = openLightbox;
            vm.closeLightbox = closeLightbox;
            vm.search = search;
            vm.installCompleted = false;
            var labels = {};
            var defaultSort = 'Latest';
            var currSort = defaultSort;
            //used to cancel any request in progress if another one needs to take it's place
            var canceler = null;
            function getActiveCategory() {
                if (vm.searchQuery !== '') {
                    return '';
                }
                for (var i = 0; i < vm.categories.length; i++) {
                    if (vm.categories[i].active === true) {
                        return vm.categories[i].name;
                    }
                }
                return '';
            }
            function init() {
                vm.loading = true;
                var labelKeys = [
                    'packager_installStateImporting',
                    'packager_installStateInstalling',
                    'packager_installStateRestarting',
                    'packager_installStateComplete',
                    'packager_installStateCompleted'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    labels.installStateImporting = values[0];
                    labels.installStateInstalling = values[1];
                    labels.installStateRestarting = values[2];
                    labels.installStateComplete = values[3];
                    labels.installStateCompleted = values[4];
                });
                $q.all([
                    ourPackageRepositoryResource.getCategories().then(function (cats) {
                        vm.categories = cats.filter(function (cat) {
                            return cat.name !== 'Umbraco Pro';
                        });
                    }),
                    ourPackageRepositoryResource.getPopular(8).then(function (pack) {
                        vm.popular = pack.packages;
                    }),
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                    })
                ]).then(function () {
                    vm.loading = false;
                });
            }
            function selectCategory(selectedCategory, categories) {
                for (var i = 0; i < categories.length; i++) {
                    var category = categories[i];
                    if (category.name === selectedCategory.name) {
                        //it's already selected, let's unselect to show all again
                        if (category.active === true) {
                            category.active = false;
                        } else {
                            category.active = true;
                        }
                    } else {
                        category.active = false;
                    }
                }
                vm.loading = true;
                vm.searchQuery = '';
                var reset = selectedCategory.active === false;
                var searchCategory = reset ? '' : selectedCategory.name;
                currSort = defaultSort;
                $q.all([
                    ourPackageRepositoryResource.getPopular(8, searchCategory).then(function (pack) {
                        vm.popular = pack.packages;
                    }),
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                        vm.pagination.pageNumber = 1;
                    })
                ]).then(function () {
                    vm.loading = false;
                });
            }
            function showPackageDetails(selectedPackage) {
                ourPackageRepositoryResource.getDetails(selectedPackage.id).then(function (pack) {
                    packageResource.validateInstalled(pack.name, pack.latestVersion).then(function () {
                        //ok, can install
                        vm.package = pack;
                        vm.package.isValid = true;
                        vm.packageViewState = 'packageDetails';
                    }, function () {
                        //nope, cannot install
                        vm.package = pack;
                        vm.package.isValid = false;
                        vm.packageViewState = 'packageDetails';
                    });
                });
            }
            function setPackageViewState(state) {
                if (state) {
                    vm.packageViewState = state;
                }
            }
            function nextPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function prevPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function goToPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function downloadPackage(selectedPackage) {
                vm.loading = true;
                packageResource.fetch(selectedPackage.id).then(function (pack) {
                    vm.packageViewState = 'packageInstall';
                    vm.loading = false;
                    vm.localPackage = pack;
                    vm.localPackage.allowed = true;
                }, function (evt, status, headers, config) {
                    if (status == 400) {
                        //it's a validation error
                        vm.installState.type = 'error';
                        vm.zipFile.serverErrorMessage = evt.message;
                    }
                });
            }
            function error(e, args) {
                //This will return a rejection meaning that the promise change above will stop
                return $q.reject();
            }
            function installPackage(selectedPackage) {
                vm.installState.status = labels.installStateImporting;
                vm.installState.progress = '0';
                packageResource.import(selectedPackage).then(function (pack) {
                    vm.installState.status = labels.installStateInstalling;
                    vm.installState.progress = '25';
                    return packageResource.installFiles(pack);
                }, error).then(function (pack) {
                    vm.installState.status = labels.installStateRestarting;
                    vm.installState.progress = '50';
                    var deferred = $q.defer();
                    //check if the app domain is restarted ever 2 seconds
                    var count = 0;
                    function checkRestart() {
                        $timeout(function () {
                            packageResource.checkRestart(pack).then(function (d) {
                                count++;
                                //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                if (d.isRestarting && count < 10) {
                                    checkRestart();
                                } else {
                                    //it's restarted!
                                    deferred.resolve(d);
                                }
                            }, error);
                        }, 2000);
                    }
                    checkRestart();
                    return deferred.promise;
                }, error).then(function (pack) {
                    vm.installState.status = labels.installStateInstalling;
                    vm.installState.progress = '75';
                    return packageResource.installData(pack);
                }, error).then(function (pack) {
                    vm.installState.status = labels.installStateComplete;
                    vm.installState.progress = '100';
                    return packageResource.cleanUp(pack);
                }, error).then(function (result) {
                    //Put the package data in local storage so we can use after reloading
                    localStorageService.set('packageInstallData', result);
                    vm.installState.status = labels.installStateCompleted;
                    vm.installCompleted = true;
                }, error);
            }
            function openLightbox(itemIndex, items) {
                vm.lightbox = {
                    show: true,
                    items: items,
                    activeIndex: itemIndex
                };
            }
            function closeLightbox() {
                vm.lightbox.show = false;
                vm.lightbox = null;
            }
            var searchDebounced = _.debounce(function (e) {
                $scope.$apply(function () {
                    //a canceler exists, so perform the cancelation operation and reset
                    if (canceler) {
                        canceler.resolve();
                        canceler = $q.defer();
                    } else {
                        canceler = $q.defer();
                    }
                    currSort = vm.searchQuery ? 'Default' : 'Latest';
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, '', vm.searchQuery, canceler).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                        vm.pagination.pageNumber = 1;
                        vm.loading = false;
                        //set back to null so it can be re-created
                        canceler = null;
                    });
                });
            }, 200);
            function search(searchQuery) {
                vm.loading = true;
                searchDebounced();
            }
            vm.reloadPage = function () {
                //reload on next digest (after cookie)
                $timeout(function () {
                    window.location.reload(true);
                });
            };
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.RepoController', PackagesRepoController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PartialViewMacrosCreateController($scope, codefileResource, $location, navigationService, formHelper, appState) {
            var vm = this;
            var node = $scope.currentNode;
            vm.snippets = [];
            vm.createFolderError = '';
            vm.folderName = '';
            vm.fileName = '';
            vm.showSnippets = false;
            vm.creatingFolder = false;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            vm.createFile = createFile;
            vm.createFileWithoutMacro = createFileWithoutMacro;
            vm.showCreateFromSnippet = showCreateFromSnippet;
            vm.createFileFromSnippet = createFileFromSnippet;
            vm.close = close;
            function onInit() {
                codefileResource.getSnippets('partialViewMacros').then(function (snippets) {
                    vm.snippets = snippets;
                });
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form
                    })) {
                    codefileResource.createContainer('partialViewMacros', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'partialViewMacros',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form
                        });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form,
                            hasErrors: true
                        });
                        vm.createFolderError = err;
                    });
                }
            }
            function createFile() {
                $location.path('/settings/partialviewmacros/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            }
            function createFileWithoutMacro() {
                $location.path('/settings/partialviewmacros/edit/' + node.id).search('create', 'true').search('nomacro', 'true');
                navigationService.hideMenu();
            }
            function createFileFromSnippet(snippet) {
                $location.path('/settings/partialviewmacros/edit/' + node.id).search('create', 'true').search('snippet', snippet.fileName);
                navigationService.hideMenu();
            }
            function showCreateFromSnippet() {
                vm.showSnippets = true;
            }
            function close() {
                var showMenu = true;
                navigationService.hideDialog(showMenu);
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.CreateController', PartialViewMacrosCreateController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.PartialViewMacros.DeleteController
 * @function
 *
 * @description
 * The controller for deleting partial view macros
 */
    function PartialViewMacrosDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            codefileResource.deleteByPath('partialViewMacros', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.DeleteController', PartialViewMacrosDeleteController);
    'use strict';
    (function () {
        'use strict';
        function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) {
            var vm = this;
            vm.header = {};
            vm.header.editorfor = 'visuallyHiddenTexts_newPartialViewMacro';
            vm.header.setPageTitle = true;
            vm.page = {};
            vm.page.loading = true;
            vm.partialViewMacroFile = {};
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            // insert buttons
            vm.page.insertDefaultButton = {
                labelKey: 'general_insert',
                addEllipsis: 'true',
                handler: function handler() {
                    vm.openInsertOverlay();
                }
            };
            vm.page.insertSubButtons = [
                {
                    labelKey: 'template_insertPageField',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openPageFieldOverlay();
                    }
                },
                {
                    labelKey: 'template_insertMacro',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openMacroOverlay();
                    }
                },
                {
                    labelKey: 'template_insertDictionaryItem',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openDictionaryItemOverlay();
                    }
                }
            ];
            // bind functions to view model
            vm.save = save;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            /* Functions bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.partialViewMacro.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.partialViewMacro,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    // create macro if needed
                    if ($routeParams.create && $routeParams.nomacro !== 'true') {
                        macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
                            navigationService.syncTree({
                                tree: 'macros',
                                path: '-1,new',
                                forceReload: true,
                                activate: false
                            });
                            completeSave(saved);
                        }, Utilities.noop);
                    } else {
                        completeSave(saved);
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                            notificationsService.error(headerValue, msgValue);
                        });
                    });
                });
            }
            function completeSave(saved) {
                localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                    localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                        notificationsService.success(headerValue, msgValue);
                    });
                });
                //check if the name changed, if so we need to redirect
                if (vm.partialViewMacro.id !== saved.id) {
                    contentEditingHelper.redirectToRenamedContent(saved.id);
                } else {
                    vm.page.saveButtonState = 'success';
                    vm.partialViewMacro = saved;
                    //sync state
                    editorState.set(vm.partialViewMacro);
                    // normal tree sync
                    navigationService.syncTree({
                        tree: 'partialViewMacros',
                        path: vm.partialViewMacro.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                    // clear $dirty state on form
                    setFormState('pristine');
                }
            }
            function openInsertOverlay() {
                var insertOverlay = {
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        umbracoField: true
                    },
                    submit: function submit(model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        editorService.close();
                    },
                    close: function close(oldModel) {
                        // close the dialog
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.insertCodeSnippet(insertOverlay);
            }
            function openMacroOverlay() {
                var macroPicker = {
                    dialogData: {},
                    submit: function submit(model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.macroPicker(macroPicker);
            }
            function openPageFieldOverlay() {
                var insertFieldEditor = {
                    submit: function submit(model) {
                        insert(model.umbracoField);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.insertField(insertFieldEditor);
            }
            function openDictionaryItemOverlay() {
                var labelKeys = [
                    'template_insertDictionaryItem',
                    'emptyStates_emptyDictionaryTree'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    var title = values[0];
                    var emptyStateMessage = values[1];
                    var dictionaryPicker = {
                        section: 'translation',
                        treeAlias: 'dictionary',
                        entityType: 'dictionary',
                        multiPicker: false,
                        title: title,
                        emptyStateMessage: emptyStateMessage,
                        select: function select(node) {
                            var code = templateHelper.getInsertDictionarySnippet(node.name);
                            insert(code);
                            editorService.close();
                        },
                        close: function close(model) {
                            // close dialog
                            editorService.close();
                            // focus editor
                            vm.editor.focus();
                        }
                    };
                    editorService.treePicker(dictionaryPicker);
                });
            }
            function openQueryBuilderOverlay() {
                var queryBuilder = {
                    submit: function submit(model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        editorService.close();
                    },
                    close: function close(model) {
                        // close dialog
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.queryBuilder(queryBuilder);
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    var snippet = 'Empty';
                    if ($routeParams.snippet) {
                        snippet = $routeParams.snippet;
                    }
                    codefileResource.getScaffold('partialViewMacros', $routeParams.id, snippet).then(function (partialViewMacro) {
                        if ($routeParams.name) {
                            partialViewMacro.name = $routeParams.name;
                        }
                        ready(partialViewMacro, false);
                    });
                } else {
                    codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
                        ready(partialViewMacro, true);
                    });
                }
            }
            function ready(partialViewMacro, syncTree) {
                vm.page.loading = false;
                vm.partialViewMacro = partialViewMacro;
                //sync state
                editorState.set(vm.partialViewMacro);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'partialViewMacros',
                        path: vm.partialViewMacro.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: { fontSize: '14px' },
                    onLoad: function onLoad(_editor) {
                        vm.editor = _editor;
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        //change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.EditController', partialViewMacrosEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, appState) {
            var vm = this;
            var node = $scope.currentNode;
            vm.snippets = [];
            vm.showSnippets = false;
            vm.creatingFolder = false;
            vm.createFolderError = '';
            vm.folderName = '';
            vm.createPartialView = createPartialView;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            vm.showCreateFromSnippet = showCreateFromSnippet;
            vm.close = close;
            function onInit() {
                codefileResource.getSnippets('partialViews').then(function (snippets) {
                    vm.snippets = snippets;
                });
            }
            function createPartialView(selectedSnippet) {
                var snippet = null;
                if (selectedSnippet && selectedSnippet.fileName) {
                    snippet = selectedSnippet.fileName;
                }
                $location.path('/settings/partialviews/edit/' + node.id).search('create', 'true').search('snippet', snippet);
                navigationService.hideMenu();
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form
                    })) {
                    codefileResource.createContainer('partialViews', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'partialViews',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form
                        });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form,
                            hasErrors: true
                        });
                        vm.createFolderError = err;
                    });
                }
            }
            function showCreateFromSnippet() {
                vm.showSnippets = true;
            }
            function close() {
                var showMenu = true;
                navigationService.hideDialog(showMenu);
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViews.CreateController', PartialViewsCreateController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.PartialViews.DeleteController
 * @function
 *
 * @description
 * The controller for deleting partial views
 */
    function PartialViewsDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            // Reset the error message
            $scope.error = null;
            codefileResource.deleteByPath('partialViews', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.error = err;
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.PartialViews.DeleteController', PartialViewsDeleteController);
    'use strict';
    (function () {
        'use strict';
        function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) {
            var vm = this;
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var id = infiniteMode ? $scope.model.id : $routeParams.id;
            var create = infiniteMode ? $scope.model.create : $routeParams.create;
            var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet;
            function close() {
                if ($scope.model.close) {
                    $scope.model.close($scope.model);
                }
            }
            vm.close = close;
            vm.header = {};
            vm.header.editorfor = 'visuallyHiddenTexts_newPartialView';
            vm.header.setPageTitle = true;
            vm.page = {};
            vm.page.loading = true;
            vm.partialView = {};
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            // insert buttons
            vm.page.insertDefaultButton = {
                labelKey: 'general_insert',
                addEllipsis: 'true',
                handler: function handler() {
                    vm.openInsertOverlay();
                }
            };
            vm.page.insertSubButtons = [
                {
                    labelKey: 'template_insertPageField',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openPageFieldOverlay();
                    }
                },
                {
                    labelKey: 'template_insertMacro',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openMacroOverlay();
                    }
                },
                {
                    labelKey: 'template_insertDictionaryItem',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openDictionaryItemOverlay();
                    }
                }
            ];
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            templateHelper.getGeneralShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            templateHelper.getEditorShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            templateHelper.getPartialViewEditorShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            // bind functions to view model
            vm.save = save;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            /* Functions bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.partialView.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.partialView,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                            notificationsService.success(headerValue, msgValue);
                        });
                    });
                    //check if the name changed, if so we need to redirect
                    if (vm.partialView.id !== saved.id) {
                        contentEditingHelper.redirectToRenamedContent(saved.id);
                    } else {
                        vm.page.saveButtonState = 'success';
                        vm.partialView = saved;
                        //sync state
                        editorState.set(vm.partialView);
                        // normal tree sync
                        navigationService.syncTree({
                            tree: 'partialViews',
                            path: vm.partialView.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                        // clear $dirty state on form
                        setFormState('pristine');
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                            notificationsService.error(headerValue, msgValue);
                        });
                    });
                });
            }
            function openInsertOverlay() {
                var insertOverlay = {
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        umbracoField: true
                    },
                    submit: function submit(model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        editorService.close();
                    },
                    close: function close() {
                        // close the dialog
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.insertCodeSnippet(insertOverlay);
            }
            function openMacroOverlay() {
                var macroPicker = {
                    dialogData: {},
                    submit: function submit(model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.macroPicker(macroPicker);
            }
            function openPageFieldOverlay() {
                var insertFieldEditor = {
                    submit: function submit(model) {
                        insert(model.umbracoField);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.insertField(insertFieldEditor);
            }
            function openDictionaryItemOverlay() {
                var labelKeys = [
                    'template_insertDictionaryItem',
                    'emptyStates_emptyDictionaryTree'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    var title = values[0];
                    var emptyStateMessage = values[1];
                    var dictionaryItem = {
                        section: 'translation',
                        treeAlias: 'dictionary',
                        entityType: 'dictionary',
                        multiPicker: false,
                        title: title,
                        emptyStateMessage: emptyStateMessage,
                        select: function select(node) {
                            var code = templateHelper.getInsertDictionarySnippet(node.name);
                            insert(code);
                            editorService.close();
                        },
                        close: function close(model) {
                            // close dialog
                            editorService.close();
                            // focus editor
                            vm.editor.focus();
                        }
                    };
                    editorService.treePicker(dictionaryItem);
                });
            }
            function openQueryBuilderOverlay() {
                var queryBuilder = {
                    title: 'Query for content',
                    submit: function submit(model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        editorService.close();
                    },
                    close: function close() {
                        // close dialog
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.queryBuilder(queryBuilder);
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if (create) {
                    if (!snippet) {
                        snippet = 'Empty';
                    }
                    codefileResource.getScaffold('partialViews', id, snippet).then(function (partialView) {
                        ready(partialView, false);
                    });
                } else {
                    codefileResource.getByPath('partialViews', id).then(function (partialView) {
                        ready(partialView, true);
                    });
                }
            }
            function ready(partialView, syncTree) {
                vm.page.loading = false;
                vm.partialView = partialView;
                //sync state
                editorState.set(vm.partialView);
                if (!infiniteMode && syncTree) {
                    navigationService.syncTree({
                        tree: 'partialViews',
                        path: vm.partialView.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: { fontSize: '14px' },
                    onLoad: function onLoad(_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // Assign new keybinding
                        _editor.commands.addCommands([
                            //Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function exec() {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoValue',
                                bindKey: 'Alt-Shift-V',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openPageFieldOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertDictionary',
                                bindKey: 'Alt-Shift-D',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openDictionaryItemOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoMacro',
                                bindKey: 'Alt-Shift-M',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openMacroOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertQuery',
                                bindKey: 'Alt-Shift-Q',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openQueryBuilderOverlay();
                                    });
                                },
                                readOnly: true
                            }
                        ]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        //change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViews.EditController', PartialViewsEditController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.BooleanController', function ($scope) {
        function updateToggleValue() {
            $scope.toggleValue = false;
            if ($scope.model && Object.toBoolean($scope.model.value)) {
                $scope.toggleValue = true;
            }
        }
        if ($scope.model.value === null) {
            $scope.model.value = '0';
        }
        updateToggleValue();
        $scope.toggle = function () {
            if (Object.toBoolean($scope.model.value)) {
                $scope.model.value = '0';
                updateToggleValue();
                return;
            }
            $scope.model.value = '1';
            updateToggleValue();
        };
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.CheckboxListController', function ($scope) {
        var vm = this;
        vm.configItems = [];
        vm.viewItems = [];
        vm.change = change;
        function init() {
            var prevalues = ($scope.model.config ? $scope.model.config.prevalues : $scope.model.prevalues) || [];
            var items = [];
            for (var i = 0; i < prevalues.length; i++) {
                var item = {};
                if (Utilities.isObject(prevalues[i])) {
                    item.value = prevalues[i].value;
                    item.label = prevalues[i].label;
                } else {
                    item.value = prevalues[i];
                    item.label = prevalues[i];
                }
                items.push({
                    value: item.value,
                    label: item.label
                });
            }
            vm.configItems = items;
            if ($scope.model.value === null || $scope.model.value === undefined) {
                $scope.model.value = [];
            }
            // update view model.
            generateViewModel($scope.model.value);
        }
        function generateViewModel(newVal) {
            vm.viewItems = [];
            var iConfigItem;
            for (var i = 0; i < vm.configItems.length; i++) {
                iConfigItem = vm.configItems[i];
                var isChecked = _.contains(newVal, iConfigItem.value);
                vm.viewItems.push({
                    checked: isChecked,
                    value: iConfigItem.value,
                    label: iConfigItem.label
                });
            }
        }
        function change(model, value) {
            var index = $scope.model.value.indexOf(value);
            if (model === true) {
                //if it doesn't exist in the model, then add it
                if (index < 0) {
                    $scope.model.value.push(value);
                }
            } else {
                //if it exists in the model, then remove it
                if (index >= 0) {
                    $scope.model.value.splice(index, 1);
                }
            }
        }
        init();
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.ColorPickerController', function ($scope) {
        //setup the default config
        var config = { useLabel: false };
        //map the user config
        Utilities.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        $scope.isConfigured = $scope.model.prevalues && _.keys($scope.model.prevalues).length > 0;
        $scope.model.items = [];
        // Make an array from the dictionary
        var items = [];
        if (Utilities.isArray($scope.model.prevalues)) {
            for (var i in $scope.model.prevalues) {
                var oldValue = $scope.model.prevalues[i];
                if (!isValidHex(oldValue.value || oldValue))
                    continue;
                if (oldValue.hasOwnProperty('value')) {
                    var hexCode = toFullHex(oldValue.value);
                    items.push({
                        value: hexCode.substr(1, hexCode.length),
                        label: oldValue.label,
                        id: i
                    });
                } else {
                    var hexCode = toFullHex(oldValue);
                    items.push({
                        value: hexCode.substr(1, hexCode.length),
                        label: oldValue,
                        id: i
                    });
                }
            }
            // Now make the editor model the array
            $scope.model.items = items;
        }
        function toFullHex(hex) {
            if (hex.length === 4 && hex.charAt(0) === '#') {
                hex = '#' + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2) + hex.charAt(3) + hex.charAt(3);
            }
            return hex.toLowerCase();
        }
        function isValidHex(str) {
            return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(str);
        }
    });
    'use strict';
    function imageFilePickerController($scope, editorService) {
        var vm = this;
        vm.model = $scope.model;
        vm.add = add;
        vm.remove = remove;
        function add() {
            var mediaPickerOptions = {
                view: 'mediapicker',
                multiPicker: false,
                disableFolderSelect: true,
                onlyImages: true,
                submit: function submit(model) {
                    vm.model.value = model.selection[0].image;
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.mediaPicker(mediaPickerOptions);
        }
        ;
        function remove() {
            vm.model.value = null;
        }
        ;
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.ImageFilePickerController', imageFilePickerController);
    'use strict';
    function mediaFolderPickerController($scope, editorService, entityResource) {
        $scope.folderName = '';
        function retrieveFolderData() {
            var id = $scope.model.value;
            if (id == null) {
                $scope.folderName = '';
                return;
            }
            entityResource.getById(id, 'Media').then(function (media) {
                $scope.media = media;
            });
        }
        retrieveFolderData();
        $scope.add = function () {
            var mediaPickerOptions = {
                view: 'mediapicker',
                multiPicker: false,
                // We only want to allow you to pick one folder at a given time
                disableFolderSelect: false,
                onlyImages: false,
                onlyFolders: true,
                submit: function submit(model) {
                    $scope.model.value = model.selection[0].udi;
                    retrieveFolderData();
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.mediaPicker(mediaPickerOptions);
        };
        $scope.remove = function () {
            $scope.model.value = null;
            retrieveFolderData();
        };
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MediaFolderPickerController', mediaFolderPickerController);
    'use strict';
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function mediaPickerController($scope, entityResource, iconHelper, editorService, angularHelper) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        $scope.allowEdit = true;
        $scope.sortable = false;
        var dialogOptions = {
            multiPicker: false,
            entityType: 'Media',
            section: 'media',
            treeAlias: 'media',
            idType: 'udi'
        };
        //combine the dialogOptions with any values returned from the server
        if ($scope.model.config) {
            Utilities.extend(dialogOptions, $scope.model.config);
        }
        $scope.openTreePicker = function () {
            var treePicker = dialogOptions;
            treePicker.submit = function (model) {
                if (treePicker.multiPicker) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(model.selection[0]);
                }
                editorService.close();
            };
            treePicker.close = function () {
                editorService.close();
            };
            editorService.treePicker(treePicker);
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            syncModelValue();
        };
        $scope.clear = function () {
            $scope.renderModel = [];
            syncModelValue();
        };
        $scope.add = function (item) {
            var itemId = dialogOptions.idType === 'udi' ? item.udi : item.id;
            var currIds = _.map($scope.renderModel, function (i) {
                return dialogOptions.idType === 'udi' ? i.udi : i.id;
            });
            if (currIds.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    icon: item.icon,
                    udi: item.udi
                });
                // store the index of the new item in the renderModel collection so we can find it again
                var itemRenderIndex = $scope.renderModel.length - 1;
                // get and update the path for the picked node
                entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                    $scope.renderModel[itemRenderIndex].path = data;
                });
            }
            syncModelValue();
        };
        function syncModelValue() {
            var currIds = _.map($scope.renderModel, function (i) {
                return dialogOptions.idType === 'udi' ? i.udi : i.id;
            });
            $scope.model.value = trim(currIds.join(), ',');
            angularHelper.getCurrentForm($scope).$setDirty();
        }
        //load media data
        var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
        if (modelIds.length > 0) {
            entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
                _.each(data, function (item, i) {
                    item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                    $scope.renderModel.push({
                        name: item.name,
                        id: item.id,
                        icon: item.icon,
                        udi: item.udi
                    });
                    // store the index of the new item in the renderModel collection so we can find it again
                    var itemRenderIndex = $scope.renderModel.length - 1;
                    // get and update the path for the picked node
                    entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                        $scope.renderModel[itemRenderIndex].path = data;
                    });
                });
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MediaPickerController', mediaPickerController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiValuesController', function ($scope, $timeout) {
        //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
        $scope.newItem = '';
        $scope.hasError = false;
        $scope.focusOnNew = false;
        if (!Utilities.isArray($scope.model.value)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.value) {
                items.push({
                    value: $scope.model.value[i].value,
                    sortOrder: $scope.model.value[i].sortOrder,
                    id: i
                });
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.value = items;
        }
        $scope.remove = function (item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.value === item.value;
            });
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            if ($scope.newItem) {
                if (!_.contains($scope.model.value, $scope.newItem)) {
                    $scope.model.value.push({ value: $scope.newItem });
                    $scope.newItem = '';
                    $scope.hasError = false;
                    $scope.focusOnNew = true;
                    return;
                }
            }
            //there was an error, do the highlight (will be set back by the directive)
            $scope.hasError = true;
        };
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> div.control-group',
            tolerance: 'pointer',
            update: function update(e, ui) {
                // Get the new and old index for the moved element (using the text as the identifier, so 
                // we'd have a problem if two prevalues were the same, but that would be unlikely)
                var newIndex = ui.item.index();
                var movedPrevalueText = $('input[type="text"]', ui.item).val();
                var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
                // Move the element in the model
                if (originalIndex > -1) {
                    var movedElement = $scope.model.value[originalIndex];
                    $scope.model.value.splice(originalIndex, 1);
                    $scope.model.value.splice(newIndex, 0, movedElement);
                }
            }
        };
        $scope.createNew = function (event) {
            if (event.keyCode == 13) {
                $scope.add(event);
            }
        };
        function getElementIndexByPrevalueText(value) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].value === value) {
                    return i;
                }
            }
            return -1;
        }
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.OverlaySizeController', function ($scope) {
        if (!$scope.model.value) {
            $scope.model.value = 'small';
        }
    });
    'use strict';
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreePickerController', function ($scope, entityResource, iconHelper, editorService) {
        $scope.renderModel = [];
        $scope.ids = [];
        $scope.allowRemove = true;
        $scope.allowEdit = true;
        $scope.sortable = false;
        var config = {
            multiPicker: false,
            entityType: 'Document',
            type: 'content',
            treeAlias: 'content',
            idType: 'udi'
        };
        //combine the config with any values returned from the server
        if ($scope.model.config) {
            Utilities.extend(config, $scope.model.config);
        }
        if ($scope.model.value) {
            if (!Array.isArray($scope.model.value)) {
                $scope.ids = $scope.model.value.split(',');
            } else {
                $scope.ids.push($scope.model.value);
            }
            entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
                _.each(data, function (item, i) {
                    item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                    $scope.renderModel.push({
                        name: item.name,
                        id: item.id,
                        icon: item.icon,
                        udi: item.udi
                    });
                    // store the index of the new item in the renderModel collection so we can find it again
                    var itemRenderIndex = $scope.renderModel.length - 1;
                    // get and update the path for the picked node
                    entityResource.getUrl(item.id, config.entityType).then(function (data) {
                        $scope.renderModel[itemRenderIndex].path = data;
                    });
                });
            });
        }
        $scope.openTreePicker = function () {
            var treePicker = config;
            treePicker.section = config.type;
            treePicker.submit = function (model) {
                if (config.multiPicker) {
                    populate(model.selection);
                } else {
                    populate(model.selection[0]);
                }
                editorService.close();
            };
            treePicker.close = function () {
                editorService.close();
            };
            editorService.treePicker(treePicker);
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            $scope.ids.splice(index, 1);
            $scope.model.value = trim($scope.ids.join(), ',');
        };
        $scope.clear = function () {
            $scope.model.value = '';
            $scope.renderModel = [];
            $scope.ids = [];
        };
        $scope.add = function (item) {
            var itemId = config.idType === 'udi' ? item.udi : item.id;
            if ($scope.ids.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.ids.push(itemId);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    icon: item.icon,
                    udi: item.udi
                });
                $scope.model.value = trim($scope.ids.join(), ',');
                // store the index of the new item in the renderModel collection so we can find it again
                var itemRenderIndex = $scope.renderModel.length - 1;
                // get and update the path for the picked node
                entityResource.getUrl(item.id, config.entityType).then(function (data) {
                    $scope.renderModel[itemRenderIndex].path = data;
                });
            }
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            $scope.model.value = trim($scope.ids.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        function populate(data) {
            if (Utilities.isArray(data)) {
                _.each(data, function (item, i) {
                    $scope.add(item);
                });
            } else {
                $scope.clear();
                $scope.add(data);
            }
        }
    });
    'use strict';
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreeSourceController', function ($scope, $timeout, entityResource, iconHelper, editorService, eventsService) {
        if (!$scope.model) {
            $scope.model = {};
        }
        if (!$scope.model.value) {
            $scope.model.value = { type: 'content' };
        }
        if (!$scope.model.config) {
            $scope.model.config = { idType: 'udi' };
        }
        if ($scope.model.value.id && $scope.model.value.type !== 'member') {
            entityResource.getById($scope.model.value.id, entityType()).then(function (item) {
                populate(item);
            });
        } else {
            $timeout(function () {
                treeSourceChanged();
            }, 100);
        }
        function entityType() {
            var ent = 'Document';
            if ($scope.model.value.type === 'media') {
                ent = 'Media';
            } else if ($scope.model.value.type === 'member') {
                ent = 'Member';
            }
            return ent;
        }
        $scope.openContentPicker = function () {
            var treePicker = {
                idType: $scope.model.config.idType,
                section: $scope.model.value.type,
                treeAlias: $scope.model.value.type,
                multiPicker: false,
                submit: function submit(model) {
                    var item = model.selection[0];
                    populate(item);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.treePicker(treePicker);
        };
        $scope.clear = function () {
            $scope.model.value.id = null;
            $scope.node = null;
            $scope.model.value.query = null;
            treeSourceChanged();
        };
        function treeSourceChanged() {
            eventsService.emit('treeSourceChanged', { value: $scope.model.value.type });
        }
        //we always need to ensure we dont submit anything broken
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            if ($scope.model.value.type === 'member') {
                $scope.model.value.id = null;
                $scope.model.value.query = '';
            }
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        function populate(item) {
            $scope.clear();
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.node = item;
            $scope.node.path = '';
            $scope.model.value.id = $scope.model.config.idType === 'udi' ? item.udi : item.id;
            entityResource.getUrl(item.id, entityType()).then(function (data) {
                $scope.node.path = data;
            });
        }
    });
    'use strict';
    function TreeSourceTypePickerController($scope, contentTypeResource, mediaTypeResource, memberTypeResource, editorService, eventsService, angularHelper) {
        var vm = this;
        vm.loading = false;
        vm.itemTypes = [];
        vm.remove = remove;
        vm.add = add;
        var allItemTypes = null;
        var currentItemType = null;
        function init() {
            vm.loading = true;
            switch (currentItemType) {
            case 'content':
                contentTypeResource.getAll().then(getAllItemTypesCallback);
                break;
            case 'media':
                mediaTypeResource.getAll().then(getAllItemTypesCallback);
                break;
            case 'member':
                memberTypeResource.getTypes().then(getAllItemTypesCallback);
                break;
            }
        }
        function getAllItemTypesCallback(all) {
            allItemTypes = all;
            vm.loading = false;
            // the model value is a comma separated list of content type aliases
            var currentItemTypes = _.map(($scope.model.value || '').split(','), function (s) {
                return s.trim();
            });
            vm.itemTypes = _.filter(allItemTypes, function (itemType) {
                return currentItemTypes.indexOf(itemType.alias) >= 0;
            });
        }
        function add() {
            if (!currentItemType) {
                return;
            }
            var editor = {
                multiPicker: true,
                filterCssClass: 'not-allowed not-published',
                filter: function filter(item) {
                    // filter out folders (containers), element types (for content) and already selected items
                    return item.nodeType === 'container' || item.metaData.isElement || !!_.findWhere(vm.itemTypes, { udi: item.udi });
                },
                submit: function submit(model) {
                    var newItemTypes = _.map(model.selection, function (selected) {
                        return _.findWhere(allItemTypes, { udi: selected.udi });
                    });
                    vm.itemTypes = _.uniq(_.union(vm.itemTypes, newItemTypes));
                    updateModel();
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            switch (currentItemType) {
            case 'content':
                editorService.contentTypePicker(editor);
                break;
            case 'media':
                editorService.mediaTypePicker(editor);
                break;
            case 'member':
                editorService.memberTypePicker(editor);
                break;
            }
        }
        function remove(itemType) {
            vm.itemTypes = _.without(vm.itemTypes, itemType);
            updateModel();
        }
        function updateModel() {
            // the model value is a comma separated list of content type aliases
            $scope.model.value = _.pluck(vm.itemTypes, 'alias').join();
            angularHelper.getCurrentForm($scope).$setDirty();
        }
        var evts = [];
        evts.push(eventsService.on('treeSourceChanged', function (e, args) {
            // reset the model value if we changed node type (but not on the initial load)
            if (!!currentItemType && currentItemType !== args.value) {
                vm.itemTypes = [];
                updateModel();
            }
            currentItemType = args.value;
            init();
        }));
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
        if ($scope.model.config.itemType) {
            currentItemType = $scope.model.config.itemType;
            init();
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreeSourceTypePickerController', TreeSourceTypePickerController);
    'use strict';
    (function () {
        'use strict';
        function InlineBlockEditor($scope) {
            var bc = this;
            bc.openBlock = function (block) {
                // if we are closing:
                if (block.active === true) {
                    // boardcast the formSubmitting event to trigger syncronization or none-live property-editors
                    $scope.$broadcast('formSubmitting', { scope: $scope });
                    // Some property editors need to performe an action after all property editors have reacted to the formSubmitting.
                    $scope.$broadcast('formSubmittingFinalPhase', { scope: $scope });
                    block.active = false;
                } else {
                    $scope.api.activateBlock(block);
                }
            };
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor', InlineBlockEditor);
    }());
    'use strict';
    function _defineProperty(obj, key, value) {
        if (key in obj) {
            Object.defineProperty(obj, key, {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true
            });
        } else {
            obj[key] = value;
        }
        return obj;
    }
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.BlockList.BlockConfigurationController
 * @function
 *
 * @description
 * The controller for the content type editor property settings dialog
 */
    (function () {
        'use strict';
        function TransferProperties(fromObject, toObject) {
            for (var p in fromObject) {
                toObject[p] = fromObject[p];
            }
        }
        function BlockConfigurationController($scope, elementTypeResource, overlayService, localizationService, editorService, eventsService, udiService) {
            var unsubscribe = [];
            var vm = this;
            vm.openBlock = null;
            function onInit() {
                if (!$scope.model.value) {
                    $scope.model.value = [];
                }
                loadElementTypes();
            }
            function loadElementTypes() {
                return elementTypeResource.getAll().then(function (elementTypes) {
                    vm.elementTypes = elementTypes;
                });
            }
            function updateUsedElementTypes(event, args) {
                var key = args.documentType.key;
                for (var i = 0; i < vm.elementTypes.length; i++) {
                    if (vm.elementTypes[i].key === key) {
                        vm.elementTypes[i] = args.documentType;
                    }
                }
            }
            unsubscribe.push(eventsService.on('editors.documentType.saved', updateUsedElementTypes));
            vm.requestRemoveBlockByIndex = function (index) {
                localizationService.localizeMany([
                    'general_delete',
                    'blockEditor_confirmDeleteBlockTypeMessage',
                    'blockEditor_confirmDeleteBlockTypeNotice'
                ]).then(function (data) {
                    var contentElementType = vm.getElementTypeByKey($scope.model.value[index].contentElementTypeKey);
                    overlayService.confirmDelete({
                        title: data[0],
                        content: localizationService.tokenReplace(data[1], [contentElementType ? contentElementType.name : '(Unavailable ElementType)']),
                        confirmMessage: data[2],
                        close: function close() {
                            overlayService.close();
                        },
                        submit: function submit() {
                            vm.removeBlockByIndex(index);
                            overlayService.close();
                        }
                    });
                });
            };
            vm.removeBlockByIndex = function (index) {
                $scope.model.value.splice(index, 1);
            };
            vm.sortableOptions = {
                'ui-floating': true,
                items: 'umb-block-card',
                cursor: 'grabbing',
                placeholder: 'umb-block-card --sortable-placeholder'
            };
            vm.getAvailableElementTypes = function () {
                return vm.elementTypes.filter(function (type) {
                    return !$scope.model.value.find(function (entry) {
                        return type.key === entry.contentElementTypeKey;
                    });
                });
            };
            vm.getElementTypeByKey = function (key) {
                if (vm.elementTypes) {
                    return vm.elementTypes.find(function (type) {
                        return type.key === key;
                    }) || null;
                }
            };
            vm.openAddDialog = function () {
                localizationService.localize('blockEditor_headlineCreateBlock').then(function (localizedTitle) {
                    var contentTypePicker = {
                        title: localizedTitle,
                        section: 'settings',
                        treeAlias: 'documentTypes',
                        entityType: 'documentType',
                        isDialog: true,
                        filter: function filter(node) {
                            if (node.metaData.isElement === true) {
                                var key = udiService.getKey(node.udi);
                                // If a Block with this ElementType as content already exists, we will emit it as a posible option.
                                return $scope.model.value.find(function (entry) {
                                    return key === entry.contentElementTypeKey;
                                });
                            }
                            return true;
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            vm.addBlockFromElementTypeKey(udiService.getKey(node.udi));
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        },
                        extraActions: [{
                                style: 'primary',
                                labelKey: 'blockEditor_labelcreateNewElementType',
                                action: function action() {
                                    vm.createElementTypeAndCallback(function (documentTypeKey) {
                                        vm.addBlockFromElementTypeKey(documentTypeKey);
                                        // At this point we will close the contentTypePicker.
                                        editorService.close();
                                    });
                                }
                            }]
                    };
                    editorService.treePicker(contentTypePicker);
                });
            };
            vm.createElementTypeAndCallback = function (callback) {
                var _editor;
                var editor = (_editor = {
                    create: true,
                    infiniteMode: true,
                    noTemplate: true,
                    isElement: true
                }, _defineProperty(_editor, 'noTemplate', true), _defineProperty(_editor, 'submit', function submit(model) {
                    loadElementTypes().then(function () {
                        callback(model.documentTypeKey);
                    });
                    editorService.close();
                }), _defineProperty(_editor, 'close', function close() {
                    editorService.close();
                }), _editor);
                editorService.documentTypeEditor(editor);
            };
            vm.addBlockFromElementTypeKey = function (key) {
                var blockType = {
                    'contentElementTypeKey': key,
                    'settingsElementTypeKey': null,
                    'labelTemplate': '',
                    'view': null,
                    'stylesheet': null,
                    'editorSize': 'medium',
                    'iconColor': null,
                    'backgroundColor': null,
                    'thumbnail': null
                };
                $scope.model.value.push(blockType);
                vm.openBlockOverlay(blockType);
            };
            vm.openBlockOverlay = function (block) {
                var elementType = vm.getElementTypeByKey(block.contentElementTypeKey);
                if (elementType) {
                    localizationService.localize('blockEditor_blockConfigurationOverlayTitle', [elementType.name]).then(function (data) {
                        var clonedBlockData = Utilities.copy(block);
                        vm.openBlock = block;
                        var overlayModel = {
                            block: clonedBlockData,
                            title: data,
                            view: 'views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html',
                            size: 'small',
                            submit: function submit(overlayModel) {
                                loadElementTypes();
                                // lets load elementType again, to ensure we are up to date.
                                TransferProperties(overlayModel.block, block);
                                // transfer properties back to block object. (Doing this cause we dont know if block object is added to model jet, therefor we cant use index or replace the object.)
                                overlayModel.close();
                            },
                            close: function close() {
                                editorService.close();
                                vm.openBlock = null;
                            }
                        };
                        // open property settings editor
                        editorService.open(overlayModel);
                    });
                } else {
                    alert('Cannot be edited cause ElementType does not exist.');
                }
            };
            $scope.$on('$destroy', function () {
                unsubscribe.forEach(function (u) {
                    u();
                });
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.BlockList.BlockConfigurationController', BlockConfigurationController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.BlockList.BlockConfigurationOverlayController
 * @function
 *
 * @description
 * The controller for the content type editor property settings dialog
 */
    (function () {
        'use strict';
        function BlockConfigurationOverlayController($scope, overlayService, localizationService, editorService, elementTypeResource, eventsService, udiService, angularHelper) {
            var unsubscribe = [];
            var vm = this;
            vm.block = $scope.model.block;
            vm.colorPickerOptions = {
                type: 'color',
                allowEmpty: true,
                showAlpha: true
            };
            loadElementTypes();
            function loadElementTypes() {
                return elementTypeResource.getAll().then(function (elementTypes) {
                    vm.elementTypes = elementTypes;
                    vm.contentPreview = vm.getElementTypeByKey(vm.block.contentElementTypeKey);
                    vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey);
                });
            }
            vm.getElementTypeByKey = function (key) {
                return vm.elementTypes.find(function (type) {
                    return type.key === key;
                });
            };
            vm.openElementType = function (elementTypeKey) {
                var elementType = vm.getElementTypeByKey(elementTypeKey);
                if (elementType) {
                    var elementTypeId = elementType.id;
                    var editor = {
                        id: elementTypeId,
                        submit: function submit(model) {
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.documentTypeEditor(editor);
                }
            };
            vm.createElementTypeAndCallback = function (callback) {
                var editor = {
                    create: true,
                    infiniteMode: true,
                    isElement: true,
                    noTemplate: true,
                    submit: function submit(model) {
                        callback(model.documentTypeKey);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.documentTypeEditor(editor);
            };
            vm.addSettingsForBlock = function ($event, block) {
                localizationService.localize('blockEditor_headlineAddSettingsElementType').then(function (localizedTitle) {
                    var settingsTypePicker = {
                        title: localizedTitle,
                        section: 'settings',
                        treeAlias: 'documentTypes',
                        entityType: 'documentType',
                        isDialog: true,
                        filter: function filter(node) {
                            if (node.metaData.isElement === true) {
                                return false;
                            }
                            return true;
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            vm.applySettingsToBlock(block, udiService.getKey(node.udi));
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        },
                        extraActions: [{
                                style: 'primary',
                                labelKey: 'blockEditor_labelcreateNewElementType',
                                action: function action() {
                                    vm.createElementTypeAndCallback(function (key) {
                                        vm.applySettingsToBlock(block, key);
                                        // At this point we will close the contentTypePicker.
                                        editorService.close();
                                    });
                                }
                            }]
                    };
                    editorService.treePicker(settingsTypePicker);
                });
            };
            vm.applySettingsToBlock = function (block, key) {
                block.settingsElementTypeKey = key;
                vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey);
            };
            vm.requestRemoveSettingsForBlock = function (block) {
                localizationService.localizeMany([
                    'general_remove',
                    'defaultdialogs_confirmremoveusageof'
                ]).then(function (data) {
                    var settingsElementType = vm.getElementTypeByKey(block.settingsElementTypeKey);
                    overlayService.confirmRemove({
                        title: data[0],
                        content: localizationService.tokenReplace(data[1], [settingsElementType ? settingsElementType.name : '(Unavailable ElementType)']),
                        close: function close() {
                            overlayService.close();
                        },
                        submit: function submit() {
                            vm.removeSettingsForBlock(block);
                            overlayService.close();
                        }
                    });
                });
            };
            vm.removeSettingsForBlock = function (block) {
                block.settingsElementTypeKey = null;
            };
            function updateUsedElementTypes(event, args) {
                var key = args.documentType.key;
                for (var i = 0; i < vm.elementTypes.length; i++) {
                    if (vm.elementTypes[i].key === key) {
                        vm.elementTypes[i] = args.documentType;
                    }
                }
                if (vm.contentPreview && vm.contentPreview.key === key) {
                    vm.contentPreview = args.documentType;
                    $scope.$evalAsync();
                }
                if (vm.settingsPreview && vm.settingsPreview.key === key) {
                    vm.settingsPreview = args.documentType;
                    $scope.$evalAsync();
                }
            }
            unsubscribe.push(eventsService.on('editors.documentType.saved', updateUsedElementTypes));
            vm.addViewForBlock = function (block) {
                localizationService.localize('blockEditor_headlineAddCustomView').then(function (localizedTitle) {
                    var filePicker = {
                        title: localizedTitle,
                        section: 'settings',
                        treeAlias: 'files',
                        entityType: 'file',
                        isDialog: true,
                        filter: function filter(i) {
                            return !(i.name.indexOf('.html') !== -1);
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            var filepath = decodeURIComponent(node.id.replace(/\+/g, ' '));
                            block.view = '~/' + filepath;
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.treePicker(filePicker);
                });
            };
            vm.requestRemoveViewForBlock = function (block) {
                localizationService.localizeMany([
                    'general_remove',
                    'defaultdialogs_confirmremoveusageof'
                ]).then(function (data) {
                    overlayService.confirmRemove({
                        title: data[0],
                        content: localizationService.tokenReplace(data[1], [block.view]),
                        close: function close() {
                            overlayService.close();
                        },
                        submit: function submit() {
                            vm.removeViewForBlock(block);
                            overlayService.close();
                        }
                    });
                });
            };
            vm.removeViewForBlock = function (block) {
                block.view = null;
            };
            vm.addStylesheetForBlock = function (block) {
                localizationService.localize('blockEditor_headlineAddCustomStylesheet').then(function (localizedTitle) {
                    var filePicker = {
                        title: localizedTitle,
                        section: 'settings',
                        treeAlias: 'files',
                        entityType: 'file',
                        isDialog: true,
                        filter: function filter(i) {
                            return !(i.name.indexOf('.css') !== -1);
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            var filepath = decodeURIComponent(node.id.replace(/\+/g, ' '));
                            block.stylesheet = '~/' + filepath;
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.treePicker(filePicker);
                });
            };
            vm.requestRemoveStylesheetForBlock = function (block) {
                localizationService.localizeMany([
                    'general_remove',
                    'defaultdialogs_confirmremoveusageof'
                ]).then(function (data) {
                    overlayService.confirmRemove({
                        title: data[0],
                        content: localizationService.tokenReplace(data[1], [block.stylesheet]),
                        close: function close() {
                            overlayService.close();
                        },
                        submit: function submit() {
                            vm.removeStylesheetForBlock(block);
                            overlayService.close();
                        }
                    });
                });
            };
            vm.removeStylesheetForBlock = function (block) {
                block.stylesheet = null;
            };
            vm.addThumbnailForBlock = function (block) {
                localizationService.localize('blockEditor_headlineAddThumbnail').then(function (localizedTitle) {
                    var thumbnailPicker = {
                        title: localizedTitle,
                        section: 'settings',
                        treeAlias: 'files',
                        entityType: 'file',
                        isDialog: true,
                        filter: function filter(i) {
                            return !(i.name.indexOf('.jpg') !== -1 || i.name.indexOf('.jpeg') !== -1 || i.name.indexOf('.png') !== -1 || i.name.indexOf('.svg') !== -1 || i.name.indexOf('.webp') !== -1 || i.name.indexOf('.gif') !== -1);
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(file) {
                            var id = decodeURIComponent(file.id.replace(/\+/g, ' '));
                            block.thumbnail = '~/' + id;
                            editorService.close();
                        },
                        close: function close() {
                            editorService.close();
                        }
                    };
                    editorService.treePicker(thumbnailPicker);
                });
            };
            vm.removeThumbnailForBlock = function (entry) {
                entry.thumbnail = null;
            };
            vm.changeIconColor = function (color) {
                angularHelper.safeApply($scope, function () {
                    vm.block.iconColor = color ? color.toString() : null;
                });
            };
            vm.changeBackgroundColor = function (color) {
                angularHelper.safeApply($scope, function () {
                    vm.block.backgroundColor = color ? color.toString() : null;
                });
            };
            vm.submit = function () {
                if ($scope.model && $scope.model.submit) {
                    $scope.model.submit($scope.model);
                }
            };
            vm.close = function () {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close($scope.model);
                }
            };
            $scope.$on('$destroy', function () {
                unsubscribe.forEach(function (u) {
                    u();
                });
            });
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.BlockList.BlockConfigurationOverlayController', BlockConfigurationOverlayController);
    }());
    'use strict';
    (function () {
        'use strict';
        angular.module('umbraco').controller('Umbraco.PropertyEditors.BlockListPropertyEditor.CreateButtonController', function Controller($scope) {
            var vm = this;
            vm.plusPosX = 0;
            vm.onMouseMove = function ($event) {
                vm.plusPosX = $event.offsetX;
            };
        });
    }());
    'use strict';
    function booleanEditorController($scope) {
        // Setup the default config
        // This allow to overwrite the configuration when property editor is re-used
        // in e.g. third party packages, dashboard or content app. For example when using umb-property-editor.
        // At the moment this use "1/0" as default for "truevalue" and "falsevalue", but allow "True/False" as well.
        // Maybe sometime later we can make it support "Yes/No" or "On/Off" as well similar to ng-true-value and ng-false-value in Angular.
        var config = {
            truevalue: '1',
            falsevalue: '0',
            showLabels: false
        };
        if ($scope.model.config) {
            $scope.model.config.showLabels = $scope.model.config.showLabels ? Object.toBoolean($scope.model.config.showLabels) : config.showLabels;
        }
        // Map the user config
        Utilities.extend(config, $scope.model.config);
        // Map back to the model
        $scope.model.config = config;
        function setupViewModel() {
            $scope.renderModel = { value: false };
            if ($scope.model.config && $scope.model.config.default && Object.toBoolean($scope.model.config.default) && $scope.model && !$scope.model.value) {
                $scope.renderModel.value = true;
            }
            if ($scope.model && $scope.model.value && Object.toBoolean($scope.model.value)) {
                $scope.renderModel.value = true;
            }
        }
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        setupViewModel();
        if ($scope.model && !$scope.model.value) {
            $scope.model.value = $scope.renderModel.value === true ? $scope.model.config.truevalue : $scope.model.config.falsevalue;
        }
        // Here we declare a special method which will be called whenever the value has changed from the server
        // this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again if it has changed from the server
            setupViewModel();
        };
        // If another property editor changes the model.value we want to pick that up and reflect the value in this one.
        var unsubscribe = $scope.$watch('model.value', function (newVal, oldVal) {
            if (newVal !== oldVal) {
                setupViewModel();
            }
        });
        // Update the value when the toggle is clicked
        $scope.toggle = function () {
            setDirty();
            if ($scope.renderModel.value) {
                $scope.model.value = $scope.model.config.falsevalue;
                setupViewModel();
                return;
            }
            $scope.model.value = $scope.model.config.truevalue;
            setupViewModel();
        };
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.BooleanController', booleanEditorController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ChangePasswordController', function ($scope, $routeParams) {
        $scope.isNew = $routeParams.create;
        function resetModel() {
            //the model config will contain an object, if it does not we'll create defaults
            //NOTE: We will not support doing the password regex on the client side because the regex on the server side
            //based on the membership provider cannot always be ported to js from .net directly.        
            /*
    {
        hasPassword: true/false,
        requiresQuestionAnswer: true/false,
        enableReset: true/false,
        enablePasswordRetrieval: true/false,
        minPasswordLength: 10
    }
    */
            //set defaults if they are not available
            if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
                $scope.model.config.disableToggle = false;
            }
            if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
                $scope.model.config.hasPassword = false;
            }
            if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
                $scope.model.config.enablePasswordRetrieval = true;
            }
            if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
                $scope.model.config.requiresQuestionAnswer = false;
            }
            if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
                $scope.model.config.enableReset = true;
            }
            if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
                $scope.model.config.minPasswordLength = 0;
            }
            if (!$scope.model.config || $scope.model.config.minNonAlphaNumericChars === undefined) {
                $scope.model.config.minNonAlphaNumericChars = 0;
            }
            //set the model defaults
            if (!Utilities.isObject($scope.model.value)) {
                //if it's not an object then just create a new one
                $scope.model.value = {
                    newPassword: null,
                    oldPassword: null,
                    reset: null,
                    answer: null
                };
            } else {
                //just reset the values
                if (!$scope.isNew) {
                    //if it is new, then leave the generated pass displayed
                    $scope.model.value.newPassword = null;
                    $scope.model.value.oldPassword = null;
                }
                $scope.model.value.reset = null;
                $scope.model.value.answer = null;
            }
        }
        resetModel();
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.CheckboxListController', function ($scope, validationMessageService) {
        var vm = this;
        vm.configItems = [];
        vm.viewItems = [];
        vm.change = change;
        function init() {
            vm.uniqueId = String.CreateGuid();
            // currently the property editor will only work if our input is an object.
            if (Utilities.isObject($scope.model.config.items)) {
                // formatting the items in the dictionary into an array
                var sortedItems = [];
                var vals = _.values($scope.model.config.items);
                var keys = _.keys($scope.model.config.items);
                for (var i = 0; i < vals.length; i++) {
                    sortedItems.push({
                        key: keys[i],
                        sortOrder: vals[i].sortOrder,
                        value: vals[i].value
                    });
                }
                // ensure the items are sorted by the provided sort order
                sortedItems.sort(function (a, b) {
                    return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
                });
                vm.configItems = sortedItems;
                if ($scope.model.value === null || $scope.model.value === undefined) {
                    $scope.model.value = [];
                }
                // update view model.
                generateViewModel($scope.model.value);
                //watch the model.value in case it changes so that we can keep our view model in sync
                $scope.$watchCollection('model.value', updateViewModel);
            }
            // Set the message to use for when a mandatory field isn't completed.
            // Will either use the one provided on the property type or a localised default.
            validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
                $scope.mandatoryMessage = value;
            });
        }
        function updateViewModel(newVal) {
            var i = vm.configItems.length;
            while (i--) {
                var item = vm.configItems[i];
                // are this item the same in the model
                if (item.checked !== (newVal.indexOf(item.value) !== -1)) {
                    // if not lets update the full model.
                    generateViewModel(newVal);
                    return;
                }
            }
        }
        function generateViewModel(newVal) {
            vm.viewItems = [];
            var iConfigItem;
            for (var i = 0; i < vm.configItems.length; i++) {
                iConfigItem = vm.configItems[i];
                var isChecked = _.contains(newVal, iConfigItem.value);
                vm.viewItems.push({
                    checked: isChecked,
                    key: iConfigItem.key,
                    value: iConfigItem.value
                });
            }
        }
        function change(model, value) {
            var index = $scope.model.value.indexOf(value);
            if (model === true) {
                //if it doesn't exist in the model, then add it
                if (index < 0) {
                    $scope.model.value.push(value);
                }
            } else {
                //if it exists in the model, then remove it
                if (index >= 0) {
                    $scope.model.value.splice(index, 1);
                }
            }
        }
        init();
    });
    'use strict';
    function ColorPickerController($scope, $timeout) {
        var vm = this;
        // setup the default config
        var config = {
            items: [],
            multiple: false
        };
        // map the user config
        Utilities.extend(config, $scope.model.config);
        // map back to the model
        $scope.model.config = config;
        $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
        if ($scope.isConfigured) {
            for (var key in $scope.model.config.items) {
                if (!$scope.model.config.items[key].hasOwnProperty('value'))
                    $scope.model.config.items[key] = {
                        value: $scope.model.config.items[key],
                        label: $scope.model.config.items[key]
                    };
            }
            $scope.model.useLabel = Object.toBoolean($scope.model.config.useLabel);
            initActiveColor();
        }
        if (!Utilities.isArray($scope.model.config.items)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.config.items) {
                var oldValue = $scope.model.config.items[i];
                if (oldValue.hasOwnProperty('value')) {
                    items.push({
                        value: oldValue.value,
                        label: oldValue.label,
                        sortOrder: oldValue.sortOrder,
                        id: i
                    });
                } else {
                    items.push({
                        value: oldValue,
                        label: oldValue,
                        sortOrder: sortOrder,
                        id: i
                    });
                }
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.config.items = items;
        }
        vm.selectColor = function (color) {
            // this is required to re-validate
            $timeout(function () {
                var newColor = color ? color.value : null;
                vm.modelValueForm.selectedColor.$setViewValue(newColor);
            });
        };
        // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
        $scope.validateMandatory = function () {
            var isValid = !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value != '' && (!$scope.model.value.hasOwnProperty('value') || $scope.model.value.value !== '');
            return {
                isValid: isValid,
                errorMsg: $scope.model.validation.mandatoryMessage || 'Value cannot be empty',
                errorKey: 'required'
            };
        };
        // Finds the color best matching the model's color,
        // and sets the model color to that one. This is useful when
        // either the value or label was changed on the data type.
        function initActiveColor() {
            // no value
            if (!$scope.model.value)
                return;
            // Backwards compatibility, the color used to be stored as a hex value only
            if (typeof $scope.model.value === 'string') {
                $scope.model.value = {
                    value: $scope.model.value,
                    label: $scope.model.value
                };
            }
            // Complex color (value and label)?
            if (!$scope.model.value.hasOwnProperty('value'))
                return;
            var modelColor = $scope.model.value.value;
            var modelLabel = $scope.model.value.label;
            // Check for a full match or partial match.
            var foundItem = null;
            // Look for a fully matching color.
            for (var key in $scope.model.config.items) {
                var item = $scope.model.config.items[key];
                if (item.value == modelColor && item.label == modelLabel) {
                    foundItem = item;
                    break;
                }
            }
            // Look for a color with a matching value.
            if (!foundItem) {
                for (var key in $scope.model.config.items) {
                    var item = $scope.model.config.items[key];
                    if (item.value == modelColor) {
                        foundItem = item;
                        break;
                    }
                }
            }
            // Look for a color with a matching label.
            if (!foundItem) {
                for (var key in $scope.model.config.items) {
                    var item = $scope.model.config.items[key];
                    if (item.label == modelLabel) {
                        foundItem = item;
                        break;
                    }
                }
            }
            // If a match was found, set it as the active color.
            if (foundItem) {
                $scope.model.value.value = foundItem.value;
                $scope.model.value.label = foundItem.label;
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ColorPickerController', ColorPickerController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiColorPickerController', function ($scope, angularHelper, $element, eventsService) {
        var vm = this;
        vm.add = add;
        vm.remove = remove;
        vm.edit = edit;
        vm.cancel = cancel;
        vm.show = show;
        vm.hide = hide;
        vm.change = change;
        vm.labelEnabled = false;
        vm.editItem = null;
        //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
        var defaultColor = '000000';
        var defaultLabel = null;
        $scope.newColor = defaultColor;
        $scope.newLabel = defaultLabel;
        $scope.hasError = false;
        $scope.focusOnNew = false;
        $scope.options = {
            type: 'color',
            color: defaultColor,
            allowEmpty: false,
            showAlpha: false
        };
        function hide(color) {
            // show the add button
            $element.find('.btn.add').show();
        }
        function show(color) {
            // hide the add button
            $element.find('.btn.add').hide();
        }
        function change(color) {
            angularHelper.safeApply($scope, function () {
                if (color) {
                    $scope.newColor = color.toHexString().trimStart('#');
                }
            });
        }
        var evts = [];
        evts.push(eventsService.on('toggleValue', function (e, args) {
            vm.labelEnabled = args.value;
        }));
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
        if (!Utilities.isArray($scope.model.value)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.value) {
                var oldValue = $scope.model.value[i];
                if (oldValue.hasOwnProperty('value')) {
                    items.push({
                        value: oldValue.value,
                        label: oldValue.label,
                        sortOrder: oldValue.sortOrder,
                        id: i
                    });
                } else {
                    items.push({
                        value: oldValue,
                        label: oldValue,
                        sortOrder: sortOrder,
                        id: i
                    });
                }
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.value = items;
        }
        // ensure labels
        for (var i = 0; i < $scope.model.value.length; i++) {
            var item = $scope.model.value[i];
            item.label = item.hasOwnProperty('label') ? item.label : item.value;
        }
        function validLabel(label) {
            return label !== null && typeof label !== 'undefined' && label !== '' && label.length && label.length > 0;
        }
        function remove(item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.value === item.value && x.label === item.label;
            });
            setDirty();
        }
        function add(evt) {
            evt.preventDefault();
            if ($scope.newColor) {
                var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor;
                var exists = _.find($scope.model.value, function (item) {
                    return item != vm.editItem && (item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase());
                });
                if (!exists) {
                    if (vm.editItem == null) {
                        $scope.model.value.push({
                            value: $scope.newColor,
                            label: newLabel
                        });
                    } else {
                        vm.editItem.value = $scope.newColor;
                        vm.editItem.label = newLabel;
                        vm.editItem = null;
                    }
                    $scope.newLabel = '';
                    $scope.hasError = false;
                    $scope.focusOnNew = true;
                    setDirty();
                    return;
                }
                // there was an error, do the highlight (will be set back by the directive)
                $scope.hasError = true;
            }
        }
        function edit(item, evt) {
            evt.preventDefault();
            vm.editItem = item;
            $scope.newColor = item.value;
            $scope.newLabel = item.label;
        }
        function cancel(evt) {
            evt.preventDefault();
            vm.editItem = null;
            $scope.newColor = defaultColor;
            $scope.newLabel = defaultLabel;
        }
        function setDirty() {
            if (vm.modelValueForm) {
                vm.modelValueForm.selectedColor.$setDirty();
            }
        }
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            //handle: ".handle, .thumbnail",
            items: '> div.control-group',
            tolerance: 'pointer',
            update: function update(e, ui) {
                setDirty();
            }
        };
    });
    'use strict';
    function _slicedToArray(arr, i) {
        return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
    }
    function _nonIterableRest() {
        throw new TypeError('Invalid attempt to destructure non-iterable instance');
    }
    function _iterableToArrayLimit(arr, i) {
        if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === '[object Arguments]')) {
            return;
        }
        var _arr = [];
        var _n = true;
        var _d = false;
        var _e = undefined;
        try {
            for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                _arr.push(_s.value);
                if (i && _arr.length === i)
                    break;
            }
        } catch (err) {
            _d = true;
            _e = err;
        } finally {
            try {
                if (!_n && _i['return'] != null)
                    _i['return']();
            } finally {
                if (_d)
                    throw _e;
            }
        }
        return _arr;
    }
    function _arrayWithHoles(arr) {
        if (Array.isArray(arr))
            return arr;
    }
    /**
 * The controller that is used for a couple different Property Editors: Multi Node Tree Picker, Content Picker,
 * since this is used by MNTP and it supports content, media and members, there is code to deal with all 3 of those types
 * @param {any} $scope
 * @param {any} $q
 * @param {any} $routeParams
 * @param {any} $location
 * @param {any} entityResource
 * @param {any} editorState
 * @param {any} iconHelper
 * @param {any} angularHelper
 * @param {any} navigationService
 * @param {any} localizationService
 * @param {any} editorService
 * @param {any} userService
 */
    function contentPickerController($scope, $q, $routeParams, $location, entityResource, editorState, iconHelper, navigationService, localizationService, editorService, userService, overlayService) {
        var vm = {
            labels: {
                general_recycleBin: '',
                general_add: ''
            }
        };
        var unsubscribe;
        function subscribe() {
            unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                var currIds = _.map($scope.renderModel, function (i) {
                    return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                });
                $scope.model.value = trim(currIds.join(), ',');
            });
        }
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        /** Performs validation based on the renderModel data */
        function validate() {
            if ($scope.contentPickerForm) {
                //Validate!
                var hasItemsOrMandatory = $scope.renderModel.length !== 0 || $scope.model.validation && $scope.model.validation.mandatory;
                if (hasItemsOrMandatory && $scope.minNumberOfItems && $scope.minNumberOfItems > $scope.renderModel.length) {
                    $scope.contentPickerForm.minCount.$setValidity('minCount', false);
                } else {
                    $scope.contentPickerForm.minCount.$setValidity('minCount', true);
                }
                if ($scope.maxNumberOfItems && $scope.maxNumberOfItems < $scope.renderModel.length) {
                    $scope.contentPickerForm.maxCount.$setValidity('maxCount', false);
                } else {
                    $scope.contentPickerForm.maxCount.$setValidity('maxCount', true);
                }
            }
        }
        function startWatch() {
            //due to the way angular-sortable works, it needs to update a model, we don't want it to update renderModel since renderModel
            //is updated based on changes to model.value so if we bound angular-sortable to that and put a watch on it we'd end up in a
            //infinite loop. Instead we have a custom array model for angular-sortable and we'll watch that which we'll use to sync the model.value
            //which in turn will sync the renderModel.
            $scope.$watchCollection('sortableModel', function (newVal, oldVal) {
                $scope.model.value = newVal.join();
            });
            //if the underlying model changes, update the view model, this ensures that the view is always consistent with the underlying
            //model if it changes (i.e. based on server updates, or if used in split view, etc...)
            $scope.$watch('model.value', function (newVal, oldVal) {
                if (newVal !== oldVal) {
                    syncRenderModel(true);
                }
            });
        }
        $scope.renderModel = [];
        $scope.sortableModel = [];
        $scope.labels = vm.labels;
        $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
        //the default pre-values
        var defaultConfig = {
            multiPicker: false,
            showOpenButton: false,
            showEditButton: false,
            showPathOnHover: false,
            dataTypeKey: null,
            maxNumber: 1,
            minNumber: 0,
            startNode: {
                query: '',
                type: 'content',
                id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1    // get start node for simple Content Picker
            }
        };
        // sortable options
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            distance: 10,
            opacity: 0.7,
            tolerance: 'pointer',
            scroll: true,
            zIndex: 6000,
            update: function update(e, ui) {
                setDirty();
            }
        };
        var removeAllEntriesAction = {
            labelKey: 'clipboard_labelForRemoveAllEntries',
            labelTokens: [],
            icon: 'trash',
            method: removeAllEntries,
            isDisabled: true
        };
        if ($scope.model.config) {
            //special case, if the `startNode` is falsy on the server config delete it entirely so the default value is merged in
            if (!$scope.model.config.startNode) {
                delete $scope.model.config.startNode;
            }
            //merge the server config on top of the default config, then set the server config to use the result
            $scope.model.config = Utilities.extend(defaultConfig, $scope.model.config);
            // if the property is mandatory, set the minCount config to 1 (unless of course it is set to something already),
            // that way the minCount/maxCount validation handles the mandatory as well
            if ($scope.model.validation && $scope.model.validation.mandatory && !$scope.model.config.minNumber) {
                $scope.model.config.minNumber = 1;
            }
            if ($scope.model.config.multiPicker === true && $scope.umbProperty) {
                var propertyActions = [removeAllEntriesAction];
                $scope.umbProperty.setPropertyActions(propertyActions);
            }
            // use these to avoid the nested property lookups/null-checks
            $scope.minNumberOfItems = $scope.model.config.minNumber ? parseInt($scope.model.config.minNumber) : 0;
            $scope.maxNumberOfItems = $scope.model.config.maxNumber ? parseInt($scope.model.config.maxNumber) : 0;
        }
        //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
        $scope.model.config.multiPicker = Object.toBoolean($scope.model.config.multiPicker);
        $scope.model.config.showOpenButton = Object.toBoolean($scope.model.config.showOpenButton);
        $scope.model.config.showEditButton = Object.toBoolean($scope.model.config.showEditButton);
        $scope.model.config.showPathOnHover = Object.toBoolean($scope.model.config.showPathOnHover);
        var entityType = $scope.model.config.startNode.type === 'member' ? 'Member' : $scope.model.config.startNode.type === 'media' ? 'Media' : 'Document';
        $scope.allowOpenButton = false;
        $scope.allowEditButton = entityType === 'Document';
        $scope.allowRemoveButton = true;
        //the dialog options for the picker
        var dialogOptions = {
            multiPicker: $scope.model.config.multiPicker,
            entityType: entityType,
            filterCssClass: 'not-allowed not-published',
            startNodeId: null,
            dataTypeKey: $scope.model.dataTypeKey,
            currentNode: editorState ? editorState.current : null,
            callback: function callback(data) {
                if (Utilities.isArray(data)) {
                    _.each(data, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(data);
                }
                setDirty();
            },
            treeAlias: $scope.model.config.startNode.type,
            section: $scope.model.config.startNode.type,
            idType: 'udi'
        };
        //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
        // pre-value config on to the dialog options
        Utilities.extend(dialogOptions, $scope.model.config);
        dialogOptions.dataTypeKey = $scope.model.dataTypeKey;
        // if we can't pick more than one item, explicitly disable multiPicker in the dialog options
        if ($scope.maxNumberOfItems === 1) {
            dialogOptions.multiPicker = false;
        }
        // add the current filter (if any) as title for the filtered out nodes
        if ($scope.model.config.filter) {
            localizationService.localize('contentPicker_allowedItemTypes', [$scope.model.config.filter]).then(function (data) {
                dialogOptions.filterTitle = data;
            });
        }
        //We need to manually handle the filter for members here since the tree displayed is different and only contains
        // searchable list views
        if (entityType === 'Member') {
            //first change the not allowed filter css class
            dialogOptions.filterCssClass = 'not-allowed';
            var currFilter = dialogOptions.filter;
            //now change the filter to be a method
            dialogOptions.filter = function (i) {
                //filter out the list view nodes
                if (i.metaData.isContainer) {
                    return true;
                }
                if (!currFilter) {
                    return false;
                }
                //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller,
                // but not much we can do about that since members require special filtering.
                var filterItem = currFilter.toLowerCase().split(',');
                // NOTE: when used in a mini list view, the item content type alias is metaData.ContentTypeAlias (in regular views it's metaData.contentType)
                var itemContentType = i.metaData.contentType || i.metaData.ContentTypeAlias;
                var found = filterItem.indexOf(itemContentType.toLowerCase()) >= 0;
                if (!currFilter.startsWith('!') && !found || currFilter.startsWith('!') && found) {
                    return true;
                }
                return false;
            };
        }
        if ($routeParams.section === 'settings' && $routeParams.tree === 'documentTypes') {
            //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query
            dialogOptions.startNodeId = -1;
        } else if ($scope.model.config.startNode.query) {
            //if we have a query for the startnode, we will use that.
            var rootId = $routeParams.id;
            entityResource.getByQuery($scope.model.config.startNode.query, rootId, 'Document').then(function (ent) {
                dialogOptions.startNodeId = ($scope.model.config.idType === 'udi' ? ent.udi : ent.id).toString();
            });
        } else {
            dialogOptions.startNodeId = $scope.model.config.startNode.id;
        }
        //dialog
        $scope.openCurrentPicker = function () {
            $scope.currentPicker = dialogOptions;
            $scope.currentPicker.submit = function (model) {
                if (Utilities.isArray(model.selection)) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                    setDirty();
                }
                setDirty();
                editorService.close();
            };
            $scope.currentPicker.close = function () {
                editorService.close();
            };
            //open the correct editor based on the entity type
            switch (entityType) {
            case 'Document':
                editorService.contentPicker($scope.currentPicker);
                break;
            case 'Media':
                editorService.mediaPicker($scope.currentPicker);
                break;
            case 'Member':
                editorService.memberPicker($scope.currentPicker);
                break;
            default:
            }
        };
        $scope.remove = function (index) {
            var currIds = $scope.model.value ? $scope.model.value.split(',') : [];
            if (currIds.length > 0) {
                currIds.splice(index, 1);
                setDirty();
                $scope.model.value = currIds.join();
            }
            removeAllEntriesAction.isDisabled = currIds.length === 0;
        };
        $scope.showNode = function (index) {
            var item = $scope.renderModel[index];
            var id = item.id;
            var section = $scope.model.config.startNode.type.toLowerCase();
            entityResource.getPath(id, entityType).then(function (path) {
                navigationService.changeSection(section);
                navigationService.showTree(section, {
                    tree: section,
                    path: path,
                    forceReload: false,
                    activate: true
                });
                var routePath = section + '/' + section + '/edit/' + id.toString();
                $location.path(routePath).search('');
            });
        };
        $scope.add = function (item) {
            var currIds = $scope.model.value ? $scope.model.value.split(',') : [];
            var itemId = ($scope.model.config.idType === 'udi' ? item.udi : item.id).toString();
            if (currIds.indexOf(itemId) < 0) {
                currIds.push(itemId);
                $scope.model.value = currIds.join();
            }
            removeAllEntriesAction.isDisabled = false;
        };
        $scope.clear = function () {
            $scope.model.value = null;
            removeAllEntriesAction.isDisabled = true;
        };
        $scope.openEditor = function (item) {
            var editor = {
                id: entityType === 'Member' ? item.key : item.id,
                submit: function submit(model) {
                    var node = entityType === 'Member' ? model.memberNode : entityType === 'Media' ? model.mediaNode : model.contentNode;
                    // update the node
                    item.name = node.name;
                    if (entityType !== 'Member') {
                        if (entityType === 'Document') {
                            item.published = node.hasPublishedVersion;
                        }
                        entityResource.getUrl(node.id, entityType).then(function (data) {
                            item.url = data;
                        });
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            switch (entityType) {
            case 'Document':
                editorService.contentEditor(editor);
                break;
            case 'Media':
                editorService.mediaEditor(editor);
                break;
            case 'Member':
                editorService.memberEditor(editor);
                break;
            }
        };
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            if (unsubscribe) {
                unsubscribe();
            }
        });
        function setDirty() {
            if ($scope.contentPickerForm && $scope.contentPickerForm.modelValue) {
                $scope.contentPickerForm.modelValue.$setDirty();
            }
        }
        /** Syncs the renderModel based on the actual model.value and returns a promise */
        function syncRenderModel(doValidation) {
            var valueIds = $scope.model.value ? $scope.model.value.split(',') : [];
            //sync the sortable model
            $scope.sortableModel = valueIds;
            removeAllEntriesAction.isDisabled = valueIds.length === 0;
            //load current data if anything selected
            if (valueIds.length > 0) {
                //need to determine which items we already have loaded
                var renderModelIds = _.map($scope.renderModel, function (d) {
                    return ($scope.model.config.idType === 'udi' ? d.udi : d.id).toString();
                });
                //get the ids that no longer exist
                var toRemove = _.difference(renderModelIds, valueIds);
                //remove the ones that no longer exist
                for (var j = 0; j < toRemove.length; j++) {
                    var index = renderModelIds.indexOf(toRemove[j]);
                    $scope.renderModel.splice(index, 1);
                }
                //get the ids that we need to lookup entities for
                var missingIds = _.difference(valueIds, renderModelIds);
                if (missingIds.length > 0) {
                    var requests = [
                        entityResource.getByIds(missingIds, entityType),
                        entityResource.getUrlsByIds(missingIds, entityType)
                    ];
                    return $q.all(requests).then(function (_ref) {
                        var _ref2 = _slicedToArray(_ref, 2), data = _ref2[0], urlMap = _ref2[1];
                        _.each(valueIds, function (id, i) {
                            var entity = _.find(data, function (d) {
                                return $scope.model.config.idType === 'udi' ? d.udi == id : d.id == id;
                            });
                            if (entity) {
                                entity.url = entity.trashed ? vm.labels.general_recycleBin : urlMap[id];
                                addSelectedItem(entity);
                            }
                        });
                        if (doValidation) {
                            validate();
                        }
                        setSortingState($scope.renderModel);
                        return $q.when(true);
                    });
                } else {
                    //if there's nothing missing, make sure it's sorted correctly
                    var current = $scope.renderModel;
                    $scope.renderModel = [];
                    for (var k = 0; k < valueIds.length; k++) {
                        var id = valueIds[k];
                        var found = _.find(current, function (d) {
                            return $scope.model.config.idType === 'udi' ? d.udi == id : d.id == id;
                        });
                        if (found) {
                            $scope.renderModel.push(found);
                        }
                    }
                    if (doValidation) {
                        validate();
                    }
                    setSortingState($scope.renderModel);
                    return $q.when(true);
                }
            } else {
                $scope.renderModel = [];
                if (doValidation) {
                    validate();
                }
                setSortingState($scope.renderModel);
                return $q.when(true);
            }
        }
        function addSelectedItem(item) {
            // set icon
            if (item.icon) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            }
            // set default icon
            if (!item.icon) {
                switch (entityType) {
                case 'Document':
                    item.icon = 'icon-document';
                    break;
                case 'Media':
                    item.icon = 'icon-picture';
                    break;
                case 'Member':
                    item.icon = 'icon-user';
                    break;
                }
            }
            $scope.renderModel.push({
                'name': item.name,
                'id': item.id,
                'udi': item.udi,
                'icon': item.icon,
                'path': item.path,
                'url': item.url,
                'key': item.key,
                'trashed': item.trashed,
                'published': item.metaData && item.metaData.IsPublished === false && entityType === 'Document' ? false : true    // only content supports published/unpublished content so we set everything else to published so the UI looks correct
            });
        }
        function setSortingState(items) {
            // disable sorting if the list only consist of one item
            if (items.length > 1) {
                $scope.sortableOptions.disabled = false;
            } else {
                $scope.sortableOptions.disabled = true;
            }
        }
        function removeAllEntries() {
            localizationService.localizeMany([
                'content_nestedContentDeleteAllItems',
                'general_delete'
            ]).then(function (data) {
                overlayService.confirmDelete({
                    title: data[1],
                    content: data[0],
                    close: function close() {
                        overlayService.close();
                    },
                    submit: function submit() {
                        $scope.clear();
                        overlayService.close();
                    }
                });
            });
        }
        function init() {
            userService.getCurrentUser().then(function (user) {
                switch (entityType) {
                case 'Document':
                    var hasAccessToContent = user.allowedSections.indexOf('content') !== -1;
                    $scope.allowOpenButton = hasAccessToContent;
                    break;
                case 'Media':
                    var hasAccessToMedia = user.allowedSections.indexOf('media') !== -1;
                    $scope.allowOpenButton = hasAccessToMedia;
                    break;
                case 'Member':
                    var hasAccessToMember = user.allowedSections.indexOf('member') !== -1;
                    $scope.allowOpenButton = hasAccessToMember;
                    break;
                default:
                }
            });
            localizationService.localizeMany([
                'general_recycleBin',
                'general_add'
            ]).then(function (data) {
                vm.labels.general_recycleBin = data[0];
                vm.labels.general_add = data[1];
                syncRenderModel(false).then(function () {
                    //everything is loaded, start the watch on the model
                    startWatch();
                    subscribe();
                    validate();
                });
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ContentPickerController', contentPickerController);
    'use strict';
    function dateTimePickerController($scope, angularHelper, dateHelper, validationMessageService) {
        var flatPickr = null;
        function onInit() {
            $scope.hasDatetimePickerValue = $scope.model.value ? true : false;
            $scope.model.datetimePickerValue = null;
            $scope.serverTime = null;
            $scope.serverTimeNeedsOffsetting = false;
            // setup the default config
            var config = {
                pickTime: true,
                useSeconds: true,
                format: 'YYYY-MM-DD HH:mm:ss',
                icons: {
                    time: 'icon-time',
                    date: 'icon-calendar',
                    up: 'icon-chevron-up',
                    down: 'icon-chevron-down'
                }
            };
            // map the user config
            $scope.model.config = Utilities.extend(config, $scope.model.config);
            ;
            // ensure the format doesn't get overwritten with an empty string
            if ($scope.model.config.format === '' || $scope.model.config.format === undefined || $scope.model.config.format === null) {
                $scope.model.config.format = $scope.model.config.pickTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
            }
            // check whether a server time offset is needed
            if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
                // Will return something like 120
                var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                // Will return something like -120
                var localOffset = new Date().getTimezoneOffset();
                // If these aren't equal then offsetting is needed
                // note the minus in front of serverOffset needed 
                // because C# and javascript return the inverse offset
                $scope.serverTimeNeedsOffsetting = -serverOffset !== localOffset;
            }
            var dateFormat = $scope.model.config.pickTime ? 'Y-m-d H:i:S' : 'Y-m-d';
            // date picker config
            $scope.datePickerConfig = {
                enableTime: $scope.model.config.pickTime,
                dateFormat: dateFormat,
                time_24hr: true
            };
            // Don't show calendar if date format has been set to only time
            var timeFormat = $scope.model.config.format.toLowerCase();
            var timeFormatPattern = /^h{1,2}:m{1,2}(:s{1,2})?\s?a?$/gmi;
            if (timeFormat.match(timeFormatPattern)) {
                $scope.datePickerConfig.enableTime = true;
                $scope.datePickerConfig.noCalendar = true;
            }
            setDatePickerVal();
            // Set the message to use for when a mandatory field isn't completed.
            // Will either use the one provided on the property type or a localised default.
            validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
                $scope.mandatoryMessage = value;
            });
        }
        $scope.clearDate = function () {
            $scope.hasDatetimePickerValue = false;
            if ($scope.model) {
                $scope.model.datetimePickerValue = null;
                $scope.model.value = null;
            }
            if ($scope.datePickerForm && $scope.datePickerForm.datepicker) {
                $scope.datePickerForm.datepicker.$setValidity('pickerError', true);
            }
        };
        $scope.datePickerSetup = function (instance) {
            flatPickr = instance;
        };
        $scope.datePickerChange = function (date) {
            var momentDate = moment(date);
            setDate(momentDate);
            setDatePickerVal();
        };
        $scope.inputChanged = function () {
            if ($scope.model.datetimePickerValue === '' && $scope.hasDatetimePickerValue) {
                // $scope.hasDatetimePickerValue indicates that we had a value before the input was changed,
                // but now the input is empty.
                $scope.clearDate();
            } else if ($scope.model.datetimePickerValue) {
                var momentDate = moment($scope.model.datetimePickerValue, $scope.model.config.format, true);
                if (!momentDate || !momentDate.isValid()) {
                    momentDate = moment(new Date($scope.model.datetimePickerValue));
                }
                if (momentDate && momentDate.isValid()) {
                    setDate(momentDate);
                }
                setDatePickerVal();
                flatPickr.setDate($scope.model.value, false);
            }
        };
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            if (newVal != oldVal) {
                //check for c# System.DateTime.MinValue being passed as the clear indicator
                var minDate = moment('0001-01-01');
                var newDate = moment(newVal);
                if (newDate.isAfter(minDate)) {
                    setDate(newDate);
                } else {
                    $scope.clearDate();
                }
            }
        };
        function setDate(momentDate) {
            angularHelper.safeApply($scope, function () {
                // when a date is changed, update the model
                if (momentDate && momentDate.isValid()) {
                    $scope.datePickerForm.datepicker.$setValidity('pickerError', true);
                    $scope.hasDatetimePickerValue = true;
                    $scope.model.datetimePickerValue = momentDate.format($scope.model.config.format);
                } else {
                    $scope.hasDatetimePickerValue = false;
                    $scope.model.datetimePickerValue = null;
                }
                updateModelValue(momentDate);
            });
        }
        function updateModelValue(momentDate) {
            if ($scope.hasDatetimePickerValue) {
                if ($scope.model.config.pickTime) {
                    //check if we are supposed to offset the time
                    if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
                        $scope.model.value = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                        $scope.serverTime = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z');
                    } else {
                        $scope.model.value = momentDate.format('YYYY-MM-DD HH:mm:ss');
                    }
                } else {
                    $scope.model.value = momentDate.format('YYYY-MM-DD');
                }
            } else {
                $scope.model.value = null;
            }
            setDirty();
        }
        function setDirty() {
            if ($scope.datePickerForm) {
                $scope.datePickerForm.datepicker.$setDirty();
            }
        }
        /** Sets the value of the date picker control adn associated viewModel objects based on the model value */
        function setDatePickerVal() {
            if ($scope.model.value) {
                var dateVal;
                //check if we are supposed to offset the time
                if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && $scope.serverTimeNeedsOffsetting) {
                    //get the local time offset from the server
                    dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                    $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z');
                } else {
                    //create a normal moment , no offset required
                    var dateVal = $scope.model.value ? moment($scope.model.value, 'YYYY-MM-DD HH:mm:ss') : moment();
                }
                $scope.model.datetimePickerValue = dateVal.format($scope.model.config.format);
            } else {
                $scope.clearDate();
            }
        }
        $scope.$watch('model.value', function (newVal, oldVal) {
            if (newVal !== oldVal) {
                $scope.hasDatetimePickerValue = newVal ? true : false;
                setDatePickerVal();
            }
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.DatepickerController', dateTimePickerController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownFlexibleController', function ($scope, validationMessageService) {
        //setup the default config
        var config = {
            items: [],
            multiple: false
        };
        //map the user config
        Utilities.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        //ensure this is a bool, old data could store zeros/ones or string versions
        $scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple);
        //ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected
        $scope.$on('formSubmitting', function () {
            if ($scope.model.value && ($scope.model.value.length === 0 || $scope.model.value[0] === null)) {
                $scope.model.value = null;
            }
        });
        function convertArrayToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            for (var i = 0; i < model.length; i++) {
                newItems.push({
                    id: model[i],
                    sortOrder: 0,
                    value: model[i]
                });
            }
            return newItems;
        }
        function convertObjectToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            var vals = _.values($scope.model.config.items);
            var keys = _.keys($scope.model.config.items);
            for (var i = 0; i < vals.length; i++) {
                var label = vals[i].value ? vals[i].value : vals[i];
                newItems.push({
                    id: keys[i],
                    sortOrder: vals[i].sortOrder,
                    value: label
                });
            }
            return newItems;
        }
        $scope.updateSingleDropdownValue = function () {
            $scope.model.value = [$scope.model.singleDropdownValue];
        };
        if (Utilities.isArray($scope.model.config.items)) {
            //PP: I dont think this will happen, but we have tests that expect it to happen..
            //if array is simple values, convert to array of objects
            if (!Utilities.isObject($scope.model.config.items[0])) {
                $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
            }
        } else if (Utilities.isObject($scope.model.config.items)) {
            $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
        } else {
            throw 'The items property must be either an array or a dictionary';
        }
        //sort the values
        $scope.model.config.items.sort(function (a, b) {
            return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
        });
        //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
        // to "" gets selected by default
        if ($scope.model.value === null || $scope.model.value === undefined) {
            if ($scope.model.config.multiple) {
                $scope.model.value = [];
            } else {
                $scope.model.value = '';
            }
        }
        // if we run in single mode we'll store the value in a local variable
        // so we can pass an array as the model as our PropertyValueEditor expects that
        $scope.model.singleDropdownValue = '';
        if (!Object.toBoolean($scope.model.config.multiple) && $scope.model.value) {
            $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value;
        }
        // if we run in multiple mode, make sure the model is an array (in case the property was previously saved in single mode)
        // also explicitly set the model to null if it's an empty array, so mandatory validation works on the client
        if ($scope.model.config.multiple === '1' && $scope.model.value) {
            $scope.model.value = !Array.isArray($scope.model.value) ? [$scope.model.value] : $scope.model.value;
            if ($scope.model.value.length === 0) {
                $scope.model.value = null;
            }
        }
        // Set the message to use for when a mandatory field isn't completed.
        // Will either use the one provided on the property type or a localised default.
        validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
            $scope.mandatoryMessage = value;
        });
    });
    'use strict';
    function emailController($scope, validationMessageService) {
        // Set the message to use for when a mandatory field isn't completed.
        // Will either use the one provided on the property type or a localised default.
        validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
            $scope.mandatoryMessage = value;
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.EmailController', emailController);
    'use strict';
    /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
    function entityPicker($scope, entityResource) {
        //set the default to DocumentType
        if (!$scope.model.config.entityType) {
            $scope.model.config.entityType = 'DocumentType';
        }
        //Determine the select list options and which value to publish
        if (!$scope.model.config.publishBy) {
            $scope.selectOptions = 'entity.id as entity.name for entity in entities';
        } else {
            $scope.selectOptions = 'entity.' + $scope.model.config.publishBy + ' as entity.name for entity in entities';
        }
        entityResource.getAll($scope.model.config.entityType).then(function (data) {
            //convert the ids to strings so the drop downs work properly when comparing
            _.each(data, function (d) {
                d.id = d.id.toString();
            });
            $scope.entities = data;
        });
        if ($scope.model.value === null || $scope.model.value === undefined) {
            if ($scope.model.config.multiple) {
                $scope.model.value = [];
            } else {
                $scope.model.value = '';
            }
        } else {
            //if it's multiple, change the value to an array
            if (Object.toBoolean($scope.model.config.multiple)) {
                if (_.isString($scope.model.value)) {
                    $scope.model.value = $scope.model.value.split(',');
                }
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.EntityPickerController', entityPicker);
    'use strict';
    function EyeDropperColorPickerController($scope, angularHelper) {
        var vm = this;
        //setup the default config
        var config = {
            showAlpha: true,
            showPalette: true,
            allowEmpty: true
        };
        // map the user config
        Utilities.extend(config, $scope.model.config);
        // map back to the model
        $scope.model.config = config;
        vm.options = $scope.model.config;
        vm.color = $scope.model.value || null;
        vm.selectColor = function (color) {
            angularHelper.safeApply($scope, function () {
                vm.color = color ? color.toString() : null;
                $scope.model.value = vm.color;
                $scope.propertyForm.selectedColor.$setViewValue(vm.color);
            });
        };
        // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
        $scope.validateMandatory = function () {
            var isValid = !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value != '';
            return {
                isValid: isValid,
                errorMsg: $scope.model.validation.mandatoryMessage || 'Value cannot be empty',
                errorKey: 'required'
            };
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.EyeDropperColorPickerController', EyeDropperColorPickerController);
    'use strict';
    (function () {
        'use strict';
        /**
   * @ngdoc controller
   * @name Umbraco.Editors.FileUploadController
   * @function
   *
   * @description
   * The controller for the file upload property editor.
   *
  */
        function fileUploadController($scope, fileManager) {
            $scope.fileChanged = onFileChanged;
            //declare a special method which will be called whenever the value has changed from the server
            $scope.model.onValueChanged = onValueChanged;
            $scope.fileExtensionsString = $scope.model.config.fileExtensions ? $scope.model.config.fileExtensions.map(function (x) {
                return '.' + x.value;
            }).join(',') : '';
            /**
     * Called when the file selection value changes
     * @param {any} value
     */
            function onFileChanged(value) {
                $scope.model.value = value;
            }
            /**
     * called whenever the value has changed from the server
     * @param {any} newVal
     * @param {any} oldVal
     */
            function onValueChanged(newVal, oldVal) {
                //clear current uploaded files
                fileManager.setFiles({
                    propertyAlias: $scope.model.alias,
                    culture: $scope.model.culture,
                    segment: $scope.model.segment,
                    files: []
                });
            }
        }
        ;
        angular.module('umbraco').controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController).run(function (mediaHelper) {
            if (mediaHelper && mediaHelper.registerFileResolver) {
                //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
                // they contain different data structures so if we need to query against it we need to be aware of this.
                mediaHelper.registerFileResolver('Umbraco.UploadField', function (property, entity, thumbnail) {
                    if (thumbnail) {
                        if (mediaHelper.detectIfImageByExtension(property.value)) {
                            //get default big thumbnail from image processor
                            var thumbnailUrl = property.value + '?rnd=' + moment(entity.updateDate).format('YYYYMMDDHHmmss') + '&width=500&animationprocessmode=first';
                            return thumbnailUrl;
                        } else {
                            return null;
                        }
                    } else {
                        return property.value;
                    }
                });
            }
        });
    }());
    'use strict';
    angular.module('umbraco')    //this controller is obsolete and should not be used anymore
                                 //it proxies everything to the system media list view which has overtaken
                                 //all the work this property editor used to perform
.controller('Umbraco.PropertyEditors.FolderBrowserController', function ($rootScope, $scope, contentTypeResource) {
        //get the system media listview
        contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
            $scope.fakeProperty = {
                alias: 'contents',
                config: dt.config,
                description: '',
                editor: dt.editor,
                hideLabel: true,
                id: 1,
                label: 'Contents:',
                validation: {
                    mandatory: false,
                    pattern: null
                },
                value: '',
                view: dt.view
            };
        });
    });
    'use strict';
    function ConfigController($scope) {
        var vm = this;
        vm.submit = submit;
        vm.close = close;
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        function close() {
            if ($scope.model.close) {
                $scope.model.close();
            }
        }
        vm.showEmptyState = false;
        vm.showConfig = false;
        vm.showStyles = false;
        $scope.$watchCollection('model.config', onWatch);
        $scope.$watchCollection('model.styles', onWatch);
        function onWatch() {
            vm.showConfig = $scope.model.config && ($scope.model.config.length > 0 || Object.keys($scope.model.config).length > 0);
            vm.showStyles = $scope.model.styles && ($scope.model.styles.length > 0 || Object.keys($scope.model.styles).length > 0);
            vm.showEmptyState = vm.showConfig === false && vm.showStyles === false;
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.ConfigController', ConfigController);
    'use strict';
    function EditConfigController($scope, angularHelper) {
        var vm = this;
        vm.submit = submit;
        vm.close = close;
        vm.aceOption = {
            mode: 'json',
            theme: 'chrome',
            showPrintMargin: false,
            advanced: {
                fontSize: '14px',
                enableSnippets: true,
                enableBasicAutocompletion: true,
                enableLiveAutocompletion: false
            },
            onLoad: function onLoad(_editor) {
                vm.editor = _editor;
                vm.configJson = Utilities.toJson($scope.model.config, true);
                vm.editor.setValue(vm.configJson);
                vm.editor.on('blur', blurAceEditor);
            }
        };
        function blurAceEditor(event, _editor) {
            var code = _editor.getValue();
            //var form = angularHelper.getCurrentForm($scope);
            var form = vm.gridConfigEditor;
            var isValid = isValidJson(code);
            if (isValid) {
                $scope.model.config = Utilities.fromJson(code);
                setValid(form);
            } else {
                setInvalid(form);
            }
        }
        function isValidJson(model) {
            var flag = true;
            try {
                Utilities.fromJson(model);
            } catch (err) {
                flag = false;
            }
            return flag;
        }
        function setValid(form) {
            form.$setValidity('json', true);
        }
        function setInvalid(form) {
            form.$setValidity('json', false);
        }
        function submit() {
            if ($scope.model && $scope.model.submit) {
                $scope.model.submit($scope.model);
            }
        }
        function close() {
            if ($scope.model.close) {
                $scope.model.close();
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.EditConfigController', EditConfigController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController', function ($scope, localizationService) {
        var vm = this;
        vm.toggleAllowed = toggleAllowed;
        vm.configureSection = configureSection;
        vm.deleteSection = deleteSection;
        vm.selectRow = selectRow;
        vm.percentage = percentage;
        vm.scaleUp = scaleUp;
        vm.scaleDown = scaleDown;
        vm.close = close;
        vm.submit = submit;
        vm.labels = {};
        function init() {
            $scope.currentLayout = $scope.model.currentLayout;
            $scope.columns = $scope.model.columns;
            $scope.rows = $scope.model.rows;
            $scope.currentSection = null;
            // Setup copy of rows on sections
            if ($scope.currentLayout && $scope.currentLayout.sections) {
                $scope.currentLayout.sections.forEach(function (section) {
                    section.rows = Utilities.copy($scope.rows);
                    // Check if rows are selected
                    section.rows.forEach(function (row) {
                        row.selected = section.allowed && section.allowed.includes(row.name);
                    });
                });
            }
            var labelKeys = [
                'grid_addGridLayout',
                'grid_allowAllRowConfigurations'
            ];
            localizationService.localizeMany(labelKeys).then(function (data) {
                vm.labels.title = data[0];
                vm.labels.allowAllRowConfigurations = data[1];
                setTitle(vm.labels.title);
            });
        }
        function setTitle(value) {
            if (!$scope.model.title) {
                $scope.model.title = value;
            }
        }
        function scaleUp(section, max, overflow) {
            var add = 1;
            if (overflow !== true) {
                add = max > 1 ? 1 : max;
            }
            //var add = (max > 1) ? 1 : max;
            section.grid = section.grid + add;
        }
        function scaleDown(section) {
            var remove = section.grid > 1 ? 1 : 0;
            section.grid = section.grid - remove;
        }
        function percentage(spans) {
            return (spans / $scope.columns * 100).toFixed(8);
        }
        /****************
      Section
  *****************/
        function configureSection(section, template) {
            if (section === null || section === undefined) {
                var space = $scope.availableLayoutSpace > 4 ? 4 : $scope.availableLayoutSpace;
                section = {
                    grid: space,
                    rows: Utilities.copy($scope.rows)
                };
                template.sections.push(section);
            }
            section.allowAll = section.allowAll || !section.allowed || !section.allowed.length;
            $scope.currentSection = section;
        }
        function toggleAllowed(section) {
            section.allowAll = !section.allowAll;
            if (section.allowed) {
                delete section.allowed;
            } else {
                section.allowed = [];
            }
        }
        function deleteSection(section, template) {
            if ($scope.currentSection === section) {
                $scope.currentSection = null;
            }
            var index = template.sections.indexOf(section);
            template.sections.splice(index, 1);
        }
        function selectRow(section, row) {
            section.allowed = section.allowed || [];
            var index = section.allowed.indexOf(row.name);
            if (row.selected === true) {
                if (index === -1) {
                    section.allowed.push(row.name);
                }
            } else {
                section.allowed.splice(index, 1);
            }
        }
        function close() {
            if ($scope.model.close) {
                cleanUpRows();
                $scope.model.close();
            }
        }
        function submit() {
            if ($scope.model.submit) {
                cleanUpRows();
                $scope.model.submit($scope.currentLayout);
            }
        }
        function cleanUpRows() {
            $scope.currentLayout.sections.forEach(function (section) {
                if (section.rows) {
                    delete section.rows;
                }
            });
        }
        $scope.$watch('currentLayout', function (layout) {
            if (layout) {
                var total = 0;
                _.forEach(layout.sections, function (section) {
                    total = total + section.grid;
                });
                $scope.availableLayoutSpace = $scope.columns - total;
            }
        }, true);
        init();
    });
    'use strict';
    function RowConfigController($scope, localizationService) {
        var vm = this;
        vm.configureCell = configureCell;
        vm.closeArea = closeArea;
        vm.deleteArea = deleteArea;
        vm.selectEditor = selectEditor;
        vm.toggleAllowed = toggleAllowed;
        vm.percentage = percentage;
        vm.scaleUp = scaleUp;
        vm.scaleDown = scaleDown;
        vm.close = close;
        vm.submit = submit;
        vm.labels = {};
        function init() {
            $scope.currentRow = $scope.model.currentRow;
            $scope.columns = $scope.model.columns;
            $scope.editors = $scope.model.editors;
            $scope.nameChanged = false;
            var labelKeys = [
                'grid_addRowConfiguration',
                'grid_allowAllEditors'
            ];
            localizationService.localizeMany(labelKeys).then(function (data) {
                vm.labels.title = data[0];
                vm.labels.allowAllEditors = data[1];
                setTitle(vm.labels.title);
            });
        }
        function setTitle(value) {
            if (!$scope.model.title) {
                $scope.model.title = value;
            }
        }
        function scaleUp(section, max, overflow) {
            var add = 1;
            if (overflow !== true) {
                add = max > 1 ? 1 : max;
            }
            //var add = (max > 1) ? 1 : max;
            section.grid = section.grid + add;
        }
        ;
        function scaleDown(section) {
            var remove = section.grid > 1 ? 1 : 0;
            section.grid = section.grid - remove;
        }
        function percentage(spans) {
            return (spans / $scope.columns * 100).toFixed(8);
        }
        /****************
      area
  *****************/
        function configureCell(cell, row) {
            if ($scope.currentCell && $scope.currentCell === cell) {
                delete $scope.currentCell;
            } else {
                if (cell === null) {
                    var available = $scope.availableRowSpace;
                    var space = 4;
                    if (available < 4 && available > 0) {
                        space = available;
                    }
                    cell = { grid: space };
                    row.areas.push(cell);
                }
                cell.allowed = cell.allowed || [];
                $scope.editors.forEach(function (e) {
                    e.allowed = cell.allowed.indexOf(e.alias) !== -1;
                });
                cell.allowAll = cell.allowAll || !cell.allowed || !cell.allowed.length;
                $scope.currentCell = cell;
            }
        }
        function toggleAllowed(cell) {
            cell.allowAll = !cell.allowAll;
            if (cell.allowed) {
                delete cell.allowed;
            } else {
                cell.allowed = [];
            }
        }
        function deleteArea(cell, row) {
            if ($scope.currentCell === cell) {
                $scope.currentCell = null;
            }
            var index = row.areas.indexOf(cell);
            row.areas.splice(index, 1);
        }
        // This doesn't seem to be used?
        function closeArea() {
            $scope.currentCell = null;
        }
        function selectEditor(cell, editor) {
            cell.allowed = cell.allowed || [];
            var index = cell.allowed.indexOf(editor.alias);
            if (editor.allowed === true) {
                if (index === -1) {
                    cell.allowed.push(editor.alias);
                }
            } else {
                cell.allowed.splice(index, 1);
            }
        }
        function close() {
            if ($scope.model.close) {
                $scope.model.close();
            }
        }
        function submit() {
            if ($scope.model.submit) {
                $scope.model.submit($scope.currentRow);
            }
        }
        $scope.$watch('currentRow', function (row) {
            if (row) {
                var total = 0;
                _.forEach(row.areas, function (area) {
                    total = total + area.grid;
                });
                $scope.availableRowSpace = $scope.columns - total;
                var originalName = $scope.currentRow.name;
                if (originalName) {
                    if (originalName != row.name) {
                        $scope.nameChanged = true;
                    } else {
                        $scope.nameChanged = false;
                    }
                }
            }
        }, true);
        init();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController', RowConfigController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.EmbedController', function ($scope, $timeout, $sce, editorService) {
        function onInit() {
            $scope.control.icon = $scope.control.icon || 'icon-movie-alt';
            var embedPreview = Utilities.isObject($scope.control.value) && $scope.control.value.preview ? $scope.control.value.preview : $scope.control.value;
            $scope.trustedValue = embedPreview ? $sce.trustAsHtml(embedPreview) : null;
            if (!$scope.control.value) {
                $timeout(function () {
                    if ($scope.control.$initializing) {
                        $scope.setEmbed();
                    }
                }, 200);
            }
        }
        $scope.setEmbed = function () {
            var modify = Utilities.isObject($scope.control.value) ? $scope.control.value : null;
            var embed = {
                modify: modify,
                submit: function submit(model) {
                    var embed = {
                        constrain: model.embed.constrain,
                        height: model.embed.height,
                        width: model.embed.width,
                        url: model.embed.url,
                        info: model.embed.info,
                        preview: model.embed.preview
                    };
                    $scope.control.value = embed;
                    $scope.trustedValue = $sce.trustAsHtml(embed.preview);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.embed(embed);
        };
        onInit();
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MacroController', function ($scope, $timeout, editorService, macroResource, macroService, localizationService, $routeParams) {
        $scope.control.icon = $scope.control.icon || 'icon-settings-alt';
        localizationService.localize('grid_clickToInsertMacro').then(function (label) {
            $scope.title = label;
        });
        $scope.setMacro = function () {
            var dialogData = {
                richTextEditor: true,
                macroData: $scope.control.value || { macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias ? $scope.control.editor.config.macroAlias : '' }
            };
            var macroPicker = {
                dialogData: dialogData,
                submit: function submit(model) {
                    var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                    $scope.control.value = {
                        macroAlias: macroObject.macroAlias,
                        macroParamsDictionary: macroObject.macroParamsDictionary
                    };
                    $scope.setPreview($scope.control.value);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.macroPicker(macroPicker);
        };
        $scope.setPreview = function (macro) {
            var contentId = $routeParams.id;
            macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary).then(function (htmlResult) {
                $scope.title = macro.macroAlias;
                if (htmlResult.trim().length > 0 && htmlResult.indexOf('Macro:') < 0) {
                    $scope.preview = htmlResult;
                }
            });
        };
        $timeout(function () {
            if ($scope.control.$initializing) {
                $scope.setMacro();
            } else if ($scope.control.value) {
                $scope.setPreview($scope.control.value);
            }
        }, 200);
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MediaController', function ($scope, userService, editorService, localizationService) {
        $scope.control.icon = $scope.control.icon || 'icon-picture';
        $scope.thumbnailUrl = getThumbnailUrl();
        if (!$scope.model.config.startNodeId) {
            if ($scope.model.config.ignoreUserStartNodes === true) {
                $scope.model.config.startNodeId = -1;
                $scope.model.config.startNodeIsVirtual = true;
            } else {
                userService.getCurrentUser().then(function (userData) {
                    $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                    $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
                });
            }
        }
        $scope.setImage = function () {
            var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : null;
            var mediaPicker = {
                startNodeId: startNodeId,
                startNodeIsVirtual: startNodeId ? $scope.model.config.startNodeIsVirtual : null,
                cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : null,
                showDetails: true,
                disableFolderSelect: true,
                onlyImages: true,
                dataTypeKey: $scope.model.dataTypeKey,
                submit: function submit(model) {
                    updateControlValue(model.selection[0]);
                    editorService.close();
                },
                close: function close() {
                    return editorService.close();
                }
            };
            editorService.mediaPicker(mediaPicker);
        };
        $scope.editImage = function () {
            var mediaCropDetailsConfig = {
                size: 'small',
                target: $scope.control.value,
                submit: function submit(model) {
                    updateControlValue(model.target);
                    editorService.close();
                },
                close: function close() {
                    return editorService.close();
                }
            };
            localizationService.localize('defaultdialogs_editSelectedMedia').then(function (value) {
                mediaCropDetailsConfig.title = value;
                editorService.mediaCropDetails(mediaCropDetailsConfig);
            });
        };
        /**
   *
   */
        function getThumbnailUrl() {
            if ($scope.control.value && $scope.control.value.image) {
                var url = $scope.control.value.image;
                if ($scope.control.editor.config && $scope.control.editor.config.size) {
                    if ($scope.control.value.coordinates) {
                        // New way, crop by percent must come before width/height.
                        var coords = $scope.control.value.coordinates;
                        url += '?crop='.concat(coords.x1, ',').concat(coords.y1, ',').concat(coords.x2, ',').concat(coords.y2, '&cropmode=percentage');
                    } else {
                        // Here in order not to break existing content where focalPoint were used.
                        // For some reason width/height have to come first when mode=crop.
                        if ($scope.control.value.focalPoint) {
                            url += '?center='.concat($scope.control.value.focalPoint.top, ',').concat($scope.control.value.focalPoint.left);
                            url += '&mode=crop';
                        } else {
                            // Prevent black padding and no crop when focal point not set / changed from default
                            url += '?center=0.5,0.5&mode=crop';
                        }
                    }
                    url += '&width=' + $scope.control.editor.config.size.width;
                    url += '&height=' + $scope.control.editor.config.size.height;
                    url += '&animationprocessmode=first';
                }
                // set default size if no crop present (moved from the view)
                if (url.includes('?') === false) {
                    url += '?width=800&upscale=false&animationprocessmode=false';
                }
                return url;
            }
            return null;
        }
        /**
   *
   * @param {object} selectedImage
   */
        function updateControlValue(selectedImage) {
            // we could apply selectedImage directly to $scope.control.value,
            // but this allows excluding fields in future if needed
            $scope.control.value = {
                focalPoint: selectedImage.focalPoint,
                coordinates: selectedImage.coordinates,
                id: selectedImage.id,
                udi: selectedImage.udi,
                image: selectedImage.image,
                caption: selectedImage.caption,
                altText: selectedImage.altText
            };
            $scope.thumbnailUrl = getThumbnailUrl();
        }
    });
    'use strict';
    (function () {
        'use strict';
        function GridRichTextEditorController() {
            var vm = this;
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.RichTextEditorController', GridRichTextEditorController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.TextStringController', function () {
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridController', function ($scope, localizationService, gridService, umbRequestHelper, angularHelper, $element, eventsService, editorService, overlayService, $interpolate) {
        // Grid status variables
        var placeHolder = '';
        var currentForm = angularHelper.getCurrentForm($scope);
        $scope.currentRowWithActiveChild = null;
        $scope.currentCellWithActiveChild = null;
        $scope.active = null;
        $scope.currentRow = null;
        $scope.currentCell = null;
        $scope.currentToolsControl = null;
        $scope.currentControl = null;
        $scope.openRTEToolbarId = null;
        $scope.hasSettings = false;
        $scope.showRowConfigurations = true;
        $scope.sortMode = false;
        $scope.reorderKey = 'general_reorder';
        // *********************************************
        // Sortable options
        // *********************************************
        var draggedRteSettings;
        // holds a dictionary of RTE settings to remember when dragging things around.
        $scope.sortableOptionsRow = {
            distance: 10,
            cursor: 'move',
            placeholder: 'ui-sortable-placeholder',
            handle: '.umb-row-title-bar',
            helper: 'clone',
            forcePlaceholderSize: true,
            tolerance: 'pointer',
            zIndex: 1000000000000000000,
            scrollSensitivity: 100,
            cursorAt: {
                top: 40,
                left: 60
            },
            sort: function sort(event, ui) {
                /* prevent vertical scroll out of the screen */
                var max = $('.umb-grid').width() - 150;
                if (parseInt(ui.helper.css('left')) > max) {
                    ui.helper.css({ 'left': max + 'px' });
                }
                if (parseInt(ui.helper.css('left')) < 20) {
                    ui.helper.css({ 'left': 20 });
                }
            },
            start: function start(e, ui) {
                // Fade out row when sorting
                ui.item[0].style.display = 'block';
                ui.item[0].style.opacity = '0.5';
                draggedRteSettings = {};
                ui.item.find('.umb-rte').each(function (key, value) {
                    // remove all RTEs in the dragged row and save their settings
                    var rteId = value.id;
                    var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                    if (editor) {
                        draggedRteSettings[rteId] = editor.settings;
                    }
                });
            },
            stop: function stop(e, ui) {
                // Fade in row when sorting stops
                ui.item[0].style.opacity = '1';
                // reset all RTEs affected by the dragging
                ui.item.parents('.umb-column').find('.umb-rte').each(function (key, value) {
                    var rteId = value.id;
                    var settings = draggedRteSettings[rteId];
                    if (!settings) {
                        var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                        if (editor) {
                            settings = editor.settings;
                        }
                    }
                    if (settings) {
                        tinyMCE.execCommand('mceRemoveEditor', false, rteId);
                        tinyMCE.init(settings);
                    }
                });
                currentForm.$setDirty();
            }
        };
        var notIncludedRte = [];
        // used for RTEs that has been affected by the sorting
        var cancelMove = false;
        var startingArea;
        $scope.sortableOptionsCell = {
            distance: 10,
            cursor: 'move',
            uiFloating: true,
            placeholder: 'ui-sortable-placeholder',
            handle: '.umb-control-handle',
            helper: 'clone',
            connectWith: '.umb-cell-inner',
            forcePlaceholderSize: true,
            tolerance: 'pointer',
            zIndex: 1000000000000000000,
            scrollSensitivity: 100,
            cursorAt: {
                top: 45,
                left: 90
            },
            sort: function sort(event, ui) {
                /* prevent vertical scroll out of the screen */
                var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($('.umb-grid').offset().left);
                var max = $('.umb-grid').width() - 220;
                if (position > max) {
                    ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                }
                if (position < 0) {
                    ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                }
            },
            over: function over(event, ui) {
                var area = event.target.getScope_HackForSortable().area;
                var allowedEditors = area.$allowedEditors.map(function (e) {
                    return e.alias;
                });
                if ($.inArray(ui.item[0].getScope_HackForSortable().control.editor.alias, allowedEditors) < 0 || startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1) {
                    $scope.$apply(function () {
                        area.dropNotAllowed = true;
                    });
                    ui.placeholder.hide();
                    cancelMove = true;
                } else {
                    if (area.controls.length == 0) {
                        $scope.$apply(function () {
                            area.dropOnEmpty = true;
                        });
                        ui.placeholder.hide();
                    } else {
                        ui.placeholder.show();
                    }
                    cancelMove = false;
                }
            },
            out: function out(event, ui) {
                $scope.$apply(function () {
                    var dropArea = event.target.getScope_HackForSortable().area;
                    dropArea.dropNotAllowed = false;
                    dropArea.dropOnEmpty = false;
                });
            },
            update: function update(event, ui) {
                /* add all RTEs which are affected by the dragging */
                if (!ui.sender) {
                    if (cancelMove) {
                        ui.item.sortable.cancel();
                    }
                    ui.item.parents('.umb-cell-content').find('.umb-rte').each(function (key, value) {
                        var rteId = value.id;
                        if ($.inArray(rteId, notIncludedRte) < 0) {
                            // remember this RTEs settings, cause we need to update it later.
                            var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                            if (editor) {
                                draggedRteSettings[rteId] = editor.settings;
                            }
                            notIncludedRte.splice(0, 0, rteId);
                        }
                    });
                } else {
                    $(event.target).find('.umb-rte').each(function () {
                        var rteId = $(this).attr('id');
                        if ($.inArray(rteId, notIncludedRte) < 0) {
                            // remember this RTEs settings, cause we need to update it later.
                            var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                            if (editor) {
                                draggedRteSettings[rteId] = editor.settings;
                            }
                            notIncludedRte.splice(0, 0, $(this).attr('id'));
                        }
                    });
                }
                currentForm.$setDirty();
            },
            start: function start(event, ui) {
                //Get the starting area for reference
                var area = event.target.getScope_HackForSortable().area;
                startingArea = area;
                // fade out control when sorting
                ui.item[0].style.display = 'block';
                ui.item[0].style.opacity = '0.5';
                // reset dragged RTE settings in case a RTE isn't dragged
                draggedRteSettings = {};
                notIncludedRte = [];
                ui.item[0].style.display = 'block';
                ui.item.find('.umb-rte').each(function (key, value) {
                    var rteId = value.id;
                    // remember this RTEs settings, cause we need to update it later.
                    var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                    // save the dragged RTE settings
                    if (editor) {
                        draggedRteSettings[rteId] = editor.settings;
                        // remove the dragged RTE
                        tinyMCE.execCommand('mceRemoveEditor', false, rteId);
                    }
                });
            },
            stop: function stop(event, ui) {
                // Fade in control when sorting stops
                ui.item[0].style.opacity = '1';
                ui.item.offsetParent().find('.umb-rte').each(function (key, value) {
                    var rteId = value.id;
                    if ($.inArray(rteId, notIncludedRte) < 0) {
                        var editor = _.findWhere(tinyMCE.editors, { id: rteId });
                        if (editor) {
                            draggedRteSettings[rteId] = editor.settings;
                        }
                        // add all dragged's neighbouring RTEs in the new cell
                        notIncludedRte.splice(0, 0, rteId);
                    }
                });
                // reconstruct the dragged RTE (could be undefined when dragging something else than RTE)
                if (draggedRteSettings !== undefined) {
                    tinyMCE.init(draggedRteSettings);
                }
                _.forEach(notIncludedRte, function (rteId) {
                    // reset all the other RTEs
                    if (draggedRteSettings === undefined || rteId !== draggedRteSettings.id) {
                        tinyMCE.execCommand('mceRemoveEditor', false, rteId);
                        if (draggedRteSettings[rteId]) {
                            tinyMCE.init(draggedRteSettings[rteId]);
                        }
                    }
                });
                $scope.$apply(function () {
                    var cell = event.target.getScope_HackForSortable().area;
                    if (hasActiveChild(cell, cell.controls)) {
                        $scope.currentCellWithActiveChild = cell;
                    }
                    $scope.active = cell;
                });
            }
        };
        $scope.toggleSortMode = function () {
            $scope.sortMode = !$scope.sortMode;
            if ($scope.sortMode) {
                $scope.reorderKey = 'general_reorderDone';
            } else {
                $scope.reorderKey = 'general_reorder';
            }
        };
        $scope.showReorderButton = function () {
            if ($scope.model.value && $scope.model.value.sections) {
                for (var i = 0; $scope.model.value.sections.length > i; i++) {
                    var section = $scope.model.value.sections[i];
                    if (section.rows && section.rows.length > 0) {
                        return true;
                    }
                }
            }
        };
        // *********************************************
        // Add items overlay menu
        // *********************************************
        $scope.openEditorOverlay = function (event, area, index, key) {
            var dialog = {
                view: 'itempicker',
                filter: area.$allowedEditors.length > 15,
                availableItems: area.$allowedEditors,
                event: event,
                submit: function submit(model) {
                    if (model.selectedItem) {
                        $scope.addControl(model.selectedItem, area, index);
                        overlayService.close();
                    }
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('grid_insertControl').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
        };
        // *********************************************
        // Template management functions
        // *********************************************
        $scope.addTemplate = function (template) {
            $scope.model.value = Utilities.copy(template);
            //default row data
            _.forEach($scope.model.value.sections, function (section) {
                $scope.initSection(section);
            });
        };
        // *********************************************
        // Row management function
        // *********************************************
        $scope.clickRow = function (index, rows, $event) {
            $scope.currentRowWithActiveChild = null;
            $scope.active = rows[index];
            $event.stopPropagation();
        };
        function getAllowedLayouts(section) {
            var layouts = $scope.model.config.items.layouts;
            //This will occur if it is a new section which has been
            // created from a 'template'
            if (section.allowed && section.allowed.length > 0) {
                return _.filter(layouts, function (layout) {
                    return _.indexOf(section.allowed, layout.name) >= 0;
                });
            } else {
                return layouts;
            }
        }
        $scope.addRow = function (section, layout, isInit) {
            //copy the selected layout into the rows collection
            var row = Utilities.copy(layout);
            // Init row value
            row = $scope.initRow(row);
            // Push the new row
            if (row) {
                section.rows.push(row);
            }
            if (!isInit) {
                currentForm.$setDirty();
            }
            $scope.showRowConfigurations = false;
            eventsService.emit('grid.rowAdded', {
                scope: $scope,
                element: $element,
                row: row
            });
            if (!isInit) {
                // TODO: find a nicer way to do this without relying on setTimeout
                setTimeout(function () {
                    var newRowEl = $element.find('[data-rowid=\'' + row.$uniqueId + '\']');
                    if (newRowEl !== null) {
                        newRowEl.focus();
                    }
                }, 0);
            }
        };
        $scope.removeRow = function (section, $index) {
            if (section.rows.length > 0) {
                section.rows.splice($index, 1);
                $scope.currentRow = null;
                $scope.currentRowWithActiveChild = null;
                $scope.openRTEToolbarId = null;
                currentForm.$setDirty();
            }
            if (section.rows.length === 0) {
                $scope.showRowConfigurations = true;
            }
        };
        var shouldApply = function shouldApply(item, itemType, gridItem) {
            if (item.applyTo === undefined || item.applyTo === null || item.applyTo === '') {
                return true;
            }
            if (typeof item.applyTo === 'string') {
                return item.applyTo === itemType;
            }
            if (itemType === 'row') {
                if (item.applyTo.row === undefined) {
                    return false;
                }
                if (item.applyTo.row === null || item.applyTo.row === '') {
                    return true;
                }
                var rows = item.applyTo.row.split(',');
                return _.indexOf(rows, gridItem.name) !== -1;
            } else if (itemType === 'cell') {
                if (item.applyTo.cell === undefined) {
                    return false;
                }
                if (item.applyTo.cell === null || item.applyTo.cell === '') {
                    return true;
                }
                var cells = item.applyTo.cell.split(',');
                var cellSize = gridItem.grid.toString();
                return _.indexOf(cells, cellSize) !== -1;
            }
        };
        $scope.editGridItemSettings = function (gridItem, itemType) {
            placeHolder = '{0}';
            var styles, config;
            if (itemType === 'control') {
                styles = null;
                config = Utilities.copy(gridItem.editor.config.settings);
            } else {
                styles = _.filter(Utilities.copy($scope.model.config.items.styles), function (item) {
                    return shouldApply(item, itemType, gridItem);
                });
                config = _.filter(Utilities.copy($scope.model.config.items.config), function (item) {
                    return shouldApply(item, itemType, gridItem);
                });
            }
            if (Utilities.isObject(gridItem.config)) {
                _.each(config, function (cfg) {
                    var val = gridItem.config[cfg.key];
                    if (val) {
                        cfg.value = stripModifier(val, cfg.modifier);
                    }
                });
            }
            if (Utilities.isObject(gridItem.styles)) {
                _.each(styles, function (style) {
                    var val = gridItem.styles[style.key];
                    if (val) {
                        style.value = stripModifier(val, style.modifier);
                    }
                });
            }
            var dialogOptions = {
                view: 'views/propertyeditors/grid/dialogs/config.html',
                size: 'small',
                styles: styles,
                config: config,
                submit: function submit(model) {
                    var styleObject = {};
                    var configObject = {};
                    _.each(model.styles, function (style) {
                        if (style.value) {
                            styleObject[style.key] = addModifier(style.value, style.modifier);
                        }
                    });
                    _.each(model.config, function (cfg) {
                        cfg.alias = cfg.key;
                        cfg.label = cfg.value;
                        if (cfg.value) {
                            configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
                        }
                    });
                    gridItem.styles = styleObject;
                    gridItem.config = configObject;
                    gridItem.hasConfig = gridItemHasConfig(styleObject, configObject);
                    currentForm.$setDirty();
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            localizationService.localize('general_settings').then(function (value) {
                dialogOptions.title = value;
                editorService.open(dialogOptions);
            });
        };
        function stripModifier(val, modifier) {
            if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
                return val;
            } else {
                var paddArray = modifier.split(placeHolder);
                if (paddArray.length == 1) {
                    if (modifier.indexOf(placeHolder) === 0) {
                        return val.slice(0, -paddArray[0].length);
                    } else {
                        return val.slice(paddArray[0].length, 0);
                    }
                } else {
                    if (paddArray[1].length === 0) {
                        return val.slice(paddArray[0].length);
                    }
                    return val.slice(paddArray[0].length, -paddArray[1].length);
                }
            }
        }
        var addModifier = function addModifier(val, modifier) {
            if (!modifier || modifier.indexOf(placeHolder) < 0) {
                return val;
            } else {
                return modifier.replace(placeHolder, val);
            }
        };
        function gridItemHasConfig(styles, config) {
            if (_.isEmpty(styles) && _.isEmpty(config)) {
                return false;
            } else {
                return true;
            }
        }
        // *********************************************
        // Area management functions
        // *********************************************
        $scope.clickCell = function (index, cells, row, $event) {
            $scope.currentCellWithActiveChild = null;
            $scope.active = cells[index];
            $scope.currentRowWithActiveChild = row;
            $event.stopPropagation();
        };
        $scope.cellPreview = function (cell) {
            if (cell && cell.$allowedEditors) {
                var editor = cell.$allowedEditors[0];
                return editor.icon;
            } else {
                return 'icon-layout';
            }
        };
        // *********************************************
        // Control management functions
        // *********************************************
        $scope.clickControl = function (index, controls, cell, $event) {
            $scope.active = controls[index];
            $scope.currentCellWithActiveChild = cell;
            $event.stopPropagation();
        };
        function hasActiveChild(item, children) {
            var activeChild = false;
            for (var i = 0; children.length > i; i++) {
                var child = children[i];
                if (child.active) {
                    activeChild = true;
                }
            }
            if (activeChild) {
                return true;
            }
        }
        $scope.setUniqueId = function () {
            return String.CreateGuid();
        };
        $scope.addControl = function (editor, cell, index, initialize) {
            initialize = initialize !== false;
            var newControl = {
                value: null,
                editor: editor,
                $initializing: initialize
            };
            if (index === undefined) {
                index = cell.controls.length;
            }
            $scope.active = newControl;
            //populate control
            $scope.initControl(newControl, index + 1);
            cell.controls.push(newControl);
            eventsService.emit('grid.itemAdded', {
                scope: $scope,
                element: $element,
                cell: cell,
                item: newControl
            });
        };
        $scope.addTinyMce = function (cell) {
            var rte = $scope.getEditor('rte');
            $scope.addControl(rte, cell);
        };
        $scope.getEditor = function (alias) {
            return _.find($scope.availableEditors, function (editor) {
                return editor.alias === alias;
            });
        };
        $scope.removeControl = function (cell, $index) {
            $scope.currentControl = null;
            cell.controls.splice($index, 1);
            currentForm.$setDirty();
        };
        $scope.percentage = function (spans) {
            return (spans / $scope.model.config.items.columns * 100).toFixed(8);
        };
        $scope.clearPrompt = function (scopedObject, e) {
            scopedObject.deletePrompt = false;
            e.preventDefault();
            e.stopPropagation();
        };
        $scope.togglePrompt = function (scopedObject) {
            scopedObject.deletePrompt = !scopedObject.deletePrompt;
        };
        $scope.hidePrompt = function (scopedObject) {
            scopedObject.deletePrompt = false;
        };
        $scope.toggleAddRow = function () {
            $scope.showRowConfigurations = !$scope.showRowConfigurations;
        };
        $scope.getTemplateName = function (control) {
            var templateName = control.editor.name;
            if (control.editor.nameExp) {
                var valueOfTemplate = control.editor.nameExp(control);
                if (valueOfTemplate != '') {
                    templateName += ': ';
                    templateName += valueOfTemplate;
                }
            }
            return templateName;
        };
        // *********************************************
        // Initialization
        // these methods are called from ng-init on the template
        // so we can controll their first load data
        //
        // intialization sets non-saved data like percentage sizing, allowed editors and
        // other data that should all be pre-fixed with $ to strip it out on save
        // *********************************************
        // *********************************************
        // Init template + sections
        // *********************************************
        $scope.initContent = function () {
            var clear = true;
            //settings indicator shortcut
            if ($scope.model.config.items.config && $scope.model.config.items.config.length > 0 || $scope.model.config.items.styles && $scope.model.config.items.styles.length > 0) {
                $scope.hasSettings = true;
            }
            //ensure the grid has a column value set,
            //if nothing is found, set it to 12
            if (!$scope.model.config.items.columns) {
                $scope.model.config.items.columns = 12;
            } else if (Utilities.isString($scope.model.config.items.columns)) {
                $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
            }
            if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) {
                if ($scope.model.value.name && Utilities.isArray($scope.model.config.items.templates)) {
                    //This will occur if it is an existing value, in which case
                    // we need to determine which layout was applied by looking up
                    // the name
                    // TODO: We need to change this to an immutable ID!!
                    var found = _.find($scope.model.config.items.templates, function (t) {
                        return t.name === $scope.model.value.name;
                    });
                    if (found && Utilities.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) {
                        //Cool, we've found the template associated with our current value with matching sections counts, now we need to
                        // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't
                        // allowed for this template based on the current config.
                        _.each(found.sections, function (templateSection, index) {
                            Utilities.extend($scope.model.value.sections[index], Utilities.copy(templateSection));
                        });
                    }
                }
                _.forEach($scope.model.value.sections, function (section, index) {
                    if (section.grid > 0) {
                        $scope.initSection(section);
                        //we do this to ensure that the grid can be reset by deleting the last row
                        if (section.rows.length > 0) {
                            clear = false;
                        }
                    } else {
                        $scope.model.value.sections.splice(index, 1);
                    }
                });
            } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
                $scope.addTemplate($scope.model.config.items.templates[0]);
                clear = false;
            }
            if (clear) {
                $scope.model.value = undefined;
            }
        };
        $scope.initSection = function (section) {
            section.$percentage = $scope.percentage(section.grid);
            section.$allowedLayouts = getAllowedLayouts(section);
            if (!section.rows || section.rows.length === 0) {
                section.rows = [];
                if (section.$allowedLayouts.length === 1) {
                    $scope.addRow(section, section.$allowedLayouts[0], true);
                }
            } else {
                _.forEach(section.rows, function (row, index) {
                    if (!row.$initialized) {
                        var initd = $scope.initRow(row);
                        //if init fails, remove
                        if (!initd) {
                            section.rows.splice(index, 1);
                        } else {
                            section.rows[index] = initd;
                        }
                    }
                });
                // if there is more than one row added - hide row add tools
                $scope.showRowConfigurations = false;
            }
        };
        // *********************************************
        // Init layout / row
        // *********************************************
        $scope.initRow = function (row) {
            //merge the layout data with the original config data
            //if there are no config info on this, splice it out
            var original = _.find($scope.model.config.items.layouts, function (o) {
                return o.name === row.name;
            });
            if (!original) {
                return null;
            } else {
                //make a copy to not touch the original config
                original = Utilities.copy(original);
                original.styles = row.styles;
                original.config = row.config;
                original.hasConfig = gridItemHasConfig(row.styles, row.config);
                //sync area configuration
                _.each(original.areas, function (area, areaIndex) {
                    if (area.grid > 0) {
                        var currentArea = row.areas[areaIndex];
                        if (currentArea) {
                            area.config = currentArea.config;
                            area.styles = currentArea.styles;
                            area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config);
                        }
                        //set editor permissions
                        if (!area.allowed || area.allowAll === true) {
                            area.$allowedEditors = $scope.availableEditors;
                            area.$allowsRTE = true;
                        } else {
                            area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
                                return _.indexOf(area.allowed, editor.alias) >= 0;
                            });
                            if (_.indexOf(area.allowed, 'rte') >= 0) {
                                area.$allowsRTE = true;
                            }
                        }
                        //copy over existing controls into the new areas
                        if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
                            area.controls = currentArea.controls;
                            _.forEach(area.controls, function (control, controlIndex) {
                                $scope.initControl(control, controlIndex);
                            });
                        } else {
                            //if empty
                            area.controls = [];
                            //if only one allowed editor
                            if (area.$allowedEditors.length === 1) {
                                $scope.addControl(area.$allowedEditors[0], area, 0, false);
                            }
                        }
                        //set width
                        area.$percentage = $scope.percentage(area.grid);
                        area.$uniqueId = $scope.setUniqueId();
                    } else {
                        original.areas.splice(areaIndex, 1);
                    }
                });
                //replace the old row
                original.$initialized = true;
                //set a disposable unique ID
                original.$uniqueId = $scope.setUniqueId();
                //set a no disposable unique ID (util for row styling)
                original.id = !row.id ? $scope.setUniqueId() : row.id;
                return original;
            }
        };
        // *********************************************
        // Init control
        // *********************************************
        $scope.initControl = function (control, index) {
            control.$index = index;
            control.$uniqueId = $scope.setUniqueId();
            //error handling in case of missing editor..
            //should only happen if stripped earlier
            if (!control.editor) {
                control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
            }
            if (!control.$editorPath) {
                var editorConfig = $scope.getEditor(control.editor.alias);
                if (editorConfig) {
                    control.editor = editorConfig;
                    //if its an absolute path
                    if (control.editor.view.startsWith('/') || control.editor.view.startsWith('~/')) {
                        control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
                    } else {
                        //use convention
                        control.$editorPath = 'views/propertyeditors/grid/editors/' + control.editor.view + '.html';
                    }
                } else {
                    control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
                }
            }
        };
        gridService.getGridEditors().then(function (response) {
            $scope.availableEditors = response.data;
            //Localize the grid editor names
            $scope.availableEditors.forEach(function (value) {
                //If no translation is provided, keep using the editor name from the manifest
                localizationService.localize('grid_' + value.alias, undefined, value.name).then(function (v) {
                    value.name = v;
                });
                // setup nametemplate
                value.nameExp = !!value.nameTemplate ? $interpolate(value.nameTemplate) : undefined;
            });
            $scope.contentReady = true;
            // *********************************************
            // Init grid
            // *********************************************
            eventsService.emit('grid.initializing', {
                scope: $scope,
                element: $element
            });
            $scope.initContent();
            eventsService.emit('grid.initialized', {
                scope: $scope,
                element: $element
            });
        });
        //Clean the grid value before submitting to the server, we don't need
        // all of that grid configuration in the value to be stored!! All of that
        // needs to be merged in at runtime to ensure that the real config values are used
        // if they are ever updated.
        var unsubscribe = $scope.$on('formSubmitting', function (e, args) {
            if (args.action === 'save' && $scope.model.value && $scope.model.value.sections) {
                _.each($scope.model.value.sections, function (section) {
                    if (section.rows) {
                        _.each(section.rows, function (row) {
                            if (row.areas) {
                                _.each(row.areas, function (area) {
                                    //Remove the 'editors' - these are the allowed editors, these will
                                    // be injected at runtime to this editor, it should not be persisted
                                    if (area.editors) {
                                        delete area.editors;
                                    }
                                    if (area.controls) {
                                        _.each(area.controls, function (control) {
                                            if (control.editor) {
                                                //replace
                                                var alias = control.editor.alias;
                                                control.editor = { alias: alias };
                                            }
                                        });
                                    }
                                });
                            }
                        });
                    }
                });
            }
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    });
    'use strict';
    (function () {
        'use strict';
        function umbGridHackScope() {
            function link($scope, $element) {
                // Since the grid used the el.scope() method, which should only be used by debugging, and only are avilable in debug-mode. I had to make a replica method doig the same:
                $element[0].getScope_HackForSortable = function () {
                    return $scope;
                };
            }
            var directive = {
                restrict: 'A',
                link: link
            };
            return directive;
        }
        angular.module('umbraco').directive('umbGridHackScope', umbGridHackScope);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditorController', function ($scope, gridService, editorService, localizationService, overlayService) {
        var vm = this;
        vm.configureTemplate = configureTemplate;
        vm.deleteTemplate = deleteTemplate;
        vm.configureLayout = configureLayout;
        vm.deleteLayout = deleteLayout;
        vm.toggleCollection = toggleCollection;
        vm.percentage = percentage;
        vm.zeroWidthFilter = zeroWidthFilter;
        vm.removeConfigValue = removeConfigValue;
        vm.editConfig = editConfig;
        vm.editStyles = editStyles;
        var emptyModel = {
            styles: [{
                    label: 'Set a background image',
                    description: 'Set a row background',
                    key: 'background-image',
                    view: 'imagepicker',
                    modifier: 'url({0})'
                }],
            config: [{
                    label: 'Class',
                    description: 'Set a css class',
                    key: 'class',
                    view: 'textstring'
                }],
            columns: 12,
            templates: [
                {
                    name: '1 column layout',
                    sections: [{ grid: 12 }]
                },
                {
                    name: '2 column layout',
                    sections: [
                        { grid: 4 },
                        { grid: 8 }
                    ]
                }
            ],
            layouts: [
                {
                    label: 'Headline',
                    name: 'Headline',
                    areas: [{
                            grid: 12,
                            editors: ['headline']
                        }]
                },
                {
                    label: 'Article',
                    name: 'Article',
                    areas: [
                        { grid: 4 },
                        { grid: 8 }
                    ]
                }
            ]
        };
        /****************
      Template
  *****************/
        function configureTemplate(template) {
            var index = $scope.model.value.templates.indexOf(template);
            if (template === undefined) {
                template = {
                    name: '',
                    sections: []
                };
            }
            var layoutConfigOverlay = {
                currentLayout: Utilities.copy(template),
                rows: $scope.model.value.layouts,
                columns: $scope.model.value.columns,
                view: 'views/propertyEditors/grid/dialogs/layoutconfig.html',
                size: 'small',
                submit: function submit(model) {
                    if (index === -1) {
                        $scope.model.value.templates.push(model);
                    } else {
                        $scope.model.value.templates[index] = model;
                    }
                    editorService.close();
                },
                close: function close(model) {
                    editorService.close();
                }
            };
            editorService.open(layoutConfigOverlay);
        }
        function deleteTemplate(template, index, event) {
            var dialog = {
                view: 'views/propertyEditors/grid/overlays/layoutdeleteconfirm.html',
                layout: template,
                submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                submitButtonStyle: 'danger',
                submit: function submit(model) {
                    $scope.model.value.templates.splice(index, 1);
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('general_delete').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
            event.preventDefault();
            event.stopPropagation();
        }
        /****************
      Row
  *****************/
        function configureLayout(layout) {
            var index = $scope.model.value.layouts.indexOf(layout);
            if (layout === undefined) {
                layout = {
                    name: '',
                    areas: []
                };
            }
            var rowConfigOverlay = {
                currentRow: Utilities.copy(layout),
                editors: $scope.editors,
                columns: $scope.model.value.columns,
                view: 'views/propertyEditors/grid/dialogs/rowconfig.html',
                size: 'small',
                submit: function submit(model) {
                    if (index === -1) {
                        $scope.model.value.layouts.push(model);
                    } else {
                        $scope.model.value.layouts[index] = model;
                    }
                    editorService.close();
                },
                close: function close(model) {
                    editorService.close();
                }
            };
            editorService.open(rowConfigOverlay);
        }
        function deleteLayout(layout, index, event) {
            var dialog = {
                view: 'views/propertyEditors/grid/overlays/rowdeleteconfirm.html',
                layout: layout,
                submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                submitButtonStyle: 'danger',
                submit: function submit(model) {
                    $scope.model.value.layouts.splice(index, 1);
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            localizationService.localize('general_delete').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
            event.preventDefault();
            event.stopPropagation();
        }
        /****************
      Utilities
  *****************/
        function toggleCollection(collection, toggle) {
            if (toggle) {
                collection = [];
            } else {
                collection = null;
            }
        }
        ;
        function percentage(spans) {
            return (spans / $scope.model.value.columns * 100).toFixed(8);
        }
        function zeroWidthFilter(cell) {
            return cell.grid > 0;
        }
        /****************
      Config
  *****************/
        function removeConfigValue(collection, index) {
            collection.splice(index, 1);
        }
        var editConfigCollection = function editConfigCollection(configValues, title, callback) {
            var editConfigCollectionOverlay = {
                config: configValues,
                title: title,
                view: 'views/propertyeditors/grid/dialogs/editconfig.html',
                size: 'small',
                submit: function submit(model) {
                    callback(model.config);
                    editorService.close();
                },
                close: function close(model) {
                    editorService.close();
                }
            };
            editorService.open(editConfigCollectionOverlay);
        };
        function editConfig() {
            editConfigCollection($scope.model.value.config, 'Settings', function (data) {
                $scope.model.value.config = data;
            });
        }
        function editStyles() {
            editConfigCollection($scope.model.value.styles, 'Styling', function (data) {
                $scope.model.value.styles = data;
            });
        }
        /****************
      Editors
  *****************/
        gridService.getGridEditors().then(function (response) {
            $scope.editors = response.data;
        });
        /* Init grid data */
        if (!$scope.model.value || $scope.model.value === '' || !$scope.model.value.templates) {
            $scope.model.value = emptyModel;
        } else {
            if (!$scope.model.value.columns) {
                $scope.model.value.columns = emptyModel.columns;
            }
            if (!$scope.model.value.config) {
                $scope.model.value.config = [];
            }
            if (!$scope.model.value.styles) {
                $scope.model.value.styles = [];
            }
        }
        /****************
      Clean up
  *****************/
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var ts = $scope.model.value.templates;
            var ls = $scope.model.value.layouts;
            _.each(ts, function (t) {
                _.each(t.sections, function (section, index) {
                    if (section.grid === 0) {
                        t.sections.splice(index, 1);
                    }
                });
            });
            _.each(ls, function (l) {
                _.each(l.areas, function (area, index) {
                    if (area.grid === 0) {
                        l.areas.splice(index, 1);
                    }
                });
            });
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.IdWithGuidValueController
 * @function
 * 
 * @description
 * The controller for the idwithguid property editor, which formats the ID as normal
 * with the GUID in smaller text below, as used across the backoffice.
*/
    function IdWithGuidValueController($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if ($scope.model.value.length > 1) {
                $scope.displayid = $scope.model.value[0];
                $scope.displayguid = $scope.model.value[1];
            } else {
                $scope.displayid = $scope.model.value;
            }
        }
        //format the display value on init:
        formatDisplayValue();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.IdWithGuidValueController', IdWithGuidValueController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ImageCropperController', function ($scope, fileManager, $timeout, mediaHelper) {
        var config = Utilities.copy($scope.model.config);
        $scope.filesSelected = onFileSelected;
        $scope.filesChanged = onFilesChanged;
        $scope.fileUploaderInit = onFileUploaderInit;
        $scope.imageLoaded = imageLoaded;
        $scope.crop = crop;
        $scope.done = done;
        $scope.clear = clear;
        $scope.reset = reset;
        $scope.close = close;
        $scope.isCustomCrop = isCustomCrop;
        $scope.focalPointChanged = focalPointChanged;
        //declare a special method which will be called whenever the value has changed from the server
        $scope.model.onValueChanged = onValueChanged;
        var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
        $scope.acceptFileExt = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes);
        /**
   * Called when the umgImageGravity component updates the focal point value
   * @param {any} left
   * @param {any} top
   */
        function focalPointChanged(left, top) {
            //update the model focalpoint value
            $scope.model.value.focalPoint = {
                left: left,
                top: top
            };
            //set form to dirty to track changes
            setDirty();
        }
        /**
   * Used to assign a new model value
   * @param {any} src
   */
        function setModelValueWithSrc(src) {
            if (!$scope.model.value || !$scope.model.value.src) {
                //we are copying to not overwrite the original config
                $scope.model.value = Utilities.extend(Utilities.copy($scope.model.config), { src: src });
            }
        }
        /**
   * called whenever the value has changed from the server
   * @param {any} newVal
   * @param {any} oldVal
   */
        function onValueChanged(newVal, oldVal) {
            //clear current uploaded files
            fileManager.setFiles({
                propertyAlias: $scope.model.alias,
                culture: $scope.model.culture,
                segment: $scope.model.segment,
                files: []
            });
        }
        /**
   * Called when the a new file is selected
   * @param {any} value
   */
        function onFileSelected(value, files) {
            setModelValueWithSrc(value);
            //set form to dirty to track changes
            setDirty();
        }
        function setDirty() {
            if ($scope.imageCropperForm) {
                $scope.imageCropperForm.modelValue.$setDirty();
            }
        }
        function imageLoaded(isCroppable, hasDimensions) {
            $scope.isCroppable = isCroppable;
            $scope.hasDimensions = hasDimensions;
        }
        ;
        /**
   * Called when the file collection changes
   * @param {any} value
   * @param {any} files
   */
        function onFilesChanged(files) {
            if (files && files[0]) {
                $scope.imageSrc = files[0].fileSrc;
                //set form to dirty to track changes
                setDirty();
            }
        }
        /**
   * Called when the file uploader initializes
   * @param {any} value
   */
        function onFileUploaderInit(value, files) {
            //move previously saved value to the editor
            if ($scope.model.value) {
                //backwards compat with the old file upload (incase some-one swaps them..)
                if (Utilities.isString($scope.model.value)) {
                    setModelValueWithSrc($scope.model.value);
                } else {
                    //sync any config changes with the editor and drop outdated crops
                    _.each($scope.model.value.crops, function (saved) {
                        var configured = _.find(config.crops, function (item) {
                            return item.alias === saved.alias;
                        });
                        if (configured && configured.height === saved.height && configured.width === saved.width) {
                            configured.coordinates = saved.coordinates;
                        }
                    });
                    $scope.model.value.crops = config.crops;
                    //restore focalpoint if missing
                    if (!$scope.model.value.focalPoint) {
                        $scope.model.value.focalPoint = {
                            left: 0.5,
                            top: 0.5
                        };
                    }
                }
                //if there are already files in the client assigned then set the src
                if (files && files[0]) {
                    $scope.imageSrc = files[0].fileSrc;
                } else {
                    $scope.imageSrc = $scope.model.value.src;
                }
            }
        }
        /**
   * crop a specific crop
   * @param {any} targetCrop
   */
        function crop(targetCrop) {
            if (!$scope.currentCrop) {
                // clone the crop so we can discard the changes
                $scope.currentCrop = Utilities.copy(targetCrop);
                $scope.currentPoint = null;
                //set form to dirty to track changes
                setDirty();
            } else {
                // we have a crop open already - close the crop (this will discard any changes made)
                close();
                // the crop editor needs a digest cycle to close down properly, otherwise its state
                // is reused for the new crop... and that's really bad
                $timeout(function () {
                    crop(targetCrop);
                    $scope.pendingCrop = false;
                });
                // this is necessary to keep the screen from flickering too badly while we wait for the new crop to open
                // - check the view for its usage (basically it makes sure we keep the space reserved for the new crop)
                $scope.pendingCrop = true;
            }
        }
        ;
        /** done cropping */
        function done() {
            if (!$scope.currentCrop) {
                return;
            }
            // find the original crop by crop alias and update its coordinates
            var editedCrop = _.find($scope.model.value.crops, function (crop) {
                return crop.alias === $scope.currentCrop.alias;
            });
            editedCrop.coordinates = $scope.currentCrop.coordinates;
            $scope.close();
            //set form to dirty to track changes
            setDirty();
        }
        ;
        function reset() {
            $scope.currentCrop.coordinates = undefined;
            $scope.done();
        }
        function close() {
            $scope.currentCrop = undefined;
            $scope.currentPoint = undefined;
        }
        /**
   * crop a specific crop
   * @param {any} crop
   */
        function clear(crop) {
            //clear current uploaded files
            fileManager.setFiles({
                propertyAlias: $scope.model.alias,
                culture: $scope.model.culture,
                segment: $scope.model.segment,
                files: []
            });
            //clear the ui
            $scope.imageSrc = null;
            if ($scope.model.value) {
                $scope.model.value = null;
            }
            //set form to dirty to track changes
            setDirty();
        }
        ;
        function isCustomCrop(crop) {
            return !!crop.coordinates;
        }
        var unsubscribe = $scope.$on('formSubmitting', function () {
            $scope.currentCrop = null;
            $scope.currentPoint = null;
        });
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }).run(function (mediaHelper, umbRequestHelper) {
        if (mediaHelper && mediaHelper.registerFileResolver) {
            //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
            // they contain different data structures so if we need to query against it we need to be aware of this.
            mediaHelper.registerFileResolver('Umbraco.ImageCropper', function (property, entity, thumbnail) {
                if (property.value && property.value.src) {
                    if (thumbnail === true) {
                        return property.value.src + '?width=500&mode=max&animationprocessmode=first';
                    } else {
                        return property.value.src;
                    }    //this is a fallback in case the cropper has been asssigned a upload field
                } else if (Utilities.isString(property.value)) {
                    if (thumbnail) {
                        if (mediaHelper.detectIfImageByExtension(property.value)) {
                            var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: property.value }]);
                            return thumbnailUrl;
                        } else {
                            return null;
                        }
                    } else {
                        return property.value;
                    }
                }
                return null;
            });
        }
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.CropSizesController', function ($scope) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.editMode = false;
        $scope.setFocus = false;
        $scope.remove = function (item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.alias === item.alias;
            });
        };
        $scope.edit = function (item, evt) {
            evt.preventDefault();
            $scope.editMode = true;
            $scope.setFocus = false;
            $scope.newItem = item;
        };
        $scope.cancel = function (evt) {
            evt.preventDefault();
            $scope.editMode = false;
            $scope.setFocus = true;
            $scope.newItem = null;
        };
        $scope.change = function () {
            // Listen to the change event and set focus 2 false
            if ($scope.setFocus) {
                $scope.setFocus = false;
                return;
            }
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            $scope.editMode = false;
            $scope.setFocus = true;
            if ($scope.newItem && $scope.newItem.alias && Utilities.isNumber($scope.newItem.width) && Utilities.isNumber($scope.newItem.height) && $scope.newItem.width > 0 && $scope.newItem.height > 0) {
                var exists = _.find($scope.model.value, function (item) {
                    return $scope.newItem.alias === item.alias;
                });
                if (!exists) {
                    $scope.model.value.push($scope.newItem);
                    $scope.newItem = {};
                    $scope.hasError = false;
                    $scope.cropAdded = false;
                    return;
                } else {
                    $scope.newItem = null;
                    $scope.hasError = false;
                    return;
                }
            }
            //there was an error, do the highlight (will be set back by the directive)
            $scope.hasError = true;
        };
        $scope.createNew = function (event) {
            if (event.keyCode == 13) {
                $scope.add(event);
            }
        };
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            tolerance: 'pointer'
        };
    });
    'use strict';
    function iconPreValsController($scope, editorService) {
        if (!$scope.model.value) {
            $scope.model.value = 'icon-list';
        }
        var valueArray = $scope.model.value.split(' ');
        $scope.icon = valueArray[0];
        $scope.color = valueArray[1];
        $scope.openIconPicker = function () {
            var iconPicker = {
                icon: $scope.icon,
                color: $scope.color,
                size: 'medium',
                submit: function submit(model) {
                    if (model.icon) {
                        if (model.color) {
                            $scope.model.value = model.icon + ' ' + model.color;
                            $scope.color = model.color;
                        } else {
                            $scope.model.value = model.icon;
                        }
                        $scope.icon = model.icon;
                        $scope.iconForm.$setDirty();
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.iconPicker(iconPicker);
        };
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.IconPickerController', iconPreValsController);
    'use strict';
    function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.hasError = false;
        $scope.errorMsg = '';
        $scope.propertyAliases = [];
        $scope.selectedField = null;
        $scope.systemFields = [
            { value: 'sortOrder' },
            { value: 'updateDate' },
            { value: 'updater' },
            { value: 'createDate' },
            { value: 'owner' },
            { value: 'published' },
            { value: 'contentTypeAlias' },
            { value: 'email' },
            { value: 'username' }
        ];
        $scope.getLocalizedKey = function (alias) {
            switch (alias) {
            case 'name':
                return 'general_name';
            case 'sortOrder':
                return 'general_sort';
            case 'updateDate':
                return 'content_updateDate';
            case 'updater':
                return 'content_updatedBy';
            case 'createDate':
                return 'content_createDate';
            case 'owner':
                return 'content_createBy';
            case 'published':
                return 'content_isPublished';
            case 'contentTypeAlias':
                //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'
                return 'content_documentType';
            case 'email':
                return 'general_email';
            case 'username':
                return 'general_username';
            }
            return alias;
        };
        $scope.changeField = function () {
            $scope.hasError = false;
            $scope.errorMsg = '';
        };
        $scope.removeField = function (e) {
            var index = $scope.model.value.indexOf(e);
            $scope.model.value.splice(index, 1);
        };
        //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
        _.each($scope.systemFields, function (e, i) {
            var key = $scope.getLocalizedKey(e.value);
            localizationService.localize(key).then(function (v) {
                e.name = v;
                switch (e.value) {
                case 'updater':
                    e.name += ' (Content only)';
                    break;
                case 'published':
                    e.name += ' (Content only)';
                    break;
                case 'email':
                    e.name += ' (Members only)';
                    break;
                case 'username':
                    e.name += ' (Members only)';
                    break;
                }
            });
        });
        // Return a helper with preserved width of cells
        var fixHelper = function fixHelper(e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            var row = ui.clone();
            row.css('background-color', 'lightgray');
            return row;
        };
        $scope.sortableOptions = {
            helper: fixHelper,
            handle: '.handle',
            opacity: 0.5,
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> tr',
            tolerance: 'pointer',
            forcePlaceholderSize: true,
            start: function start(e, ui) {
                ui.placeholder.height(ui.item.height());
            },
            update: function update(e, ui) {
                // Get the new and old index for the moved element (using the text as the identifier)
                var newIndex = ui.item.index();
                var movedAlias = $('.alias-value', ui.item).text().trim();
                var originalIndex = getAliasIndexByText(movedAlias);
                // Move the element in the model
                if (originalIndex > -1) {
                    var movedElement = $scope.model.value[originalIndex];
                    $scope.model.value.splice(originalIndex, 1);
                    $scope.model.value.splice(newIndex, 0, movedElement);
                }
            }
        };
        contentTypeResource.getAllPropertyTypeAliases().then(function (data) {
            $scope.propertyAliases = data;
        });
        $scope.addField = function () {
            var val = $scope.selectedField;
            if (val) {
                var isSystem = val.startsWith('_system_');
                if (isSystem) {
                    val = val.trimStart('_system_');
                }
                var exists = _.find($scope.model.value, function (i) {
                    return i.alias === val;
                });
                if (!exists) {
                    $scope.hasError = false;
                    $scope.errorMsg = '';
                    $scope.model.value.push({
                        alias: val,
                        isSystem: isSystem ? 1 : 0
                    });
                } else {
                    //there was an error, do the highlight (will be set back by the directive)
                    $scope.hasError = true;
                    $scope.errorMsg = 'Property is already added';
                }
            } else {
                $scope.hasError = true;
                $scope.errorMsg = 'No property selected';
            }
        };
        function getAliasIndexByText(value) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].alias === value) {
                    return i;
                }
            }
            return -1;
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.IncludePropertiesListViewController', includePropsPreValsController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController
 * @function
 *
 * @description
 * The controller for configuring layouts for list views
 */
    (function () {
        'use strict';
        function ListViewLayoutsPreValsController($scope, editorService, localizationService, overlayService) {
            var vm = this;
            vm.focusLayoutName = false;
            vm.layoutsSortableOptions = {
                axis: 'y',
                containment: 'parent',
                distance: 10,
                tolerance: 'pointer',
                opacity: 0.7,
                scroll: true,
                cursor: 'move',
                handle: '.list-view-layout__sort-handle'
            };
            vm.addLayout = addLayout;
            vm.removeLayout = removeLayout;
            vm.openIconPicker = openIconPicker;
            function addLayout() {
                vm.focusLayoutName = false;
                var layout = {
                    'name': '',
                    'path': '',
                    'icon': 'icon-stop',
                    'selected': true
                };
                $scope.model.value.push(layout);
            }
            function removeLayout(template, index, event) {
                var dialog = {
                    view: 'views/propertyEditors/listview/overlays/removeListViewLayout.html',
                    layout: template,
                    submitButtonLabelKey: 'defaultdialogs_yesRemove',
                    submitButtonStyle: 'danger',
                    submit: function submit(model) {
                        $scope.model.value.splice(index, 1);
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                localizationService.localize('general_remove').then(function (value) {
                    dialog.title = value;
                    overlayService.open(dialog);
                });
                event.preventDefault();
                event.stopPropagation();
            }
            function openIconPicker(layout) {
                var iconPicker = {
                    icon: layout.icon.split(' ')[0],
                    color: layout.icon.split(' ')[1],
                    size: 'medium',
                    submit: function submit(model) {
                        if (model.icon) {
                            if (model.color) {
                                layout.icon = model.icon + ' ' + model.color;
                            } else {
                                layout.icon = model.icon;
                            }
                        }
                        vm.focusLayoutName = true;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.iconPicker(iconPicker);
            }
        }
        angular.module('umbraco').controller('Umbraco.PrevalueEditors.ListViewLayoutsPreValsController', ListViewLayoutsPreValsController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.EditController
 * @function
 *
 * @description
 * The controller for the content type editor
 */
    (function () {
        'use strict';
        function ListViewGridLayoutController($scope, mediaHelper, $location, listViewHelper, mediaTypeHelper) {
            var vm = this;
            var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
            vm.nodeId = $scope.contentId;
            // Use list of allowed file types if provided
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
            if (vm.acceptedFileTypes === '') {
                // If not provided, we pass in a disallowed list by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
            vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
            vm.activeDrag = false;
            vm.mediaDetailsTooltip = {};
            vm.itemsWithoutFolders = [];
            vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
            vm.acceptedMediatypes = [];
            vm.dragEnter = dragEnter;
            vm.dragLeave = dragLeave;
            vm.onFilesQueue = onFilesQueue;
            vm.onUploadComplete = onUploadComplete;
            vm.hoverMediaItemDetails = hoverMediaItemDetails;
            vm.selectContentItem = selectContentItem;
            vm.selectItem = selectItem;
            vm.selectFolder = selectFolder;
            vm.goToItem = goToItem;
            function activate() {
                vm.itemsWithoutFolders = filterOutFolders($scope.items);
                //no need to make another REST/DB call if this data is not used when we are browsing the bin
                if ($scope.entityType === 'media' && !vm.isRecycleBin) {
                    mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                        vm.acceptedMediatypes = types;
                    });
                }
            }
            function filterOutFolders(items) {
                var newArray = [];
                if (items && items.length) {
                    for (var i = 0; items.length > i; i++) {
                        var item = items[i];
                        var isFolder = !mediaHelper.hasFilePropertyType(item);
                        if (!isFolder) {
                            newArray.push(item);
                        }
                    }
                }
                return newArray;
            }
            function dragEnter(el, event) {
                vm.activeDrag = true;
            }
            function dragLeave(el, event) {
                vm.activeDrag = false;
            }
            function onFilesQueue() {
                vm.activeDrag = false;
            }
            function onUploadComplete() {
                $scope.getContent($scope.contentId);
            }
            function hoverMediaItemDetails(item, event, hover) {
                if (hover && !vm.mediaDetailsTooltip.show) {
                    vm.mediaDetailsTooltip.event = event;
                    vm.mediaDetailsTooltip.item = item;
                    vm.mediaDetailsTooltip.show = true;
                } else if (!hover && vm.mediaDetailsTooltip.show) {
                    vm.mediaDetailsTooltip.show = false;
                }
            }
            function selectContentItem(item, $event, $index) {
                listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event);
            }
            function selectItem(item, $event, $index) {
                listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event);
            }
            function selectFolder(folder, $event, $index) {
                listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event);
            }
            function goToItem(item, $event, $index) {
                listViewHelper.editItem(item, $scope);
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.GridLayoutController', ListViewGridLayoutController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ListViewListLayoutController($scope, listViewHelper, mediaHelper, mediaTypeHelper, urlHelper) {
            var vm = this;
            var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
            vm.nodeId = $scope.contentId;
            // Use list of allowed file types if provided
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
            if (vm.acceptedFileTypes === '') {
                // If not provided, we pass in a disallowed list by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
            vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
            vm.activeDrag = false;
            vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
            vm.acceptedMediatypes = [];
            vm.selectItem = selectItem;
            vm.clickItem = clickItem;
            vm.selectAll = selectAll;
            vm.isSelectedAll = isSelectedAll;
            vm.isSortDirection = isSortDirection;
            vm.sort = sort;
            vm.dragEnter = dragEnter;
            vm.dragLeave = dragLeave;
            vm.onFilesQueue = onFilesQueue;
            vm.onUploadComplete = onUploadComplete;
            markAsSensitive();
            function activate() {
                if ($scope.entityType === 'media') {
                    mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                        vm.acceptedMediatypes = types;
                    });
                }
            }
            function selectAll() {
                listViewHelper.selectAllItemsToggle($scope.items, $scope.selection);
            }
            function isSelectedAll() {
                return listViewHelper.isSelectedAll($scope.items, $scope.selection);
            }
            function selectItem(selectedItem, $index, $event) {
                listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event);
            }
            function clickItem(item) {
                listViewHelper.editItem(item, $scope);
            }
            function isSortDirection(col, direction) {
                return listViewHelper.setSortingDirection(col, direction, $scope.options);
            }
            function sort(field, allow, isSystem) {
                if (allow) {
                    $scope.options.orderBySystemField = isSystem;
                    listViewHelper.setSorting(field, allow, $scope.options);
                    $scope.getContent($scope.contentId);
                }
            }
            // Dropzone upload functions
            function dragEnter(el, event) {
                vm.activeDrag = true;
            }
            function dragLeave(el, event) {
                vm.activeDrag = false;
            }
            function onFilesQueue() {
                vm.activeDrag = false;
            }
            function onUploadComplete() {
                $scope.getContent($scope.contentId);
            }
            function markAsSensitive() {
                $scope.options.includeProperties.forEach(function (option) {
                    option.isSensitive = false;
                    if ($scope.items && $scope.items.length) {
                        $scope.items.forEach(function (item) {
                            item.properties.forEach(function (property) {
                                if (option.alias === property.alias) {
                                    option.isSensitive = property.isSensitive;
                                }
                            });
                        });
                    }
                });
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.ListLayoutController', ListViewListLayoutController);
    }());
    'use strict';
    function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper, eventsService) {
        //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
        // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
        // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
        // the list view tab entirely when it's new.
        if ($routeParams.create) {
            $scope.isNew = true;
            return;
        }
        //Now we need to check if this is for media, members or content because that will depend on the resources we use
        var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
        //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
        if ($scope.model.config.entityType && $scope.model.config.entityType === 'member' || appState.getSectionState('currentSection') === 'member') {
            $scope.entityType = 'member';
            contentResource = $injector.get('memberResource');
            getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
            getListResultsCallback = contentResource.getPagedResults;
            deleteItemCallback = contentResource.deleteByKey;
            getIdCallback = function getIdCallback(selected) {
                return selected.key;
            };
            createEditUrlCallback = function createEditUrlCallback(item) {
                return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.key + '?page=' + $scope.options.pageNumber + '&listName=' + $scope.contentId;
            };
        } else {
            //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
            if ($scope.model.config.entityType && $scope.model.config.entityType === 'media' || appState.getSectionState('currentSection') === 'media') {
                $scope.entityType = 'media';
                contentResource = $injector.get('mediaResource');
                getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;
            } else {
                $scope.entityType = 'content';
                contentResource = $injector.get('contentResource');
                getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;
            }
            getListResultsCallback = contentResource.getChildren;
            deleteItemCallback = contentResource.deleteById;
            getIdCallback = function getIdCallback(selected) {
                return selected.id;
            };
            createEditUrlCallback = function createEditUrlCallback(item) {
                return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.id + '?list=' + $routeParams.id + '&page=' + $scope.options.pageNumber + '&filter=' + $scope.options.filter + '&orderBy=' + $scope.options.orderBy + '&orderDirection=' + $scope.options.orderDirection;
            };
        }
        $scope.pagination = [];
        $scope.isNew = false;
        $scope.actionInProgress = false;
        $scope.selection = [];
        $scope.folders = [];
        $scope.page = { createDropdownOpen: false };
        $scope.listViewResultSet = {
            totalPages: 0,
            items: []
        };
        $scope.createAllowedButtonSingle = false;
        $scope.createAllowedButtonSingleWithBlueprints = false;
        $scope.createAllowedButtonMultiWithBlueprints = false;
        //when this is null, we don't check permissions
        $scope.currentNodePermissions = null;
        if ($scope.entityType === 'content') {
            //Just ensure we do have an editorState
            if (editorState.current) {
                //Fetch current node allowed actions for the current user
                //This is the current node & not each individual child node in the list
                var currentUserPermissions = editorState.current.allowedActions;
                //Create a nicer model rather than the funky & hard to remember permissions strings
                $scope.currentNodePermissions = {
                    'canCopy': _.contains(currentUserPermissions, 'O'),
                    //Magic Char = O
                    'canCreate': _.contains(currentUserPermissions, 'C'),
                    //Magic Char = C
                    'canDelete': _.contains(currentUserPermissions, 'D'),
                    //Magic Char = D
                    'canMove': _.contains(currentUserPermissions, 'M'),
                    //Magic Char = M
                    'canPublish': _.contains(currentUserPermissions, 'U'),
                    //Magic Char = U
                    'canUnpublish': _.contains(currentUserPermissions, 'U')    //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
                };
            }
        }
        //when this is null, we don't check permissions
        $scope.buttonPermissions = null;
        //When we are dealing with 'content', we need to deal with permissions on child nodes.
        // Currently there is no real good way to
        if ($scope.entityType === 'content') {
            var idsWithPermissions = null;
            $scope.buttonPermissions = {
                canCopy: true,
                canCreate: true,
                canDelete: true,
                canMove: true,
                canPublish: true,
                canUnpublish: true
            };
            $scope.$watch('selection.length', function (newVal, oldVal) {
                if (idsWithPermissions == null && newVal > 0 || idsWithPermissions != null) {
                    //get all of the selected ids
                    var ids = _.map($scope.selection, function (i) {
                        return i.id.toString();
                    });
                    //remove the dictionary items that don't have matching ids
                    var filtered = {};
                    _.each(idsWithPermissions, function (value, key, list) {
                        if (_.contains(ids, key)) {
                            filtered[key] = value;
                        }
                    });
                    idsWithPermissions = filtered;
                    //find all ids that we haven't looked up permissions for
                    var existingIds = _.keys(idsWithPermissions);
                    var missingLookup = _.map(_.difference(ids, existingIds), function (i) {
                        return Number(i);
                    });
                    if (missingLookup.length > 0) {
                        currentUserResource.getPermissions(missingLookup).then(function (p) {
                            $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions);
                        });
                    } else {
                        $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions);
                    }
                }
            });
        }
        var listParamsForCurrent = $routeParams.id == $routeParams.list;
        $scope.options = {
            useInfiniteEditor: $scope.model.config.useInfiniteEditor === true,
            pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
            pageNumber: listParamsForCurrent && $routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0 ? $routeParams.page : 1,
            filter: (listParamsForCurrent && $routeParams.filter ? $routeParams.filter : '').trim(),
            orderBy: (listParamsForCurrent && $routeParams.orderBy ? $routeParams.orderBy : $scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
            orderDirection: (listParamsForCurrent && $routeParams.orderDirection ? $routeParams.orderDirection : $scope.model.config.orderDirection ? $scope.model.config.orderDirection : 'desc').trim(),
            orderBySystemField: true,
            includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
                {
                    alias: 'updateDate',
                    header: 'Last edited',
                    isSystem: 1
                },
                {
                    alias: 'updater',
                    header: 'Last edited by',
                    isSystem: 1
                }
            ],
            layout: {
                layouts: $scope.model.config.layouts,
                activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts)
            },
            allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish,
            allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish,
            allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy,
            allowBulkMove: $scope.entityType !== 'member' && $scope.model.config.bulkActionPermissions.allowBulkMove,
            allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete,
            cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture
        };
        _.each($scope.options.includeProperties, function (property) {
            property.nameExp = !!property.nameTemplate ? $interpolate(property.nameTemplate) : undefined;
        });
        //watch for culture changes in the query strings and update accordingly
        $scope.$watch(function () {
            return $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
        }, function (newVal, oldVal) {
            if (newVal && newVal !== oldVal) {
                //update the options
                $scope.options.cultureName = newVal;
                $scope.reloadView($scope.contentId);
            }
        });
        // Check if selected order by field is actually custom field
        for (var j = 0; j < $scope.options.includeProperties.length; j++) {
            var includedProperty = $scope.options.includeProperties[j];
            if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) {
                $scope.options.orderBySystemField = includedProperty.isSystem === 1;
                break;
            }
        }
        //update all of the system includeProperties to enable sorting
        _.each($scope.options.includeProperties, function (e, i) {
            e.allowSorting = true;
            // Special case for members, only the configured system fields should be enabled sorting
            // (see MemberRepository.ApplySystemOrdering)
            if (e.isSystem && $scope.entityType === 'member') {
                e.allowSorting = e.alias === 'username' || e.alias === 'email' || e.alias === 'updateDate' || e.alias === 'createDate' || e.alias === 'contentTypeAlias';
            }
            if (e.isSystem) {
                //localize the header
                var key = getLocalizedKey(e.alias);
                localizationService.localize(key).then(function (v) {
                    e.header = v;
                });
            }
        });
        $scope.selectLayout = function (layout) {
            $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, layout, $scope.model.config.layouts);
        };
        function showNotificationsAndReset(err, reload, successMsgPromise) {
            //check if response is ysod
            if (err.status && err.status >= 500) {
                // Open ysod overlay
                overlayService.ysod(err);
            }
            $timeout(function () {
                $scope.bulkStatus = '';
                $scope.actionInProgress = false;
            }, 500);
            if (successMsgPromise) {
                localizationService.localize('bulk_done').then(function (v) {
                    successMsgPromise.then(function (successMsg) {
                        notificationsService.success(v, successMsg);
                    });
                });
            }
        }
        $scope.next = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        $scope.goToPage = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        $scope.prev = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        /*Loads the search results, based on parameters set in prev,next,sort and so on*/
        /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
  with simple values */
        $scope.getContent = function (contentId) {
            $scope.reloadView($scope.contentId, true);
        };
        $scope.reloadView = function (id, reloadActiveNode) {
            if (!id) {
                return;
            }
            $scope.viewLoaded = false;
            $scope.folders = [];
            listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
            getListResultsCallback(id, $scope.options).then(function (data) {
                $scope.actionInProgress = false;
                $scope.listViewResultSet = data;
                //update all values for display
                var section = appState.getSectionState('currentSection');
                if ($scope.listViewResultSet.items) {
                    _.each($scope.listViewResultSet.items, function (e, index) {
                        setPropertyValues(e);
                        // create the folders collection (only for media list views)
                        if (section === 'media' && !mediaHelper.hasFilePropertyType(e)) {
                            $scope.folders.push(e);
                        }
                    });
                }
                $scope.viewLoaded = true;
                //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
                // 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
                // available page and then re-load again
                if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
                    $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
                    //reload!
                    $scope.reloadView(id, reloadActiveNode);
                }    // in the media section, the list view items are by default also shown in the tree, so we need 
                     // to refresh the current tree node when changing the folder contents (adding and removing)
                else if (reloadActiveNode && section === 'media') {
                    var activeNode = appState.getTreeState('selectedNode');
                    if (activeNode) {
                        if (activeNode.expanded) {
                            navigationService.reloadNode(activeNode);
                        }
                    } else {
                        navigationService.reloadSection(section);
                    }
                }
            });
        };
        $scope.makeSearch = function () {
            if ($scope.options.filter !== null && $scope.options.filter !== undefined) {
                $scope.options.pageNumber = 1;
                $scope.reloadView($scope.contentId);
            }
        };
        $scope.onSearchStartTyping = function () {
            $scope.viewLoaded = false;
        };
        $scope.selectedItemsCount = function () {
            return $scope.selection.length;
        };
        $scope.clearSelection = function () {
            listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
        };
        $scope.getIcon = function (entry) {
            return iconHelper.convertFromLegacyIcon(entry.icon);
        };
        function serial(selected, fn, getStatusMsg, index) {
            return fn(selected, index).then(function (content) {
                index++;
                getStatusMsg(index, selected.length).then(function (value) {
                    $scope.bulkStatus = value;
                });
                return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content;
            }, function (err) {
                var reload = index > 0;
                showNotificationsAndReset(err, reload);
                return err;
            });
        }
        function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) {
            var selected = $scope.selection;
            if (selected.length === 0)
                return;
            if (confirmMsg && !confirm(confirmMsg))
                return;
            $scope.actionInProgress = true;
            getStatusMsg(0, selected.length).then(function (value) {
                $scope.bulkStatus = value;
            });
            return serial(selected, fn, getStatusMsg, 0).then(function (result) {
                // executes once the whole selection has been processed
                // in case of an error (caught by serial), result will be the error
                if (!(result.data && Utilities.isArray(result.data.notifications)))
                    showNotificationsAndReset(result, true, getSuccessMsg(selected.length));
            });
        }
        $scope.delete = function (numberOfItems, totalItems) {
            var dialog = {
                view: 'views/propertyeditors/listview/overlays/delete.html',
                deletesVariants: selectionHasVariants(),
                isTrashed: $scope.isTrashed,
                submitButtonLabelKey: 'contentTypeEditor_yesDelete',
                submitButtonStyle: 'danger',
                submit: function submit(model) {
                    performDelete();
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                },
                numberOfItems: numberOfItems,
                totalItems: totalItems
            };
            localizationService.localize('general_delete').then(function (value) {
                dialog.title = value;
                overlayService.open(dialog);
            });
        };
        function performDelete() {
            applySelected(function (selected, index) {
                return deleteItemCallback(getIdCallback(selected[index]));
            }, function (count, total) {
                var key = total === 1 ? 'bulk_deletedItemOfItem' : 'bulk_deletedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_deletedItem' : 'bulk_deletedItems';
                return localizationService.localize(key, [total]);
            }).then(function () {
                $scope.reloadView($scope.contentId, true);
            });
        }
        function selectionHasVariants() {
            var variesByCulture = false;
            // check if any of the selected nodes has variants
            $scope.selection.forEach(function (selectedItem) {
                $scope.listViewResultSet.items.forEach(function (resultItem) {
                    if ((selectedItem.id === resultItem.id || selectedItem.key === resultItem.key) && resultItem.variesByCulture) {
                        variesByCulture = true;
                    }
                });
            });
            return variesByCulture;
        }
        $scope.publish = function () {
            var dialog = {
                view: 'views/propertyeditors/listview/overlays/listviewpublish.html',
                submitButtonLabelKey: 'actions_publish',
                submit: function submit(model) {
                    // create a comma separated array of selected cultures
                    var selectedCultures = [];
                    if (model.languages && model.languages.length > 0) {
                        model.languages.forEach(function (language) {
                            if (language.publish) {
                                selectedCultures.push(language.culture);
                            }
                        });
                    }
                    performPublish(selectedCultures);
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            // if any of the selected nodes has variants we want to 
            // show a dialog where the languages can be chosen
            if (selectionHasVariants()) {
                languageResource.getAll().then(function (languages) {
                    dialog.languages = languages;
                    overlayService.open(dialog);
                }, function (error) {
                    notificationsService.error(error);
                });
            } else {
                overlayService.open(dialog);
            }
        };
        function performPublish(cultures) {
            applySelected(function (selected, index) {
                return contentResource.publishById(getIdCallback(selected[index]), cultures);
            }, function (count, total) {
                var key = total === 1 ? 'bulk_publishedItemOfItem' : 'bulk_publishedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_publishedItem' : 'bulk_publishedItems';
                return localizationService.localize(key, [total]);
            }).then(function () {
                $scope.reloadView($scope.contentId);
            });
        }
        $scope.unpublish = function () {
            var dialog = {
                view: 'views/propertyeditors/listview/overlays/listviewunpublish.html',
                submitButtonLabelKey: 'actions_unpublish',
                submitButtonStyle: 'warning',
                submit: function submit(model) {
                    // create a comma separated array of selected cultures
                    var selectedCultures = [];
                    if (model.languages && model.languages.length > 0) {
                        model.languages.forEach(function (language) {
                            if (language.unpublish) {
                                selectedCultures.push(language.culture);
                            }
                        });
                    }
                    performUnpublish(selectedCultures);
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            // if any of the selected nodes has variants we want to 
            // show a dialog where the languages can be chosen
            if (selectionHasVariants()) {
                languageResource.getAll().then(function (languages) {
                    dialog.languages = languages;
                    overlayService.open(dialog);
                }, function (error) {
                    notificationsService.error(error);
                });
            } else {
                overlayService.open(dialog);
            }
        };
        function performUnpublish(cultures) {
            applySelected(function (selected, index) {
                return contentResource.unpublish(getIdCallback(selected[index]), cultures);
            }, function (count, total) {
                var key = total === 1 ? 'bulk_unpublishedItemOfItem' : 'bulk_unpublishedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_unpublishedItem' : 'bulk_unpublishedItems';
                return localizationService.localize(key, [total]);
            }).then(function () {
                $scope.reloadView($scope.contentId, true);
            });
        }
        $scope.move = function () {
            var move = {
                section: $scope.entityType,
                currentNode: $scope.contentId,
                submit: function submit(model) {
                    if (model.target) {
                        performMove(model.target);
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.move(move);
        };
        function performMove(target) {
            //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return
            // a specific value from one of the methods, so we'll have to try this way. Even though the first method
            // will fire once per every node moved, the destination path will be the same and we need to use that to sync.
            var newPath = null;
            applySelected(function (selected, index) {
                return contentResource.move({
                    parentId: target.id,
                    id: getIdCallback(selected[index])
                }).then(function (path) {
                    newPath = path;
                    return path;
                });
            }, function (count, total) {
                var key = total === 1 ? 'bulk_movedItemOfItem' : 'bulk_movedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_movedItem' : 'bulk_movedItems';
                return localizationService.localize(key, [total]);
            }).then(function () {
                //executes if all is successful, let's sync the tree
                if (newPath) {
                    // reload the current view so the moved items are no longer shown
                    $scope.reloadView($scope.contentId);
                    //we need to do a double sync here: first refresh the node where the content was moved,
                    // then refresh the node where the content was moved from
                    navigationService.syncTree({
                        tree: target.nodeType ? target.nodeType : target.metaData.treeAlias,
                        path: newPath,
                        forceReload: true,
                        activate: false
                    }).then(function (args) {
                        //get the currently edited node (if any)
                        var activeNode = appState.getTreeState('selectedNode');
                        if (activeNode) {
                            navigationService.reloadNode(activeNode);
                        }
                    });
                }
            });
        }
        $scope.copy = function () {
            var copyEditor = {
                section: $scope.entityType,
                currentNode: $scope.contentId,
                submit: function submit(model) {
                    if (model.target) {
                        performCopy(model.target, model.relateToOriginal, model.includeDescendants);
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.copy(copyEditor);
        };
        function performCopy(target, relateToOriginal, includeDescendants) {
            applySelected(function (selected, index) {
                return contentResource.copy({
                    parentId: target.id,
                    id: getIdCallback(selected[index]),
                    relateToOriginal: relateToOriginal,
                    recursive: includeDescendants
                });
            }, function (count, total) {
                var key = total === 1 ? 'bulk_copiedItemOfItem' : 'bulk_copiedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_copiedItem' : 'bulk_copiedItems';
                return localizationService.localize(key, [total]);
            });
        }
        function getCustomPropertyValue(alias, properties) {
            var value = '';
            var index = 0;
            var foundAlias = false;
            for (var i = 0; i < properties.length; i++) {
                if (properties[i].alias == alias) {
                    foundAlias = true;
                    break;
                }
                index++;
            }
            if (foundAlias) {
                value = properties[index].value;
            }
            return value;
        }
        /** 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 */
        function setPropertyValues(result) {
            //set the edit url
            result.editPath = createEditUrlCallback(result);
            _.each($scope.options.includeProperties, function (e, i) {
                var alias = e.alias;
                // First try to pull the value directly from the alias (e.g. updatedBy)
                var value = result[alias];
                // If this returns an object, look for the name property of that (e.g. owner.name)
                if (value === Object(value)) {
                    value = value['name'];
                }
                // If we've got nothing yet, look at a user defined property
                if (typeof value === 'undefined') {
                    value = getCustomPropertyValue(alias, result.properties);
                }
                // If we have a date, format it
                if (isDate(value)) {
                    value = value.substring(0, value.length - 3);
                }
                if (e.nameExp) {
                    if (/{{.*\s*\w+\s*\|\s*\w+\s*.*}}/.test(e.nameTemplate)) {
                        //check whether the name template has a filter
                        value = {
                            value: value,
                            expression: e.nameExp
                        };
                    } else {
                        var newValue = e.nameExp({ value: value });
                        if (newValue && (newValue = newValue.trim())) {
                            value = newValue;
                        }
                    }
                }
                // set what we've got on the result
                result[alias] = value;
            });
        }
        function isDate(val) {
            if (Utilities.isString(val)) {
                return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
            }
            return false;
        }
        function initView() {
            var id = $routeParams.id;
            if (id === undefined) {
                // no ID found in route params - don't list anything as we don't know for sure where we are
                return;
            }
            // Get current id for node to load it's children
            $scope.contentId = editorState.current ? editorState.current.id : id;
            $scope.isTrashed = editorState.current ? editorState.current.trashed : id === '-20' || id === '-21';
            $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
            $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed;
            $scope.options.allowBulkCopy = $scope.options.allowBulkCopy && !$scope.isTrashed;
            $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || $scope.options.allowBulkUnpublish || $scope.options.allowBulkCopy || $scope.options.allowBulkMove || $scope.options.allowBulkDelete;
            if ($scope.isTrashed === false) {
                getContentTypesCallback(id).then(function (listViewAllowedTypes) {
                    $scope.listViewAllowedTypes = listViewAllowedTypes;
                    var blueprints = false;
                    _.each(listViewAllowedTypes, function (allowedType) {
                        if (_.isEmpty(allowedType.blueprints)) {
                            // this helps the view understand that there are no blueprints available
                            allowedType.blueprints = null;
                        } else {
                            blueprints = true;
                            // turn the content type blueprints object into an array of sortable objects for the view
                            allowedType.blueprints = _.map(_.pairs(allowedType.blueprints || {}), function (pair) {
                                return {
                                    id: pair[0],
                                    name: pair[1]
                                };
                            });
                        }
                    });
                    if (listViewAllowedTypes.length === 1 && blueprints === false) {
                        $scope.createAllowedButtonSingle = true;
                    }
                    if (listViewAllowedTypes.length === 1 && blueprints === true) {
                        $scope.createAllowedButtonSingleWithBlueprints = true;
                    }
                    if (listViewAllowedTypes.length > 1) {
                        $scope.createAllowedButtonMultiWithBlueprints = true;
                    }
                });
            }
            $scope.reloadView($scope.contentId);
        }
        function getLocalizedKey(alias) {
            switch (alias) {
            case 'sortOrder':
                return 'general_sort';
            case 'updateDate':
                return 'content_updateDate';
            case 'updater':
                return 'content_updatedBy';
            case 'createDate':
                return 'content_createDate';
            case 'owner':
                return 'content_createBy';
            case 'published':
                return 'content_isPublished';
            case 'contentTypeAlias':
                return $scope.entityType === 'content' ? 'content_documentType' : $scope.entityType === 'media' ? 'content_mediatype' : 'content_membertype';
            case 'email':
                return 'general_email';
            case 'username':
                return 'general_username';
            }
            return alias;
        }
        function getItemKey(itemId) {
            for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
                var item = $scope.listViewResultSet.items[i];
                if (item.id === itemId) {
                    return item.key;
                }
            }
        }
        function createBlank(entityType, docTypeAlias) {
            if ($scope.options.useInfiniteEditor) {
                var editorModel = {
                    create: true,
                    submit: function submit(model) {
                        editorService.close();
                        $scope.reloadView($scope.contentId);
                    },
                    close: function close() {
                        editorService.close();
                        $scope.reloadView($scope.contentId);
                    }
                };
                if (entityType == 'content') {
                    editorModel.parentId = $scope.contentId;
                    editorModel.documentTypeAlias = docTypeAlias;
                    editorService.contentEditor(editorModel);
                    return;
                }
                if (entityType == 'media') {
                    editorService.mediaEditor(editorModel);
                    return;
                }
                if (entityType == 'member') {
                    editorModel.doctype = docTypeAlias;
                    editorService.memberEditor(editorModel);
                    return;
                }
            }
            $location.path('/' + entityType + '/' + entityType + '/edit/' + $scope.contentId).search('doctype', docTypeAlias).search('create', 'true');
        }
        function createFromBlueprint(entityType, docTypeAlias, blueprintId) {
            $location.path('/' + entityType + '/' + entityType + '/edit/' + $scope.contentId).search('doctype', docTypeAlias).search('create', 'true').search('blueprintId', blueprintId);
        }
        function toggleDropdown() {
            $scope.page.createDropdownOpen = !$scope.page.createDropdownOpen;
        }
        function leaveDropdown() {
            $scope.page.createDropdownOpen = false;
        }
        $scope.createBlank = createBlank;
        $scope.createFromBlueprint = createFromBlueprint;
        $scope.toggleDropdown = toggleDropdown;
        $scope.leaveDropdown = leaveDropdown;
        // if this listview has sort order in it, make sure it is updated when sorting is performed on the current content
        if (_.find($scope.options.includeProperties, function (property) {
                return property.alias === 'sortOrder';
            })) {
            var eventSubscription = eventsService.on('sortCompleted', function (e, args) {
                if (parseInt(args.id) === parseInt($scope.contentId)) {
                    $scope.reloadView($scope.contentId);
                }
            });
            $scope.$on('$destroy', function () {
                eventsService.unsubscribe(eventSubscription);
            });
        }
        //GO!
        initView();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ListViewController', listViewController);
    'use strict';
    (function () {
        'use strict';
        function ListViewPublishController($scope, $routeParams, localizationService) {
            var vm = this;
            vm.loading = true;
            vm.changeSelection = changeSelection;
            function changeSelection(language) {
                // disable submit button if nothing is selected
                var firstSelected = _.find(vm.languages, function (language) {
                    return language.publish;
                });
                $scope.model.disableSubmitButton = !firstSelected;
                //need to set the Save state to true if publish is true
                language.save = language.publish;
            }
            function onInit() {
                vm.languages = $scope.model.languages;
                if (!$scope.model.title) {
                    localizationService.localize('content_readyToPublish').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                // node has variants
                if (vm.languages && vm.languages.length > 0) {
                    var culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
                    if (culture) {
                        // sort languages so the active on is on top
                        vm.languages = _.sortBy(vm.languages, function (language) {
                            return language.culture === culture ? 0 : 1;
                        });
                        var active = _.find(vm.languages, function (language) {
                            return language.culture === culture;
                        });
                        if (active) {
                            //ensure that the current one is selected
                            active.publish = true;
                        }
                    }
                }
                vm.loading = false;
            }
            onInit();
            //when this dialog is closed, reset all 'publish' flags
            $scope.$on('$destroy', function () {
                if (vm.languages && vm.languages.length > 0) {
                    for (var i = 0; i < vm.languages.length; i++) {
                        vm.languages[i].publish = false;
                        vm.languages[i].save = false;
                    }
                }
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.ListViewPublishController', ListViewPublishController);
    }());
    'use strict';
    (function () {
        'use strict';
        function ListViewUnpublishController($scope, $routeParams, localizationService) {
            var vm = this;
            vm.loading = true;
            vm.changeSelection = changeSelection;
            function changeSelection(language) {
                // disable submit button if nothing is selected
                var firstSelected = _.find(vm.languages, function (language) {
                    return language.unpublish;
                });
                $scope.model.disableSubmitButton = !firstSelected;
                if (language.isMandatory) {
                    $scope.model.languages.forEach(function (lang) {
                        if (lang !== language) {
                            lang.unpublish = true;
                            lang.disabled = language.unpublish;
                        }
                    });
                }
            }
            function onInit() {
                vm.languages = $scope.model.languages;
                if (!$scope.model.title) {
                    localizationService.localize('content_unpublish').then(function (value) {
                        $scope.model.title = value;
                    });
                }
                // node has variants
                if (vm.languages && vm.languages.length > 0) {
                    var culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
                    if (culture) {
                        // sort languages so the active on is on top
                        vm.languages = _.sortBy(vm.languages, function (language) {
                            return language.culture === culture ? 0 : 1;
                        });
                        var active = _.find(vm.languages, function (language) {
                            return language.culture === culture;
                        });
                        if (active) {
                            //ensure that the current one is selected
                            active.unpublish = true;
                            changeSelection(active);
                        }
                    }
                }
                vm.loading = false;
            }
            onInit();
            //when this dialog is closed, reset all 'publish' flags
            $scope.$on('$destroy', function () {
                if (vm.languages && vm.languages.length > 0) {
                    for (var i = 0; i < vm.languages.length; i++) {
                        vm.languages[i].unpublish = false;
                        vm.languages[i].save = false;
                    }
                }
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.ListViewUnpublishController', ListViewUnpublishController);
    }());
    'use strict';
    function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) {
        //Get the prevalue from the correct place
        function getPrevalues() {
            if (editorState.current.preValues) {
                return editorState.current.preValues;
            } else {
                return listViewPrevalueHelper.getPrevalues();
            }
        }
        //Watch the prevalues
        $scope.$watch(function () {
            return _.findWhere(getPrevalues(), { key: 'includeProperties' }).value;
        }, function () {
            populateFields();
        }, true);
        //Use deep watching, otherwise we won't pick up header changes
        function populateFields() {
            // Helper to find a particular value from the list of sort by options
            function findFromSortByFields(value) {
                return _.find($scope.sortByFields, function (e) {
                    return e.value.toLowerCase() === value.toLowerCase();
                });
            }
            // Get list of properties assigned as columns of the list view
            var propsPreValue = _.findWhere(getPrevalues(), { key: 'includeProperties' });
            // Populate list of options for the default sort (all the columns plus then node name)
            $scope.sortByFields = [];
            $scope.sortByFields.push({
                value: 'name',
                name: 'Name',
                isSystem: 1
            });
            if (propsPreValue != undefined) {
                for (var i = 0; i < propsPreValue.value.length; i++) {
                    var value = propsPreValue.value[i];
                    $scope.sortByFields.push({
                        value: value.alias,
                        name: value.header,
                        isSystem: value.isSystem
                    });
                }
            }
            // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared
            // beware: ensure that GetDatabaseFieldNameForOrderBy knows about those fields!
            var systemFields = [
                {
                    value: 'SortOrder',
                    key: 'general_sort'
                },
                {
                    value: 'Name',
                    key: 'general_name'
                },
                {
                    value: 'UpdateDate',
                    key: 'content_updateDate'
                },
                {
                    value: 'Updater',
                    key: 'content_updatedBy'
                },
                {
                    value: 'CreateDate',
                    key: 'content_createDate'
                },
                {
                    value: 'Owner',
                    key: 'content_createBy'
                },
                {
                    value: 'ContentTypeAlias',
                    key: 'content_documentType'
                },
                {
                    value: 'Published',
                    key: 'content_isPublished'
                },
                {
                    value: 'Email',
                    key: 'general_email'
                },
                {
                    value: 'Username',
                    key: 'general_username'
                }
            ];
            _.each(systemFields, function (e) {
                localizationService.localize(e.key).then(function (v) {
                    var sortByListValue = findFromSortByFields(e.value);
                    if (sortByListValue) {
                        sortByListValue.name = v;
                    }
                });
            });
            _.each($scope.sortByFields, function (sortByField) {
                if (!sortByField.name) {
                    sortByField.name = '(' + sortByField.value + ')';
                }
            });
            // Check existing model value is available in list and ensure a value is set
            var existingValue = findFromSortByFields($scope.model.value);
            if (existingValue) {
                // Set the existing value
                // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set
                $scope.model.value = existingValue.value;
            } else {
                // Existing value not found, set to first value
                $scope.model.value = $scope.sortByFields[0].value;
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.SortByListViewController', sortByPreValsController);
    'use strict';
    //inject umbracos assetsServce and dialog service
    function MarkdownEditorController($scope, $element, assetsService, editorService, $timeout) {
        //tell the assets service to load the markdown.editor libs from the markdown editors
        //plugin folder
        if ($scope.model.value === null || $scope.model.value === '') {
            $scope.model.value = $scope.model.config.defaultValue;
        }
        // create a unique ID for the markdown editor, so the button bar bindings can handle split view
        // - must be bound on scope, not scope.model - otherwise it won't work, because $scope.model is used in both sides of the split view
        $scope.editorId = $scope.model.alias + _.uniqueId('-');
        function openMediaPicker(callback) {
            var mediaPicker = {
                disableFolderSelect: true,
                submit: function submit(model) {
                    var selectedImagePath = model.selection[0].image;
                    callback(selectedImagePath);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.mediaPicker(mediaPicker);
        }
        function openLinkPicker(callback) {
            var linkPicker = {
                hideTarget: true,
                size: $scope.model.config.overlaySize,
                submit: function submit(model) {
                    callback(model.target.url, model.target.name);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.linkPicker(linkPicker);
        }
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        assetsService.load([
            'lib/markdown/markdown.converter.js',
            'lib/markdown/markdown.sanitizer.js',
            'lib/markdown/markdown.editor.js'
        ]).then(function () {
            // we need a short delay to wait for the textbox to appear.
            setTimeout(function () {
                //this function will execute when all dependencies have loaded
                // but in the case that they've been previously loaded, we can only
                // init the md editor after this digest because the DOM needs to be ready first
                // so run the init on a timeout
                $timeout(function () {
                    $scope.markdownEditorInitComplete = false;
                    var converter2 = new Markdown.Converter();
                    var editor2 = new Markdown.Editor(converter2, '-' + $scope.editorId);
                    editor2.run();
                    //subscribe to the image dialog clicks
                    editor2.hooks.set('insertImageDialog', function (callback) {
                        openMediaPicker(callback);
                        return true;    // tell the editor that we'll take care of getting the image url
                    });
                    //subscribe to the link dialog clicks
                    editor2.hooks.set('insertLinkDialog', function (callback) {
                        openLinkPicker(callback);
                        return true;    // tell the editor that we'll take care of getting the link url
                    });
                    editor2.hooks.set('onPreviewRefresh', function () {
                        // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library.
                        if ($scope.model.value !== $('textarea', $element).val()) {
                            if ($scope.markdownEditorInitComplete) {
                                //only set dirty after init load to avoid "unsaved" dialogue when we don't want it
                                setDirty();
                            } else {
                                $scope.markdownEditorInitComplete = true;
                            }
                            $scope.model.value = $('textarea', $element).val();
                        }
                    });
                }, 200);
            });
            // HACK: load the separate css for the editor to avoid it blocking our js loading TEMP HACK
            assetsService.loadCss('lib/markdown/markdown.css', $scope);
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MarkdownEditorController', MarkdownEditorController);
    'use strict';
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPickerController', function ($scope, entityResource, mediaHelper, $timeout, userService, localizationService, editorService, overlayService, clipboardService) {
        var vm = this;
        vm.labels = {};
        vm.labels.deletedItem = '';
        vm.add = add;
        vm.remove = remove;
        vm.copyItem = copyItem;
        vm.editItem = editItem;
        vm.showAdd = showAdd;
        vm.mediaItems = [];
        var selectedIds = [];
        //check the pre-values for multi-picker
        var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
        var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
        var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
        $scope.allowEditMedia = false;
        $scope.allowAddMedia = false;
        function setupViewModel() {
            $scope.isMultiPicker = multiPicker;
            if ($scope.model.value) {
                var ids = $scope.model.value.split(',');
                //NOTE: We need to use the entityResource NOT the mediaResource here because
                // the mediaResource has server side auth configured for which the user must have
                // access to the media section, if they don't they'll get auth errors. The entityResource
                // acts differently in that it allows access if the user has access to any of the apps that
                // might require it's use. Therefore we need to use the metaData property to get at the thumbnail
                // value.
                entityResource.getByIds(ids, 'Media').then(function (medias) {
                    // The service only returns item results for ids that exist (deleted items are silently ignored).
                    // This results in the picked items value to be set to contain only ids of picked items that could actually be found.
                    // Since a referenced item could potentially be restored later on, instead of changing the selected values here based
                    // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently
                    // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending
                    // on whether it is simply resaved or not.
                    // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders
                    // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before.
                    medias = ids.map(function (id) {
                        // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
                        // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
                        // compares and be completely sure it works.
                        var found = medias.find(function (m) {
                            return m.udi.toString() === id.toString() || m.id.toString() === id.toString();
                        });
                        var mediaItem = found || {
                            name: vm.labels.deletedItem,
                            id: $scope.model.config.idType !== 'udi' ? id : null,
                            udi: $scope.model.config.idType === 'udi' ? id : null,
                            icon: 'icon-picture',
                            thumbnail: null,
                            trashed: true
                        };
                        return mediaItem;
                    });
                    medias.forEach(function (media) {
                        return appendMedia(media);
                    });
                    sync();
                });
            }
        }
        function appendMedia(media) {
            if (!media.extension && media.id && media.metaData) {
                media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath);
            }
            // if there is no thumbnail, try getting one if the media is not a placeholder item
            if (!media.thumbnail && media.id && media.metaData) {
                media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
            }
            vm.mediaItems.push(media);
            if ($scope.model.config.idType === 'udi') {
                selectedIds.push(media.udi);
            } else {
                selectedIds.push(media.id);
            }
        }
        function sync() {
            $scope.model.value = selectedIds.join();
            removeAllEntriesAction.isDisabled = selectedIds.length === 0;
            copyAllEntriesAction.isDisabled = removeAllEntriesAction.isDisabled;
        }
        function setDirty() {
            if (vm.modelValueForm) {
                vm.modelValueForm.modelValue.$setDirty();
            }
        }
        function reloadUpdatedMediaItems(updatedMediaNodes) {
            // because the images can be edited through the media picker we need to
            // reload. We only reload the images that is already picked but has been updated.
            // We have to get the entities from the server because the media
            // can be edited without being selected
            vm.mediaItems.forEach(function (media) {
                if (updatedMediaNodes.indexOf(media.udi) !== -1) {
                    media.loading = true;
                    entityResource.getById(media.udi, 'Media').then(function (mediaEntity) {
                        Utilities.extend(media, mediaEntity);
                        media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                        media.loading = false;
                    });
                }
            });
        }
        function init() {
            // localize labels
            var labelKeys = [
                'mediaPicker_deletedItem',
                'mediaPicker_trashed'
            ];
            localizationService.localizeMany(labelKeys).then(function (data) {
                vm.labels.deletedItem = data[0];
                vm.labels.trashed = data[1];
                userService.getCurrentUser().then(function (userData) {
                    if (!$scope.model.config.startNodeId) {
                        if ($scope.model.config.ignoreUserStartNodes === true) {
                            $scope.model.config.startNodeId = -1;
                            $scope.model.config.startNodeIsVirtual = true;
                        } else {
                            $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                            $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
                        }
                    }
                    // only allow users to add and edit media if they have access to the media section
                    var hasAccessToMedia = userData.allowedSections.indexOf('media') !== -1;
                    $scope.allowEditMedia = hasAccessToMedia;
                    $scope.allowAddMedia = hasAccessToMedia;
                    setupViewModel();
                });
            });
        }
        function remove(index) {
            vm.mediaItems.splice(index, 1);
            selectedIds.splice(index, 1);
            sync();
            setDirty();
        }
        function copyAllEntries() {
            if ($scope.mediaItems.length > 0) {
                // gather aliases
                var aliases = $scope.mediaItems.map(function (mediaEntity) {
                    return mediaEntity.metaData.ContentTypeAlias;
                });
                // remove duplicate aliases
                aliases = aliases.filter(function (item, index) {
                    return aliases.indexOf(item) === index;
                });
                var data = $scope.mediaItems.map(function (mediaEntity) {
                    return { 'mediaKey': mediaEntity.key };
                });
                localizationService.localize('clipboard_labelForArrayOfItems', [$scope.model.label]).then(function (localizedLabel) {
                    clipboardService.copyArray(clipboardService.TYPES.MEDIA, aliases, data, localizedLabel, 'icon-thumbnail-list', $scope.model.id);
                });
            }
        }
        function copyItem(mediaItem) {
            var mediaEntry = {};
            mediaEntry.mediaKey = mediaItem.key;
            clipboardService.copy(clipboardService.TYPES.MEDIA, mediaItem.metaData.ContentTypeAlias, mediaEntry, mediaItem.name, mediaItem.icon, mediaItem.udi);
        }
        function pasteFromClipboard(pasteEntry, pasteType) {
            if (pasteEntry === undefined) {
                return;
            }
            pasteEntry = clipboardService.parseContentForPaste(pasteEntry, pasteType);
            entityResource.getById(pasteEntry.mediaKey, 'Media').then(function (mediaEntity) {
                if (disableFolderSelect === true && mediaEntity.metaData.ContentTypeAlias === 'Folder') {
                    return;
                }
                appendMedia(mediaEntity);
                sync();
            });
        }
        function editItem(item) {
            var mediaEditor = {
                id: item.id,
                submit: function submit(model) {
                    editorService.close();
                    // update the selected media item to match the saved media item
                    // the media picker is using media entities so we get the
                    // entity so we easily can format it for use in the media grid
                    if (model && model.mediaNode) {
                        entityResource.getById(model.mediaNode.id, 'Media').then(function (mediaEntity) {
                            // if an image is selecting more than once
                            // we need to update all the media items
                            vm.mediaItems.forEach(function (media) {
                                if (media.id === model.mediaNode.id) {
                                    Utilities.extend(media, mediaEntity);
                                    media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                                }
                            });
                        });
                    }
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.mediaEditor(mediaEditor);
        }
        function add() {
            var mediaPicker = {
                startNodeId: $scope.model.config.startNodeId,
                startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
                dataTypeKey: $scope.model.dataTypeKey,
                multiPicker: multiPicker,
                onlyImages: onlyImages,
                disableFolderSelect: disableFolderSelect,
                clickPasteItem: function clickPasteItem(item, mouseEvent) {
                    if (Array.isArray(item.data)) {
                        var indexIncrementor = 0;
                        item.data.forEach(function (entry) {
                            if (pasteFromClipboard(entry, item.type)) {
                                indexIncrementor++;
                            }
                        });
                    } else {
                        pasteFromClipboard(item.data, item.type);
                    }
                    if (!(mouseEvent.ctrlKey || mouseEvent.metaKey)) {
                        editorService.close();
                    }
                    setDirty();
                },
                submit: function submit(model) {
                    editorService.close();
                    model.selection.forEach(function (media) {
                        // if there is no thumbnail, try getting one if the media is not a placeholder item
                        if (!media.thumbnail && media.id && media.metaData) {
                            media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                        }
                        vm.mediaItems.push(media);
                        if ($scope.model.config.idType === 'udi') {
                            selectedIds.push(media.udi);
                        } else {
                            selectedIds.push(media.id);
                        }
                    });
                    sync();
                    reloadUpdatedMediaItems(model.updatedMediaNodes);
                    setDirty();
                },
                close: function close(model) {
                    editorService.close();
                    reloadUpdatedMediaItems(model.updatedMediaNodes);
                }
            };
            var allowedTypes = null;
            if (onlyImages) {
                allowedTypes = ['Image'];    // Media Type Image Alias.
            }
            mediaPicker.clickClearClipboard = function ($event) {
                clipboardService.clearEntriesOfType(clipboardService.TYPES.Media, allowedTypes);
            };
            mediaPicker.clipboardItems = clipboardService.retrieveEntriesOfType(clipboardService.TYPES.MEDIA, allowedTypes);
            mediaPicker.clipboardItems.sort(function (a, b) {
                return b.date - a.date;
            });
            editorService.mediaPicker(mediaPicker);
        }
        function showAdd() {
            if (!multiPicker) {
                if ($scope.model.value && $scope.model.value !== '') {
                    return false;
                }
            }
            return true;
        }
        function removeAllEntries() {
            localizationService.localizeMany([
                'content_nestedContentDeleteAllItems',
                'general_delete'
            ]).then(function (data) {
                overlayService.confirmDelete({
                    title: data[1],
                    content: data[0],
                    close: function close() {
                        overlayService.close();
                    },
                    submit: function submit() {
                        vm.mediaItems.length = 0;
                        // AngularJS way to empty the array.
                        selectedIds.length = 0;
                        // AngularJS way to empty the array.
                        sync();
                        setDirty();
                        overlayService.close();
                    }
                });
            });
        }
        var copyAllEntriesAction = {
            labelKey: 'clipboard_labelForCopyAllEntries',
            labelTokens: ['Media'],
            icon: 'documents',
            method: copyAllEntries,
            isDisabled: true
        };
        var removeAllEntriesAction = {
            labelKey: 'clipboard_labelForRemoveAllEntries',
            labelTokens: [],
            icon: 'trash',
            method: removeAllEntries,
            isDisabled: true
        };
        if (multiPicker === true) {
            var propertyActions = [
                copyAllEntriesAction,
                removeAllEntriesAction
            ];
            if ($scope.umbProperty) {
                $scope.umbProperty.setPropertyActions(propertyActions);
            }
        }
        $scope.sortableOptions = {
            containment: 'parent',
            cursor: 'move',
            tolerance: 'pointer',
            disabled: !multiPicker,
            items: 'li:not(.add-wrapper)',
            cancel: '.unsortable',
            update: function update() {
                setDirty();
                $timeout(function () {
                    // TODO: Instead of doing this with a timeout would be better to use a watch like we do in the
                    // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the
                    // watch do all the rest.
                    selectedIds = vm.mediaItems.map(function (media) {
                        return $scope.model.config.idType === 'udi' ? media.udi : media.id;
                    });
                    sync();
                });
            }
        };
        init();
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPicker3.CropConfigurationController', function ($scope) {
        var unsubscribe = [];
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.setFocus = false;
        $scope.remove = function (crop, evt) {
            evt.preventDefault();
            var i = $scope.model.value.indexOf(crop);
            if (i > -1) {
                $scope.model.value.splice(i, 1);
            }
        };
        $scope.edit = function (crop, evt) {
            evt.preventDefault();
            crop.editMode = true;
        };
        $scope.addNewCrop = function (evt) {
            evt.preventDefault();
            var crop = {};
            crop.editMode = true;
            $scope.model.value.push(crop);
            $scope.validate(crop);
        };
        $scope.setChanges = function (crop) {
            $scope.validate(crop);
            if (crop.hasWidthError !== true && crop.hasHeightError !== true && crop.hasAliasError !== true) {
                crop.editMode = false;
                window.dispatchEvent(new Event('resize.umbImageGravity'));
            }
        };
        $scope.isEmpty = function (crop) {
            return !crop.label && !crop.alias && !crop.width && !crop.height;
        };
        $scope.useForAlias = function (crop) {
            if (crop.alias == null || crop.alias === '') {
                crop.alias = (crop.label || '').toCamelCase();
            }
        };
        $scope.validate = function (crop) {
            $scope.validateWidth(crop);
            $scope.validateHeight(crop);
            $scope.validateAlias(crop);
        };
        $scope.validateWidth = function (crop) {
            crop.hasWidthError = !(Utilities.isNumber(crop.width) && crop.width > 0);
        };
        $scope.validateHeight = function (crop) {
            crop.hasHeightError = !(Utilities.isNumber(crop.height) && crop.height > 0);
        };
        $scope.validateAlias = function (crop, $event) {
            var exists = $scope.model.value.find(function (x) {
                return crop !== x && crop.alias === x.alias;
            });
            if (exists !== undefined || crop.alias === '') {
                // alias is not valid
                crop.hasAliasError = true;
            } else {
                // everything was good:
                crop.hasAliasError = false;
            }
        };
        $scope.confirmChanges = function (crop, event) {
            if (event.keyCode == 13) {
                $scope.setChanges(crop, event);
                event.preventDefault();
            }
        };
        $scope.focusNextField = function (event) {
            if (event.keyCode == 13) {
                var el = event.target;
                var inputs = Array.from(document.querySelectorAll('input:not(disabled)'));
                var inputIndex = inputs.indexOf(el);
                if (inputIndex > -1) {
                    var nextIndex = inputs.indexOf(el) + 1;
                    if (inputs.length > nextIndex) {
                        inputs[nextIndex].focus();
                        event.preventDefault();
                    }
                }
            }
        };
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            tolerance: 'pointer'
        };
        $scope.$on('$destroy', function () {
            var _iteratorNormalCompletion = true;
            var _didIteratorError = false;
            var _iteratorError = undefined;
            try {
                for (var _iterator = unsubscribe[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                    var subscription = _step.value;
                    subscription();
                }
            } catch (err) {
                _didIteratorError = true;
                _iteratorError = err;
            } finally {
                try {
                    if (!_iteratorNormalCompletion && _iterator.return != null) {
                        _iterator.return();
                    }
                } finally {
                    if (_didIteratorError) {
                        throw _iteratorError;
                    }
                }
            }
        });
    });
    'use strict';
    (function () {
        'use strict';
        angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPicker3PropertyEditor.CreateButtonController', function Controller($scope) {
            var vm = this;
            vm.plusPosY = 0;
            vm.onMouseMove = function ($event) {
                vm.plusPosY = $event.offsetY;
            };
        });
    }());
    'use strict';
    //this controller tells the dialogs service to open a memberPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function memberGroupPicker($scope, editorService, memberGroupResource, localizationService, overlayService) {
        var vm = this;
        vm.openMemberGroupPicker = openMemberGroupPicker;
        vm.remove = remove;
        vm.clear = clear;
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        var removeAllEntriesAction = {
            labelKey: 'clipboard_labelForRemoveAllEntries',
            labelTokens: [],
            icon: 'trash',
            method: removeAllEntries,
            isDisabled: true
        };
        $scope.renderModel = [];
        $scope.allowRemove = true;
        $scope.groupIds = [];
        if ($scope.model.config && $scope.umbProperty) {
            $scope.umbProperty.setPropertyActions([removeAllEntriesAction]);
        }
        if ($scope.model.value) {
            var groupIds = $scope.model.value.split(',');
            memberGroupResource.getByIds(groupIds).then(function (groups) {
                $scope.renderModel = groups;
            });
            removeAllEntriesAction.isDisabled = groupIds.length === 0;
        }
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        function openMemberGroupPicker() {
            var memberGroupPicker = {
                multiPicker: true,
                submit: function submit(model) {
                    var selectedGroupIds = _.map(model.selectedMemberGroups ? model.selectedMemberGroups : [model.selectedMemberGroup], function (id) {
                        return parseInt(id);
                    });
                    var currIds = renderModelIds();
                    // figure out which groups are new and fetch them
                    var newGroupIds = _.difference(selectedGroupIds, currIds);
                    removeAllEntriesAction.isDisabled = currIds.length === 0 && newGroupIds.length === 0;
                    if (newGroupIds && newGroupIds.length) {
                        memberGroupResource.getByIds(newGroupIds).then(function (groups) {
                            $scope.renderModel = _.union($scope.renderModel, groups);
                            setDirty();
                            editorService.close();
                        });
                    } else {
                        // no new groups selected
                        editorService.close();
                    }
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.memberGroupPicker(memberGroupPicker);
        }
        function remove(index) {
            $scope.renderModel.splice(index, 1);
            var currIds = renderModelIds();
            removeAllEntriesAction.isDisabled = currIds.length === 0;
            setDirty();
        }
        function clear() {
            $scope.renderModel = [];
            removeAllEntriesAction.isDisabled = true;
            setDirty();
        }
        function removeAllEntries() {
            localizationService.localizeMany([
                'content_nestedContentDeleteAllItems',
                'general_delete'
            ]).then(function (data) {
                overlayService.confirmDelete({
                    title: data[1],
                    content: data[0],
                    close: function close() {
                        overlayService.close();
                    },
                    submit: function submit() {
                        vm.clear();
                        overlayService.close();
                    }
                });
            });
        }
        function renderModelIds() {
            var currIds = $scope.renderModel.map(function (i) {
                return i.id;
            });
            return currIds;
        }
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            $scope.model.value = trim(renderModelIds().join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupPickerController', memberGroupPicker);
    'use strict';
    function memberGroupController($scope, editorService, memberGroupResource) {
        var vm = this;
        vm.pickGroup = pickGroup;
        vm.removeGroup = removeGroup;
        //set the selected to the keys of the dictionary who's value is true
        $scope.getSelected = function () {
            var selected = [];
            for (var n in $scope.model.value) {
                if ($scope.model.value[n] === true) {
                    selected.push(n);
                }
            }
            return selected;
        };
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        function pickGroup() {
            editorService.memberGroupPicker({
                multiPicker: true,
                submit: function submit(model) {
                    var selectedGroupIds = _.map(model.selectedMemberGroups ? model.selectedMemberGroups : [model.selectedMemberGroup], function (id) {
                        return parseInt(id);
                    });
                    memberGroupResource.getByIds(selectedGroupIds).then(function (selectedGroups) {
                        _.each(selectedGroups, function (group) {
                            $scope.model.value[group.name] = true;
                        });
                    });
                    setDirty();
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            });
        }
        function removeGroup(group) {
            $scope.model.value[group] = false;
            setDirty();
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupController', memberGroupController);
    'use strict';
    //this controller simply tells the dialogs service to open a memberPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function memberPickerController($scope, entityResource, iconHelper, editorService) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        var dialogOptions = {
            multiPicker: false,
            entityType: 'Member',
            section: 'member',
            treeAlias: 'member',
            filter: function filter(i) {
                return i.metaData.isContainer == true;
            },
            filterCssClass: 'not-allowed',
            callback: function callback(data) {
                if (Utilities.isArray(data)) {
                    _.each(data, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(data);
                }
            }
        };
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
        // pre-value config on to the dialog options
        if ($scope.model.config) {
            Utilities.extend(dialogOptions, $scope.model.config);
        }
        $scope.openMemberPicker = function () {
            var memberPicker = dialogOptions;
            memberPicker.submit = function (model) {
                if (model.selection) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                }
                editorService.close();
            };
            memberPicker.close = function () {
                editorService.close();
            };
            editorService.treePicker(memberPicker);
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            setDirty();
        };
        $scope.add = function (item) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
            if (currIds.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    udi: item.udi,
                    icon: item.icon
                });
                setDirty();
            }
        };
        $scope.clear = function () {
            $scope.renderModel = [];
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            $scope.model.value = trim(currIds.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        //load member data
        var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
        entityResource.getByIds(modelIds, 'Member').then(function (data) {
            _.each(data, function (item, i) {
                // set default icon if it's missing
                item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user';
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    udi: item.udi,
                    icon: item.icon
                });
            });
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberPickerController', memberPickerController);
    'use strict';
    function _toConsumableArray(arr) {
        return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
    }
    function _nonIterableSpread() {
        throw new TypeError('Invalid attempt to spread non-iterable instance');
    }
    function _iterableToArray(iter) {
        if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === '[object Arguments]')
            return Array.from(iter);
    }
    function _arrayWithoutHoles(arr) {
        if (Array.isArray(arr)) {
            for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) {
                arr2[i] = arr[i];
            }
            return arr2;
        }
    }
    function MultipleTextBoxController($scope, $timeout) {
        var backspaceHits = 0;
        // Set the visible prompt to -1 to ensure it will not be visible
        $scope.promptIsVisible = '-1';
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> div.textbox-wrapper',
            tolerance: 'pointer'
        };
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        // Add any fields that there isn't values for
        if ($scope.model.config.min > 0) {
            for (var i = 0; i < $scope.model.config.min; i++) {
                if (i + 1 > $scope.model.value.length) {
                    $scope.model.value.push({ value: '' });
                }
            }
        }
        $scope.paste = function (event, index) {
            event.preventDefault();
            var text = (event.clipboardData || window.clipboardData || event.originalEvent.clipboardData).getData('text');
            var lines = text.split(/\r?\n/).map(function (line) {
                return {
                    value: line,
                    hasFocus: false
                };
            });
            if (lines.length > 0) {
                var _$scope$model$value;
                // merge with the current text
                var currentText = $scope.model.value[index].value;
                lines[0].value = currentText.substring(0, event.target.selectionStart) + lines[0].value;
                lines[lines.length - 1].value = lines[lines.length - 1].value + currentText.substring(event.target.selectionEnd);
                // clear selection
                event.target.selectionEnd = event.target.selectionStart;
                // remove focus from existing values
                $scope.model.value.forEach(function (value) {
                    return value.hasFocus = false;
                });
                // add all the lines to the value
                lines[lines.length - 1].hasFocus = true;
                (_$scope$model$value = $scope.model.value).splice.apply(_$scope$model$value, [
                    index,
                    1
                ].concat(_toConsumableArray(lines)));
            }
        };
        $scope.addRemoveOnKeyDown = function (event, index) {
            var txtBoxValue = $scope.model.value[index];
            event.preventDefault();
            switch (event.keyCode) {
            case 13:
                if ($scope.model.config.max <= 0 && txtBoxValue.value || $scope.model.value.length < $scope.model.config.max && txtBoxValue.value) {
                    var newItemIndex = index + 1;
                    $scope.model.value.splice(newItemIndex, 0, { value: '' });
                    // Focus on the newly added value
                    $scope.model.value[newItemIndex].hasFocus = true;
                }
                break;
            case 8:
                if ($scope.model.value.length > $scope.model.config.min) {
                    var remainder = [];
                    // Used to require an extra hit on backspace for the field to be removed
                    if (txtBoxValue.value === '') {
                        backspaceHits++;
                    } else {
                        backspaceHits = 0;
                    }
                    if (txtBoxValue.value === '' && backspaceHits === 2) {
                        for (var x = 0; x < $scope.model.value.length; x++) {
                            if (x !== index) {
                                remainder.push($scope.model.value[x]);
                            }
                        }
                        $scope.model.value = remainder;
                        var prevItemIndex = index - 1;
                        // Set focus back on false as the directive only watches for true
                        if (prevItemIndex >= 0) {
                            $scope.model.value[prevItemIndex].hasFocus = false;
                            $timeout(function () {
                                // Focus on the previous value
                                $scope.model.value[prevItemIndex].hasFocus = true;
                            });
                        }
                        backspaceHits = 0;
                    }
                }
                break;
            default:
            }
            validate();
        };
        $scope.add = function () {
            if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
                $scope.model.value.push({ value: '' });
                // Focus new value
                var newItemIndex = $scope.model.value.length - 1;
                $scope.model.value[newItemIndex].hasFocus = true;
            }
            validate();
        };
        $scope.remove = function (index) {
            // Make sure not to trigger other prompts when remove is triggered
            $scope.hidePrompt();
            var remainder = [];
            for (var x = 0; x < $scope.model.value.length; x++) {
                if (x !== index) {
                    remainder.push($scope.model.value[x]);
                }
            }
            $scope.model.value = remainder;
        };
        $scope.showPrompt = function (idx, item) {
            var i = $scope.model.value.indexOf(item);
            // Make the prompt visible for the clicked tag only
            if (i === idx) {
                $scope.promptIsVisible = i;
            }
        };
        $scope.hidePrompt = function () {
            $scope.promptIsVisible = '-1';
        };
        function validate() {
            if ($scope.multipleTextboxForm) {
                var invalid = $scope.model.validation.mandatory && !$scope.model.value.length;
                $scope.multipleTextboxForm.mandatory.$setValidity('minCount', !invalid);
            }
        }
        $timeout(function () {
            validate();
        });
        // We always need to ensure we dont submit anything broken
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            // Filter to items with values
            $scope.model.value = $scope.model.value.filter(function (el) {
                return el.value.trim() !== '';
            }) || [];
        });
        // When the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MultipleTextBoxController', MultipleTextBoxController);
    'use strict';
    function multiUrlPickerController($scope, localizationService, entityResource, iconHelper, editorService) {
        var vm = { labels: { general_recycleBin: '' } };
        $scope.renderModel = [];
        if ($scope.preview) {
            return;
        }
        if (!Array.isArray($scope.model.value)) {
            $scope.model.value = [];
        }
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            distance: 10,
            opacity: 0.7,
            tolerance: 'pointer',
            scroll: true,
            zIndex: 6000,
            update: function update() {
                setDirty();
            }
        };
        $scope.model.value.forEach(function (link) {
            link.icon = iconHelper.convertFromLegacyIcon(link.icon);
            $scope.renderModel.push(link);
        });
        $scope.$on('formSubmitting', function () {
            $scope.model.value = $scope.renderModel;
        });
        $scope.$watch(function () {
            return $scope.renderModel.length;
        }, function () {
            //Validate!
            if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
                $scope.multiUrlPickerForm.minCount.$setValidity('minCount', false);
            } else {
                $scope.multiUrlPickerForm.minCount.$setValidity('minCount', true);
            }
            if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
                $scope.multiUrlPickerForm.maxCount.$setValidity('maxCount', false);
            } else {
                $scope.multiUrlPickerForm.maxCount.$setValidity('maxCount', true);
            }
            $scope.sortableOptions.disabled = $scope.renderModel.length === 1;
            //Update value
            $scope.model.value = $scope.renderModel;
        });
        $scope.remove = function ($index) {
            $scope.renderModel.splice($index, 1);
            setDirty();
        };
        $scope.openLinkPicker = function (link, $index) {
            var target = link ? {
                name: link.name,
                anchor: link.queryString,
                udi: link.udi,
                url: link.url,
                target: link.target
            } : null;
            var linkPicker = {
                currentTarget: target,
                dataTypeKey: $scope.model.dataTypeKey,
                ignoreUserStartNodes: $scope.model.config && $scope.model.config.ignoreUserStartNodes ? $scope.model.config.ignoreUserStartNodes : '0',
                hideAnchor: $scope.model.config && $scope.model.config.hideAnchor ? true : false,
                size: $scope.model.config.overlaySize,
                submit: function submit(model) {
                    if (model.target.url || model.target.anchor) {
                        // if an anchor exists, check that it is appropriately prefixed
                        if (model.target.anchor && model.target.anchor[0] !== '?' && model.target.anchor[0] !== '#') {
                            model.target.anchor = (model.target.anchor.indexOf('=') === -1 ? '#' : '?') + model.target.anchor;
                        }
                        if (link) {
                            link.udi = model.target.udi;
                            link.name = model.target.name || model.target.url || model.target.anchor;
                            link.queryString = model.target.anchor;
                            link.target = model.target.target;
                            link.url = model.target.url;
                        } else {
                            link = {
                                name: model.target.name || model.target.url || model.target.anchor,
                                queryString: model.target.anchor,
                                target: model.target.target,
                                udi: model.target.udi,
                                url: model.target.url
                            };
                            $scope.renderModel.push(link);
                        }
                        if (link.udi) {
                            var entityType = model.target.isMedia ? 'Media' : 'Document';
                            entityResource.getById(link.udi, entityType).then(function (data) {
                                link.icon = iconHelper.convertFromLegacyIcon(data.icon);
                                link.published = data.metaData && data.metaData.IsPublished === false && entityType === 'Document' ? false : true;
                                link.trashed = data.trashed;
                                if (link.trashed) {
                                    item.url = vm.labels.general_recycleBin;
                                }
                            });
                        } else {
                            link.icon = 'icon-link';
                            link.published = true;
                        }
                        setDirty();
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.linkPicker(linkPicker);
        };
        function setDirty() {
            if ($scope.multiUrlPickerForm) {
                $scope.multiUrlPickerForm.modelValue.$setDirty();
            }
        }
        function init() {
            localizationService.localizeMany(['general_recycleBin']).then(function (data) {
                vm.labels.general_recycleBin = data[0];
            });
            // if the property is mandatory, set the minCount config to 1 (unless of course it is set to something already),
            // that way the minCount/maxCount validation handles the mandatory as well
            if ($scope.model.validation && $scope.model.validation.mandatory && !$scope.model.config.minNumber) {
                $scope.model.config.minNumber = 1;
            }
            _.each($scope.model.value, function (item) {
                // we must reload the "document" link URLs to match the current editor culture
                if (item.udi && item.udi.indexOf('/document/') > 0) {
                    item.url = null;
                    entityResource.getUrlByUdi(item.udi).then(function (data) {
                        item.url = data;
                    });
                }
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MultiUrlPickerController', multiUrlPickerController);
    'use strict';
    function _typeof(obj) {
        if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {
            _typeof = function _typeof(obj) {
                return typeof obj;
            };
        } else {
            _typeof = function _typeof(obj) {
                return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype ? 'symbol' : typeof obj;
            };
        }
        return _typeof(obj);
    }
    (function () {
        'use strict';
        /**
   * When performing a copy, we do copy the ElementType Data Model, but each inner Nested Content property is still stored as the Nested Content Model, aka. each property is just storing its value. To handle this we need to ensure we handle both scenarios.
   */
        angular.module('umbraco').run([
            'clipboardService',
            function (clipboardService) {
                function resolveNestedContentPropertiesForPaste(prop, propClearingMethod) {
                    // if prop.editor is "Umbraco.NestedContent"
                    if (_typeof(prop) === 'object' && prop.editor === 'Umbraco.NestedContent') {
                        var value = prop.value;
                        for (var i = 0; i < value.length; i++) {
                            var obj = value[i];
                            // generate a new key.
                            obj.key = String.CreateGuid();
                            // Loop through all inner properties:
                            for (var k in obj) {
                                propClearingMethod(obj[k], clipboardService.TYPES.RAW);
                            }
                        }
                    }
                }
                clipboardService.registerPastePropertyResolver(resolveNestedContentPropertiesForPaste, clipboardService.TYPES.ELEMENT_TYPE);
                function resolveInnerNestedContentPropertiesForPaste(prop, propClearingMethod) {
                    // if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property data.
                    if (Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined) {
                        for (var i = 0; i < prop.length; i++) {
                            var obj = prop[i];
                            // generate a new key.
                            obj.key = String.CreateGuid();
                            // Loop through all inner properties:
                            for (var k in obj) {
                                propClearingMethod(obj[k], clipboardService.TYPES.RAW);
                            }
                        }
                    }
                }
                clipboardService.registerPastePropertyResolver(resolveInnerNestedContentPropertiesForPaste, clipboardService.TYPES.RAW);
            }
        ]);
        angular.module('umbraco').component('nestedContentPropertyEditor', {
            template: ' <div id="umb-nested-content--{{model.id}}" class="umb-nested-content" ng-class="{\'umb-nested-content--narrow\':!vm.wideMode, \'umb-nested-content--wide\':vm.wideMode}"> <umb-load-indicator class="mt2" ng-if="!vm.inited"></umb-load-indicator> <ng-form name="nestedContentForm" ng-show="vm.inited"> <div class="umb-nested-content__items" ng-hide="vm.nodes.length === 0" ui-sortable="vm.sortableOptions" ng-model="vm.nodes"> <div ng-repeat="node in vm.nodes" ng-init="node.icon = vm.getIcon($index)"> <ng-form name="ncRowForm" val-server-match="{ \'contains\': node.key }"> <div class="umb-nested-content__item" ng-class="{ \'umb-nested-content__item--active\' : vm.currentNode.key === node.key, \'umb-nested-content__item--single\' : vm.singleMode, \'--error\': ncRowForm.$invalid }"> <div class="umb-nested-content__header-bar" ng-click="vm.editNode($index)" ng-hide="vm.singleMode" umb-auto-focus="{{vm.focusOnNode && vm.currentNode.key === node.key ? \'true\' : \'false\'}}"> <div class="umb-nested-content__heading"> <umb-icon icon="{{node.icon}}" class="icon umb-nested-content__item-icon"></umb-icon> <span class="umb-nested-content__item-name" ng-class="{\'--has-icon\': vm.showIcons}" ng-bind="vm.getName($index)"></span> </div> <div class="umb-nested-content__icons"> <button type="button" class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{vm.labels.copy_icon_title}}" ng-click="vm.clickCopy($event, node);" ng-if="vm.showCopy"> <umb-icon icon="icon-documents" class="icon"></umb-icon> <span class="sr-only">{{vm.labels.copy_icon_title}}</span> </button> <button type="button" class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-class="{ \'umb-nested-content__icon--disabled\': !vm.canDeleteNode($index) }" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();"> <umb-icon icon="icon-trash" class="icon"></umb-icon> <span class="sr-only"> <localize key="general_delete">Delete</localize> </span> </button> </div> </div> <div class="umb-nested-content__content" ng-if="vm.currentNode.key === node.key && !vm.sorting"> <umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias"></umb-nested-content-editor> </div> </div> </ng-form> </div> </div> <div ng-hide="vm.hasContentTypes"> <div class="umb-nested-content__help-text"> <localize key="content_nestedContentNoContentTypes">No content types are configured for this property.</localize> </div> </div> <div class="umb-nested-content__footer-bar" ng-hide="!vm.inited || vm.hasContentTypes === false || vm.singleMode === true"> <button type="button" class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ \'--disabled\': (!vm.scaffolds.length || vm.nodes.length >= vm.maxItems) }" ng-click="vm.openNodeTypePicker($event)" aria-disabled="{{!vm.scaffolds.length || vm.nodes.length >= vm.maxItems}}"> <localize key="grid_addElement">Add element</localize> </button> </div>  <input type="hidden" name="minCount" ng-model="vm.nodes"> <input type="hidden" name="maxCount" ng-model="vm.nodes"> <div ng-messages="nestedContentForm.minCount.$error" show-validation-on-submit> <div class="help text-error" ng-message="minCount"> <localize key="validation_entriesShort" tokens="[vm.minItems, vm.minItems - vm.nodes.length]" watch-tokens="true">Minimum %0% entries, needs <strong>%1%</strong> more.</localize> </div> </div> <div ng-if="nestedContentForm.minCount.$error === true || vm.nodes.length > vm.maxItems"> <div class="help text-error"> <localize key="validation_entriesExceed" tokens="[vm.maxItems, vm.nodes.length - vm.maxItems]" watch-tokens="true">Maximum %0% entries, <strong>%1%</strong> too many.</localize> </div> </div> </ng-form> </div> ',
            controller: NestedContentController,
            controllerAs: 'vm',
            require: {
                umbProperty: '?^umbProperty',
                umbVariantContent: '?^^umbVariantContent'
            }
        });
        function NestedContentController($scope, $interpolate, $filter, serverValidationManager, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) {
            var vm = this;
            var model = $scope.$parent.$parent.model;
            var contentTypeAliases = [];
            _.each(model.config.contentTypes, function (contentType) {
                contentTypeAliases.push(contentType.ncAlias);
            });
            _.each(model.config.contentTypes, function (contentType) {
                contentType.nameExp = !!contentType.nameTemplate ? $interpolate(contentType.nameTemplate) : undefined;
            });
            vm.nodes = [];
            vm.currentNode = null;
            vm.scaffolds = null;
            vm.sorting = false;
            vm.inited = false;
            vm.minItems = model.config.minItems || 0;
            vm.maxItems = model.config.maxItems || 0;
            if (vm.maxItems === 0)
                vm.maxItems = 1000;
            vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;
            ;
            vm.showIcons = Object.toBoolean(model.config.showIcons);
            vm.wideMode = Object.toBoolean(model.config.hideLabel);
            vm.hasContentTypes = model.config.contentTypes.length > 0;
            var cultureChanged = eventsService.on('editors.content.cultureChanged', function (name, args) {
                return updateModel();
            });
            var labels = {};
            vm.labels = labels;
            localizationService.localizeMany([
                'grid_addElement',
                'content_createEmpty',
                'actions_copy'
            ]).then(function (data) {
                labels.grid_addElement = data[0];
                labels.content_createEmpty = data[1];
                labels.copy_icon_title = data[2];
            });
            function setCurrentNode(node, focusNode) {
                updateModel();
                vm.currentNode = node;
                vm.focusOnNode = focusNode;
            }
            var copyAllEntries = function copyAllEntries() {
                syncCurrentNode();
                // list aliases
                var aliases = vm.nodes.map(function (node) {
                    return node.contentTypeAlias;
                });
                // remove dublicates
                aliases = aliases.filter(function (item, index) {
                    return aliases.indexOf(item) === index;
                });
                var nodeName = '';
                if (vm.umbVariantContent) {
                    nodeName = vm.umbVariantContent.editor.content.name;
                }
                localizationService.localize('clipboard_labelForArrayOfItemsFrom', [
                    model.label,
                    nodeName
                ]).then(function (data) {
                    clipboardService.copyArray(clipboardService.TYPES.ELEMENT_TYPE, aliases, vm.nodes, data, 'icon-thumbnail-list', model.id, clearNodeForCopy);
                });
            };
            var copyAllEntriesAction = {
                labelKey: 'clipboard_labelForCopyAllEntries',
                labelTokens: [model.label],
                icon: 'documents',
                method: copyAllEntries,
                isDisabled: true
            };
            var removeAllEntries = function removeAllEntries() {
                localizationService.localizeMany([
                    'content_nestedContentDeleteAllItems',
                    'general_delete'
                ]).then(function (data) {
                    overlayService.confirmDelete({
                        title: data[1],
                        content: data[0],
                        close: function close() {
                            overlayService.close();
                        },
                        submit: function submit() {
                            vm.nodes = [];
                            setDirty();
                            updateModel();
                            overlayService.close();
                        }
                    });
                });
            };
            var removeAllEntriesAction = {
                labelKey: 'clipboard_labelForRemoveAllEntries',
                labelTokens: [],
                icon: 'trash',
                method: removeAllEntries,
                isDisabled: true
            };
            // helper to force the current form into the dirty state
            function setDirty() {
                if (vm.umbProperty) {
                    vm.umbProperty.setDirty();
                }
            }
            ;
            function addNode(alias) {
                var scaffold = getScaffold(alias);
                var newNode = createNode(scaffold, null);
                setCurrentNode(newNode, true);
                setDirty();
                validate();
            }
            ;
            vm.openNodeTypePicker = function ($event) {
                if (vm.nodes.length >= vm.maxItems) {
                    return;
                }
                var availableItems = [];
                _.each(vm.scaffolds, function (scaffold) {
                    availableItems.push({
                        alias: scaffold.contentTypeAlias,
                        name: scaffold.contentTypeName,
                        icon: iconHelper.convertFromLegacyIcon(scaffold.icon),
                        tooltip: scaffold.documentType.description
                    });
                });
                var dialog = {
                    orderBy: '$index',
                    view: 'itempicker',
                    event: $event,
                    filter: availableItems.length > 12,
                    size: availableItems.length > 6 ? 'medium' : 'small',
                    availableItems: availableItems,
                    clickPasteItem: function clickPasteItem(item) {
                        if (Array.isArray(item.data)) {
                            _.each(item.data, function (entry) {
                                pasteFromClipboard(entry);
                            });
                        } else {
                            pasteFromClipboard(item.data);
                        }
                        overlayService.close();
                    },
                    submit: function submit(model) {
                        if (model && model.selectedItem) {
                            addNode(model.selectedItem.alias);
                        }
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                if (dialog.availableItems.length === 0) {
                    return;
                }
                dialog.pasteItems = [];
                var entriesForPaste = clipboardService.retrieveEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
                _.each(entriesForPaste, function (entry) {
                    dialog.pasteItems.push({
                        date: entry.date,
                        name: entry.label,
                        data: entry.data,
                        icon: entry.icon
                    });
                });
                dialog.pasteItems.sort(function (a, b) {
                    return b.date - a.date;
                });
                dialog.title = dialog.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty;
                dialog.hideHeader = dialog.pasteItems.length > 0;
                dialog.clickClearPaste = function ($event) {
                    $event.stopPropagation();
                    $event.preventDefault();
                    clipboardService.clearEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
                    dialog.pasteItems = [];
                    // This dialog is not connected via the clipboardService events, so we need to update manually.
                    dialog.hideHeader = false;
                };
                if (dialog.availableItems.length === 1 && dialog.pasteItems.length === 0) {
                    // only one scaffold type - no need to display the picker
                    addNode(vm.scaffolds[0].contentTypeAlias);
                    dialog.close();
                    return;
                }
                overlayService.open(dialog);
            };
            vm.editNode = function (idx) {
                if (vm.currentNode && vm.currentNode.key === vm.nodes[idx].key) {
                    setCurrentNode(null, false);
                } else {
                    setCurrentNode(vm.nodes[idx], true);
                }
            };
            vm.canDeleteNode = function (idx) {
                return vm.nodes.length > vm.minItems ? true : model.config.contentTypes.length > 1;
            };
            function deleteNode(idx) {
                var removed = vm.nodes.splice(idx, 1);
                setDirty();
                removed.forEach(function (x) {
                    // remove any server validation errors associated
                    serverValidationManager.removePropertyError(x.key, vm.umbProperty.property.culture, vm.umbProperty.property.segment, '', { matchType: 'contains' });
                });
                updateModel();
                validate();
            }
            ;
            vm.requestDeleteNode = function (idx) {
                if (!vm.canDeleteNode(idx)) {
                    return;
                }
                if (model.config.confirmDeletes === true) {
                    localizationService.localizeMany([
                        'content_nestedContentDeleteItem',
                        'general_delete',
                        'general_cancel',
                        'contentTypeEditor_yesDelete'
                    ]).then(function (data) {
                        var overlay = {
                            title: data[1],
                            content: data[0],
                            closeButtonLabel: data[2],
                            submitButtonLabel: data[3],
                            submitButtonStyle: 'danger',
                            close: function close() {
                                overlayService.close();
                            },
                            submit: function submit() {
                                deleteNode(idx);
                                overlayService.close();
                            }
                        };
                        overlayService.open(overlay);
                    });
                } else {
                    deleteNode(idx);
                }
            };
            vm.getName = function (idx) {
                if (!model.value || !model.value.length) {
                    return '';
                }
                var name = '';
                if (model.value[idx]) {
                    var contentType = getContentTypeConfig(model.value[idx].ncContentTypeAlias);
                    if (contentType != null) {
                        // first try getting a name using the configured label template
                        if (contentType.nameExp) {
                            // Run the expression against the stored dictionary value, NOT the node object
                            var item = model.value[idx];
                            // Add a temporary index property
                            item['$index'] = idx + 1;
                            var newName = contentType.nameExp(item);
                            if (newName && (newName = newName.trim())) {
                                name = newName;
                            }
                            // Delete the index property as we don't want to persist it
                            delete item['$index'];
                        }
                        // if we still do not have a name and we have multiple content types to choose from, use the content type name (same as is shown in the content type picker)
                        if (!name && vm.scaffolds.length > 1) {
                            var scaffold = getScaffold(contentType.ncAlias);
                            if (scaffold) {
                                name = scaffold.contentTypeName;
                            }
                        }
                    }
                }
                if (!name) {
                    name = 'Item ' + (idx + 1);
                }
                // Update the nodes actual name value
                if (vm.nodes[idx].name !== name) {
                    vm.nodes[idx].name = name;
                }
                return name;
            };
            vm.getIcon = function (idx) {
                if (!model.value || !model.value.length) {
                    return '';
                }
                var scaffold = getScaffold(model.value[idx].ncContentTypeAlias);
                return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : 'icon-folder';
            };
            vm.sortableOptions = {
                axis: 'y',
                containment: 'parent',
                cursor: 'move',
                handle: '.umb-nested-content__header-bar',
                distance: 10,
                opacity: 0.7,
                tolerance: 'pointer',
                scroll: true,
                start: function start(ev, ui) {
                    updateModel();
                    // Yea, yea, we shouldn't modify the dom, sue me
                    $('#umb-nested-content--' + model.id + ' .umb-rte textarea').each(function () {
                        tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                        $(this).css('visibility', 'hidden');
                    });
                    $scope.$apply(function () {
                        vm.sorting = true;
                    });
                },
                update: function update(ev, ui) {
                    setDirty();
                },
                stop: function stop(ev, ui) {
                    $('#umb-nested-content--' + model.id + ' .umb-rte textarea').each(function () {
                        tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                        $(this).css('visibility', 'visible');
                    });
                    $scope.$apply(function () {
                        vm.sorting = false;
                        updateModel();
                    });
                }
            };
            function getScaffold(alias) {
                return _.find(vm.scaffolds, function (scaffold) {
                    return scaffold.contentTypeAlias === alias;
                });
            }
            function getContentTypeConfig(alias) {
                return _.find(model.config.contentTypes, function (contentType) {
                    return contentType.ncAlias === alias;
                });
            }
            function clearNodeForCopy(clonedData) {
                delete clonedData.key;
                delete clonedData.$$hashKey;
                var variant = clonedData.variants[0];
                for (var t = 0; t < variant.tabs.length; t++) {
                    var tab = variant.tabs[t];
                    for (var p = 0; p < tab.properties.length; p++) {
                        var prop = tab.properties[p];
                        // If we have ncSpecific data, lets revert to standard data model.
                        if (prop.propertyAlias) {
                            prop.alias = prop.propertyAlias;
                            delete prop.propertyAlias;
                        }
                        if (prop.ncMandatory !== undefined) {
                            prop.validation.mandatory = prop.ncMandatory;
                            delete prop.ncMandatory;
                        }
                    }
                }
            }
            vm.showCopy = clipboardService.isSupported();
            vm.showPaste = false;
            vm.clickCopy = function ($event, node) {
                syncCurrentNode();
                clipboardService.copy(clipboardService.TYPES.ELEMENT_TYPE, node.contentTypeAlias, node, null, null, null, clearNodeForCopy);
                $event.stopPropagation();
            };
            function pasteFromClipboard(newNode) {
                if (newNode === undefined) {
                    return;
                }
                newNode = clipboardService.parseContentForPaste(newNode, clipboardService.TYPES.ELEMENT_TYPE);
                // generate a new key.
                newNode.key = String.CreateGuid();
                // Ensure we have NC data in place:
                var variant = newNode.variants[0];
                for (var t = 0; t < variant.tabs.length; t++) {
                    var tab = variant.tabs[t];
                    for (var p = 0; p < tab.properties.length; p++) {
                        extendPropertyWithNCData(tab.properties[p]);
                    }
                }
                vm.nodes.push(newNode);
                setDirty();
                //updateModel();// done by setting current node...
                setCurrentNode(newNode, true);
            }
            function checkAbilityToPasteContent() {
                vm.showPaste = clipboardService.hasEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
            }
            var storageUpdate = eventsService.on('clipboardService.storageUpdate', checkAbilityToPasteContent);
            $scope.$on('$destroy', function () {
                storageUpdate();
            });
            var notSupported = [
                'Umbraco.Tags',
                'Umbraco.UploadField',
                'Umbraco.ImageCropper',
                'Umbraco.BlockList'
            ];
            // Initialize
            vm.scaffolds = [];
            contentResource.getScaffolds(-20, contentTypeAliases).then(function (scaffolds) {
                // Loop through all the content types
                _.each(model.config.contentTypes, function (contentType) {
                    // Get the scaffold from the result
                    var scaffold = scaffolds[contentType.ncAlias];
                    // make sure it's an element type before allowing the user to create new ones
                    if (scaffold.isElement) {
                        // remove all tabs except the specified tab
                        var tabs = scaffold.variants[0].tabs;
                        var tab = _.find(tabs, function (tab) {
                            return tab.id !== 0 && (tab.label.toLowerCase() === contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias === '');
                        });
                        scaffold.variants[0].tabs = [];
                        if (tab) {
                            scaffold.variants[0].tabs.push(tab);
                            tab.properties.forEach(function (property) {
                                if (_.find(notSupported, function (x) {
                                        return x === property.editor;
                                    })) {
                                    property.notSupported = true;
                                    // TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                                    property.notSupportedMessage = 'Property ' + property.label + ' uses editor ' + property.editor + ' which is not supported by Nested Content.';
                                }
                            });
                        }
                        // Ensure Culture Data for Complex Validation.
                        ensureCultureData(scaffold);
                        // Store the scaffold object
                        vm.scaffolds.push(scaffold);
                    }
                });
                // Initialize once all scaffolds have been loaded
                initNestedContent();
            });
            /**
     * Ensure that the containing content variant language and current property culture is transferred along
     * to the scaffolded content object representing this block.
     * This is required for validation along with ensuring that the umb-property inheritance is constantly maintained.
     * @param {any} content
     */
            function ensureCultureData(content) {
                if (!content || !vm.umbVariantContent || !vm.umbProperty)
                    return;
                if (vm.umbVariantContent.editor.content.language) {
                    // set the scaffolded content's language to the language of the current editor
                    content.language = vm.umbVariantContent.editor.content.language;
                }
                // currently we only ever deal with invariant content for blocks so there's only one
                content.variants[0].tabs.forEach(function (tab) {
                    tab.properties.forEach(function (prop) {
                        // set the scaffolded property to the culture of the containing property
                        prop.culture = vm.umbProperty.property.culture;
                    });
                });
            }
            var initNestedContent = function initNestedContent() {
                // Initialize when all scaffolds have loaded
                // Sort the scaffold explicitly according to the sort order defined by the data type.
                vm.scaffolds = $filter('orderBy')(vm.scaffolds, function (s) {
                    return contentTypeAliases.indexOf(s.contentTypeAlias);
                });
                // Convert stored nodes
                if (model.value) {
                    for (var i = 0; i < model.value.length; i++) {
                        var item = model.value[i];
                        var scaffold = getScaffold(item.ncContentTypeAlias);
                        if (scaffold == null) {
                            // No such scaffold - the content type might have been deleted. We need to skip it.
                            continue;
                        }
                        createNode(scaffold, item);
                    }
                }
                // Enforce min items if we only have one scaffold type
                var modelWasChanged = false;
                if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) {
                    for (var i = vm.nodes.length; i < model.config.minItems; i++) {
                        addNode(vm.scaffolds[0].contentTypeAlias);
                    }
                    modelWasChanged = true;
                }
                // If there is only one item, set it as current node
                if (vm.singleMode || vm.nodes.length === 1 && vm.maxItems === 1) {
                    setCurrentNode(vm.nodes[0], false);
                }
                validate();
                vm.inited = true;
                if (modelWasChanged) {
                    updateModel();
                }
                updatePropertyActionStates();
                checkAbilityToPasteContent();
            };
            function extendPropertyWithNCData(prop) {
                if (prop.propertyAlias === undefined) {
                    // store the original alias before we change below, see notes
                    prop.propertyAlias = prop.alias;
                    // NOTE: This is super ugly, the reason it is like this is because it controls the label/html id in the umb-property component at a higher level.
                    // not pretty :/ but we can't change this now since it would require a bunch of plumbing to be able to change the id's higher up.
                    prop.alias = model.alias + '___' + prop.alias;
                }
                // TODO: Do we need to deal with this separately?
                // Force validation to occur server side as this is the
                // only way we can have consistency between mandatory and
                // regex validation messages. Not ideal, but it works.
                if (prop.ncMandatory === undefined) {
                    prop.ncMandatory = prop.validation.mandatory;
                    prop.validation = {
                        mandatory: false,
                        pattern: ''
                    };
                }
            }
            function createNode(scaffold, fromNcEntry) {
                var node = Utilities.copy(scaffold);
                node.key = fromNcEntry && fromNcEntry.key ? fromNcEntry.key : String.CreateGuid();
                var variant = node.variants[0];
                for (var t = 0; t < variant.tabs.length; t++) {
                    var tab = variant.tabs[t];
                    for (var p = 0; p < tab.properties.length; p++) {
                        var prop = tab.properties[p];
                        extendPropertyWithNCData(prop);
                        if (fromNcEntry && fromNcEntry[prop.propertyAlias]) {
                            prop.value = fromNcEntry[prop.propertyAlias];
                        }
                    }
                }
                vm.nodes.push(node);
                return node;
            }
            function convertNodeIntoNCEntry(node) {
                var obj = {
                    key: node.key,
                    name: node.name,
                    ncContentTypeAlias: node.contentTypeAlias
                };
                for (var t = 0; t < node.variants[0].tabs.length; t++) {
                    var tab = node.variants[0].tabs[t];
                    for (var p = 0; p < tab.properties.length; p++) {
                        var prop = tab.properties[p];
                        if (typeof prop.value !== 'function') {
                            obj[prop.propertyAlias] = prop.value;
                        }
                    }
                }
                return obj;
            }
            function syncCurrentNode() {
                if (vm.currentNode) {
                    $scope.$broadcast('ncSyncVal', { key: vm.currentNode.key });
                }
            }
            function updateModel() {
                syncCurrentNode();
                if (vm.inited) {
                    var newValues = [];
                    for (var i = 0; i < vm.nodes.length; i++) {
                        newValues.push(convertNodeIntoNCEntry(vm.nodes[i]));
                    }
                    model.value = newValues;
                }
                updatePropertyActionStates();
            }
            function updatePropertyActionStates() {
                copyAllEntriesAction.isDisabled = !model.value || !model.value.length;
                removeAllEntriesAction.isDisabled = copyAllEntriesAction.isDisabled;
            }
            var propertyActions = [
                copyAllEntriesAction,
                removeAllEntriesAction
            ];
            this.$onInit = function () {
                if (vm.umbProperty) {
                    vm.umbProperty.setPropertyActions(propertyActions);
                }
            };
            var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                updateModel();
            });
            var validate = function validate() {
                if (vm.nodes.length < vm.minItems) {
                    $scope.nestedContentForm.minCount.$setValidity('minCount', false);
                } else {
                    $scope.nestedContentForm.minCount.$setValidity('minCount', true);
                }
                if (vm.nodes.length > vm.maxItems) {
                    $scope.nestedContentForm.maxCount.$setValidity('maxCount', false);
                } else {
                    $scope.nestedContentForm.maxCount.$setValidity('maxCount', true);
                }
            };
            var watcher = $scope.$watch(function () {
                return vm.nodes.length;
            }, function () {
                validate();
            });
            $scope.$on('$destroy', function () {
                unsubscribe();
                cultureChanged();
                watcher();
            });
        }
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.DocTypePickerController', [
        '$scope',
        'Umbraco.PropertyEditors.NestedContent.Resources',
        'overlayService',
        'localizationService',
        'iconHelper',
        function ($scope, ncResources, overlayService, localizationService, iconHelper) {
            var selectElementTypeModalTitle = '';
            $scope.elemTypeTabs = [];
            init();
            function init() {
                localizationService.localize('content_nestedContentSelectElementTypeModalTitle').then(function (value) {
                    selectElementTypeModalTitle = value;
                });
                ncResources.getContentTypes().then(function (elemTypes) {
                    $scope.model.elemTypes = elemTypes;
                    // convert legacy icons
                    iconHelper.formatContentTypeIcons($scope.model.elemTypes);
                    // Count doctype name occurrences
                    var elTypeNameOccurrences = _.countBy(elemTypes, 'name');
                    // Populate document type tab dictionary
                    // And append alias to name if multiple doctypes have the same name
                    elemTypes.forEach(function (value) {
                        $scope.elemTypeTabs[value.alias] = value.tabs;
                        if (elTypeNameOccurrences[value.name] > 1) {
                            value.name += ' (' + value.alias + ')';
                        }
                    });
                });
            }
            $scope.add = function () {
                $scope.model.value.push({
                    // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                    // For good measure we'll also prefix the tab alias "nc"
                    ncAlias: '',
                    ncTabAlias: '',
                    nameTemplate: ''
                });
            };
            $scope.remove = function (index) {
                $scope.model.value.splice(index, 1);
            };
            $scope.sortableOptions = {
                axis: 'y',
                cursor: 'move',
                handle: '.handle',
                placeholder: 'sortable-placeholder',
                forcePlaceholderSize: true,
                helper: function helper(e, ui) {
                    // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
                    ui.children().each(function () {
                        $(this).width($(this).width());
                    });
                    return ui;
                },
                start: function start(e, ui) {
                    var cellHeight = ui.item.height();
                    // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
                    var cellCount = 0;
                    $('td, th', ui.helper).each(function () {
                        // For each td or th try and get it's colspan attribute, and add that or 1 to the total
                        var colspan = 1;
                        var colspanAttr = $(this).attr('colspan');
                        if (colspanAttr > 1) {
                            colspan = colspanAttr;
                        }
                        cellCount += colspan;
                    });
                    // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
                    ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(cellHeight);
                }
            };
            $scope.placeholder = function (config) {
                return _.find($scope.model.elemTypes, function (elType) {
                    return elType.alias === config.ncAlias;
                });
            };
            $scope.selectableElemTypesFor = function (config) {
                // return all elemTypes that are:
                // 1. either already selected for this config, or
                // 2. not selected in any other config
                return _.filter($scope.model.elemTypes, function (elType) {
                    return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
                        return elType.alias === c.ncAlias;
                    });
                });
            };
            $scope.canAdd = function () {
                return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
                    return !_.find($scope.model.value, function (c) {
                        return elType.alias === c.ncAlias;
                    });
                });
            };
            $scope.openElemTypeModal = function ($event, config) {
                //we have to add the alias to the objects (they are stored as ncAlias)
                var selectedItems = _.each($scope.model.value, function (obj) {
                    obj.alias = obj.ncAlias;
                    return obj;
                });
                var elemTypeSelectorOverlay = {
                    view: 'itempicker',
                    title: selectElementTypeModalTitle,
                    availableItems: $scope.selectableElemTypesFor(config),
                    selectedItems: selectedItems,
                    position: 'target',
                    event: $event,
                    submit: function submit(model) {
                        config.ncAlias = model.selectedItem.alias;
                        if (model.selectedItem.tabs.length === 1) {
                            config.ncTabAlias = model.selectedItem.tabs[0];
                        }
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                overlayService.open(elemTypeSelectorOverlay);
            };
            if (!$scope.model.value) {
                $scope.model.value = [];
                $scope.add();
            }
        }
    ]);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RadioButtonsController', function ($scope, validationMessageService) {
        var vm = this;
        vm.viewItems = [];
        function init() {
            vm.uniqueId = String.CreateGuid();
            //we can't really do anything if the config isn't an object
            if (Utilities.isObject($scope.model.config.items)) {
                // formatting the items in the dictionary into an array
                var sortedItems = [];
                var vals = _.values($scope.model.config.items);
                var keys = _.keys($scope.model.config.items);
                for (var i = 0; i < vals.length; i++) {
                    sortedItems.push({
                        key: keys[i],
                        sortOrder: vals[i].sortOrder,
                        value: vals[i].value
                    });
                }
                // ensure the items are sorted by the provided sort order
                sortedItems.sort(function (a, b) {
                    return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
                });
                vm.viewItems = sortedItems;
            }
            // Set the message to use for when a mandatory field isn't completed.
            // Will either use the one provided on the property type or a localised default.
            validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
                $scope.mandatoryMessage = value;
            });
        }
        init();
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ReadOnlyValueController
 * @function
 * 
 * @description
 * The controller for the readonlyvalue property editor. 
 *  This controller offer more functionality than just a simple label as it will be able to apply formatting to the 
 *  value to be displayed. This means that we also have to apply more complex logic of watching the model value when 
 *  it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
 *  In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of 
 *  readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
*/
    function ReadOnlyValueController($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if ($scope.model.config && $scope.model.config.filter) {
                if ($scope.model.config.format) {
                    $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
                } else {
                    $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
                }
            } else {
                $scope.displayvalue = $scope.model.value;
            }
        }
        //format the display value on init:
        formatDisplayValue();
        $scope.$watch('model.value', function (newVal, oldVal) {
            //cannot just check for !newVal because it might be an empty string which we 
            //want to look for.
            if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                //update the display val again
                formatDisplayValue();
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ReadOnlyValueController', ReadOnlyValueController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RelatedLinksController', function ($scope, iconHelper, editorService) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE;
        $scope.newCaption = '';
        $scope.newLink = 'http://';
        $scope.newNewWindow = false;
        $scope.newInternal = null;
        $scope.newInternalName = '';
        $scope.newInternalIcon = null;
        $scope.addExternal = true;
        $scope.currentEditLink = null;
        $scope.hasError = false;
        $scope.internal = function ($event) {
            $scope.currentEditLink = null;
            var contentPicker = {
                section: 'content',
                treeAlias: 'content',
                multiPicker: false,
                idType: $scope.model.config.idType ? $scope.model.config.idType : 'int',
                submit: function submit(model) {
                    select(model.selection[0]);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.treePicker(contentPicker);
            $event.preventDefault();
        };
        $scope.selectInternal = function ($event, link) {
            $scope.currentEditLink = link;
            var contentPicker = {
                section: 'content',
                treeAlias: 'content',
                multiPicker: false,
                idType: $scope.model.config.idType ? $scope.model.config.idType : 'udi',
                submit: function submit(model) {
                    select(model.selection[0]);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.treePicker(contentPicker);
            $event.preventDefault();
        };
        $scope.edit = function (idx) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                $scope.model.value[i].edit = false;
            }
            $scope.model.value[idx].edit = true;
        };
        $scope.saveEdit = function (idx) {
            $scope.model.value[idx].title = $scope.model.value[idx].caption;
            $scope.model.value[idx].edit = false;
        };
        $scope.delete = function (idx) {
            $scope.model.value.splice(idx, 1);
        };
        $scope.add = function ($event) {
            if (!Utilities.isArray($scope.model.value)) {
                $scope.model.value = [];
            }
            if ($scope.newCaption == '') {
                $scope.hasError = true;
            } else {
                if ($scope.addExternal) {
                    var newExtLink = new function () {
                        this.caption = $scope.newCaption;
                        this.link = $scope.newLink;
                        this.newWindow = $scope.newNewWindow;
                        this.edit = false;
                        this.isInternal = false;
                        this.type = 'external';
                        this.title = $scope.newCaption;
                    }();
                    $scope.model.value.push(newExtLink);
                } else {
                    var newIntLink = new function () {
                        this.caption = $scope.newCaption;
                        this.link = $scope.newInternal;
                        this.newWindow = $scope.newNewWindow;
                        this.internal = $scope.newInternal;
                        this.edit = false;
                        this.isInternal = true;
                        this.internalName = $scope.newInternalName;
                        this.internalIcon = $scope.newInternalIcon;
                        this.type = 'internal';
                        this.title = $scope.newCaption;
                    }();
                    $scope.model.value.push(newIntLink);
                }
                $scope.newCaption = '';
                $scope.newLink = 'http://';
                $scope.newNewWindow = false;
                $scope.newInternal = null;
                $scope.newInternalName = '';
                $scope.newInternalIcon = null;
            }
            $event.preventDefault();
        };
        $scope.switch = function ($event) {
            $scope.addExternal = !$scope.addExternal;
            $event.preventDefault();
        };
        $scope.switchLinkType = function ($event, link) {
            link.isInternal = !link.isInternal;
            link.type = link.isInternal ? 'internal' : 'external';
            if (!link.isInternal)
                link.link = $scope.newLink;
            $event.preventDefault();
        };
        $scope.move = function (index, direction) {
            var temp = $scope.model.value[index];
            $scope.model.value[index] = $scope.model.value[index + direction];
            $scope.model.value[index + direction] = temp;
        };
        //helper for determining if a user can add items
        $scope.canAdd = function () {
            return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible();
        };
        //helper that returns if an item can be sorted
        $scope.canSort = function () {
            return countVisible() > 1;
        };
        $scope.sortableOptions = {
            axis: 'y',
            handle: '.handle',
            cursor: 'move',
            cancel: '.no-drag',
            containment: 'parent',
            placeholder: 'sortable-placeholder',
            forcePlaceholderSize: true,
            helper: function helper(e, ui) {
                // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
                ui.children().each(function () {
                    $(this).width($(this).width());
                });
                return ui;
            },
            items: '> tr:not(.unsortable)',
            tolerance: 'pointer',
            update: function update(e, ui) {
                // Get the new and old index for the moved element (using the URL as the identifier)
                var newIndex = ui.item.index();
                var movedLinkUrl = ui.item.attr('data-link');
                var originalIndex = getElementIndexByUrl(movedLinkUrl);
                // Move the element in the model
                var movedElement = $scope.model.value[originalIndex];
                $scope.model.value.splice(originalIndex, 1);
                $scope.model.value.splice(newIndex, 0, movedElement);
            },
            start: function start(e, ui) {
                //ui.placeholder.html("<td colspan='5'></td>");
                // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
                var cellCount = 0;
                $('td, th', ui.helper).each(function () {
                    // For each td or th try and get it's colspan attribute, and add that or 1 to the total
                    var colspan = 1;
                    var colspanAttr = $(this).attr('colspan');
                    if (colspanAttr > 1) {
                        colspan = colspanAttr;
                    }
                    cellCount += colspan;
                });
                // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
                ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(ui.item.height());
            }
        };
        //helper to count what is visible
        function countVisible() {
            return $scope.model.value.length;
        }
        function isNumeric(n) {
            return !isNaN(parseFloat(n)) && isFinite(n);
        }
        function getElementIndexByUrl(url) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].link == url) {
                    return i;
                }
            }
            return -1;
        }
        function select(data) {
            if ($scope.currentEditLink != null) {
                $scope.currentEditLink.internal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                $scope.currentEditLink.internalName = data.name;
                $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon);
                $scope.currentEditLink.link = $scope.model.config.idType === 'udi' ? data.udi : data.id;
            } else {
                $scope.newInternal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                $scope.newInternalName = data.name;
                $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon);
            }
        }
    });
    'use strict';
    (function () {
        'use strict';
        function CodeEditorController($scope, localizationService) {
            var vm = this;
            vm.submit = submit;
            vm.close = close;
            vm.aceOption = {};
            vm.aceOption = {
                mode: 'razor',
                theme: 'chrome',
                showPrintMargin: false,
                autoFocus: true,
                advanced: {
                    fontSize: '14px',
                    enableSnippets: false,
                    //The Razor mode snippets are awful (Need a way to override these)
                    enableBasicAutocompletion: true,
                    enableLiveAutocompletion: false,
                    wrap: true
                },
                onLoad: function onLoad(aceEditor) {
                    vm.aceEditor = aceEditor;
                }
            };
            vm.template = {};
            vm.template.content = $scope.model.content;
            //////////
            function onInit() {
                // set default title
                if (!$scope.model.title) {
                    // TODO: localize
                    $scope.model.title = 'Edit source code';
                }
            }
            function submit(model) {
                // refresh the model
                model.content = vm.aceEditor.getValue();
                if ($scope.model.submit) {
                    $scope.model.submit(model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.RTECodeEditorController', CodeEditorController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RTEController', function ($scope, $q, assetsService, $timeout, tinyMceService, angularHelper, tinyMceAssets) {
        var _this = this;
        // TODO: A lot of the code below should be shared between the grid rte and the normal rte
        $scope.isLoading = true;
        //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
        // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
        // we have this mini content editor panel that can be launched with MNTP.
        $scope.textAreaHtmlId = $scope.model.alias + '_' + String.CreateGuid();
        var editorConfig = $scope.model.config ? $scope.model.config.editor : null;
        if (!editorConfig || Utilities.isString(editorConfig)) {
            editorConfig = tinyMceService.defaultPrevalues();
        }
        //make sure there's a max image size
        if (!editorConfig.maxImageSize && editorConfig.maxImageSize !== 0) {
            editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
        }
        var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null;
        var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null;
        $scope.containerWidth = editorConfig.mode === 'distraction-free' ? width ? width : 'auto' : 'auto';
        $scope.containerHeight = editorConfig.mode === 'distraction-free' ? height ? height : 'auto' : 'auto';
        $scope.containerOverflow = editorConfig.mode === 'distraction-free' ? height ? 'auto' : 'inherit' : 'inherit';
        var promises = [];
        // we need to make sure that the element is initialized before we can init TinyMCE, because we find the placeholder by ID, so it needs to be appended to document before.
        var initPromise = $q(function (resolve, reject) {
            _this.$onInit = resolve;
        });
        promises.push(initPromise);
        //queue file loading
        tinyMceAssets.forEach(function (tinyJsAsset) {
            promises.push(assetsService.loadJs(tinyJsAsset, $scope));
        });
        //stores a reference to the editor
        var tinyMceEditor = null;
        promises.push(tinyMceService.getTinyMceEditorConfig({
            htmlId: $scope.textAreaHtmlId,
            stylesheets: editorConfig.stylesheets,
            toolbar: editorConfig.toolbar,
            mode: editorConfig.mode
        }));
        //wait for queue to end
        $q.all(promises).then(function (result) {
            var standardConfig = result[promises.length - 1];
            if (height !== null) {
                standardConfig.plugins.splice(standardConfig.plugins.indexOf('autoresize'), 1);
            }
            //create a baseline Config to extend upon
            var baseLineConfigObj = {
                maxImageSize: editorConfig.maxImageSize,
                width: width,
                height: height
            };
            baseLineConfigObj.setup = function (editor) {
                //set the reference
                tinyMceEditor = editor;
                tinyMceEditor.on('init', function (e) {
                    $timeout(function () {
                        $scope.isLoading = false;
                    });
                });
                //initialize the standard editor functionality for Umbraco
                tinyMceService.initializeEditor({
                    editor: editor,
                    model: $scope.model,
                    currentFormInput: $scope.rteForm.modelValue
                });
            };
            Utilities.extend(baseLineConfigObj, standardConfig);
            // We need to wait for DOM to have rendered before we can find the element by ID.
            $timeout(function () {
                tinymce.init(baseLineConfigObj);
            }, 150);
            //listen for formSubmitting event (the result is callback used to remove the event subscription)
            var unsubscribe = $scope.$on('formSubmitting', function () {
                if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
                    $scope.model.value = tinyMceEditor.getContent();
                }
            });
            $scope.focus = function () {
                tinyMceEditor.focus();
            };
            //when the element is disposed we need to unsubscribe!
            // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
            // element might still be there even after the modal has been hidden.
            $scope.$on('$destroy', function () {
                unsubscribe();
                if (tinyMceEditor !== undefined && tinyMceEditor != null) {
                    tinyMceEditor.destroy();
                }
            });
        });
    });
    'use strict';
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.RteController', function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) {
        var cfg = tinyMceService.defaultPrevalues();
        if ($scope.model.value) {
            if (Utilities.isString($scope.model.value)) {
                $scope.model.value = cfg;
            }
        } else {
            $scope.model.value = cfg;
        }
        if (!$scope.model.value.stylesheets) {
            $scope.model.value.stylesheets = [];
        }
        if (!$scope.model.value.toolbar) {
            $scope.model.value.toolbar = [];
        }
        if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
            $scope.model.value.maxImageSize = cfg.maxImageSize;
        }
        if (!$scope.model.value.mode) {
            $scope.model.value.mode = 'classic';
        }
        tinyMceService.configuration().then(function (config) {
            $scope.tinyMceConfig = config;
            // extend commands with properties for font-icon and if it is a custom command
            $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) {
                var icon = getFontIcon(obj.alias);
                var objCmd = Utilities.extend(obj, {
                    fontIcon: icon.name,
                    isCustom: icon.isCustom,
                    selected: $scope.model.value.toolbar.indexOf(obj.alias) >= 0,
                    icon: 'mce-ico ' + (icon.isCustom ? ' mce-i-custom ' : ' mce-i-') + icon.name
                });
                return objCmd;
            });
        });
        stylesheetResource.getAll().then(function (stylesheets) {
            $scope.stylesheets = stylesheets;
            // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible
            // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved...
            var cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath;
            $scope.model.value.stylesheets = $scope.model.value.stylesheets.filter(function (sheet) {
                return sheet.startsWith(cssPath);
            });
            $scope.stylesheets.forEach(function (stylesheet) {
                // support both current format (full stylesheet path) and legacy format (stylesheet name only) 
                stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 || $scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0;
            });
        });
        $scope.selectCommand = function (command) {
            var index = $scope.model.value.toolbar.indexOf(command.alias);
            if (command.selected && index === -1) {
                $scope.model.value.toolbar.push(command.alias);
            } else if (index >= 0) {
                $scope.model.value.toolbar.splice(index, 1);
            }
        };
        $scope.selectStylesheet = function (css) {
            // find out if the stylesheet is already selected; first look for the full stylesheet path (current format)
            var index = $scope.model.value.stylesheets.indexOf(css.path);
            if (index === -1) {
                // ... then look for the stylesheet name (legacy format)
                index = $scope.model.value.stylesheets.indexOf(css.name);
            }
            if (index === -1) {
                $scope.model.value.stylesheets.push(css.path);
            } else {
                $scope.model.value.stylesheets.splice(index, 1);
            }
        };
        // map properties for specific commands
        function getFontIcon(alias) {
            var icon = {
                name: alias,
                isCustom: false
            };
            switch (alias) {
            case 'ace':
                icon.name = 'code';
                icon.isCustom = false;
                break;
            case 'styleselect':
            case 'fontsizeselect':
                icon.name = 'icon-list';
                icon.isCustom = true;
                break;
            case 'umbembeddialog':
                icon.name = 'icon-tv';
                icon.isCustom = true;
                break;
            case 'umbmediapicker':
                icon.name = 'icon-picture';
                icon.isCustom = true;
                break;
            case 'umbmacro':
                icon.name = 'icon-settings-alt';
                icon.isCustom = true;
                break;
            case 'umbmacro':
                icon.name = 'icon-settings-alt';
                icon.isCustom = true;
                break;
            default:
                icon.name = alias;
                icon.isCustom = false;
            }
            return icon;
        }
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var commands = _.where($scope.tinyMceConfig.commands, { selected: true });
            $scope.model.value.toolbar = _.pluck(commands, 'alias');
        });
        // when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        // load TinyMCE skin which contains css for font-icons
        assetsService.loadCss('lib/tinymce/skins/lightgray/skin.min.css', $scope);
    });
    'use strict';
    function sliderController($scope) {
        var sliderRef = null;
        /** configure some defaults on init */
        function configureDefaults() {
            $scope.model.config.enableRange = $scope.model.config.enableRange ? Object.toBoolean($scope.model.config.enableRange) : false;
            $scope.model.config.initVal1 = $scope.model.config.initVal1 ? parseFloat($scope.model.config.initVal1) : 0;
            $scope.model.config.initVal2 = $scope.model.config.initVal2 ? parseFloat($scope.model.config.initVal2) : 0;
            $scope.model.config.minVal = $scope.model.config.minVal ? parseFloat($scope.model.config.minVal) : 0;
            $scope.model.config.maxVal = $scope.model.config.maxVal ? parseFloat($scope.model.config.maxVal) : 100;
            $scope.model.config.step = $scope.model.config.step ? parseFloat($scope.model.config.step) : 1;
        }
        function setModelValue(values) {
            $scope.model.value = values ? values.toString() : null;
            setDirty();
        }
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        $scope.setup = function (slider) {
            sliderRef = slider;
        };
        $scope.change = function (values) {
            setModelValue(values);
        };
        function init() {
            configureDefaults();
            // format config to fit slider plugin
            var start = $scope.model.config.enableRange ? [
                $scope.model.config.initVal1,
                $scope.model.config.initVal2
            ] : [$scope.model.config.initVal1];
            var step = $scope.model.config.step;
            var tooltips = $scope.model.config.enableRange ? [
                true,
                true
            ] : [true];
            var min = $scope.model.config.minVal ? [$scope.model.config.minVal] : [$scope.model.config.minVal];
            var max = $scope.model.config.maxVal ? [$scope.model.config.maxVal] : [$scope.model.config.maxVal];
            // set model.value to the default value if it's not set (but don't trigger setDirty, just silently update with the default value)
            if (!$scope.model.value) {
                $scope.model.value = start.toString();
            }
            // convert to array - exiting value can be a number if switching from numeric/decimal property editor
            $scope.sliderValue = $scope.model.value ? Utilities.isString($scope.model.value) || Utilities.isNumber($scope.model.value) ? $scope.model.value.toString().split(',') : null : null;
            // don't render values with decimal places if the step increment in a whole number
            var stepDecimalPlaces = $scope.model.config.step % 1 == 0 ? 0 : _.last($scope.model.config.step.toString().replace(',', '.').split('.')).length;
            // setup default
            $scope.sliderOptions = {
                'start': start,
                'step': step,
                'tooltips': tooltips,
                'format': {
                    to: function to(value) {
                        return value.toFixed(stepDecimalPlaces);
                    },
                    from: function from(value) {
                        return Number(value);
                    }
                },
                'range': {
                    'min': min,
                    'max': max
                },
                'pips': {
                    mode: 'steps',
                    density: 100,
                    filter: filterPips
                }
            };
            function filterPips(value) {
                // show a pip for min and maximum value
                return value === $scope.model.config.minVal || value === $scope.model.config.maxVal ? 1 : -1;
            }
        }
        $scope.$watch('model.value', function (newValue, oldValue) {
            if (newValue && newValue !== oldValue) {
                $scope.sliderValue = newValue.split(',');
                sliderRef.noUiSlider.set($scope.sliderValue);
            }
        });
        init();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.SliderController', sliderController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.TagsController', function ($scope) {
        $scope.valueChanged = function (value) {
            $scope.model.value = value;
        };
    });
    'use strict';
    function textAreaController($scope, validationMessageService) {
        // macro parameter editor doesn't contains a config object,
        // so we create a new one to hold any properties
        if (!$scope.model.config) {
            $scope.model.config = {};
        }
        $scope.maxChars = $scope.model.config.maxChars || 0;
        $scope.maxCharsLimit = $scope.model.config && $scope.model.config.maxChars > 0;
        $scope.charsCount = 0;
        $scope.nearMaxLimit = false;
        $scope.validLength = true;
        $scope.$on('formSubmitting', function () {
            if ($scope.validLength) {
                $scope.textareaFieldForm.textarea.$setValidity('maxChars', true);
            } else {
                $scope.textareaFieldForm.textarea.$setValidity('maxChars', false);
            }
        });
        function checkLengthVadility() {
            $scope.validLength = !($scope.maxCharsLimit === true && $scope.charsCount > $scope.maxChars);
        }
        $scope.change = function () {
            if ($scope.model.value) {
                $scope.charsCount = $scope.model.value.length;
                checkLengthVadility();
                $scope.nearMaxLimit = $scope.maxCharsLimit === true && $scope.validLength === true && $scope.charsCount > Math.max($scope.maxChars * 0.8, $scope.maxChars - 50);
            }
        };
        $scope.model.onValueChanged = $scope.change;
        $scope.change();
        // Set the message to use for when a mandatory field isn't completed.
        // Will either use the one provided on the property type or a localised default.
        validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
            $scope.mandatoryMessage = value;
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.textAreaController', textAreaController);
    'use strict';
    function textboxController($scope, validationMessageService) {
        // macro parameter editor doesn't contains a config object,
        // so we create a new one to hold any properties
        if (!$scope.model.config) {
            $scope.model.config = {};
        }
        // 512 is the maximum number that can be stored
        // in the database, so set it to the max, even
        // if no max is specified in the config
        $scope.maxChars = Math.min($scope.model.config.maxChars || 512, 512);
        $scope.charsCount = 0;
        $scope.nearMaxLimit = false;
        $scope.validLength = true;
        $scope.$on('formSubmitting', function () {
            if ($scope.validLength === true) {
                $scope.textboxFieldForm.textbox.$setValidity('maxChars', true);
            } else {
                $scope.textboxFieldForm.textbox.$setValidity('maxChars', false);
            }
        });
        function checkLengthVadility() {
            $scope.validLength = $scope.charsCount <= $scope.maxChars;
        }
        $scope.change = function () {
            if ($scope.model.value) {
                $scope.charsCount = $scope.model.value.length;
                checkLengthVadility();
                $scope.nearMaxLimit = $scope.validLength && $scope.charsCount > Math.max($scope.maxChars * 0.8, $scope.maxChars - 25);
            } else {
                $scope.charsCount = 0;
                checkLengthVadility();
            }
        };
        $scope.model.onValueChanged = $scope.change;
        $scope.change();
        // Set the message to use for when a mandatory field isn't completed.
        // Will either use the one provided on the property type or a localised default.
        validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
            $scope.mandatoryMessage = value;
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.textboxController', textboxController);
    'use strict';
    angular.module('umbraco').controller('Umbraco.PropertyEditors.UrlListController', function ($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if (Utilities.isArray($scope.model.value)) {
                //it's the json value
                $scope.renderModel = _.map($scope.model.value, function (item) {
                    return {
                        url: item.url,
                        linkText: item.linkText,
                        urlTarget: item.target ? item.target : '_blank',
                        icon: item.icon ? item.icon : 'icon-out'
                    };
                });
            } else {
                //it's the default csv value
                $scope.renderModel = _.map($scope.model.value.split(','), function (item) {
                    return {
                        url: item,
                        linkText: '',
                        urlTarget: $scope.config && $scope.config.target ? $scope.config.target : '_blank',
                        icon: $scope.config && $scope.config.icon ? $scope.config.icon : 'icon-out'
                    };
                });
            }
        }
        $scope.getUrl = function (valueUrl) {
            if (valueUrl.indexOf('/') >= 0) {
                return valueUrl;
            }
            return '#';
        };
        formatDisplayValue();
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again
            formatDisplayValue();
        };
    });
    'use strict';
    function userPickerController($scope, iconHelper, editorService, overlayService, entityResource) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
        function setDirty() {
            if ($scope.modelValueForm) {
                $scope.modelValueForm.modelValue.$setDirty();
            }
        }
        $scope.openUserPicker = function () {
            var currentSelection = [];
            var userPicker = {
                multiPicker: multiPicker,
                selection: currentSelection,
                submit: function submit(model) {
                    if (model.selection) {
                        _.each(model.selection, function (item, i) {
                            $scope.add(item);
                        });
                    }
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.userPicker(userPicker);
        };
        $scope.remove = function (index) {
            var dialog = {
                view: 'views/propertyeditors/userpicker/overlays/remove.html',
                username: $scope.renderModel[index].name,
                submitButtonLabelKey: 'defaultdialogs_yesRemove',
                submitButtonStyle: 'danger',
                submit: function submit() {
                    $scope.renderModel.splice(index, 1);
                    $scope.userName = '';
                    setDirty();
                    overlayService.close();
                },
                close: function close() {
                    overlayService.close();
                }
            };
            overlayService.open(dialog);
        };
        $scope.add = function (item) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
            if (currIds.indexOf(itemId) < 0) {
                item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user';
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    udi: item.udi,
                    icon: item.icon,
                    avatars: item.avatars
                });
                setDirty();
            }
        };
        $scope.clear = function () {
            $scope.renderModel = [];
            setDirty();
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            $scope.model.value = trim(currIds.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        //load user data - split to an array of ints (map)
        var modelIds = $scope.model.value ? $scope.model.value.split(',').map(function (x) {
            return +x;
        }) : [];
        if (modelIds.length !== 0) {
            entityResource.getAll('User').then(function (users) {
                var filteredUsers = users.filter(function (user) {
                    return modelIds.indexOf(user.id) !== -1;
                });
                filteredUsers.forEach(function (item) {
                    $scope.renderModel.push({
                        name: item.name,
                        id: item.id,
                        udi: item.udi,
                        icon: item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user',
                        avatars: item.avatars
                    });
                });
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.UserPickerController', userPickerController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.RelationTypes.CreateController
 * @function
 *
 * @description
 * The controller for creating relation types.
 */
    function RelationTypeCreateController($scope, $location, relationTypeResource, navigationService, formHelper, appState, notificationsService) {
        var vm = this;
        vm.relationType = {};
        vm.objectTypes = {};
        vm.createRelationType = createRelationType;
        init();
        function init() {
            $scope.$emit('$changeTitle', '');
            relationTypeResource.getRelationObjectTypes().then(function (data) {
                vm.objectTypes = data;
            }, function (err) {
                notificationsService.error('Could not load form.');
            });
        }
        function createRelationType() {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: $scope.createRelationTypeForm,
                    statusMessage: 'Creating relation type...'
                })) {
                var node = $scope.currentNode;
                relationTypeResource.create(vm.relationType).then(function (data) {
                    navigationService.hideMenu();
                    // Set the new item as active in the tree
                    var currentPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'relationTypes',
                        path: currentPath + ',' + data,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createRelationTypeForm
                    });
                    var currentSection = appState.getSectionState('currentSection');
                    $location.path('/' + currentSection + '/relationTypes/edit/' + data);
                }, function (err) {
                    formHelper.resetForm({
                        scope: $scope,
                        formCtrl: $scope.createRelationTypeForm,
                        hasErrors: true
                    });
                    if (err.data && err.data.message) {
                        notificationsService.error(err.data.message);
                        navigationService.hideMenu();
                    }
                });
            }
        }
        $scope.close = function () {
            navigationService.hideDialog(true);
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.RelationTypes.CreateController', RelationTypeCreateController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.RelationTypes.DeleteController
 * @function
 *
 * @description
 * The controller for deleting relation types.
 */
    function RelationTypeDeleteController($scope, $location, relationTypeResource, treeService, navigationService, appState) {
        var vm = this;
        vm.cancel = cancel;
        vm.performDelete = performDelete;
        function cancel() {
            navigationService.hideDialog();
        }
        function performDelete() {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            relationTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
                var currentSection = appState.getSectionState('currentSection');
                $location.path('/' + currentSection + '/');
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.RelationTypes.DeleteController', RelationTypeDeleteController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.RelationTypes.EditController
 * @function
 *
 * @description
 * The controller for editing relation types.
 */
    function RelationTypeEditController($scope, $routeParams, relationTypeResource, editorState, navigationService, dateHelper, userService, entityResource, formHelper, contentEditingHelper, localizationService, eventsService) {
        var vm = this;
        vm.header = {};
        vm.header.editorfor = 'relationType_tabRelationType';
        vm.header.setPageTitle = true;
        vm.page = {};
        vm.page.loading = false;
        vm.page.saveButtonState = 'init';
        vm.page.menu = {};
        vm.save = saveRelationType;
        init();
        function init() {
            vm.page.loading = true;
            vm.relationsLoading = true;
            vm.changePageNumber = changePageNumber;
            vm.options = {};
            var labelKeys = [
                'relationType_tabRelationType',
                'relationType_tabRelations'
            ];
            localizationService.localizeMany(labelKeys).then(function (data) {
                vm.page.navigation = [
                    {
                        'name': data[0],
                        'alias': 'relationType',
                        'icon': 'icon-info',
                        'view': 'views/relationTypes/views/relationType.html',
                        'active': true
                    },
                    {
                        'name': data[1],
                        'alias': 'relations',
                        'icon': 'icon-trafic',
                        'view': 'views/relationTypes/views/relations.html'
                    }
                ];
            });
            // load references when the 'relations' tab is first activated/switched to
            var appTabChange = eventsService.on('app.tabChange', function (event, args) {
                if (args.alias === 'relations') {
                    loadRelations();
                }
            });
            $scope.$on('$destroy', function () {
                appTabChange();
            });
            // Inital page/overview API call of relation type
            relationTypeResource.getById($routeParams.id).then(function (data) {
                bindRelationType(data);
                vm.page.loading = false;
            });
        }
        function changePageNumber(pageNumber) {
            vm.options.pageNumber = pageNumber;
            loadRelations();
        }
        /** Loads in the references one time  when content app changed */
        function loadRelations() {
            relationTypeResource.getPagedResults($routeParams.id, vm.options).then(function (data) {
                formatDates(data.items);
                vm.relationsLoading = false;
                vm.relations = data;
            });
        }
        function bindRelationType(relationType) {
            // Convert property value to string, since the umb-radiobutton component at the moment only handle string values.
            // Sometime later the umb-radiobutton might be able to handle value as boolean.
            relationType.isBidirectional = (relationType.isBidirectional || false).toString();
            vm.relationType = relationType;
            editorState.set(vm.relationType);
            navigationService.syncTree({
                tree: 'relationTypes',
                path: relationType.path,
                forceReload: true
            }).then(function (syncArgs) {
                vm.page.menu.currentNode = syncArgs.node;
            });
        }
        function formatDates(relations) {
            if (relations) {
                userService.getCurrentUser().then(function (currentUser) {
                    relations.forEach(function (relation) {
                        relation.timestampFormatted = dateHelper.getLocalDate(relation.createDate, currentUser.locale, 'LLL');
                    });
                });
            }
        }
        function saveRelationType() {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                vm.page.saveButtonState = 'busy';
                relationTypeResource.save(vm.relationType).then(function (data) {
                    formHelper.resetForm({ scope: $scope });
                    bindRelationType(data);
                    vm.page.saveButtonState = 'success';
                }, function (error) {
                    formHelper.resetForm({
                        scope: $scope,
                        hasErrors: true
                    });
                    contentEditingHelper.handleSaveError({ err: error });
                    notificationsService.error(error.data.message);
                    vm.page.saveButtonState = 'error';
                });
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.RelationTypes.EditController', RelationTypeEditController);
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.RelationTypes.SettingsController
 * @function
 *
 * @description
 * The controller for editing relation type settings.
 */
    function RelationTypeSettingsController($scope, localizationService) {
        var vm = this;
        vm.labels = {};
        function init() {
            var labelKeys = [
                'relationType_parentToChild',
                'relationType_bidirectional'
            ];
            localizationService.localizeMany(labelKeys).then(function (data) {
                vm.labels.parentToChild = data[0];
                vm.labels.bidirectional = data[1];
            });
        }
        init();
    }
    angular.module('umbraco').controller('Umbraco.Editors.RelationTypes.SettingsController', RelationTypeSettingsController);
    'use strict';
    (function () {
        'use strict';
        function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) {
            var vm = this;
            var node = $scope.currentNode;
            vm.creatingFolder = false;
            vm.folderName = '';
            vm.createFolderError = '';
            vm.fileExtension = '';
            vm.createFile = createFile;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            vm.close = close;
            function createFile() {
                $location.path('/settings/scripts/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form
                    })) {
                    codefileResource.createContainer('scripts', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'scripts',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form
                        });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form,
                            hasErrors: true
                        });
                        vm.createFolderError = err;
                    });
                }
            }
            function close() {
                var showMenu = true;
                navigationService.hideDialog(showMenu);
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Scripts.CreateController', ScriptsCreateController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Scripts.DeleteController
 * @function
 *
 * @description
 * The controller for deleting scripts
 */
    function ScriptsDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            codefileResource.deleteByPath('scripts', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Scripts.DeleteController', ScriptsDeleteController);
    'use strict';
    (function () {
        'use strict';
        function ScriptsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) {
            var vm = this;
            var currentPosition = null;
            vm.header = {};
            vm.header.editorfor = 'settings_script';
            vm.header.setPageTitle = true;
            vm.page = {};
            vm.page.loading = true;
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            vm.page.saveButtonState = 'init';
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            templateHelper.getGeneralShortcuts().then(function (shortcuts) {
                vm.page.keyboardShortcutsOverview.push(shortcuts);
            });
            templateHelper.getEditorShortcuts().then(function (shortcuts) {
                vm.page.keyboardShortcutsOverview.push(shortcuts);
            });
            vm.script = {};
            // bind functions to view model
            vm.save = save;
            /* Function bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.script.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.script,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    localizationService.localizeMany([
                        'speechBubbles_fileSavedHeader',
                        'speechBubbles_fileSavedText'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.success(header, message);
                    });
                    //check if the name changed, if so we need to redirect
                    if (vm.script.id !== saved.id) {
                        contentEditingHelper.redirectToRenamedContent(saved.id);
                    } else {
                        vm.page.saveButtonState = 'success';
                        vm.script = saved;
                        //sync state
                        editorState.set(vm.script);
                        // sync tree
                        navigationService.syncTree({
                            tree: 'scripts',
                            path: vm.script.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localizeMany([
                        'speechBubbles_validationFailedHeader',
                        'speechBubbles_validationFailedMessage'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.error(header, message);
                    });
                });
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    codefileResource.getScaffold('scripts', $routeParams.id).then(function (script) {
                        ready(script, false);
                    });
                } else {
                    codefileResource.getByPath('scripts', $routeParams.id).then(function (script) {
                        ready(script, true);
                    });
                }
            }
            function ready(script, syncTree) {
                vm.page.loading = false;
                vm.script = script;
                //sync state
                editorState.set(vm.script);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'scripts',
                        path: vm.script.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                vm.aceOption = {
                    mode: 'javascript',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: {
                        fontSize: '14px',
                        enableSnippets: true,
                        enableBasicAutocompletion: true,
                        enableLiveAutocompletion: false
                    },
                    onLoad: function onLoad(_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // TODO: Move all these keybinding config out into some helper/service
                        _editor.commands.addCommands([//Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function exec() {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            }]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new script
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                            });
                        }
                        vm.editor.on('change', changeAceEditor);
                    }
                };
                function changeAceEditor() {
                    setFormState('dirty');
                }
                function setFormState(state) {
                    // get the current form
                    var currentForm = angularHelper.getCurrentForm($scope);
                    // set state
                    if (state === 'dirty') {
                        currentForm.$setDirty();
                    } else if (state === 'pristine') {
                        currentForm.$setPristine();
                    }
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Scripts.EditController', ScriptsEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function StyleSheetsCreateController($scope, $location, navigationService, formHelper, codefileResource) {
            var vm = this;
            var node = $scope.currentNode;
            vm.createFile = createFile;
            vm.createRichtextStyle = createRichtextStyle;
            vm.close = close;
            vm.creatingFolder = false;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            function createFile() {
                $location.path('/settings/stylesheets/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            }
            function createRichtextStyle() {
                $location.path('/settings/stylesheets/edit/' + node.id).search('create', 'true').search('rtestyle', 'true');
                navigationService.hideMenu();
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form
                    })) {
                    codefileResource.createContainer('stylesheets', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'stylesheets',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form
                        });
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            formCtrl: form,
                            hasErrors: true
                        });
                        vm.createFolderError = err;
                    });
                }
            }
            function close() {
                var showMenu = true;
                navigationService.hideDialog(showMenu);
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.StyleSheets.CreateController', StyleSheetsCreateController);
    }());
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.StyleSheets.DeleteController
 * @function
 *
 * @description
 * The controller for deleting stylesheets
 */
    function StyleSheetsDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            codefileResource.deleteByPath('stylesheets', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.StyleSheets.DeleteController', StyleSheetsDeleteController);
    'use strict';
    (function () {
        'use strict';
        function StyleSheetsEditController($scope, $routeParams, $timeout, $http, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper, umbRequestHelper) {
            var vm = this;
            vm.page = {};
            vm.page.loading = true;
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            vm.page.saveButtonState = 'init';
            vm.header = {};
            vm.header.editorfor = 'settings_stylesheet';
            vm.header.setPageTitle = true;
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            templateHelper.getGeneralShortcuts().then(function (shortcuts) {
                vm.page.keyboardShortcutsOverview.push(shortcuts);
            });
            templateHelper.getEditorShortcuts().then(function (shortcuts) {
                vm.page.keyboardShortcutsOverview.push(shortcuts);
            });
            vm.stylesheet = {
                content: '',
                rules: []
            };
            // bind functions to view model
            vm.save = interpolateAndSave;
            /* Function bound to view model */
            function interpolateAndSave() {
                vm.page.saveButtonState = 'busy';
                var activeApp = _.find(vm.page.navigation, function (item) {
                    return item.active;
                });
                if (activeApp.alias === 'rules') {
                    // we're on the rules tab: interpolate the rules into the editor value and save the output as stylesheet content
                    interpolateRules().then(function (content) {
                        vm.stylesheet.content = content;
                        save(activeApp);
                    }, function (err) {
                    });
                } else {
                    // we're on the code tab: just save the editor value as stylesheet content
                    vm.stylesheet.content = vm.editor.getValue();
                    save(activeApp);
                }
            }
            /* Local functions */
            function save(activeApp) {
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.stylesheet,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    localizationService.localizeMany([
                        'speechBubbles_fileSavedHeader',
                        'speechBubbles_fileSavedText'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.success(header, message);
                    });
                    //check if the name changed, if so we need to redirect
                    if (vm.stylesheet.id !== saved.id) {
                        contentEditingHelper.redirectToRenamedContent(saved.id);
                    } else {
                        vm.page.saveButtonState = 'success';
                        vm.stylesheet = saved;
                        //sync state
                        editorState.set(vm.stylesheet);
                        // sync tree
                        navigationService.syncTree({
                            tree: 'stylesheets',
                            path: vm.stylesheet.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                        if (activeApp.alias === 'rules') {
                            $scope.selectApp(activeApp);
                        }
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localizeMany([
                        'speechBubbles_validationFailedHeader',
                        'speechBubbles_validationFailedMessage'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.error(header, message);
                    });
                });
            }
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    codefileResource.getScaffold('stylesheets', $routeParams.id).then(function (stylesheet) {
                        var mode = $routeParams.rtestyle ? 'RTE' : null;
                        ready(stylesheet, false);
                        generateNavigation(mode);
                    });
                } else {
                    codefileResource.getByPath('stylesheets', $routeParams.id).then(function (stylesheet) {
                        ready(stylesheet, true);
                        extractRules().then(function (rules) {
                            vm.stylesheet.rules = rules;
                            var mode = rules && rules.length > 0 ? 'RTE' : null;
                            generateNavigation(mode);
                        });
                    });
                }
            }
            function generateNavigation(mode) {
                localizationService.localizeMany([
                    'stylesheet_tabRules',
                    'stylesheet_tabCode'
                ]).then(function (data) {
                    vm.page.navigation = [
                        {
                            'name': data[0],
                            'alias': 'rules',
                            'icon': 'icon-font',
                            'view': 'views/stylesheets/views/rules/rules.html'
                        },
                        {
                            'name': data[1],
                            'alias': 'code',
                            'icon': 'icon-brackets',
                            'view': 'views/stylesheets/views/code/code.html'
                        }
                    ];
                    if (mode === 'RTE') {
                        vm.page.navigation[0].active = true;
                    } else {
                        vm.page.navigation[1].active = true;
                    }
                });
            }
            function ready(stylesheet, syncTree) {
                vm.page.loading = false;
                vm.stylesheet = stylesheet;
                vm.setDirty = function () {
                    setFormState('dirty');
                };
                //sync state
                editorState.set(vm.stylesheet);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'stylesheets',
                        path: vm.stylesheet.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                vm.aceOption = {
                    mode: 'css',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: {
                        fontSize: '14px',
                        enableSnippets: true,
                        enableBasicAutocompletion: true,
                        enableLiveAutocompletion: false
                    },
                    onLoad: function onLoad(_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // TODO: Move all these keybinding config out into some helper/service
                        _editor.commands.addCommands([//Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function exec() {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            }]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new style sheet
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                            });
                        }
                        vm.editor.on('change', changeAceEditor);
                    }
                };
                function changeAceEditor() {
                    setFormState('dirty');
                }
                function setFormState(state) {
                    // get the current form
                    var currentForm = angularHelper.getCurrentForm($scope);
                    // set state
                    if (state === 'dirty') {
                        currentForm.$setDirty();
                    } else if (state === 'pristine') {
                        currentForm.$setPristine();
                    }
                }
            }
            function interpolateRules() {
                return codefileResource.interpolateStylesheetRules(vm.stylesheet.content, vm.stylesheet.rules);
            }
            function extractRules() {
                return codefileResource.extractStylesheetRules(vm.stylesheet.content);
            }
            $scope.selectApp = function (app) {
                vm.page.loading = true;
                // are we going to the code tab?
                if (app.alias === 'code') {
                    // yes - interpolate the rules into the current editor value before displaying the editor
                    interpolateRules().then(function (content) {
                        vm.stylesheet.content = content;
                        vm.page.loading = false;
                    }, function (err) {
                    });
                } else {
                    // no - extract the rules from the current editor value before displaying the rules tab
                    extractRules().then(function (rules) {
                        vm.stylesheet.rules = rules;
                        vm.page.loading = false;
                    }, function (err) {
                    });
                }
            };
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.StyleSheets.EditController', StyleSheetsEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function RichTextRuleController($scope, formHelper) {
            var vm = this;
            vm.submit = submit;
            vm.close = close;
            function submit() {
                if ($scope.model && $scope.model.submit && formHelper.submitForm({ scope: $scope })) {
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model && $scope.model.close) {
                    $scope.model.close();
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.RichTextRuleController', RichTextRuleController);
    }());
    'use strict';
    angular.module('umbraco').controller('Umbraco.Editors.StyleSheets.RulesController', function ($scope, localizationService, editorService) {
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: 'div.umb-stylesheet-rules__listitem',
            handle: '.handle',
            tolerance: 'pointer',
            update: function update(e, ui) {
                setDirty();
            }
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            openOverlay({}, $scope.labels.addRule, function (newRule) {
                if (!$scope.model.stylesheet.rules) {
                    $scope.model.stylesheet.rules = [];
                }
                $scope.model.stylesheet.rules.push(newRule);
                setDirty();
            });
        };
        $scope.edit = function (rule, evt) {
            evt.preventDefault();
            openOverlay(rule, $scope.labels.editRule, function (newRule) {
                rule.name = newRule.name;
                rule.selector = newRule.selector;
                rule.styles = newRule.styles;
                setDirty();
            });
        };
        $scope.remove = function (rule, evt) {
            evt.preventDefault();
            $scope.model.stylesheet.rules = _.without($scope.model.stylesheet.rules, rule);
            setDirty();
        };
        function openOverlay(rule, title, onSubmit) {
            var ruleDialog = {
                title: title,
                rule: _.clone(rule),
                view: 'views/stylesheets/infiniteeditors/richtextrule/richtextrule.html',
                size: 'small',
                submit: function submit(model) {
                    onSubmit(model.rule);
                    editorService.close();
                },
                close: function close() {
                    editorService.close();
                }
            };
            editorService.open(ruleDialog);
        }
        function setDirty() {
            $scope.model.setDirty();
        }
        function init() {
            localizationService.localizeMany([
                'stylesheet_addRule',
                'stylesheet_editRule'
            ]).then(function (data) {
                $scope.labels = {
                    addRule: data[0],
                    editRule: data[1]
                };
            });
        }
        init();
    });
    'use strict';
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Templates.DeleteController
 * @function
 *
 * @description
 * The controller for the template delete dialog
 */
    function TemplatesDeleteController($scope, templateResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            // Reset the error message
            $scope.error = null;
            templateResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                // TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.error = err;
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Templates.DeleteController', TemplatesDeleteController);
    'use strict';
    (function () {
        'use strict';
        function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) {
            var vm = this;
            var oldMasterTemplateAlias = null;
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var id = infiniteMode ? $scope.model.id : $routeParams.id;
            var create = infiniteMode ? $scope.model.create : $routeParams.create;
            vm.header = {};
            vm.header.editorfor = 'template_template';
            vm.header.setPageTitle = true;
            vm.page = {};
            vm.page.loading = true;
            vm.templates = [];
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            // insert buttons
            vm.page.insertDefaultButton = {
                labelKey: 'general_insert',
                addEllipsis: 'true',
                handler: function handler() {
                    vm.openInsertOverlay();
                }
            };
            vm.page.insertSubButtons = [
                {
                    labelKey: 'template_insertPageField',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openPageFieldOverlay();
                    }
                },
                {
                    labelKey: 'template_insertPartialView',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openPartialOverlay();
                    }
                },
                {
                    labelKey: 'template_insertDictionaryItem',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openDictionaryItemOverlay();
                    }
                },
                {
                    labelKey: 'template_insertMacro',
                    addEllipsis: 'true',
                    handler: function handler() {
                        vm.openMacroOverlay();
                    }
                }
            ];
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            templateHelper.getGeneralShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            templateHelper.getEditorShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            templateHelper.getTemplateEditorShortcuts().then(function (data) {
                vm.page.keyboardShortcutsOverview.push(data);
            });
            vm.save = function (suppressNotification) {
                vm.page.saveButtonState = 'busy';
                vm.template.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: templateResource.save,
                    scope: $scope,
                    content: vm.template,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    if (!suppressNotification) {
                        localizationService.localizeMany([
                            'speechBubbles_templateSavedHeader',
                            'speechBubbles_templateSavedText'
                        ]).then(function (data) {
                            var header = data[0];
                            var message = data[1];
                            notificationsService.success(header, message);
                        });
                    }
                    vm.page.saveButtonState = 'success';
                    vm.template = saved;
                    //sync state
                    if (!infiniteMode) {
                        editorState.set(vm.template);
                    }
                    // sync tree
                    // if master template alias has changed move the node to it's new location
                    if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) {
                        // When creating a new template the id is -1. Make sure We don't remove the root node.
                        if (vm.page.menu.currentNode.id !== '-1') {
                            // move node to new location in tree
                            //first we need to remove the node that we're working on
                            treeService.removeNode(vm.page.menu.currentNode);
                        }
                        // update stored alias to the new one so the node won't move again unless the alias is changed again
                        oldMasterTemplateAlias = vm.template.masterTemplateAlias;
                        navigationService.syncTree({
                            tree: 'templates',
                            path: vm.template.path,
                            forceReload: true,
                            activate: true
                        }).then(function (args) {
                            vm.page.menu.currentNode = args.node;
                        });
                    } else {
                        // normal tree sync
                        if (!infiniteMode) {
                            navigationService.syncTree({
                                tree: 'templates',
                                path: vm.template.path,
                                forceReload: true
                            }).then(function (syncArgs) {
                                vm.page.menu.currentNode = syncArgs.node;
                            });
                        }
                    }
                    // clear $dirty state on form
                    setFormState('pristine');
                    if (infiniteMode) {
                        submit();
                    }
                }, function (err) {
                    if (suppressNotification) {
                        vm.page.saveButtonState = 'error';
                        localizationService.localizeMany([
                            'speechBubbles_validationFailedHeader',
                            'speechBubbles_validationFailedMessage'
                        ]).then(function (data) {
                            var header = data[0];
                            var message = data[1];
                            notificationsService.error(header, message);
                        });
                    }
                });
            };
            vm.init = function () {
                // we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                // load templates - used in the master template picker
                templateResource.getAll().then(function (templates) {
                    vm.templates = templates;
                });
                if (create) {
                    templateResource.getScaffold(id).then(function (template) {
                        vm.ready(template);
                    });
                } else {
                    templateResource.getById(id).then(function (template) {
                        vm.ready(template);
                    });
                }
            };
            vm.ready = function (template) {
                vm.page.loading = false;
                vm.template = template;
                // if this is a new template, bind to the blur event on the name
                if (create) {
                    $timeout(function () {
                        var nameField = $('[data-element="editor-name-field"]');
                        if (nameField) {
                            nameField.on('blur', function (event) {
                                if (event.target.value) {
                                    vm.save(true);
                                }
                            });
                        }
                    });
                }
                // sync state
                if (!infiniteMode) {
                    editorState.set(vm.template);
                    navigationService.syncTree({
                        tree: 'templates',
                        path: vm.template.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                // save state of master template to use for comparison when syncing the tree on save
                oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias);
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: {
                        fontSize: '14px',
                        enableSnippets: false,
                        //The Razor mode snippets are awful (Need a way to override these)
                        enableBasicAutocompletion: true,
                        enableLiveAutocompletion: false
                    },
                    onLoad: function onLoad(_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        // Unassigns the keybinding (That was previously auto-complete)
                        // As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // Assign new keybinding
                        _editor.commands.addCommands([
                            // Disable (alt+shift+K)
                            // Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function exec() {
                                    // Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoValue',
                                bindKey: 'Alt-Shift-V',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openPageFieldOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertPartialView',
                                bindKey: 'Alt-Shift-P',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openPartialOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertDictionary',
                                bindKey: 'Alt-Shift-D',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openDictionaryItemOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoMacro',
                                bindKey: 'Alt-Shift-M',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openMacroOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertQuery',
                                bindKey: 'Alt-Shift-Q',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openQueryBuilderOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertSection',
                                bindKey: 'Alt-Shift-S',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openSectionsOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'chooseMasterTemplate',
                                bindKey: 'Alt-Shift-T',
                                exec: function exec() {
                                    $scope.$apply(function () {
                                        openMasterTemplateOverlay();
                                    });
                                },
                                readOnly: true
                            }
                        ]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        // change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            };
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            vm.openSectionsOverlay = openSectionsOverlay;
            vm.openPartialOverlay = openPartialOverlay;
            vm.openMasterTemplateOverlay = openMasterTemplateOverlay;
            vm.selectMasterTemplate = selectMasterTemplate;
            vm.getMasterTemplateName = getMasterTemplateName;
            vm.removeMasterTemplate = removeMasterTemplate;
            vm.closeShortcuts = closeShortcuts;
            vm.submit = submit;
            vm.close = close;
            function openInsertOverlay() {
                var insertOverlay = {
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        partial: true,
                        umbracoField: true
                    },
                    submit: function submit(model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'partial':
                            var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        editorService.close();
                    },
                    close: function close(oldModel) {
                        // close the dialog
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.insertCodeSnippet(insertOverlay);
            }
            function openMacroOverlay() {
                var macroPicker = {
                    dialogData: {},
                    submit: function submit(model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.macroPicker(macroPicker);
            }
            function openPageFieldOverlay() {
                var insertFieldEditor = {
                    submit: function submit(model) {
                        insert(model.umbracoField);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.insertField(insertFieldEditor);
            }
            function openDictionaryItemOverlay() {
                var labelKeys = [
                    'template_insertDictionaryItem',
                    'emptyStates_emptyDictionaryTree'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    var title = values[0];
                    var emptyStateMessage = values[1];
                    var dictionaryItem = {
                        section: 'translation',
                        treeAlias: 'dictionary',
                        entityType: 'dictionary',
                        multiPicker: false,
                        title: title,
                        emptyStateMessage: emptyStateMessage,
                        select: function select(node) {
                            var code = templateHelper.getInsertDictionarySnippet(node.name);
                            insert(code);
                            editorService.close();
                        },
                        close: function close(model) {
                            // close dialog
                            editorService.close();
                            // focus editor
                            vm.editor.focus();
                        }
                    };
                    editorService.treePicker(dictionaryItem);
                });
            }
            function openPartialOverlay() {
                localizationService.localize('template_insertPartialView').then(function (value) {
                    var title = value;
                    var partialItem = {
                        section: 'settings',
                        treeAlias: 'partialViews',
                        entityType: 'partialView',
                        multiPicker: false,
                        title: title,
                        filter: function filter(i) {
                            if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                                return true;
                            }
                        },
                        filterCssClass: 'not-allowed',
                        select: function select(node) {
                            var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name);
                            insert(code);
                            editorService.close();
                        },
                        close: function close(model) {
                            // close dialog
                            editorService.close();
                            // focus editor
                            vm.editor.focus();
                        }
                    };
                    editorService.treePicker(partialItem);
                });
            }
            function openQueryBuilderOverlay() {
                var queryBuilder = {
                    submit: function submit(model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                        // focus editor
                        vm.editor.focus();
                    }
                };
                editorService.queryBuilder(queryBuilder);
            }
            function openSectionsOverlay() {
                var templateSections = {
                    isMaster: vm.template.isMasterTemplate,
                    submit: function submit(model) {
                        if (model.insertType === 'renderBody') {
                            var code = templateHelper.getRenderBodySnippet();
                            insert(code);
                        }
                        if (model.insertType === 'renderSection') {
                            var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
                            insert(code);
                        }
                        if (model.insertType === 'addSection') {
                            var code = templateHelper.getAddSectionSnippet(model.sectionName);
                            wrap(code);
                        }
                        editorService.close();
                    },
                    close: function close(model) {
                        editorService.close();
                        vm.editor.focus();
                    }
                };
                editorService.templateSections(templateSections);
            }
            function openMasterTemplateOverlay() {
                // make collection of available master templates
                var availableMasterTemplates = [];
                // filter out the current template and the selected master template
                vm.templates.forEach(function (template) {
                    if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) {
                        var templatePathArray = template.path.split(',');
                        // filter descendant templates of current template
                        if (templatePathArray.indexOf(String(vm.template.id)) === -1) {
                            availableMasterTemplates.push(template);
                        }
                    }
                });
                localizationService.localize('template_mastertemplate').then(function (title) {
                    var editor = {
                        title: title,
                        filterCssClass: 'not-allowed',
                        filter: function filter(item) {
                            return !availableMasterTemplates.some(function (template) {
                                return template.id == item.id;
                            });
                        },
                        submit: function submit(model) {
                            var template = model.selection[0];
                            if (template && template.alias) {
                                vm.template.masterTemplateAlias = template.alias;
                                setLayout(template.alias + '.cshtml');
                            } else {
                                vm.template.masterTemplateAlias = null;
                                setLayout(null);
                            }
                            editorService.close();
                        },
                        close: function close() {
                            return editorService.close();
                        }
                    };
                    editorService.templatePicker(editor);
                });
            }
            function selectMasterTemplate(template) {
                if (template && template.alias) {
                    vm.template.masterTemplateAlias = template.alias;
                    setLayout(template.alias + '.cshtml');
                } else {
                    vm.template.masterTemplateAlias = null;
                    setLayout(null);
                }
            }
            function getMasterTemplateName(masterTemplateAlias, templates) {
                if (masterTemplateAlias) {
                    var templateName = '';
                    templates.forEach(function (template) {
                        if (template.alias === masterTemplateAlias) {
                            templateName = template.name;
                        }
                    });
                    return templateName;
                }
            }
            function removeMasterTemplate() {
                vm.template.masterTemplateAlias = null;
                // call set layout with no paramters to set layout to null
                setLayout();
            }
            function setLayout(templatePath) {
                var templateCode = vm.editor.getValue();
                var newValue = templatePath;
                var layoutDefRegex = new RegExp('(@{[\\s\\S]*?Layout\\s*?=\\s*?)("[^"]*?"|null)(;[\\s\\S]*?})', 'gi');
                if (newValue !== undefined && newValue !== '') {
                    if (layoutDefRegex.test(templateCode)) {
                        // Declaration exists, so just update it
                        templateCode = templateCode.replace(layoutDefRegex, '$1"' + newValue + '"$3');
                    } else {
                        // Declaration doesn't exist, so prepend to start of doc
                        // TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
                        templateCode = '@{\n\tLayout = "' + newValue + '";\n}\n' + templateCode;
                    }
                } else {
                    if (layoutDefRegex.test(templateCode)) {
                        // Declaration exists, so just update it
                        templateCode = templateCode.replace(layoutDefRegex, '$1null$3');
                    }
                }
                vm.editor.setValue(templateCode);
                vm.editor.clearSelection();
                vm.editor.navigateFileStart();
                vm.editor.focus();
                // set form state to $dirty
                setFormState('dirty');
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function wrap(str) {
                var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange());
                str = str.replace('{0}', selectedContent);
                vm.editor.insert(str);
                vm.editor.focus();
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            function closeShortcuts() {
                vm.showKeyboardShortcut = false;
            }
            function submit() {
                if ($scope.model.submit) {
                    $scope.model.template = vm.template;
                    $scope.model.submit($scope.model);
                }
            }
            function close() {
                if ($scope.model.close) {
                    $scope.model.close();
                }
            }
            vm.init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Templates.EditController', TemplatesEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper, editorService, overlayService) {
            var infiniteMode = $scope.model && $scope.model.infiniteMode;
            var id = infiniteMode ? $scope.model.id : $routeParams.id;
            var create = infiniteMode ? $scope.model.create : $routeParams.create;
            var vm = this;
            var contentPickerOpen = false;
            vm.page = {};
            vm.page.rootIcon = 'icon-folder';
            vm.page.submitButtonLabelKey = infiniteMode ? 'buttons_saveAndClose' : 'buttons_save';
            vm.userGroup = {};
            vm.labels = {};
            vm.showBackButton = !infiniteMode;
            vm.goToPage = goToPage;
            vm.openSectionPicker = openSectionPicker;
            vm.openContentPicker = openContentPicker;
            vm.openMediaPicker = openMediaPicker;
            vm.openUserPicker = openUserPicker;
            vm.removeSection = removeSection;
            vm.removeAssignedPermissions = removeAssignedPermissions;
            vm.removeUser = removeUser;
            vm.clearStartNode = clearStartNode;
            vm.save = save;
            vm.openGranularPermissionsPicker = openGranularPermissionsPicker;
            vm.setPermissionsForNode = setPermissionsForNode;
            function init() {
                vm.loading = true;
                var labelKeys = [
                    'general_cancel',
                    'defaultdialogs_selectContentStartNode',
                    'defaultdialogs_selectMediaStartNode',
                    'defaultdialogs_selectNode',
                    'general_groups',
                    'content_contentRoot',
                    'media_mediaRoot'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.cancel = values[0];
                    vm.labels.selectContentStartNode = values[1];
                    vm.labels.selectMediaStartNode = values[2];
                    vm.labels.selectNode = values[3];
                    vm.labels.groups = values[4];
                    vm.labels.contentRoot = values[5];
                    vm.labels.mediaRoot = values[6];
                });
                localizationService.localize('general_add').then(function (name) {
                    vm.labels.add = name;
                });
                localizationService.localize('user_noStartNode').then(function (name) {
                    vm.labels.noStartNode = name;
                });
                if (create) {
                    // get user group scaffold
                    userGroupsResource.getUserGroupScaffold().then(function (userGroup) {
                        vm.userGroup = userGroup;
                        setSectionIcon(vm.userGroup.sections);
                        makeBreadcrumbs();
                        vm.loading = false;
                    });
                } else {
                    // get user group
                    userGroupsResource.getUserGroup(id).then(function (userGroup) {
                        vm.userGroup = userGroup;
                        formatGranularPermissionSelection();
                        setSectionIcon(vm.userGroup.sections);
                        makeBreadcrumbs();
                        vm.loading = false;
                    });
                }
            }
            function save() {
                vm.page.saveButtonState = 'busy';
                contentEditingHelper.contentEditorPerformSave({
                    saveMethod: userGroupsResource.saveUserGroup,
                    scope: $scope,
                    content: vm.userGroup,
                    rebindCallback: function rebindCallback(orignal, saved) {
                    }
                }).then(function (saved) {
                    vm.userGroup = saved;
                    if (infiniteMode) {
                        $scope.model.submit(vm.userGroup);
                    } else {
                        formatGranularPermissionSelection();
                        setSectionIcon(vm.userGroup.sections);
                        makeBreadcrumbs();
                        vm.page.saveButtonState = 'success';
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                });
            }
            function goToPage(ancestor) {
                $location.path(ancestor.path);
            }
            function openSectionPicker() {
                var currentSelection = [];
                Utilities.copy(vm.userGroup.sections, currentSelection);
                var sectionPicker = {
                    selection: currentSelection,
                    submit: function submit(model) {
                        vm.userGroup.sections = model.selection;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.sectionPicker(sectionPicker);
            }
            function openContentPicker() {
                var contentPicker = {
                    title: vm.labels.selectContentStartNode,
                    section: 'content',
                    treeAlias: 'content',
                    hideSubmitButton: true,
                    hideHeader: false,
                    submit: function submit(model) {
                        if (model.selection) {
                            vm.userGroup.contentStartNode = model.selection[0];
                            if (vm.userGroup.contentStartNode.id === '-1') {
                                vm.userGroup.contentStartNode.name = vm.labels.contentRoot;
                                vm.userGroup.contentStartNode.icon = 'icon-folder';
                            }
                        }
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(contentPicker);
            }
            function openMediaPicker() {
                var mediaPicker = {
                    title: vm.labels.selectMediaStartNode,
                    section: 'media',
                    treeAlias: 'media',
                    entityType: 'media',
                    hideSubmitButton: true,
                    hideHeader: false,
                    submit: function submit(model) {
                        if (model.selection) {
                            vm.userGroup.mediaStartNode = model.selection[0];
                            if (vm.userGroup.mediaStartNode.id === '-1') {
                                vm.userGroup.mediaStartNode.name = vm.labels.mediaRoot;
                                vm.userGroup.mediaStartNode.icon = 'icon-folder';
                            }
                        }
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(mediaPicker);
            }
            function openUserPicker() {
                var currentSelection = [];
                Utilities.copy(vm.userGroup.users, currentSelection);
                var userPicker = {
                    selection: currentSelection,
                    submit: function submit(model) {
                        vm.userGroup.users = model.selection;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.userPicker(userPicker);
            }
            /**
     * The granular permissions structure gets returned from the server in the dictionary format with each key being the permission category
     * however the list to display the permissions isn't via the dictionary way so we need to format it
     */
            function formatGranularPermissionSelection() {
                vm.userGroup.assignedPermissions.forEach(function (node) {
                    formatGranularPermissionSelectionForNode(node);
                });
            }
            function formatGranularPermissionSelectionForNode(node) {
                //the dictionary is assigned via node.permissions we will reformat to node.allowedPermissions
                node.allowedPermissions = [];
                Object.values(node.permissions).forEach(function (permissions) {
                    permissions.forEach(function (p) {
                        if (p.checked) {
                            node.allowedPermissions.push(p);
                        }
                    });
                });
            }
            function openGranularPermissionsPicker() {
                var contentPicker = {
                    title: vm.labels.selectNode,
                    section: 'content',
                    treeAlias: 'content',
                    hideSubmitButton: true,
                    submit: function submit(model) {
                        if (model.selection) {
                            var node = model.selection[0];
                            //check if this is already in our selection
                            var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                return i.id === node.id;
                            });
                            node = found ? found : node;
                            setPermissionsForNode(node);
                        }
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(contentPicker);
                contentPickerOpen = true;
            }
            function setPermissionsForNode(node) {
                //clone the current defaults to pass to the model
                if (!node.permissions) {
                    node.permissions = Utilities.copy(vm.userGroup.defaultPermissions);
                }
                vm.nodePermissions = {
                    node: node,
                    submit: function submit(model) {
                        if (model && model.node && model.node.permissions) {
                            formatGranularPermissionSelectionForNode(node);
                            if (!vm.userGroup.assignedPermissions) {
                                vm.userGroup.assignedPermissions = [];
                            }
                            //check if this is already in our selection
                            var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                return i.id === node.id;
                            });
                            if (!found) {
                                vm.userGroup.assignedPermissions.push(node);
                            }
                        }
                        editorService.close();
                        if (contentPickerOpen) {
                            editorService.close();
                            contentPickerOpen = false;
                        }
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.nodePermissions(vm.nodePermissions);
            }
            function removeSection(index) {
                vm.userGroup.sections.splice(index, 1);
            }
            function removeAssignedPermissions(index) {
                vm.userGroup.assignedPermissions.splice(index, 1);
            }
            function removeUser(index) {
                var dialog = {
                    view: 'views/users/views/overlays/remove.html',
                    username: vm.userGroup.users[index].username,
                    userGroupName: vm.userGroup.name.toLowerCase(),
                    submitButtonLabelKey: 'defaultdialogs_yesRemove',
                    submitButtonStyle: 'danger',
                    submit: function submit() {
                        vm.userGroup.users.splice(index, 1);
                        overlayService.close();
                    },
                    close: function close() {
                        overlayService.close();
                    }
                };
                overlayService.open(dialog);
            }
            function clearStartNode(type) {
                if (type === 'content') {
                    vm.userGroup.contentStartNode = null;
                } else if (type === 'media') {
                    vm.userGroup.mediaStartNode = null;
                }
            }
            function makeBreadcrumbs() {
                vm.breadcrumbs = [
                    {
                        'name': vm.labels.groups,
                        'path': '/users/users/groups'
                    },
                    { 'name': vm.userGroup.name }
                ];
            }
            function setSectionIcon(sections) {
                sections.forEach(function (section) {
                    section.icon = 'icon-section';
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.GroupController', UserGroupEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UsersOverviewController($scope, $location, $routeParams, localizationService) {
            var vm = this;
            var usersUri = $routeParams.method;
            //note on the below, we dont assign a view unless it's the right route since if we did that it will load in that controller
            //for the view which is unecessary and will cause extra overhead/requests to occur
            vm.page = {};
            vm.page.labels = {};
            vm.page.name = '';
            vm.page.navigation = [];
            function onInit() {
                loadNavigation();
            }
            function loadNavigation() {
                var labels = [
                    'sections_users',
                    'general_groups',
                    'user_userManagement'
                ];
                localizationService.localizeMany(labels).then(function (data) {
                    vm.page.labels.users = data[0];
                    vm.page.labels.groups = data[1];
                    vm.page.name = data[2];
                    vm.page.navigation = [
                        {
                            'name': vm.page.labels.users,
                            'icon': 'icon-user',
                            'action': function action() {
                                $location.path('/users/users/users').search('create', null);
                            },
                            'view': !usersUri || usersUri === 'users' ? 'views/users/views/users/users.html' : null,
                            'active': !usersUri || usersUri === 'users',
                            'alias': 'users'
                        },
                        {
                            'name': vm.page.labels.groups,
                            'icon': 'icon-users',
                            'action': function action() {
                                $location.path('/users/users/groups').search('create', null);
                            },
                            'view': usersUri === 'groups' ? 'views/users/views/groups/groups.html' : null,
                            'active': usersUri === 'groups',
                            'alias': 'userGroups'
                        }
                    ];
                });
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.OverviewController', UsersOverviewController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UserEditController($scope, eventsService, $q, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper, editorService, overlayService, externalLoginInfoService) {
            var currentLoggedInUser = null;
            var vm = this;
            vm.page = {};
            vm.page.rootIcon = 'icon-folder';
            vm.user = { changePassword: null };
            vm.breadcrumbs = [];
            vm.showBackButton = true;
            vm.avatarFile = {};
            vm.labels = {};
            vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB';
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
            vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
            //create the initial model for change password
            vm.changePasswordModel = {
                config: {},
                isChanging: false,
                value: {}
            };
            vm.goToPage = goToPage;
            vm.openUserGroupPicker = openUserGroupPicker;
            vm.openContentPicker = openContentPicker;
            vm.openMediaPicker = openMediaPicker;
            vm.editSelectedItem = editSelectedItem;
            vm.removeSelectedItem = removeSelectedItem;
            vm.disableUser = disableUser;
            vm.enableUser = enableUser;
            vm.unlockUser = unlockUser;
            vm.resendInvite = resendInvite;
            vm.deleteNonLoggedInUser = deleteNonLoggedInUser;
            vm.changeAvatar = changeAvatar;
            vm.clearAvatar = clearAvatar;
            vm.save = save;
            vm.allowGroupEdit = allowGroupEdit;
            vm.changePassword = changePassword;
            vm.toggleChangePassword = toggleChangePassword;
            vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
            function init() {
                vm.loading = true;
                var labelKeys = [
                    'general_saving',
                    'general_cancel',
                    'defaultdialogs_selectContentStartNode',
                    'defaultdialogs_selectMediaStartNode',
                    'sections_users',
                    'content_contentRoot',
                    'media_mediaRoot',
                    'user_noStartNodes',
                    'user_defaultInvitationMessage',
                    'user_deleteUserConfirmation'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.saving = values[0];
                    vm.labels.cancel = values[1];
                    vm.labels.selectContentStartNode = values[2];
                    vm.labels.selectMediaStartNode = values[3];
                    vm.labels.users = values[4];
                    vm.labels.contentRoot = values[5];
                    vm.labels.mediaRoot = values[6];
                    vm.labels.noStartNodes = values[7];
                    vm.labels.defaultInvitationMessage = values[8];
                    vm.labels.deleteUserConfirmation = values[9];
                });
                // get user
                usersResource.getUser($routeParams.id).then(function (user) {
                    vm.user = user;
                    makeBreadcrumbs(vm.user);
                    setUserDisplayState();
                    formatDatesToLocal(vm.user);
                    vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username;
                    //go get the config for the membership provider and add it to the model
                    authResource.getMembershipProviderConfig().then(function (data) {
                        vm.changePasswordModel.config = data;
                        //the user has a password if they are not states: Invited, NoCredentials
                        vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                        vm.changePasswordModel.config.disableToggle = true;
                        //this is only relavent for membership providers now (it's basically obsolete)
                        vm.changePasswordModel.config.enableReset = false;
                        //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password
                        //if the user has access to the user section. So if this editor is being access, the user of course has access to this section.
                        //the authorization check is also done on the server side when submitted.
                        // only update the setting if not the current logged in user, otherwise leave the value as it is
                        // currently set in the web.config
                        if (!vm.user.isCurrentUser) {
                            vm.changePasswordModel.config.allowManuallyChangingPassword = true;
                        }
                        $scope.$emit('$setAccessibleHeader', false, 'general_user', false, vm.user.name, '', true);
                        vm.loading = false;
                    });
                });
            }
            function getLocalDate(date, culture, format) {
                if (date) {
                    var dateVal;
                    var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                    var localOffset = new Date().getTimezoneOffset();
                    var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                    if (serverTimeNeedsOffsetting) {
                        dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset);
                    } else {
                        dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
                    }
                    return dateVal.locale(culture).format(format);
                }
            }
            function toggleChangePassword() {
                //reset it
                vm.user.changePassword = null;
                localizationService.localizeMany([
                    'general_cancel',
                    'general_confirm',
                    'general_changePassword'
                ]).then(function (data) {
                    var overlay = {
                        view: 'changepassword',
                        title: data[2],
                        changePassword: vm.user.changePassword,
                        config: vm.changePasswordModel.config,
                        closeButtonLabel: data[0],
                        submitButtonLabel: data[1],
                        submitButtonStyle: 'success',
                        close: function close() {
                            return overlayService.close();
                        },
                        submit: function submit(model) {
                            overlayService.close();
                            vm.changePasswordModel.value = model.changePassword;
                            changePassword();
                        }
                    };
                    overlayService.open(overlay);
                });
            }
            function save() {
                if (formHelper.submitForm({ scope: $scope })) {
                    vm.page.saveButtonState = 'busy';
                    vm.user.resetPasswordValue = null;
                    //save current nav to be restored later so that the tabs dont change
                    var currentNav = vm.user.navigation;
                    usersResource.saveUser(vm.user).then(function (saved) {
                        //if the user saved, then try to execute all extended save options
                        extendedSave(saved).then(function (result) {
                            //if all is good, then reset the form
                            formHelper.resetForm({ scope: $scope });
                        }, function () {
                            formHelper.resetForm({
                                scope: $scope,
                                hasErrors: true
                            });
                        });
                        vm.user = _.omit(saved, 'navigation');
                        //restore
                        vm.user.navigation = currentNav;
                        setUserDisplayState();
                        formatDatesToLocal(vm.user);
                        vm.page.saveButtonState = 'success';
                    }, function (err) {
                        formHelper.resetForm({
                            scope: $scope,
                            hasErrors: true
                        });
                        contentEditingHelper.handleSaveError({
                            err: err,
                            showNotifications: true
                        });
                        vm.page.saveButtonState = 'error';
                    });
                }
            }
            /**
     *
     */
            function changePassword() {
                //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
                if (vm.changePasswordModel.value) {
                    //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user
                    //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat).
                    //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password.
                    vm.changePasswordModel.value.reset = !vm.changePasswordModel.value.oldPassword && !vm.user.isCurrentUser || vm.changePasswordModel.config.allowManuallyChangingPassword;
                }
                // since we don't send the entire user model, the id is required
                vm.changePasswordModel.value.id = vm.user.id;
                usersResource.changePassword(vm.changePasswordModel.value).then(function () {
                    vm.changePasswordModel.isChanging = false;
                    vm.changePasswordModel.value = {};
                    //the user has a password if they are not states: Invited, NoCredentials
                    vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                }, function (err) {
                    contentEditingHelper.handleSaveError({
                        err: err,
                        showNotifications: true
                    });
                });
            }
            /**
     * Used to emit the save event and await any async operations being performed by editor extensions
     * @param {any} savedUser
     */
            function extendedSave(savedUser) {
                //used to track any promises added by the event handlers to be awaited
                var promises = [];
                var args = {
                    //getPromise: getPromise,
                    user: savedUser,
                    //a promise can be added by the event handler if the handler needs an async operation to be awaited
                    addPromise: function addPromise(p) {
                        promises.push(p);
                    }
                };
                //emit the event
                eventsService.emit('editors.user.editController.save', args);
                //await all promises to complete
                var resultPromise = $q.all(promises);
                return resultPromise;
            }
            function goToPage(ancestor) {
                $location.path(ancestor.path);
            }
            function openUserGroupPicker() {
                var currentSelection = [];
                Utilities.copy(vm.user.userGroups, currentSelection);
                var userGroupPicker = {
                    selection: currentSelection,
                    submit: function submit(model) {
                        // apply changes
                        if (model.selection) {
                            vm.user.userGroups = model.selection;
                        }
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.userGroupPicker(userGroupPicker);
            }
            function openContentPicker() {
                var contentPicker = {
                    title: vm.labels.selectContentStartNode,
                    section: 'content',
                    treeAlias: 'content',
                    multiPicker: true,
                    selection: vm.user.startContentIds,
                    hideHeader: false,
                    submit: function submit(model) {
                        // select items
                        if (model.selection) {
                            model.selection.forEach(function (item) {
                                if (item.id === '-1') {
                                    item.name = vm.labels.contentRoot;
                                    item.icon = 'icon-folder';
                                }
                                multiSelectItem(item, vm.user.startContentIds);
                            });
                        }
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.treePicker(contentPicker);
            }
            function openMediaPicker() {
                var mediaPicker = {
                    title: vm.labels.selectMediaStartNode,
                    section: 'media',
                    treeAlias: 'media',
                    entityType: 'media',
                    multiPicker: true,
                    hideHeader: false,
                    show: true,
                    submit: function submit(model) {
                        // select items
                        if (model.selection) {
                            model.selection.forEach(function (item) {
                                if (item.id === '-1') {
                                    item.name = vm.labels.mediaRoot;
                                    item.icon = 'icon-folder';
                                }
                                multiSelectItem(item, vm.user.startMediaIds);
                            });
                        }
                        // close overlay
                        editorService.close();
                    },
                    close: function close() {
                        // close overlay
                        editorService.close();
                    }
                };
                editorService.treePicker(mediaPicker);
            }
            function multiSelectItem(item, selection) {
                var found = false;
                // check if item is already in the selected list
                if (selection.length > 0) {
                    selection.forEach(function (selectedItem) {
                        if (selectedItem.udi === item.udi) {
                            found = true;
                        }
                    });
                }
                // only add the selected item if it is not already selected
                if (!found) {
                    selection.push(item);
                }
            }
            function editSelectedItem(index, selection) {
                var group = selection[index];
                var editor = {
                    id: group.id,
                    submit: function submit(model) {
                        selection[index] = model;
                        editorService.close();
                    },
                    close: function close() {
                        editorService.close();
                    }
                };
                editorService.userGroupEditor(editor);
            }
            function removeSelectedItem(index, selection) {
                selection.splice(index, 1);
            }
            function disableUser() {
                vm.disableUserButtonState = 'busy';
                usersResource.disableUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 1;
                    setUserDisplayState();
                    vm.disableUserButtonState = 'success';
                }, function (error) {
                    vm.disableUserButtonState = 'error';
                });
            }
            function enableUser() {
                vm.enableUserButtonState = 'busy';
                usersResource.enableUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 0;
                    setUserDisplayState();
                    vm.enableUserButtonState = 'success';
                }, function (error) {
                    vm.enableUserButtonState = 'error';
                });
            }
            function unlockUser() {
                vm.unlockUserButtonState = 'busy';
                usersResource.unlockUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 0;
                    vm.user.failedPasswordAttempts = 0;
                    setUserDisplayState();
                    vm.unlockUserButtonState = 'success';
                }, function (error) {
                    vm.unlockUserButtonState = 'error';
                });
            }
            function resendInvite() {
                vm.resendInviteButtonState = 'busy';
                if (vm.resendInviteMessage) {
                    vm.user.message = vm.resendInviteMessage;
                } else {
                    vm.user.message = vm.labels.defaultInvitationMessage;
                }
                usersResource.inviteUser(vm.user).then(function (data) {
                    vm.resendInviteButtonState = 'success';
                    vm.resendInviteMessage = '';
                    formHelper.showNotifications(data);
                }, function (error) {
                    vm.resendInviteButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function deleteNonLoggedInUser() {
                vm.deleteNotLoggedInUserButtonState = 'busy';
                var confirmationMessage = vm.labels.deleteUserConfirmation;
                localizationService.localizeMany([
                    'general_delete',
                    'general_cancel',
                    'contentTypeEditor_yesDelete'
                ]).then(function (data) {
                    var overlay = {
                        view: 'confirm',
                        title: data[0],
                        content: confirmationMessage,
                        closeButtonLabel: data[1],
                        submitButtonLabel: data[2],
                        submitButtonStyle: 'danger',
                        close: function close() {
                            vm.deleteNotLoggedInUserButtonState = 'danger';
                            overlayService.close();
                        },
                        submit: function submit() {
                            performDelete();
                            overlayService.close();
                        }
                    };
                    overlayService.open(overlay);
                });
            }
            function performDelete() {
                usersResource.deleteNonLoggedInUser(vm.user.id).then(function (data) {
                    goToPage(vm.breadcrumbs[0]);
                }, function (error) {
                    vm.deleteNotLoggedInUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function clearAvatar() {
                // get user
                usersResource.clearAvatar(vm.user.id).then(function (data) {
                    vm.user.avatars = data;
                });
            }
            function changeAvatar(files, event) {
                if (files && files.length > 0) {
                    upload(files[0]);
                }
            }
            ;
            function upload(file) {
                vm.avatarFile.uploadProgress = 0;
                Upload.upload({
                    url: umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSetAvatar', { id: vm.user.id }),
                    fields: {},
                    file: file
                }).progress(function (evt) {
                    if (vm.avatarFile.uploadStatus !== 'done' && vm.avatarFile.uploadStatus !== 'error') {
                        // set uploading status on file
                        vm.avatarFile.uploadStatus = 'uploading';
                        // calculate progress in percentage
                        var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                        // set percentage property on file
                        vm.avatarFile.uploadProgress = progressPercentage;
                    }
                }).success(function (data, status, headers, config) {
                    // set done status on file
                    vm.avatarFile.uploadStatus = 'done';
                    vm.avatarFile.uploadProgress = 100;
                    vm.user.avatars = data;
                }).error(function (evt, status, headers, config) {
                    // set status done
                    vm.avatarFile.uploadStatus = 'error';
                    // If file not found, server will return a 404 and display this message
                    if (status === 404) {
                        vm.avatarFile.serverErrorMessage = 'File not found';
                    } else if (status == 400) {
                        //it's a validation error
                        vm.avatarFile.serverErrorMessage = evt.message;
                    } else {
                        //it's an unhandled error
                        //if the service returns a detailed error
                        if (evt.InnerException) {
                            vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                            //Check if its the common "too large file" exception
                            if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                vm.avatarFile.serverErrorMessage = 'File too large to upload';
                            }
                        } else if (evt.Message) {
                            vm.avatarFile.serverErrorMessage = evt.Message;
                        }
                    }
                });
            }
            function makeBreadcrumbs() {
                vm.breadcrumbs = [
                    {
                        'name': vm.labels.users,
                        'path': '/users/users/users'
                    },
                    { 'name': vm.user.name }
                ];
            }
            function setUserDisplayState() {
                vm.user.userDisplayState = usersHelper.getUserStateFromValue(vm.user.userState);
            }
            function formatDatesToLocal(user) {
                // get current backoffice user and format dates
                userService.getCurrentUser().then(function (currentUser) {
                    currentLoggedInUser = currentUser;
                    user.formattedLastLogin = getLocalDate(user.lastLoginDate, currentUser.locale, 'LLL');
                    user.formattedLastLockoutDate = getLocalDate(user.lastLockoutDate, currentUser.locale, 'LLL');
                    user.formattedCreateDate = getLocalDate(user.createDate, currentUser.locale, 'LLL');
                    user.formattedUpdateDate = getLocalDate(user.updateDate, currentUser.locale, 'LLL');
                    user.formattedLastPasswordChangeDate = getLocalDate(user.lastPasswordChangeDate, currentUser.locale, 'LLL');
                });
            }
            function allowGroupEdit(group) {
                if (!currentLoggedInUser) {
                    return false;
                }
                if (currentLoggedInUser.userGroups.indexOf(group.alias) === -1 && currentLoggedInUser.userGroups.indexOf('admin') === -1) {
                    return false;
                }
                return true;
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.UserController', UserEditController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UserGroupsController($scope, $timeout, $location, $filter, userService, userGroupsResource, formHelper, localizationService, listViewHelper, overlayService) {
            var vm = this;
            vm.userGroups = [];
            vm.selection = [];
            vm.clickUserGroupName = clickUserGroupName;
            vm.createUserGroup = createUserGroup;
            vm.clearSelection = clearSelection;
            vm.selectUserGroup = selectUserGroup;
            vm.deleteUserGroups = deleteUserGroups;
            vm.filter = null;
            var currentUser = null;
            function onInit() {
                vm.loading = true;
                userService.getCurrentUser().then(function (user) {
                    currentUser = user;
                    // Get usergroups
                    userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                        // only allow editing and selection if user is member of the group or admin
                        vm.userGroups = _.map(userGroups, function (ug) {
                            ug.hasAccess = user.userGroups.indexOf(ug.alias) !== -1 || user.userGroups.indexOf('admin') !== -1;
                            return ug;
                        });
                        vm.filteredUserGroups = vm.userGroups;
                        vm.loading = false;
                    });
                });
            }
            function createUserGroup() {
                // clear all query params
                $location.search({});
                // go to create user group
                $location.path('users/users/group/-1').search('create', 'true');
                ;
            }
            function goToUserGroup(userGroup, $event) {
                // only allow editing if user is member of the group or admin
                if (currentUser.userGroups.indexOf(userGroup.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                    return;
                }
                $location.path(getEditPath(userGroup)).search('create', null);
            }
            function clickUserGroupName(item, $event) {
                if (!($event.metaKey || $event.ctrlKey)) {
                    goToUserGroup(item, $event);
                    $event.preventDefault();
                }
                $event.stopPropagation();
            }
            ;
            function getEditPath(userGroup) {
                // only allow editing if user is member of the group or admin
                if (currentUser.userGroups.indexOf(userGroup.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                    return '';
                }
                return 'users/users/group/' + userGroup.id;
            }
            function selectUserGroup(userGroup, $index, $event) {
                // Only allow selection if user is member of the group or admin
                if (currentUser.userGroups.indexOf(userGroup.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                    return;
                }
                // Disallow selection of the admin/translators group, the checkbox is not visible in the UI, but clicking(and thus selecting) is still possible.
                // Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group.
                if (userGroup.isSystemUserGroup)
                    return;
                listViewHelper.selectHandler(userGroup, $index, vm.userGroups, vm.selection, $event);
                if (event) {
                    event.stopPropagation();
                }
            }
            function deleteUserGroups() {
                if (vm.selection.length > 0) {
                    localizationService.localizeMany([
                        'general_delete',
                        'defaultdialogs_confirmdelete',
                        'general_cancel',
                        'contentTypeEditor_yesDelete'
                    ]).then(function (data) {
                        var overlay = {
                            title: data[0],
                            content: data[1] + '?',
                            closeButtonLabel: data[2],
                            submitButtonLabel: data[3],
                            submitButtonStyle: 'danger',
                            close: function close() {
                                overlayService.close();
                            },
                            submit: function submit() {
                                userGroupsResource.deleteUserGroups(_.pluck(vm.selection, 'id')).then(function (data) {
                                    clearSelection();
                                    onInit();
                                }, Utilities.noop);
                                overlayService.close();
                            }
                        };
                        overlayService.open(overlay);
                    });
                }
            }
            function clearSelection() {
                vm.userGroups.forEach(function (userGroup) {
                    userGroup.selected = false;
                });
                vm.selection = [];
            }
            var unbindFilterWatcher = $scope.$watch('vm.filter', function (newVal, oldVal) {
                vm.filteredUserGroups = $filter('filter')(vm.userGroups, vm.filter);
            });
            $scope.$on('$destroy', function () {
                unbindFilterWatcher();
            });
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.GroupsController', UserGroupsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function DetailsController($scope, externalLoginInfoService) {
            var vm = this;
            vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.DetailsController', DetailsController);
    }());
    'use strict';
    (function () {
        'use strict';
        function UsersController($scope, $timeout, $location, $routeParams, usersResource, userGroupsResource, userService, localizationService, usersHelper, formHelper, dateHelper, editorService, listViewHelper, externalLoginInfoService) {
            var vm = this;
            vm.page = {};
            vm.users = [];
            vm.userGroups = [];
            vm.userStates = [];
            vm.selection = [];
            vm.newUser = {};
            vm.usersOptions = {};
            vm.userSortData = [
                {
                    label: 'Name (A-Z)',
                    key: 'Name',
                    direction: 'Ascending'
                },
                {
                    label: 'Name (Z-A)',
                    key: 'Name',
                    direction: 'Descending'
                },
                {
                    label: 'Newest',
                    key: 'CreateDate',
                    direction: 'Descending'
                },
                {
                    label: 'Oldest',
                    key: 'CreateDate',
                    direction: 'Ascending'
                },
                {
                    label: 'Last login',
                    key: 'LastLoginDate',
                    direction: 'Descending'
                }
            ];
            localizationService.localizeMany(_.map(vm.userSortData, function (userSort) {
                return 'user_sort' + userSort.key + userSort.direction;
            })).then(function (data) {
                var reg = /^\[[\S\s]*]$/g;
                _.each(data, function (value, index) {
                    if (!reg.test(value)) {
                        // Only translate if key exists
                        vm.userSortData[index].label = value;
                    }
                });
            });
            vm.labels = {};
            localizationService.localizeMany(['user_stateAll']).then(function (data) {
                vm.labels.all = data[0];
            });
            vm.userStatesFilter = [];
            vm.newUser.userGroups = [];
            vm.usersViewState = 'overview';
            vm.selectedBulkUserGroups = [];
            vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
            vm.allowDisableUser = true;
            vm.allowEnableUser = true;
            vm.allowUnlockUser = true;
            vm.allowSetUserGroup = true;
            vm.layouts = [
                {
                    'icon': 'icon-thumbnails-small',
                    'path': '1',
                    'selected': true
                },
                {
                    'icon': 'icon-list',
                    'path': '2',
                    'selected': true
                }
            ];
            // Get last selected layout for "users" (defaults to first layout = card layout)
            vm.activeLayout = listViewHelper.getLayout('users', vm.layouts);
            vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
            // returns the object representing the user create button, returns null if deny local login is true
            function getCreateUserButton() {
                if (!vm.denyLocalLogin) {
                    return {
                        type: 'button',
                        labelKey: 'user_createUser',
                        handler: function handler() {
                            vm.setUsersViewState('createUser');
                        }
                    };
                }
                return null;
            }
            // No default buttons with denyLocalLogin
            // Don't show the invite button if no email is configured
            if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) {
                vm.defaultButton = {
                    type: 'button',
                    labelKey: 'user_inviteUser',
                    handler: function handler() {
                        vm.setUsersViewState('inviteUser');
                    }
                };
                var createUserBtn = getCreateUserButton();
                if (createUserBtn) {
                    vm.subButtons = [createUserBtn];
                }
            } else {
                vm.defaultButton = getCreateUserButton();
            }
            vm.toggleFilter = toggleFilter;
            vm.setUsersViewState = setUsersViewState;
            vm.selectLayout = selectLayout;
            vm.isSelectable = isSelectable;
            vm.selectUser = selectUser;
            vm.clearSelection = clearSelection;
            vm.clickUser = clickUser;
            vm.getEditPath = getEditPath;
            vm.disableUsers = disableUsers;
            vm.enableUsers = enableUsers;
            vm.unlockUsers = unlockUsers;
            vm.openBulkUserGroupPicker = openBulkUserGroupPicker;
            vm.openUserGroupPicker = openUserGroupPicker;
            vm.removeSelectedUserGroup = removeSelectedUserGroup;
            vm.selectAll = selectAll;
            vm.areAllSelected = areAllSelected;
            vm.searchUsers = searchUsers;
            vm.onBlurSearch = onBlurSearch;
            vm.getFilterName = getFilterName;
            vm.setUserStatesFilter = setUserStatesFilter;
            vm.setUserGroupFilter = setUserGroupFilter;
            vm.setOrderByFilter = setOrderByFilter;
            vm.changePageNumber = changePageNumber;
            vm.createUser = createUser;
            vm.inviteUser = inviteUser;
            vm.getSortLabel = getSortLabel;
            vm.toggleNewUserPassword = toggleNewUserPassword;
            vm.copySuccess = copySuccess;
            vm.copyError = copyError;
            vm.goToUser = goToUser;
            function init() {
                initViewOptions();
                if ($routeParams.create) {
                    setUsersViewState('createUser');
                } else if ($routeParams.invite) {
                    setUsersViewState('inviteUser');
                }
                // Get users
                getUsers();
                // Get user groups
                userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                    vm.userGroups = userGroups;
                    initUserGroupSelections();
                });
            }
            function initViewOptions() {
                // Start with default view options.
                vm.usersOptions.filter = '';
                vm.usersOptions.orderBy = 'Name';
                vm.usersOptions.orderDirection = 'Ascending';
                // Update from querystring if available.
                initViewOptionFromQueryString('filter');
                initViewOptionFromQueryString('orderBy');
                initViewOptionFromQueryString('orderDirection');
                initViewOptionFromQueryString('pageNumber');
                initViewOptionFromQueryString('userStates', true);
                initViewOptionFromQueryString('userGroups', true);
            }
            function initViewOptionFromQueryString(key, isCollection) {
                var value = $location.search()[key];
                if (value) {
                    if (isCollection) {
                        value = value.split(',');
                    }
                    vm.usersOptions[key] = value;
                }
            }
            function initUserStateSelections() {
                initUsersOptionsFilterSelections(vm.userStatesFilter, vm.usersOptions.userStates, 'key');
            }
            function initUserGroupSelections() {
                initUsersOptionsFilterSelections(vm.userGroups, vm.usersOptions.userGroups, 'alias');
            }
            function initUsersOptionsFilterSelections(filterCollection, selectedCollection, keyField) {
                if (selectedCollection && selectedCollection.length > 0 && filterCollection && filterCollection.length > 0) {
                    for (var i = 0; i < selectedCollection.length; i++) {
                        for (var j = 0; j < filterCollection.length; j++) {
                            if (filterCollection[j][keyField] === selectedCollection[i]) {
                                filterCollection[j].selected = true;
                            }
                        }
                    }
                }
            }
            function getSortLabel(sortKey, sortDirection) {
                var found = _.find(vm.userSortData, function (i) {
                    return i.key === sortKey && i.direction === sortDirection;
                });
                return found ? found.label : sortKey;
            }
            function toggleFilter(type) {
                // hack: on-outside-click prevents us from closing the dropdown when clicking on another link
                // so I had to do this manually
                switch (type) {
                case 'state':
                    vm.page.showStatusFilter = !vm.page.showStatusFilter;
                    vm.page.showGroupFilter = false;
                    vm.page.showOrderByFilter = false;
                    break;
                case 'group':
                    vm.page.showGroupFilter = !vm.page.showGroupFilter;
                    vm.page.showStatusFilter = false;
                    vm.page.showOrderByFilter = false;
                    break;
                case 'orderBy':
                    vm.page.showOrderByFilter = !vm.page.showOrderByFilter;
                    vm.page.showStatusFilter = false;
                    vm.page.showGroupFilter = false;
                    break;
                }
            }
            function setUsersViewState(state) {
                if (state === 'createUser') {
                    clearAddUserForm();
                    $location.search('create', 'true');
                    $location.search('invite', null);
                } else if (state === 'inviteUser') {
                    clearAddUserForm();
                    $location.search('create', null);
                    $location.search('invite', 'true');
                } else if (state === 'overview') {
                    $location.search('create', null);
                    $location.search('invite', null);
                }
                vm.usersViewState = state;
            }
            function selectLayout(selectedLayout) {
                // save the selected layout for "users" so it's applied next time the user visits this section
                vm.activeLayout = listViewHelper.setLayout('users', selectedLayout, vm.layouts);
            }
            function isSelectable(user) {
                return !user.isCurrentUser;
            }
            function selectUser(user) {
                if (!isSelectable(user)) {
                    return;
                }
                if (user.selected) {
                    var index = vm.selection.indexOf(user.id);
                    vm.selection.splice(index, 1);
                    user.selected = false;
                } else {
                    user.selected = true;
                    vm.selection.push(user.id);
                }
                setBulkActions(vm.users);
            }
            function clearSelection() {
                vm.users.forEach(function (user) {
                    user.selected = false;
                });
                vm.selection = [];
            }
            function clickUser(user, $event) {
                $event.stopPropagation();
                if ($event) {
                    // targeting a new tab/window?
                    if ($event.ctrlKey || $event.shiftKey || $event.metaKey || // apple
                        $event.button && $event.button === 1    // middle click, >IE9 + everyone else
) {
                        // yes, let the link open itself
                        return;
                    }
                }
                goToUser(user);
                $event.preventDefault();
            }
            function disableUsers() {
                vm.disableUserButtonState = 'busy';
                usersResource.disableUsers(vm.selection).then(function (data) {
                    // update userState
                    vm.selection.forEach(function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 1;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    vm.disableUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.disableUserButtonState = 'error';
                });
            }
            function enableUsers() {
                vm.enableUserButtonState = 'busy';
                usersResource.enableUsers(vm.selection).then(function (data) {
                    // update userState
                    vm.selection.forEach(function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 0;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    vm.enableUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.enableUserButtonState = 'error';
                });
            }
            function unlockUsers() {
                vm.unlockUserButtonState = 'busy';
                usersResource.unlockUsers(vm.selection).then(function (data) {
                    // update userState
                    vm.selection.forEach(function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 0;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    vm.unlockUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.unlockUserButtonState = 'error';
                });
            }
            function getUserFromArrayById(userId, users) {
                return _.find(users, function (u) {
                    return u.id === userId;
                });
            }
            function openBulkUserGroupPicker() {
                var firstSelectedUser = getUserFromArrayById(vm.selection[0], vm.users);
                vm.selectedBulkUserGroups = _.clone(firstSelectedUser.userGroups);
                var userGroupPicker = {
                    selection: vm.selectedBulkUserGroups,
                    submit: function submit(model) {
                        usersResource.setUserGroupsOnUsers(model.selection, vm.selection).then(function (data) {
                            // sorting to ensure they show up in right order when updating the UI
                            vm.selectedBulkUserGroups.sort(function (a, b) {
                                return a.alias > b.alias ? 1 : a.alias < b.alias ? -1 : 0;
                            });
                            // apply changes to UI
                            _.each(vm.selection, function (userId) {
                                var user = getUserFromArrayById(userId, vm.users);
                                user.userGroups = vm.selectedBulkUserGroups;
                            });
                            vm.selectedBulkUserGroups = [];
                            editorService.close();
                            clearSelection();
                        }, Utilities.noop);
                    },
                    close: function close() {
                        vm.selectedBulkUserGroups = [];
                        editorService.close();
                    }
                };
                editorService.userGroupPicker(userGroupPicker);
            }
            function openUserGroupPicker() {
                var currentSelection = [];
                Utilities.copy(vm.newUser.userGroups, currentSelection);
                var userGroupPicker = {
                    selection: currentSelection,
                    submit: function submit(model) {
                        // apply changes
                        if (model.selection) {
                            vm.newUser.userGroups = model.selection;
                        }
                        editorService.close();
                    },
                    close: function close() {
                        // rollback on close
                        editorService.close();
                    }
                };
                editorService.userGroupPicker(userGroupPicker);
            }
            function removeSelectedUserGroup(index, selection) {
                selection.splice(index, 1);
            }
            function selectAll() {
                if (areAllSelected()) {
                    vm.selection = [];
                    vm.users.forEach(function (user) {
                        user.selected = false;
                    });
                } else {
                    // clear selection so we don't add the same user twice
                    vm.selection = [];
                    // select all users
                    vm.users.forEach(function (user) {
                        // prevent the current user to be selected
                        if (!user.isCurrentUser) {
                            user.selected = true;
                            vm.selection.push(user.id);
                        }
                    });
                }
            }
            function areAllSelected() {
                // we need to check if the current user is part of the selection and 
                // subtract the user from the total selection to find out if all users are selected
                var includesCurrentUser = vm.users.some(function (user) {
                    return user.isCurrentUser === true;
                });
                if (includesCurrentUser) {
                    if (vm.selection.length === vm.users.length - 1) {
                        return true;
                    }
                } else {
                    if (vm.selection.length === vm.users.length) {
                        return true;
                    }
                }
            }
            var search = _.debounce(function () {
                $scope.$apply(function () {
                    vm.usersOptions.pageNumber = 1;
                    getUsers();
                });
            }, 500);
            function searchUsers() {
                search();
            }
            function onBlurSearch() {
                updateLocation('filter', vm.usersOptions.filter);
            }
            function getFilterName(array) {
                var name = vm.labels.all;
                var found = false;
                array.forEach(function (item) {
                    if (item.selected) {
                        if (!found) {
                            name = item.name;
                            found = true;
                        } else {
                            name = name + ', ' + item.name;
                        }
                    }
                });
                return name;
            }
            function setUserStatesFilter(userState) {
                if (!vm.usersOptions.userStates) {
                    vm.usersOptions.userStates = [];
                }
                //If the selection is "ALL" then we need to unselect everything else since this is an 'odd' filter
                if (userState.key === 'All') {
                    vm.userStatesFilter.forEach(function (i) {
                        i.selected = false;
                    });
                    //we can't unselect All
                    userState.selected = true;
                    //reset the selection passed to the server
                    vm.usersOptions.userStates = [];
                } else {
                    vm.userStatesFilter.forEach(function (i) {
                        if (i.key === 'All') {
                            i.selected = false;
                        }
                    });
                    var indexOfAll = vm.usersOptions.userStates.indexOf('All');
                    if (indexOfAll >= 0) {
                        vm.usersOptions.userStates.splice(indexOfAll, 1);
                    }
                }
                if (userState.selected) {
                    vm.usersOptions.userStates.push(userState.key);
                } else {
                    var index = vm.usersOptions.userStates.indexOf(userState.key);
                    vm.usersOptions.userStates.splice(index, 1);
                }
                updateLocation('userStates', vm.usersOptions.userStates.join(','));
                changePageNumber(1);
            }
            function setUserGroupFilter(userGroup) {
                if (!vm.usersOptions.userGroups) {
                    vm.usersOptions.userGroups = [];
                }
                if (userGroup.selected) {
                    vm.usersOptions.userGroups.push(userGroup.alias);
                } else {
                    var index = vm.usersOptions.userGroups.indexOf(userGroup.alias);
                    vm.usersOptions.userGroups.splice(index, 1);
                }
                updateLocation('userGroups', vm.usersOptions.userGroups.join(','));
                changePageNumber(1);
            }
            function setOrderByFilter(value, direction) {
                vm.usersOptions.orderBy = value;
                vm.usersOptions.orderDirection = direction;
                updateLocation('orderBy', value);
                updateLocation('orderDirection', direction);
                getUsers();
            }
            function changePageNumber(pageNumber) {
                vm.usersOptions.pageNumber = pageNumber;
                updateLocation('pageNumber', pageNumber);
                getUsers();
            }
            function updateLocation(key, value) {
                $location.search('filter', vm.usersOptions.filter);
                // update filter, but first when something else requests a url update.
                $location.search(key, value);
            }
            function createUser(addUserForm) {
                if (formHelper.submitForm({
                        formCtrl: addUserForm,
                        scope: $scope
                    })) {
                    vm.newUser.id = -1;
                    vm.newUser.parentId = -1;
                    vm.page.createButtonState = 'busy';
                    usersResource.createUser(vm.newUser).then(function (saved) {
                        vm.page.createButtonState = 'success';
                        vm.newUser = saved;
                        setUsersViewState('createUserSuccess');
                        getUsers();
                    }, function (err) {
                        formHelper.handleError(err);
                        vm.page.createButtonState = 'error';
                    });
                }
            }
            function inviteUser(addUserForm) {
                if (formHelper.submitForm({
                        formCtrl: addUserForm,
                        scope: $scope
                    })) {
                    vm.newUser.id = -1;
                    vm.newUser.parentId = -1;
                    vm.page.createButtonState = 'busy';
                    usersResource.inviteUser(vm.newUser).then(function (saved) {
                        //success
                        vm.page.createButtonState = 'success';
                        vm.newUser = saved;
                        setUsersViewState('inviteUserSuccess');
                        getUsers();
                    }, function (err) {
                        //error
                        formHelper.handleError(err);
                        vm.page.createButtonState = 'error';
                    });
                }
            }
            function toggleNewUserPassword() {
                vm.newUser.showPassword = !vm.newUser.showPassword;
            }
            // copy to clip board success
            function copySuccess() {
                if (vm.page.copyPasswordButtonState !== 'success') {
                    $timeout(function () {
                        vm.page.copyPasswordButtonState = 'success';
                    });
                    $timeout(function () {
                        resetClipboardButtonState();
                    }, 1000);
                }
            }
            // copy to clip board error
            function copyError() {
                if (vm.page.copyPasswordButtonState !== 'error') {
                    $timeout(function () {
                        vm.page.copyPasswordButtonState = 'error';
                    });
                    $timeout(function () {
                        resetClipboardButtonState();
                    }, 1000);
                }
            }
            function resetClipboardButtonState() {
                vm.page.copyPasswordButtonState = 'init';
            }
            function goToUser(user) {
                $location.path(pathToUser(user)).search('orderBy', vm.usersOptions.orderBy).search('orderDirection', vm.usersOptions.orderDirection).search('pageNumber', vm.usersOptions.pageNumber).search('userStates', getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userStates)).search('userGroups', getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userGroups)).search('create', null).search('invite', null);
            }
            function getUsersOptionsFilterCollectionAsDelimitedStringOrNull(collection) {
                if (collection && collection.length > 0) {
                    return collection.join(',');
                }
                return null;
            }
            function getEditPath(user) {
                return pathToUser(user) + usersOptionsAsQueryString();
            }
            function pathToUser(user) {
                return '/users/users/user/' + user.id;
            }
            function usersOptionsAsQueryString() {
                var qs = '?orderBy=' + vm.usersOptions.orderBy + '&orderDirection=' + vm.usersOptions.orderDirection + '&pageNumber=' + vm.usersOptions.pageNumber + '&filter=' + vm.usersOptions.filter;
                qs += addUsersOptionsFilterCollectionToQueryString('userStates', vm.usersOptions.userStates);
                qs += addUsersOptionsFilterCollectionToQueryString('userGroups', vm.usersOptions.userGroups);
                qs += '&mculture=' + $location.search().mculture;
                return qs;
            }
            function addUsersOptionsFilterCollectionToQueryString(name, collection) {
                if (collection && collection.length > 0) {
                    return '&' + name + '=' + collection.join(',');
                }
                return '';
            }
            // helpers
            function getUsers() {
                vm.loading = true;
                // Get users
                usersResource.getPagedResults(vm.usersOptions).then(function (data) {
                    vm.users = data.items;
                    vm.usersOptions.pageNumber = data.pageNumber;
                    vm.usersOptions.pageSize = data.pageSize;
                    vm.usersOptions.totalItems = data.totalItems;
                    vm.usersOptions.totalPages = data.totalPages;
                    formatDates(vm.users);
                    setUserDisplayState(vm.users);
                    vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates);
                    initUserStateSelections();
                    vm.loading = false;
                }, function (error) {
                    vm.loading = false;
                });
            }
            function setUserDisplayState(users) {
                users.forEach(function (user) {
                    user.userDisplayState = usersHelper.getUserStateFromValue(user.userState);
                });
            }
            function formatDates(users) {
                users.forEach(function (user) {
                    if (user.lastLoginDate) {
                        var dateVal;
                        var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                        var localOffset = new Date().getTimezoneOffset();
                        var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                        if (serverTimeNeedsOffsetting) {
                            dateVal = dateHelper.convertToLocalMomentTime(user.lastLoginDate, serverOffset);
                        } else {
                            dateVal = moment(user.lastLoginDate, 'YYYY-MM-DD HH:mm:ss');
                        }
                        // get current backoffice user and format date
                        userService.getCurrentUser().then(function (currentUser) {
                            user.formattedLastLogin = dateVal.locale(currentUser.locale).format('LLL');
                        });
                    }
                });
            }
            function setBulkActions(users) {
                // reset all states
                vm.allowDisableUser = true;
                vm.allowEnableUser = true;
                vm.allowUnlockUser = true;
                vm.allowSetUserGroup = true;
                var firstSelectedUserGroups;
                users.forEach(function (user) {
                    if (!user.selected) {
                        return;
                    }
                    // if the current user is selected prevent any bulk actions with the user included
                    if (user.isCurrentUser) {
                        vm.allowDisableUser = false;
                        vm.allowEnableUser = false;
                        vm.allowUnlockUser = false;
                        vm.allowSetUserGroup = false;
                        return false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Disabled') {
                        vm.allowDisableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Active') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Invited') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'LockedOut') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key !== 'LockedOut') {
                        vm.allowUnlockUser = false;
                    }
                    // store the user group aliases of the first selected user
                    if (vm.allowSetUserGroup === true) {
                        if (!firstSelectedUserGroups) {
                            firstSelectedUserGroups = user.userGroups.map(function (ug) {
                                return ug.alias;
                            });
                        } else {
                            // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing.
                            // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't
                            // really support that in the user group picker.
                            var userGroups = user.userGroups.map(function (ug) {
                                return ug.alias;
                            });
                            if (_.difference(firstSelectedUserGroups, userGroups).length > 0) {
                                vm.allowSetUserGroup = false;
                            }
                        }
                    }
                });
            }
            function clearAddUserForm() {
                // clear form data
                vm.newUser.name = '';
                vm.newUser.email = '';
                vm.newUser.userGroups = [];
                vm.newUser.message = '';
                // clear button state
                vm.page.createButtonState = 'init';
                $scope.$emit('$setAccessibleHeader', true, 'general_user', false, '', '', true);
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.UsersController', UsersController);
    }());
}());