diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umblaunchminieditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umblaunchminieditor.directive.js new file mode 100644 index 0000000000..1967b36734 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umblaunchminieditor.directive.js @@ -0,0 +1,75 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbLaunchMiniEditor +* @restrict E +* @function +* @description +* Used on a button to launch a mini content editor editor dialog +**/ +angular.module("umbraco.directives") + .directive('umbLaunchMiniEditor', function (dialogService, editorState, fileManager, contentEditingHelper) { + return { + restrict: 'A', + replace: false, + scope: { + node: '=umbLaunchMiniEditor', + }, + link: function(scope, element, attrs) { + + element.click(function() { + + //We need to store the current files selected in the file manager locally because the fileManager + // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager + // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, + // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. + var currFiles = _.groupBy(fileManager.getFiles(), "alias"); + fileManager.clearFiles(); + + //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that + // any property editors that are working with editorState get given the correct entity, otherwise strange things will + // start happening. + var currEditorState = editorState.getCurrent(); + + dialogService.open({ + template: "views/common/dialogs/content/edit.html", + id: scope.node.id, + closeOnSave: true, + tabFilter: ["Generic properties"], + callback: function (data) { + + //set the node name back + scope.node.name = data.name; + + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); + }); + + //reset the editor state + editorState.set(currEditorState); + + //Now we need to check if the content item that was edited was actually the same content item + // as the main content editor and if so, update all property data + if (data.id === scope.node.id) { + var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data); + } + }, + closeCallback: function () { + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); + }); + + //reset the editor state + editorState.set(currEditorState); + + } + }); + + }); + + } + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index 07e4242e98..1177da323d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -259,11 +259,11 @@ angular.module('umbraco.services').factory("editorState", function() { */ reset: function() { current = null; - } + }, /** * @ngdoc function - * @name umbraco.services.angularHelper#current + * @name umbraco.services.angularHelper#getCurrent * @methodOf umbraco.services.editorState * @function * @@ -276,8 +276,13 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ + getCurrent: function() { + return current; + } }; + //TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. + //create a get/set property but don't allow setting Object.defineProperty(state, "current", { get: function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js index fb82432586..02655b3d11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js @@ -1,4 +1,4 @@ -function ContentEditDialogController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) { +function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) { $scope.defaultButton = null; $scope.subButtons = []; @@ -48,6 +48,12 @@ function ContentEditDialogController($scope, $routeParams, $q, $timeout, $window }); $scope.defaultButton = buttons.defaultButton; $scope.subButtons = buttons.subButtons; + + //This is a total hack but we have really no other way of sharing data to the property editors of this + // content item, so we'll just set the property on the content item directly + $scope.content.isDialogEditor = true; + + editorState.set($scope.content); } //check if the entity is being passed in, otherwise load it from the server diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index afb3743e0a..ccb5fa50f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -3,163 +3,133 @@ angular.module('umbraco') .controller("Umbraco.PropertyEditors.ContentPickerController", - function ($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager) { - $scope.renderModel = []; - $scope.ids = $scope.model.value ? $scope.model.value.split(',') : []; + function ($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper) { - //configuration - $scope.cfg = { - multiPicker: "0", - entityType: "Document", - filterCssClass: "not-allowed not-published", + $scope.renderModel = []; + $scope.ids = $scope.model.value ? $scope.model.value.split(',') : []; - startNode: { - query: "", - type: "content", - id: -1 - } - }; + $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; - if ($scope.model.config) { - $scope.cfg = angular.extend($scope.cfg, $scope.model.config); - } + //configuration + $scope.cfg = { + multiPicker: "0", + entityType: "Document", + filterCssClass: "not-allowed not-published", - //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! - $scope.cfg.multiPicker = ($scope.cfg.multiPicker === "0" ? false : true); + startNode: { + query: "", + type: "content", + id: -1 + } + }; - if ($scope.cfg.startNode.type === "member") { - $scope.cfg.entityType = "Member"; - } - else if ($scope.cfg.startNode.type === "media") { - $scope.cfg.entityType = "Media"; - } + if ($scope.model.config) { + $scope.cfg = angular.extend($scope.cfg, $scope.model.config); + } - //if we have a query for the startnode, we will use that. - if($scope.cfg.startNode.query){ - var rootId = $routeParams.id; - entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function(ent){ - $scope.cfg.startNodeId = ent.id; - }); - }else{ - $scope.cfg.startNodeId = $scope.cfg.startNode.id; - } + //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! + $scope.cfg.multiPicker = ($scope.cfg.multiPicker === "0" ? false : true); - $scope.cfg.callback = populate; - $scope.cfg.treeAlias = $scope.cfg.startNode.type; - $scope.cfg.section = $scope.cfg.startNode.type; + if ($scope.cfg.startNode.type === "member") { + $scope.cfg.entityType = "Member"; + } + else if ($scope.cfg.startNode.type === "media") { + $scope.cfg.entityType = "Media"; + } - //load current data - entityResource.getByIds($scope.ids, $scope.cfg.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 }); - }); - }); + //if we have a query for the startnode, we will use that. + if ($scope.cfg.startNode.query) { + var rootId = $routeParams.id; + entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function (ent) { + $scope.cfg.startNodeId = ent.id; + }); + } else { + $scope.cfg.startNodeId = $scope.cfg.startNode.id; + } + + $scope.cfg.callback = populate; + $scope.cfg.treeAlias = $scope.cfg.startNode.type; + $scope.cfg.section = $scope.cfg.startNode.type; + + //load current data + entityResource.getByIds($scope.ids, $scope.cfg.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 }); + }); + }); - //dialog - $scope.openContentPicker = function () { - var d = dialogService.treePicker($scope.cfg); - }; + //dialog + $scope.openContentPicker = function () { + var d = dialogService.treePicker($scope.cfg); + }; - $scope.remove = function (index) { - $scope.renderModel.splice(index, 1); - }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + }; + + $scope.add = function (item) { + if ($scope.ids.indexOf(item.id) < 0) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); + } + }; - $scope.edit = function (node) { + $scope.clear = function () { + $scope.renderModel = []; + }; - //We need to store the current files selected in the file manager locally because the fileManager - //is a singleton and is shared globally. The mini dialog will also be referencing the fileManager - //and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, - //clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. + //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required + // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable + // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. + // In their source code there is no event so we need to just subscribe to our model changes here. + //This also makes it easier to manage models, we update one and the rest will just work. + $scope.$watch(function () { + //return the joined Ids as a string to watch + return _.map($scope.renderModel, function (i) { + return i.id; + }).join(); + }, function (newVal) { + $scope.ids = _.map($scope.renderModel, function (i) { + return i.id; + }); + $scope.model.value = trim($scope.ids.join(), ","); - var currFiles = _.groupBy(fileManager.getFiles(), "alias"); - fileManager.clearFiles(); + //Validate! + if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + $scope.contentPickerForm.minCount.$setValidity("minCount", false); + } + else { + $scope.contentPickerForm.minCount.$setValidity("minCount", true); + } - dialogService.open({ - template: "views/common/dialogs/content/edit.html", - id: node.id, - closeOnSave:true, - tabFilter: ["Generic properties"], - callback: function(data) { - node.name = data.name; - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function (val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - }, - closeCallback: function() { - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function(val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - } - }); - }; + if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); + } + }); - $scope.add = function (item) { - if ($scope.ids.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); - } - }; + $scope.$on("formSubmitting", function (ev, args) { + $scope.model.value = trim($scope.ids.join(), ","); + }); - $scope.clear = function () { - $scope.renderModel = []; - }; + function trim(str, chr) { + var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } - //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required - // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable - // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. - // In their source code there is no event so we need to just subscribe to our model changes here. - //This also makes it easier to manage models, we update one and the rest will just work. - $scope.$watch(function () { - //return the joined Ids as a string to watch - return _.map($scope.renderModel, function (i) { - return i.id; - }).join(); - }, function (newVal) { - $scope.ids = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim($scope.ids.join(), ","); - - //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { - $scope.contentPickerForm.minCount.$setValidity("minCount", false); - } - else { - $scope.contentPickerForm.minCount.$setValidity("minCount", true); - } - - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); - } - }); - -$scope.$on("formSubmitting", function (ev, args) { - $scope.model.value = trim($scope.ids.join(), ","); -}); - -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 (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } -} -}); \ No newline at end of file + function populate(data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + } + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index 931b95dc9d..921b0a82ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -15,8 +15,8 @@ {{node.name}} -
- Edit +
+ Edit