From e0bf6afc1ac2609aaafaf2c4ba4b8e19528cfea6 Mon Sep 17 00:00:00 2001 From: "Ismet D." <64033786+idumlupinar@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:41:00 -0700 Subject: [PATCH 1/5] Implement requested panel handling in PersonaBarContainer Added requested panel handling to the PersonaBarContainer class. --- .../Containers/PersonaBarContainer.cs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.Library/Containers/PersonaBarContainer.cs b/Dnn.AdminExperience/Library/Dnn.PersonaBar.Library/Containers/PersonaBarContainer.cs index 4e1f47e5551..9d92ae3555b 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.Library/Containers/PersonaBarContainer.cs +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.Library/Containers/PersonaBarContainer.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information namespace Dnn.PersonaBar.Library.Containers @@ -32,6 +32,9 @@ namespace Dnn.PersonaBar.Library.Containers public class PersonaBarContainer : IPersonaBarContainer { + /// The query string key that names a Persona Bar item to open when the page loads. + private const string RequestedPanelQueryKey = "personabar"; + private static IPersonaBarContainer instance; private readonly IPersonaBarController personaBarController; private readonly IHostSettings hostSettings; @@ -104,6 +107,46 @@ public virtual void FilterMenu(PersonaBarMenu menu) { } + /// + /// Gets the identifier of the Persona Bar item requested in the query string (e.g. ?personabar=Dnn.Pages), + /// only if it is a panel in the current user's menu, so the URL can never open anything the user cannot already open. + /// + /// The current user's menu. + /// The matching menu item identifier, or . + private static string GetRequestedPanel(PersonaBarMenu menu) + { + var requested = HttpContextSource.Current?.Request.QueryString[RequestedPanelQueryKey]; + if (string.IsNullOrWhiteSpace(requested)) + { + return null; + } + + return FindPanelIdentifier(menu.MenuItems, requested.Trim()); + } + + private static string FindPanelIdentifier(IEnumerable menuItems, string identifier) + { + foreach (var menuItem in menuItems) + { + // Only items that open a panel: root menus (Content, Manage, ...) have no Path, and items with a Link navigate away instead. + if (string.Equals(menuItem.Identifier, identifier, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(menuItem.ModuleName) + && !string.IsNullOrEmpty(menuItem.Path) + && string.IsNullOrEmpty(menuItem.Link)) + { + return menuItem.Identifier; + } + + var childIdentifier = FindPanelIdentifier(menuItem.Children, identifier); + if (childIdentifier != null) + { + return childIdentifier; + } + } + + return null; + } + private Dictionary GetConfiguration(PortalSettings portalSettings) { var settings = new Dictionary(); @@ -123,6 +166,13 @@ private Dictionary GetConfiguration(PortalSettings portalSetting settings.Add("userMode", Personalization.GetUserMode().ToString()); settings.Add("userSettings", PersonaBarUserSettingsController.Instance.GetPersonaBarUserSettings()); settings.Add("menuStructure", JObject.FromObject(menuStructure)); + + var requestedPanel = GetRequestedPanel(menuStructure); + if (requestedPanel != null) + { + settings.Add("requestedPanel", requestedPanel); + } + settings.Add("sku", DotNetNukeContext.Current.Application.SKU); settings.Add("debugMode", HttpContextSource.Current != null && HttpContextSource.Current.IsDebuggingEnabled); settings.Add("portalId", portalId); From 19ff4e37fd54f568db326518b25eafc89c194441 Mon Sep 17 00:00:00 2001 From: "Ismet D." <64033786+idumlupinar@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:44:05 -0700 Subject: [PATCH 2/5] Refactor util.js to improve structure and readability --- .../admin/personaBar/scripts/util.js | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/util.js b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/util.js index df7e6f68a26..87eaadc52b2 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/util.js +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/util.js @@ -319,6 +319,41 @@ define(['jquery', 'purify.min'], function ($, DOMPurify) { getPanelIdFromPath: function getPanelIdFromPath(path) { return path + '-panel'; }, + + /** + * Builds a URL of the current page that opens the given Persona Bar item when loaded, + * so the item can be opened in a new browser tab. + * + * @method getPanelUrl + * @param {String} identifier the menu item identifier, e.g. Dnn.Pages + * @return {String} the URL, or null if it cannot be built + */ + getPanelUrl: function getPanelUrl(identifier) { + try { + var url = new URL(window.top.location.href); + url.searchParams.set('personabar', identifier); + url.hash = ''; + return url.toString(); + } catch (e) { + return null; + } + }, + + /** + * Gets the URL a menu item's link points to: its own Link (resolved like checkMenuLink in main.js), + * or a URL of the current page that opens its panel. + * + * @method getMenuItemUrl + * @param {Object} menuItem the menu item from the menu structure + * @return {String} the URL, or null if the item has neither a link nor a panel + */ + getMenuItemUrl: function getMenuItemUrl(menuItem) { + if (menuItem.Link) { + return menuItem.Link.indexOf('://') > -1 ? menuItem.Link : config.siteRoot + menuItem.Link; + } + + return menuItem.ModuleName && menuItem.Path ? this.getPanelUrl(menuItem.Identifier) : null; + }, parseQueryParameter: function (item) { item.Query = ''; var pathInfo; @@ -378,7 +413,8 @@ define(['jquery', 'purify.min'], function ($, DOMPurify) { css: menuItem.CssClass, icon: menuItem.IconFile, displayName: menuItem.DisplayName, - settings: menuItem.Settings + settings: menuItem.Settings, + href: util.getMenuItemUrl(menuItem) } topMenuItem.menuItems.push(subMenuItem); }); From 335257df8ff662d15540d551d966c2e30d2a0402 Mon Sep 17 00:00:00 2001 From: "Ismet D." <64033786+idumlupinar@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:45:46 -0700 Subject: [PATCH 3/5] Fix character encoding in index.html Correct HTML character encoding for proper rendering. --- .../Dnn.PersonaBar.UI/admin/personaBar/index.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/index.html b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/index.html index 0ffd6773ac1..d4b4e3b1b7b 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/index.html +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/index.html @@ -1,4 +1,4 @@ - + PersonaBar @@ -56,7 +56,6 @@
  • + }">
From 7704c9e64ec0c68d701c13192596068930656760 Mon Sep 17 00:00:00 2001 From: "Ismet D." <64033786+idumlupinar@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:50:24 -0700 Subject: [PATCH 4/5] Open requested Persona Bar panel on load and let modifier-clicks open new tabs clicks: a plain click still opens the panel in place. Ctrl, Cmd and Shift clicks are left to the browser, so it opens a new tab or window. page load: if the server sent a requested panel, it opens that panel and then removes the parameter from the address bar. hidden Persona Bar: when the Persona Bar is disabled (not visible), the links do nothing, as before. --- .../admin/personaBar/scripts/main.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/main.js b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/main.js index 950a7d12bae..6759d8a83d6 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/main.js +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/scripts/main.js @@ -622,6 +622,19 @@ require(['jquery', 'knockout', 'dayjs', '../util', '../sf', '../config', './../e return false; } + function removeRequestedPanelFromUrl() { + // Keep the address bar clean once the requested panel is open; later reloads restore panels as usual. + try { + var url = new URL(window.top.location.href); + if (url.searchParams.has('personabar')) { + url.searchParams.delete('personabar'); + window.top.history.replaceState(window.top.history.state, '', url.toString()); + } + } catch (e) { + // Leave the URL as it is. + } + } + function calculateHoverMenuPosition($menu) { if ($menu.length === 0) { return; @@ -868,6 +881,12 @@ require(['jquery', 'knockout', 'dayjs', '../util', '../sf', '../config', './../e }); $(".btn_panel, .hovermenu > ul > li").click(function handleClickOnHoverMenuItem(evt) { + // Ctrl/Cmd/Shift-click on an item's link: let the browser open it in a new tab or window. + if ((evt.ctrlKey || evt.metaKey || evt.shiftKey) && $(evt.target).closest('a.hovermenu-link[href]').length) { + evt.stopPropagation(); + return; + } + evt.preventDefault(); evt.stopPropagation(); @@ -1069,6 +1088,9 @@ require(['jquery', 'knockout', 'dayjs', '../util', '../sf', '../config', './../e })(); } else { $(".personabarnav > li.btn_panel, .hovermenu > ul > li").addClass("disabled"); + $(".hovermenu a.hovermenu-link").click(function (e) { + e.preventDefault(); + }); } (function setupEditButton() { @@ -1179,6 +1201,14 @@ require(['jquery', 'knockout', 'dayjs', '../util', '../sf', '../config', './../e return; } + // A page opened from a Persona Bar link (?personabar=) opens that item. + // The server only passes identifiers of panels in the current user's menu. + if (config.visible && config.requestedPanel) { + util.loadPanel(config.requestedPanel, {}); + removeRequestedPanelFromUrl(); + return; + } + var settings = util.persistent.load(); if (settings.expandPersonaBar && settings.activeIdentifier) { var identifier = settings.activeIdentifier; From dfa55e3ebd39671883dd9e7abeaaeb911699179e Mon Sep 17 00:00:00 2001 From: "Ismet D." <64033786+idumlupinar@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:53:42 -0700 Subject: [PATCH 5/5] Style Persona Bar menu links like the previous item text --- .../Dnn.PersonaBar.UI/admin/personaBar/css/main.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/css/main.css b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/css/main.css index aef155ddef0..30ea365355d 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/css/main.css +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/css/main.css @@ -745,6 +745,16 @@ span.dnnFormError:after { margin-top: 0; } +/* Menu item links (for opening items in a new tab) look exactly like the item text. */ +.hovermenu > ul > li > a.hovermenu-link, +.hovermenu > ul > li > a.hovermenu-link:hover, +.hovermenu > ul > li > a.hovermenu-link:focus, +.hovermenu > ul > li > a.hovermenu-link:visited { + color: inherit; + cursor: inherit; + text-decoration: none; +} + .hovermenu > ul > li.disabled { cursor: default; }