diff --git a/.gitignore b/.gitignore index 2d5a5bbf79..0776fc2050 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,5 @@ src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css tools/NDepend/ src/Umbraco.Web.UI/App_Plugins/* +src/*.psess +src/*.vspx diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js index 57f46bec99..5ffb4929fe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js @@ -51,24 +51,26 @@ angular.module('umbraco.mocks'). return { register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')) - .respond(returnNodebyId); + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')) + .respond(returnNodebyId); - - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) - .respond(returnEmptyNode); + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) + .respond(returnEmptyNode); $httpBackend .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/DeleteById')) .respond(returnDeletedNode); + + $httpBackend + .whenDELETE(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/EmptyRecycleBin')) + .respond(returnDeletedNode); }, - expectGetById: function() { - $httpBackend - .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')); + $httpBackend + .expectGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')); } }; }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 7f1ae94031..9b5eb5908f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -26,7 +26,9 @@ angular.module('umbraco.mocks'). { seperator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} }, { name: "Public Access", cssclass: "group", alias: "publicaccess", metaData: {} }, - { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } + { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }, + + { seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } ]; return [200, menu, null]; @@ -52,10 +54,10 @@ angular.module('umbraco.mocks'). } var children = [ - { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-file-alt", view: section + "/edit/" + level + "" + 1234, children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-file-alt", view: section + "/edit/" + level + "" + 1235, children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-file-alt", view: section + "/edit/" + level + "" + 1236, children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-file-alt", view: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl } + { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-file-alt", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-file-alt", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-file-alt", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-file-alt", routePath: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl } ]; return [200, children, null]; @@ -78,15 +80,16 @@ angular.module('umbraco.mocks'). name: "content", id: -1, children: [ - { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Recycle Bin", id: 1237, childNodesUrl: url, icon: "icon-trash", view: section + "/trash/view/", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } + { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, + { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, + { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, + { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: section + "/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } ], expanded: true, hasChildren: true, level: 0, - menuUrl: menuUrl + menuUrl: menuUrl, + metaData: { treeType: "Umbraco.Web.Trees.ContentTreeController" } }; break; @@ -96,16 +99,16 @@ angular.module('umbraco.mocks'). name: "developer", id: -1, children: [ - { name: "Data types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Pacakges", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "XSLT Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Razor Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "Data types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.DataTypeTreeController" } }, + { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.MacrosTreeController" } }, + { name: "Pacakges", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.PackagesTreeController" } }, + { name: "XSLT Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.XsltTreeController" } }, + { name: "Razor Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.RazorTreeController" } } ], expanded: true, hasChildren: true, level: 0, - isContainer: true + isContainer: true }; break; @@ -114,11 +117,11 @@ angular.module('umbraco.mocks'). name: "settings", id: -1, children: [ - { name: "Stylesheets", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "Stylesheets", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.StylesheetTreeController" } }, + { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.TemplatesTreeController" } }, + { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.DictionaryTreeController" } }, + { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.MediaTypesTreeController" } }, + { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeType: "Umbraco.Web.Trees.ContentTypesTreeController" } } ], expanded: true, hasChildren: true, @@ -133,15 +136,16 @@ angular.module('umbraco.mocks'). name: "randomTree", id: -1, children: [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ], expanded: true, hasChildren: true, level: 0, - menuUrl: menuUrl + menuUrl: menuUrl, + metaData: { treeType: "Umbraco.Web.Trees.RandomTreeController" } }; break; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 409f22cc55..dda52c5a98 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -16,6 +16,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + emptyRecycleBin: function() { + return umbRequestHelper.resourcePromise( + $http.delete( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + deleteById: function(id) { return umbRequestHelper.resourcePromise( $http.delete( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index fde4494d1e..1b1393979c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -12,7 +12,7 @@ function treeService($q, treeResource, iconHelper) { var treeArray = []; var currentSection = "content"; - /** ensures there's a view and level property on each tree node */ + /** ensures there's a routePath, parent and level property on each tree node */ function ensureParentLevelAndView(parentNode, treeNodes, section, level) { //if no level is set, then we make it 1 var childLevel = (level ? level : 1); @@ -33,7 +33,60 @@ function treeService($q, treeResource, iconHelper) { throw "Cannot remove a node that doesn't have a parent"; } //remove the current item from it's siblings - treeNode.parent.children.splice(treeNode.parent.children.indexOf(treeNode), 1); + treeNode.parent.children.splice(treeNode.parent.children.indexOf(treeNode), 1); + }, + + removeChildNodes : function(treeNode) { + treeNode.children = []; + treeNode.hasChildren = false; + }, + + /** Gets a child node by id */ + getChildNode: function(treeNode, id) { + var found = _.find(treeNode.children, function (child) { + return child.id === id; + }); + return found === undefined ? null : found; + }, + + /** Gets a descendant node by id */ + getDescendantNode: function(treeNode, id) { + //check the first level + var found = this.getChildNode(treeNode, id); + if (found) { + return found; + } + + //check each child of this node + for (var i = 0; i < treeNode.children.length; i++) { + if (treeNode.children[i].hasChildren) { + //recurse + found = this.getDescendantNode(treeNode.children[i], id); + if (found) { + return found; + } + } + } + + //not found + return found === undefined ? null : found; + }, + + /** Gets the root node of the current tree type for a given tree node */ + getTreeRoot: function(treeNode) { + //all root nodes have metadata key 'treeType' + var root = null; + var current = treeNode; + while (root === null && current !== undefined) { + + if (current.metaData && current.metaData["treeType"]) { + root = current; + } + else { + current = current.parent; + } + } + return root; }, getTree: function (options) { @@ -97,7 +150,6 @@ function treeService($q, treeResource, iconHelper) { * * @description * Attempts to return a tree node's menu item based on the alias supplied, otherwise returns null. - * @param {object} args An arguments object * @param {object} args.treeNode The tree node to get the menu item for * @param {object} args.menuItemAlias The menu item alias to attempt to find diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 84bd4ce84f..4fa91a1321 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -33,6 +33,11 @@ app.config(function ($routeProvider) { if (!rp.method) return "views/common/dashboard.html"; + ////here we detect recycle bins, all recycle bins start with -2* (i.e. -20, -21) + //if (rp.id.startsWith("-2")) { + // return 'views/' + rp.section + '/recyclebin.html'; + //} + return 'views/' + rp.section + '/' + rp.method + '.html'; } }) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentcreate.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js similarity index 87% rename from src/Umbraco.Web.UI.Client/src/views/content/contentcreate.controller.js rename to src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index 3fa2ea8c8d..de85970d71 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/contentcreate.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -1,5 +1,5 @@ angular.module('umbraco') -.controller("Umbraco.Editors.ContentCreateController", +.controller("Umbraco.Editors.Content.CreateController", function ($scope, $routeParams, contentTypeResource, iconHelper) { contentTypeResource.getAllowedTypes($scope.currentNode.id) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentdelete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js similarity index 67% rename from src/Umbraco.Web.UI.Client/src/views/content/contentdelete.controller.js rename to src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index 2a2deee7b3..1ee3114a49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/contentdelete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -17,6 +17,12 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio $scope.currentNode.loading = false; //TODO: Need to sync tree, etc... treeService.removeNode($scope.currentNode); + + //ensure the recycle bin has child nodes now + var rootNode = treeService.getTreeRoot($scope.currentNode); + var recycleBin = treeService.getDescendantNode(rootNode, -20); + recycleBin.hasChildren = true; + navigationService.hideMenu(); }); @@ -27,4 +33,4 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio }; } -angular.module("umbraco").controller("Umbraco.Editors.ContentDeleteController", ContentDeleteController); +angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js rename to src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 3f047288ca..62035c79f1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -91,4 +91,4 @@ function ContentEditController($scope, $routeParams, $location, contentResource, }; } -angular.module("umbraco").controller("Umbraco.Editors.ContentEditController", ContentEditController); +angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js new file mode 100644 index 0000000000..860d65d301 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.emptyrecyclebin.controller.js @@ -0,0 +1,30 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.Content.EmptyRecycleBinController + * @function + * + * @description + * The controller for deleting content + */ +function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //(used in the UI) + $scope.currentNode.loading = true; + + contentResource.emptyRecycleBin($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //TODO: Need to sync tree, etc... + treeService.removeChildNodes($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index 98f9e549da..a91b4dc4c2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -1,4 +1,4 @@ -
+

Create a page under {{currentNode.name}}

diff --git a/src/Umbraco.Web.UI.Client/src/views/content/delete.html b/src/Umbraco.Web.UI.Client/src/views/content/delete.html index f79eb7032a..ed065eeaba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/delete.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index eaf420738f..655823b6fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -1,5 +1,5 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html b/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html new file mode 100644 index 0000000000..2e40482e35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html @@ -0,0 +1,8 @@ +
+
+ + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html b/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html new file mode 100644 index 0000000000..92d389c71f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/recyclebin.html @@ -0,0 +1,9 @@ + +
+
+ +

Recycle bin

+
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/recyclebin/view.html b/src/Umbraco.Web.UI.Client/src/views/content/recyclebin/view.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index 78b493d3d0..b2746a3255 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -1,4 +1,4 @@ -
+

Create a page under {{currentNode.name}}

diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index dc4fe83b0c..a5d5335cde 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -1,5 +1,5 @@  - + diff --git a/src/Umbraco.Web.UI.Client/src/views/media/mediacreate.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.create.controller.js similarity index 68% rename from src/Umbraco.Web.UI.Client/src/views/media/mediacreate.controller.js rename to src/Umbraco.Web.UI.Client/src/views/media/media.create.controller.js index 318935368f..f11e8b2f16 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/mediacreate.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.create.controller.js @@ -1,6 +1,6 @@ -function mediaCreateController ($scope, $routeParams,mediaTypeResource) { - $scope.allowedTypes = mediaTypeResource.getAllowedTypes($scope.currentNode.id); -} - -angular.module('umbraco') - .controller("Umbraco.Editors.MediaCreateController", mediaCreateController); \ No newline at end of file +function mediaCreateController ($scope, $routeParams,mediaTypeResource) { + $scope.allowedTypes = mediaTypeResource.getAllowedTypes($scope.currentNode.id); +} + +angular.module('umbraco') + .controller("Umbraco.Editors.Media.CreateController", mediaCreateController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js similarity index 94% rename from src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js rename to src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 76f9eaa52e..6a77dfc680 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -1,63 +1,63 @@ -function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { - - if ($routeParams.create) { - - mediaResource.getScaffold($routeParams.id, $routeParams.doctype) - .then(function (data) { - $scope.contentLoaded = true; - $scope.content = data; - }); - } - else { - mediaResource.getById($routeParams.id) - .then(function (data) { - $scope.contentLoaded = true; - $scope.content = data; - - //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.executeAndClearAllSubscriptions(); - - }); - } - - $scope.files = []; - $scope.addFiles = function (propertyId, files) { - //this will clear the files for the current property and then add the new ones for the current property - $scope.files = _.reject($scope.files, function (item) { - return item.id == propertyId; - }); - for (var i = 0; i < files.length; i++) { - //save the file object to the scope's files collection - $scope.files.push({ id: propertyId, file: files[i] }); - } - }; - - //ensure there is a form object assigned. - var currentForm = angularHelper.getRequiredCurrentForm($scope); - - $scope.save = function (cnt) { - - $scope.$broadcast("saving", { scope: $scope }); - - //don't continue if the form is invalid - if (currentForm.$invalid) return; - - serverValidationManager.reset(); - - mediaResource.saveMedia(cnt, $routeParams.create, $scope.files) - .then(function (data) { - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data - }); - }, function (err) { - contentEditingHelper.handleSaveError(err, $scope); - }); - }; -} - -angular.module("umbraco") - .controller("Umbraco.Editors.MediaEditController", mediaEditController); \ No newline at end of file +function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { + + if ($routeParams.create) { + + mediaResource.getScaffold($routeParams.id, $routeParams.doctype) + .then(function (data) { + $scope.contentLoaded = true; + $scope.content = data; + }); + } + else { + mediaResource.getById($routeParams.id) + .then(function (data) { + $scope.contentLoaded = true; + $scope.content = data; + + //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.executeAndClearAllSubscriptions(); + + }); + } + + $scope.files = []; + $scope.addFiles = function (propertyId, files) { + //this will clear the files for the current property and then add the new ones for the current property + $scope.files = _.reject($scope.files, function (item) { + return item.id == propertyId; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the scope's files collection + $scope.files.push({ id: propertyId, file: files[i] }); + } + }; + + //ensure there is a form object assigned. + var currentForm = angularHelper.getRequiredCurrentForm($scope); + + $scope.save = function (cnt) { + + $scope.$broadcast("saving", { scope: $scope }); + + //don't continue if the form is invalid + if (currentForm.$invalid) return; + + serverValidationManager.reset(); + + mediaResource.saveMedia(cnt, $routeParams.create, $scope.files) + .then(function (data) { + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + newContent: data + }); + }, function (err) { + contentEditingHelper.handleSaveError(err, $scope); + }); + }; +} + +angular.module("umbraco") + .controller("Umbraco.Editors.Media.EditController", mediaEditController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js index dc11575b4b..d5da9e3ef4 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js @@ -20,7 +20,7 @@ describe('edit content controller tests', function () { //this controller requires an angular form controller applied to it scope.contentForm = angularHelper.getNullForm("contentForm"); - controller = $controller('Umbraco.Editors.ContentEditController', { + controller = $controller('Umbraco.Editors.Content.EditController', { $scope: scope, $routeParams: routeParams }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js new file mode 100644 index 0000000000..b6602e3911 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -0,0 +1,130 @@ +describe('tree service tests', function () { + var treeService; + + function ensureParentLevelAndView(parentNode, treeNodes, section, level) { + //if no level is set, then we make it 1 + var childLevel = (level ? level : 1); + for (var i = 0; i < treeNodes.length; i++) { + treeNodes[i].level = childLevel; + //if there is not route path specified, then set it automatically + if (!treeNodes[i].routePath) { + treeNodes[i].routePath = section + "/edit/" + treeNodes[i].id; + } + treeNodes[i].parent = parentNode; + } + } + + function getContentTree() { + + var url = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren?treeType=content&id=1234&level=1"; + var menuUrl = "/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu?treeType=content&id=1234&parentId=456"; + + var t = { + name: "content", + id: -1, + children: [ + { + name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl, + children: [ + { name: "random-name-1", childNodesUrl: url, id: 11, icon: "icon-home", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-2", childNodesUrl: url, id: 12, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-3", childNodesUrl: url, id: 13, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-4", childNodesUrl: url, id: 14, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ] + }, + { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, + { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, + { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: "content/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl, + metaData: { treeType: "Umbraco.Web.Trees.ContentTreeController" } + }; + + ensureParentLevelAndView(t, t.children, "content", 0); + ensureParentLevelAndView(t.children[0], t.children[0].children, "content", 1); + + return t; + } + + beforeEach(module('umbraco.services')); + + beforeEach(inject(function ($injector) { + treeService = $injector.get('treeService'); + })); + + describe('query existing node structure of the tree', function () { + + it('can get a descendant node', function() { + + var tree = getContentTree(); + var found = treeService.getDescendantNode(tree, 13); + + expect(found).toBeDefined(); + expect(found).not.toBeNull(); + expect(found.id).toBe(13); + expect(found.name).toBe("random-name-3"); + }); + + it('returns null for a descendant node that doesnt exist', function () { + + var tree = getContentTree(); + var found = treeService.getDescendantNode(tree, 123456); + + expect(found).toBeNull(); + }); + + it('can get a child node', function () { + + var tree = getContentTree(); + var found = treeService.getChildNode(tree, 1235); + + expect(found).toBeDefined(); + expect(found).not.toBeNull(); + expect(found.id).toBe(1235); + expect(found.name).toBe("Components"); + }); + + it('returns null for a child node that doesnt exist', function () { + + var tree = getContentTree(); + var found = treeService.getChildNode(tree, 123456); + + expect(found).toBeNull(); + }); + + it('returns null for a descendant node that doesnt exist', function () { + + var tree = getContentTree(); + var found = treeService.getDescendantNode(tree, 123456); + + expect(found).toBeNull(); + }); + + it('can get the root node from a child node', function () { + + var tree = getContentTree(); + var testNode = tree.children[0].children[3]; + var root = treeService.getTreeRoot(testNode); + + expect(root).toBeDefined(); + expect(root).not.toBeNull(); + expect(root.id).toBe(-1); + expect(root.name).toBe("content"); + }); + + it('can get the root node from the root node', function () { + + var tree = getContentTree(); + var root = treeService.getTreeRoot(tree); + + expect(root).toBeDefined(); + expect(root).not.toBeNull(); + expect(root.id).toBe(-1); + expect(root.name).toBe("content"); + }); + + }); +}); \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 735f5b47d4..e781213998 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -176,6 +176,33 @@ namespace Umbraco.Web.Editors return display; } + /// + /// Deletes an item + /// + /// + /// + public HttpResponseMessage DeleteById(int id) + { + var foundContent = Services.ContentService.GetById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + Services.ContentService.Delete(foundContent, UmbracoUser.Id); + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Empties the recycle bin + /// + /// + [HttpDelete] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(); + return Request.CreateResponse(HttpStatusCode.OK); + } + private void ShowMessageForStatus(PublishStatus status, ContentItemDisplay display) { switch (status.StatusType) diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index c8e6dbb422..4968a8256f 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -43,7 +43,8 @@ namespace Umbraco.Web.Trees queryStrings, ui.GetText("general", "recycleBin"), "icon-trash", - RecycleBinSmells)); + RecycleBinSmells, + queryStrings.GetValue("application") + "/recyclebin")); return nodes; } diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 8e9c5d1704..b2ad9b45e8 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -280,8 +280,11 @@ namespace Umbraco.Web.Trees Icon = xmlTreeNode.Icon, Title = xmlTreeNode.Text, NodeType = xmlTreeNode.NodeType - }; + if (isRoot) + { + node.AdditionalData.Add("treeType", xmlTreeNode.TreeType); + } //This is a special case scenario, we know that content/media works based on the normal Belle routing/editing so we'll ensure we don't // pass in the legacy JS handler so we do it the new way, for all other trees (Currently, this is a WIP), we'll render diff --git a/src/umbraco.cms/Actions/ActionEmptyTranscan.cs b/src/umbraco.cms/Actions/ActionEmptyTranscan.cs index 24d04bf355..c28c775d53 100644 --- a/src/umbraco.cms/Actions/ActionEmptyTranscan.cs +++ b/src/umbraco.cms/Actions/ActionEmptyTranscan.cs @@ -10,9 +10,7 @@ namespace umbraco.BusinessLogic.Actions public class ActionEmptyTranscan : IAction { //create singleton -#pragma warning disable 612,618 - private static readonly ActionEmptyTranscan m_instance = new ActionEmptyTranscan(); -#pragma warning restore 612,618 + private static readonly ActionEmptyTranscan InnerInstance = new ActionEmptyTranscan(); /// /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. @@ -24,7 +22,7 @@ namespace umbraco.BusinessLogic.Actions public static ActionEmptyTranscan Instance { - get { return m_instance; } + get { return InnerInstance; } } #region IAction Members @@ -57,7 +55,7 @@ namespace umbraco.BusinessLogic.Actions { get { - return "emptyTrashcan"; + return "emptyRecycleBin"; } } diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 4c0fc0d343..d12afb28dc 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -203,7 +203,6 @@ - diff --git a/src/umbraco.sln b/src/umbraco.sln index 40e367de59..3fcce7326d 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -89,6 +89,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{245872FE-BA7D-494A-AFFF-B364BB8FF170}" + ProjectSection(SolutionItems) = preProject + Performance1.psess = Performance1.psess + Performance2.psess = Performance2.psess + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -181,4 +187,7 @@ Global {73529637-28F5-419C-A6BB-D094E39DE614} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} {B555AAE6-0F56-442F-AC9F-EF497DB38DE7} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal