DEV: Supports href attribute for hamburger links API bridge to sidebar (#17750)

In the old `decorateWidget("hamburger-menu:generalLinks", callbackFn)`
API, the return value of the callback function can either return a
`route` or `href`. The API bridge added in
de54bdd73d2c501cb22902d638a14cddd2c15520 supported `route` but not `href` and
hence the need for this commit.
This commit is contained in:
Alan Guo Xiang Tan 2022-08-02 15:30:13 +08:00 committed by GitHub
parent b9c1e63bd1
commit df264e49a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 19 deletions

View File

@ -9,6 +9,7 @@ import MyPostsSectionLink from "discourse/lib/sidebar/community-section/my-posts
import GroupsSectionLink from "discourse/lib/sidebar/community-section/groups-section-link"; import GroupsSectionLink from "discourse/lib/sidebar/community-section/groups-section-link";
import UsersSectionLink from "discourse/lib/sidebar/community-section/users-section-link"; import UsersSectionLink from "discourse/lib/sidebar/community-section/users-section-link";
import { inject as service } from "@ember/service";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
@ -21,12 +22,15 @@ const MAIN_SECTION_LINKS = [
const MORE_SECTION_LINKS = [GroupsSectionLink, UsersSectionLink]; const MORE_SECTION_LINKS = [GroupsSectionLink, UsersSectionLink];
export default class SidebarCommunitySection extends GlimmerComponent { export default class SidebarCommunitySection extends GlimmerComponent {
@service router;
moreSectionLinks = [...MORE_SECTION_LINKS, ...customSectionLinks].map( moreSectionLinks = [...MORE_SECTION_LINKS, ...customSectionLinks].map(
(sectionLinkClass) => { (sectionLinkClass) => {
return new sectionLinkClass({ return new sectionLinkClass({
topicTrackingState: this.topicTrackingState, topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser, currentUser: this.currentUser,
appEvents: this.appEvents, appEvents: this.appEvents,
router: this.router,
}); });
} }
); );

View File

@ -10,9 +10,11 @@ export default class SectionLink extends GlimmerComponent {
get prefixCSS() { get prefixCSS() {
const color = this.args.prefixColor; const color = this.args.prefixColor;
if (!color || !color.match(/^\w{6}$/)) { if (!color || !color.match(/^\w{6}$/)) {
return htmlSafe(""); return htmlSafe("");
} }
return htmlSafe("color: #" + color); return htmlSafe("color: #" + color);
} }
} }

View File

@ -97,6 +97,7 @@ import { downloadCalendar } from "discourse/lib/download-calendar";
import { consolePrefix } from "discourse/lib/source-identifier"; import { consolePrefix } from "discourse/lib/source-identifier";
import { addSectionLink } from "discourse/lib/sidebar/custom-community-section-links"; import { addSectionLink } from "discourse/lib/sidebar/custom-community-section-links";
import { addSidebarSection } from "discourse/lib/sidebar/custom-sections"; import { addSidebarSection } from "discourse/lib/sidebar/custom-sections";
import DiscourseURL from "discourse/lib/url";
// If you add any methods to the API ensure you bump up the version number // If you add any methods to the API ensure you bump up the version number
// based on Semantic Versioning 2.0.0. Please update the changelog at // based on Semantic Versioning 2.0.0. Please update the changelog at
@ -484,15 +485,27 @@ class PluginApi {
if (siteSettings.enable_experimental_sidebar_hamburger) { if (siteSettings.enable_experimental_sidebar_hamburger) {
try { try {
const { route, label, rawLabel, className } = fn(); const { href, route, label, rawLabel, className } = fn();
const textContent = rawLabel || I18n.t(label); const textContent = rawLabel || I18n.t(label);
this.addCommunitySectionLink({ const args = {
name: className || textContent.replace(/\s+/g, "-").toLowerCase(), name: className || textContent.replace(/\s+/g, "-").toLowerCase(),
route,
title: textContent, title: textContent,
text: textContent, text: textContent,
}); };
if (href) {
if (DiscourseURL.isInternal(href)) {
args.href = href;
} else {
// Skip external links support for now
return;
}
} else {
args.route = route;
}
this.addCommunitySectionLink(args);
} catch { } catch {
deprecated( deprecated(
`Usage of \`api.decorateWidget('hamburger-menu:generalLinks')\` is incompatible with the \`enable_experimental_sidebar_hamburger\` site setting. Please use \`api.addCommunitySectionLink\` instead.` `Usage of \`api.decorateWidget('hamburger-menu:generalLinks')\` is incompatible with the \`enable_experimental_sidebar_hamburger\` site setting. Please use \`api.addCommunitySectionLink\` instead.`
@ -1699,7 +1712,8 @@ class PluginApi {
* *
* @param {(addCommunitySectionLinkCallback|Object)} arg - A callback function or an Object. * @param {(addCommunitySectionLinkCallback|Object)} arg - A callback function or an Object.
* @param {string} arg.name - The name of the link. Needs to be dasherized and lowercase. * @param {string} arg.name - The name of the link. Needs to be dasherized and lowercase.
* @param {string} arg.route - The Ember route of the link. * @param {string=} arg.route - The Ember route name to generate the href attribute for the link.
* @param {string=} arg.href - The href attribute for the link.
* @param {string} arg.title - The title attribute for the link. * @param {string} arg.title - The title attribute for the link.
* @param {string} arg.text - The text to display for the link. * @param {string} arg.text - The text to display for the link.
*/ */

View File

@ -2,7 +2,8 @@
* Base class representing a sidebar topics section link interface. * Base class representing a sidebar topics section link interface.
*/ */
export default class BaseSectionLink { export default class BaseSectionLink {
constructor({ topicTrackingState, currentUser, appEvents } = {}) { constructor({ topicTrackingState, currentUser, appEvents, router } = {}) {
this.router = router;
this.topicTrackingState = topicTrackingState; this.topicTrackingState = topicTrackingState;
this.currentUser = currentUser; this.currentUser = currentUser;
this.appEvents = appEvents; this.appEvents = appEvents;
@ -32,6 +33,11 @@ export default class BaseSectionLink {
*/ */
get model() {} get model() {}
/**
* @returns {Object} Models for <LinkTo> component. See https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo
*/
get models() {}
/** /**
* @returns {Object} Query parameters for <LinkTo> component. See https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo * @returns {Object} Query parameters for <LinkTo> component. See https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo
*/ */

View File

@ -1,6 +1,42 @@
import BaseSectionLink from "discourse/lib/sidebar/community-section/base-section-link"; import BaseSectionLink from "discourse/lib/sidebar/community-section/base-section-link";
export let customSectionLinks = []; export let customSectionLinks = [];
class RouteInfoHelper {
constructor(router, url) {
this.routeInfo = router.recognize(url);
}
get route() {
return this.routeInfo.name;
}
get models() {
return this.#getParameters;
}
get query() {
return this.routeInfo.queryParams;
}
/**
* Extracted from https://github.com/emberjs/rfcs/issues/658
* Retrieves all parameters for a `RouteInfo` object and its parents in
* correct oder, so that you can pass them to e.g.
* `transitionTo(routeName, ...params)`.
*/
get #getParameters() {
let allParameters = [];
let current = this.routeInfo;
do {
const { params, paramNames } = current;
const currentParameters = paramNames.map((n) => params[n]);
allParameters = [...currentParameters, ...allParameters];
} while ((current = current.parent));
return allParameters;
}
}
/** /**
* Appends an additional section link under the topics section * Appends an additional section link under the topics section
@ -8,31 +44,56 @@ export let customSectionLinks = [];
* @param {BaseSectionLink} baseSectionLink Factory class to inherit from. * @param {BaseSectionLink} baseSectionLink Factory class to inherit from.
* @returns {BaseSectionLink} A class that extends BaseSectionLink. * @returns {BaseSectionLink} A class that extends BaseSectionLink.
* *
* @param {(addSectionLinkCallback|Object)} arg - A callback function or an Object. * @param {(addSectionLinkCallback|Object)} args - A callback function or an Object.
* @param {string} arg.name - The name of the link. Needs to be dasherized and lowercase. * @param {string} arg.name - The name of the link. Needs to be dasherized and lowercase.
* @param {string} arg.route - The Ember route of the link. * @param {string=} arg.route - The Ember route name to generate the href attribute for the link.
* @param {string} arg.title - The title attribute for the link. * @param {string=} arg.href - The href attribute for the link.
* @param {string=} arg.title - The title attribute for the link.
* @param {string} arg.text - The text to display for the link. * @param {string} arg.text - The text to display for the link.
*/ */
export function addSectionLink(arg) { export function addSectionLink(args) {
if (typeof arg === "function") { if (typeof args === "function") {
customSectionLinks.push(arg.call(this, BaseSectionLink)); customSectionLinks.push(args.call(this, BaseSectionLink));
} else { } else {
const klass = class extends BaseSectionLink { const klass = class extends BaseSectionLink {
constructor() {
super(...arguments);
if (args.href) {
this.routeInfoHelper = new RouteInfoHelper(this.router, args.href);
}
}
get name() { get name() {
return arg.name; return args.name;
} }
get route() { get route() {
return arg.route; if (args.href) {
return this.routeInfoHelper.route;
} else {
return args.route;
}
}
get models() {
if (args.href) {
return this.routeInfoHelper.models;
}
}
get query() {
if (args.href) {
return this.routeInfoHelper.query;
}
} }
get text() { get text() {
return arg.text; return args.text;
} }
get title() { get title() {
return arg.title; return args.title;
} }
}; };

View File

@ -17,7 +17,8 @@
@content={{sectionLink.text}} @content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}} @currentWhen={{sectionLink.currentWhen}}
@badgeText={{sectionLink.badgeText}} @badgeText={{sectionLink.badgeText}}
@model={{sectionLink.model}} /> @model={{sectionLink.model}}
@models={{sectionLink.models}} />
{{/each}} {{/each}}
<Sidebar::MoreSectionLinks @sectionLinks={{this.moreSectionLinks}} /> <Sidebar::MoreSectionLinks @sectionLinks={{this.moreSectionLinks}} />

View File

@ -7,7 +7,8 @@
@content={{this.activeSectionLink.text}} @content={{this.activeSectionLink.text}}
@currentWhen={{this.activeSectionLink.currentWhen}} @currentWhen={{this.activeSectionLink.currentWhen}}
@badgeText={{this.activeSectionLink.badgeText}} @badgeText={{this.activeSectionLink.badgeText}}
@model={{this.activeSectionLink.model}} /> @model={{this.activeSectionLink.model}}
@models={{this.activeSectionLink.models}} />
{{/if}} {{/if}}
<details class="sidebar-more-section-links-details" {{on "toggle" this.toggleSectionLinks}}> <details class="sidebar-more-section-links-details" {{on "toggle" this.toggleSectionLinks}}>
@ -29,7 +30,8 @@
@content={{sectionLink.text}} @content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}} @currentWhen={{sectionLink.currentWhen}}
@badgeText={{sectionLink.badgeText}} @badgeText={{sectionLink.badgeText}}
@model={{sectionLink.model}} /> @model={{sectionLink.model}}
@models={{sectionLink.models}} />
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}

View File

@ -444,6 +444,13 @@ acceptance("Sidebar - Plugin API", function (needs) {
className: "my-custom-top", className: "my-custom-top",
}; };
}); });
api.decorateWidget("hamburger-menu:generalLinks", () => {
return {
href: "/c/bug?status=open",
rawLabel: "open bugs",
};
});
}); });
await visit("/"); await visit("/");
@ -457,6 +464,11 @@ acceptance("Sidebar - Plugin API", function (needs) {
"adds custom latest section link to community section" "adds custom latest section link to community section"
); );
assert.ok(
customlatestSectionLink.href.endsWith("/latest"),
"sets the right href attribute for the custom latest section link"
);
assert.strictEqual( assert.strictEqual(
customlatestSectionLink.textContent.trim(), customlatestSectionLink.textContent.trim(),
I18n.t("filters.latest.title"), I18n.t("filters.latest.title"),
@ -476,6 +488,11 @@ acceptance("Sidebar - Plugin API", function (needs) {
"adds custom unread section link to community section" "adds custom unread section link to community section"
); );
assert.ok(
customUnreadSectionLink.href.endsWith("/unread"),
"sets the right href attribute for the custom unread section link"
);
assert.strictEqual( assert.strictEqual(
customUnreadSectionLink.textContent.trim(), customUnreadSectionLink.textContent.trim(),
"my unreads", "my unreads",
@ -490,5 +507,19 @@ acceptance("Sidebar - Plugin API", function (needs) {
customTopSectionLInk, customTopSectionLInk,
"adds custom top section link to community section with right link class" "adds custom top section link to community section with right link class"
); );
const openBugsSectionLink = query(
".sidebar-section-community .sidebar-section-link-open-bugs"
);
assert.ok(
openBugsSectionLink,
"adds custom open bugs section link to community section with right link class"
);
assert.ok(
openBugsSectionLink.href.endsWith("/c/bug?status=open"),
"sets the right href attribute for the custom open bugs section link"
);
}); });
}); });