diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 5a05d4c891..ec5c0bc211 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -21,7 +21,7 @@ angular.module('umbraco.services') var currentSection = $routeParams.section; var currentId = $routeParams.id; - var currentNode; + var ui = {}; function setMode(mode) { @@ -64,7 +64,6 @@ angular.module('umbraco.services') } return { - currentNode: currentNode, mode: "default", ui: ui, @@ -231,22 +230,47 @@ angular.module('umbraco.services') * @param {Object} args.action the clicked action containing `name` and `alias` */ showDialog: function (args) { + + if (!args) { + throw "showDialog is missing the args parameter"; + } + if (!args.action) { + throw "The args parameter must have an 'action' property as the clicked menu action object"; + } + setMode("dialog"); var scope = args.scope || $rootScope.$new(); scope.currentNode = args.node; - //this.currentNode = item; - this.ui.dialogTitle = args.action.name; + //the title might be in the meta data, check there first + if (args.action.metaData["dialogTitle"]) { + this.ui.dialogTitle = args.action.metaData["dialogTitle"]; + } + else { + this.ui.dialogTitle = args.action.name; + } - var templateUrl = "views/" + this.ui.currentTree + "/" + args.action.alias + ".html"; - var iframe = false; + var templateUrl; + var iframe; + + //TODO: fix hardcoded hack for content/media... once these trees are converted over to + // new c# trees we won't need to do this any longer. + var isCreateForContent = args.action.alias === "create" && (this.ui.currentTree === "content" && this.ui.currentTree === "media"); - ///TODO: fix hardcoded hack, this is to support legacy create dialogs - if(args.action.alias === "create" && this.ui.currentTree !== "content" && this.ui.currentTree !== "media"){ - templateUrl = "create.aspx?nodeId=" + args.node.id + "&nodeType=" + args.node.nodetype + "&nodeName=" + args.node.name + "&rnd=73.8&rndo=75.1"; + if (args.action.metaData["actionUrl"] && !isCreateForContent) { + templateUrl = args.action.metaData["actionUrl"]; iframe = true; } + else { + templateUrl = "views/" + this.ui.currentTree + "/" + args.action.alias + ".html"; + iframe = false; + } + + //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with + // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog + // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, + // though would be v-easy, just not sure we want to ever support that? return dialogService.open( { diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 04a0ca0984..7dabf8f709 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -84,7 +84,8 @@ namespace Umbraco.Web.Trees { return byControllerAttempt.Result; } - var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url); + + var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url, configTree.ApplicationAlias); if (legacyAttempt.Success) { return legacyAttempt.Result; @@ -108,7 +109,7 @@ namespace Umbraco.Web.Trees { return byControllerAttempt.Result; } - var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url); + var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); if (legacyAttempt.Success) { return legacyAttempt.Result; diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index 860214adf8..62812ec8e0 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -73,15 +73,15 @@ namespace Umbraco.Web.Trees return new Attempt(true, instance.GetNodes(id, formCollection)); } - internal static Attempt TryGetRootNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) + internal static Attempt TryGetRootNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) { var xmlTreeNodeAttempt = TryGetRootXmlNodeFromLegacyTree(appTree, formCollection, urlHelper); if (xmlTreeNodeAttempt.Success == false) { return new Attempt(xmlTreeNodeAttempt.Error); } - return new Attempt(true, - LegacyTreeDataConverter.ConvertFromLegacy(xmlTreeNodeAttempt.Result.NodeID, xmlTreeNodeAttempt.Result, urlHelper, isRoot: true)); + return new Attempt(true, + LegacyTreeDataConverter.ConvertFromLegacy(xmlTreeNodeAttempt.Result.NodeID, xmlTreeNodeAttempt.Result, urlHelper, currentSection, isRoot: true)); } internal static Attempt TryGetRootXmlNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) @@ -107,14 +107,14 @@ namespace Umbraco.Web.Trees : new Attempt(true, treeDef); } - internal static Attempt TryLoadFromLegacyTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, UrlHelper urlHelper) + internal static Attempt TryLoadFromLegacyTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) { var xTreeAttempt = appTree.TryGetXmlTree(id, formCollection); if (xTreeAttempt.Success == false) { return new Attempt(xTreeAttempt.Error); } - return new Attempt(true, LegacyTreeDataConverter.ConvertFromLegacy(id, xTreeAttempt.Result, urlHelper)); + return new Attempt(true, LegacyTreeDataConverter.ConvertFromLegacy(id, xTreeAttempt.Result, urlHelper, currentSection)); } internal static Attempt TryGetMenuFromLegacyTreeRootNode(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) @@ -125,7 +125,9 @@ namespace Umbraco.Web.Trees return new Attempt(rootAttempt.Error); } - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result); + var currentSection = formCollection.GetRequiredString("section"); + + var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); return new Attempt(true, result); } @@ -137,7 +139,9 @@ namespace Umbraco.Web.Trees return new Attempt(xTreeAttempt.Error); } - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(nodeId, xTreeAttempt.Result); + var currentSection = formCollection.GetRequiredString("section"); + + var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(nodeId, xTreeAttempt.Result, currentSection); if (result == null) { return new Attempt(new ApplicationException("Could not find the node with id " + nodeId + " in the collection of nodes contained with parent id " + parentId)); diff --git a/src/Umbraco.Web/Trees/LegacyTreeApiController.cs b/src/Umbraco.Web/Trees/LegacyTreeApiController.cs index b93afdb7b6..be8b07dc46 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeApiController.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeApiController.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Trees public TreeNodeCollection GetNodes(string id, FormDataCollection queryStrings) { var tree = GetTree(queryStrings); - var attempt = tree.TryLoadFromLegacyTree(id, queryStrings, Url); + var attempt = tree.TryLoadFromLegacyTree(id, queryStrings, Url, tree.ApplicationAlias); if (attempt.Success == false) { var msg = "Could not render tree " + queryStrings.GetRequiredString("treeType") + " for node id " + id; diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index f8d9cf336e..5932767d8b 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Web.Http.Routing; using Umbraco.Core; +using umbraco; using umbraco.BusinessLogic.Actions; using umbraco.cms.helpers; using umbraco.cms.presentation.Trees; @@ -16,14 +17,14 @@ namespace Umbraco.Web.Trees /// internal class LegacyTreeDataConverter { - /// /// Gets the menu item collection from a legacy tree node based on it's parent node's child collection /// /// The node id /// The node collection that contains the node id + /// /// - internal static MenuItemCollection ConvertFromLegacyMenu(string nodeId, XmlTree xmlTree) + internal static MenuItemCollection ConvertFromLegacyMenu(string nodeId, XmlTree xmlTree, string currentSection) { var xmlTreeNode = xmlTree.treeCollection.FirstOrDefault(x => x.NodeID == nodeId); if (xmlTreeNode == null) @@ -31,15 +32,16 @@ namespace Umbraco.Web.Trees return null; } - return ConvertFromLegacyMenu(xmlTreeNode); + return ConvertFromLegacyMenu(xmlTreeNode, currentSection); } /// /// Gets the menu item collection from a legacy tree node /// /// + /// /// - internal static MenuItemCollection ConvertFromLegacyMenu(XmlTreeNode xmlTreeNode) + internal static MenuItemCollection ConvertFromLegacyMenu(XmlTreeNode xmlTreeNode, string currentSection) { var collection = new MenuItemCollection(); @@ -55,7 +57,15 @@ namespace Umbraco.Web.Trees } else { - collection.AddMenuItem(t); + var menuItem = collection.AddMenuItem(t); + + //Now we need to figure out how to deal with the legacy menu actions t.JsSource + var tryGetLegacyUrl = GetUrlAndTitleFromLegacyAction(t, xmlTreeNode, currentSection); + if (tryGetLegacyUrl.Success) + { + menuItem.SetActionUrl(tryGetLegacyUrl.Result.Url, tryGetLegacyUrl.Result.ActionMethod); + menuItem.SetDialogTitle(tryGetLegacyUrl.Result.DialogTitle); + } numAdded++; } } @@ -71,7 +81,141 @@ namespace Umbraco.Web.Trees return collection; } - internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, bool isRoot = false) + /// + /// This will look at a legacy IAction's JsFunctionName and convert it to a URL if possible. + /// + /// + /// + /// + internal static Attempt GetUrlAndTitleFromLegacyAction(IAction action, XmlTreeNode actionNode, string currentSection) + { + if (action.JsFunctionName.IsNullOrWhiteSpace()) + { + return Attempt.False; + } + + switch (action.JsFunctionName) + { + case "UmbClientMgr.appActions().actionNew()": + return new Attempt( + true, + new LegacyUrlAction( + "create.aspx?nodeId=" + actionNode.NodeID + "&nodeType=" + actionNode.NodeType + "&nodeName=" + actionNode.Text + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "create"))); + case "UmbClientMgr.appActions().actionNewFolder()": + return new Attempt( + true, + new LegacyUrlAction( + "createFolder.aspx?nodeId=" + actionNode.NodeID + "&nodeType=" + actionNode.NodeType + "&nodeName=" + actionNode.Text + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "create"))); + case "UmbClientMgr.appActions().actionSort()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/sort.aspx?id=" + actionNode.NodeID + "&nodeType=" + actionNode.NodeType + "&app=" + currentSection + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "sort"))); + case "UmbClientMgr.appActions().actionRights()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/cruds.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "rights"))); + case "UmbClientMgr.appActions().actionProtect()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/protectPage.aspx?mode=cut&nodeId=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "protect"))); + case "UmbClientMgr.appActions().actionRollback()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/rollback.aspx?nodeId=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "rollback"))); + case "UmbClientMgr.appActions().actionNotify()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/notifications.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "notify"))); + case "UmbClientMgr.appActions().actionPublish()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/publish.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "publish"))); + case "UmbClientMgr.appActions().actionToPublish()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/SendPublish.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "sendtopublish"))); + case "UmbClientMgr.appActions().actionRePublish()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/republish.aspx?rnd=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + "Republishing entire site")); + case "UmbClientMgr.appActions().actionAssignDomain()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/assignDomain2.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "assignDomain"))); + case "UmbClientMgr.appActions().actionLiveEdit()": + return new Attempt( + true, + new LegacyUrlAction( + "canvas.aspx?redir=/" + actionNode.NodeID + ".aspx", + "", + ActionUrlMethod.BlankWindow)); + case "UmbClientMgr.appActions().actionSendToTranslate()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/sendToTranslation.aspx?id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "sendToTranslate"))); + case "UmbClientMgr.appActions().actionEmptyTranscan()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/emptyTrashcan.aspx?type=" + currentSection, + ui.GetText("actions", "emptyTrashcan"))); + case "UmbClientMgr.appActions().actionImport()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/importDocumentType.aspx", + ui.GetText("actions", "importDocumentType"))); + case "UmbClientMgr.appActions().actionExport()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/exportDocumentType.aspx?nodeId=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + "")); + case "UmbClientMgr.appActions().actionAudit()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/viewAuditTrail.aspx?nodeId=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "auditTrail"))); + case "UmbClientMgr.appActions().actionMove()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/moveOrCopy.aspx?app=" + currentSection + "&mode=cut&id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "move"))); + case "UmbClientMgr.appActions().actionCopy()": + return new Attempt( + true, + new LegacyUrlAction( + "dialogs/moveOrCopy.aspx?app=" + currentSection + "&mode=copy&id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, + ui.GetText("actions", "copy"))); + } + return Attempt.False; + } + + internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, string currentSection, bool isRoot = false) { // /umbraco/tree.aspx?rnd=d0d0ff11a1c347dabfaa0fc75effcc2a&id=1046&treeType=content&contextMenu=false&isDialog=false @@ -92,7 +236,8 @@ namespace Umbraco.Web.Trees { "id=" + (isRoot ? "-1" : xmlTreeNode.NodeID), "treeType=" + xmlTreeNode.TreeType, - "parentId=" + (isRoot ? "-1" : parentId) + "parentId=" + (isRoot ? "-1" : parentId), + "section=" + currentSection }); //TODO: Might need to add stuff to additional attributes @@ -117,7 +262,7 @@ namespace Umbraco.Web.Trees return node; } - internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper) + internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper, string currentSection) { //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert // that over to use the old school ui.xml stuff for these old trees and however the old menu items worked. @@ -125,10 +270,30 @@ namespace Umbraco.Web.Trees var collection = new TreeNodeCollection(); foreach (var x in xmlTree.treeCollection) { - collection.Add(ConvertFromLegacy(parentId, x, urlHelper)); + collection.Add(ConvertFromLegacy(parentId, x, urlHelper, currentSection)); } return collection; } + internal class LegacyUrlAction + { + public LegacyUrlAction(string url, string dialogTitle) + : this(url, dialogTitle, ActionUrlMethod.Dialog) + { + + } + + public LegacyUrlAction(string url, string dialogTitle, ActionUrlMethod actionMethod) + { + Url = url; + ActionMethod = actionMethod; + DialogTitle = dialogTitle; + } + + public string Url { get; private set; } + public ActionUrlMethod ActionMethod { get; private set; } + public string DialogTitle { get; private set; } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/MenuItem.cs b/src/Umbraco.Web/Trees/MenuItem.cs index 3608a06319..1178dd4cbf 100644 --- a/src/Umbraco.Web/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Trees/MenuItem.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.Trees } public MenuItem(IAction legacyMenu) + : this() { Name = legacyMenu.Alias; Alias = legacyMenu.Alias; @@ -25,6 +26,10 @@ namespace Umbraco.Web.Trees /// A dictionary to support any additional meta data that should be rendered for the node which is /// useful for custom action commands such as 'create', 'copy', etc... /// + /// + /// We will also use the meta data collection for dealing with legacy menu items (i.e. for loading custom URLs or + /// executing custom JS) + /// [DataMember(Name = "metaData")] public Dictionary AdditionalData { get; private set; } @@ -41,5 +46,55 @@ namespace Umbraco.Web.Trees [DataMember(Name = "cssclass")] public string Icon { get; set; } + + /// + /// The action to execute when the menu item is clicked. This is generally a route path. + /// + [DataMember(Name = "action")] + public string Action { get; set; } + + + } + + public static class MenuItemExtensions + { + /// + /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title + /// + internal const string DialogTitleKey = "dialogTitle"; + internal const string ActionUrlKey = "actionUrl"; + internal const string ActionUrlMethodKey = "actionUrlMethod"; + + /// + /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) + /// instead of the menu name + /// + /// + /// + public static void SetDialogTitle(this MenuItem menuItem, string dialogTitle) + { + menuItem.AdditionalData[DialogTitleKey] = dialogTitle; + } + + /// + /// Configures the menu item to launch a URL with the specified action (dialog or new window) + /// + /// + /// + /// + public static void SetActionUrl(this MenuItem menuItem, string url, ActionUrlMethod method = ActionUrlMethod.Dialog) + { + menuItem.AdditionalData[ActionUrlKey] = url; + menuItem.AdditionalData[ActionUrlMethodKey] = url; + } + } + + /// + /// Specifies the action to take for a menu item when a URL is specified + /// + public enum ActionUrlMethod + { + Dialog, + BlankWindow } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Trees/MenuItemCollection.cs index 0b94eaa4e0..1dbb18e4ba 100644 --- a/src/Umbraco.Web/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Web/Trees/MenuItemCollection.cs @@ -19,9 +19,11 @@ namespace Umbraco.Web.Trees /// /// Adds a menu item /// - public void AddMenuItem(IAction action) + internal MenuItem AddMenuItem(IAction action) { - _menuItems.Add(new MenuItem(action)); + var item = new MenuItem(action); + _menuItems.Add(item); + return item; } /// diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs index 95c2d45c2a..6b8001d572 100644 --- a/src/umbraco.cms/Actions/Action.cs +++ b/src/umbraco.cms/Actions/Action.cs @@ -30,6 +30,7 @@ namespace umbraco.BusinessLogic.Actions /// which is enabling thirdparty developers to extend the core functionality of /// umbraco without changing the codebase. /// + [Obsolete("Actions and ActionHandlers are obsolete and should no longer be used")] public class Action { private static readonly List ActionHandlers = new List(); @@ -48,7 +49,7 @@ namespace umbraco.BusinessLogic.Actions /// /// /// TODO: this shouldn't be needed... we should restart the app pool when a package is installed! - /// + /// public static void ReRegisterActionsAndHandlers() { lock (Lock) diff --git a/src/umbraco.cms/Actions/ActionNew.cs b/src/umbraco.cms/Actions/ActionNew.cs index ec07d8096a..303baa3e35 100644 --- a/src/umbraco.cms/Actions/ActionNew.cs +++ b/src/umbraco.cms/Actions/ActionNew.cs @@ -10,9 +10,7 @@ namespace umbraco.BusinessLogic.Actions public class ActionNew : IAction { //create singleton -#pragma warning disable 612,618 - private static readonly ActionNew m_instance = new ActionNew(); -#pragma warning restore 612,618 + private static readonly ActionNew InternalInstance = new ActionNew(); /// /// 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 ActionNew Instance { - get { return m_instance; } + get { return InternalInstance; } } #region IAction Members