diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs index c8adf2686bb..644fb57316e 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs @@ -12,6 +12,7 @@ @currentWhen={{sectionLink.currentWhen}} @prefixType={{sectionLink.prefixType}} @prefixValue={{sectionLink.prefixValue}} + @prefixColor={{sectionLink.prefixColor}} @models={{sectionLink.models}} data-tag-name={{sectionLink.tagName}} /> diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js index f0913b3d673..31ecf39dfb3 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js @@ -1,4 +1,5 @@ import Component from "@glimmer/component"; +import { isHex } from "discourse/components/sidebar/section-link"; export default class extends Component { get prefixValue() { @@ -11,8 +12,10 @@ export default class extends Component { let hexValues = this.args.prefixValue; hexValues = hexValues.reduce((acc, color) => { - if (color?.match(/^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) { - acc.push(`#${color} 50%`); + const hexCode = isHex(color); + + if (hexCode) { + acc.push(`#${hexCode} 50%`); } return acc; diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js index 6ced6142349..00e497d64e7 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js @@ -1,6 +1,21 @@ import Component from "@glimmer/component"; import { inject as service } from "@ember/service"; +/** + * Checks if a given string is a valid color hex code. + * + * @param {String|undefined} input Input string to check if it is a valid color hex code. Can be in the form of "FFFFFF" or "#FFFFFF" or "FFF" or "#FFF". + * @returns {String|undefined} Returns the matching color hex code without the leading `#` if it is valid, otherwise returns undefined. Example: "FFFFFF" or "FFF". + */ +export function isHex(input) { + const match = input?.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/); + + if (match) { + return match[1]; + } else { + return; + } +} export default class SectionLink extends Component { @service currentUser; @@ -53,12 +68,12 @@ export default class SectionLink extends Component { } get prefixColor() { - const color = this.args.prefixColor; + const hexCode = isHex(this.args.prefixColor); - if (!color || !color.match(/^\w{6}$/)) { - return ""; + if (hexCode) { + return `#${hexCode}`; + } else { + return; } - - return "#" + color; } } diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs index b09db23259e..7c997cb3488 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs @@ -22,6 +22,7 @@ @currentWhen={{sectionLink.currentWhen}} @prefixType={{sectionLink.prefixType}} @prefixValue={{sectionLink.prefixValue}} + @prefixColor={{sectionLink.prefixColor}} @badgeText={{sectionLink.badgeText}} @models={{sectionLink.models}} @suffixCSSClass={{sectionLink.suffixCSSClass}} diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 4da8c699a14..4915ae94b53 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -109,6 +109,7 @@ import { registerCustomCategorySectionLinkPrefix, registerCustomCountable as registerUserCategorySectionLinkCountable, } from "discourse/lib/sidebar/user/categories-section/category-section-link"; +import { registerCustomTagSectionLinkPrefixIcon } from "discourse/lib/sidebar/user/tags-section/base-tag-section-link"; import { REFRESH_COUNTS_APP_EVENT_NAME as REFRESH_USER_SIDEBAR_CATEGORIES_SECTION_COUNTS_APP_EVENT_NAME } from "discourse/components/sidebar/user/categories-section"; import DiscourseURL from "discourse/lib/url"; import { registerNotificationTypeRenderer } from "discourse/lib/notification-types-manager"; @@ -1965,6 +1966,38 @@ class PluginApi { }); } + /** + * EXPERIMENTAL. Do not use. + * Register a custom prefix for a sidebar tag section link. + * + * Example: + * + * ``` + * api.registerCustomTagSectionLinkPrefixValue({ + * tagName: "tag1", + * prefixType: "icon", + * prefixValue: "wrench", + * prefixColor: "#FF0000" + * }); + * ``` + * + * @params {Object} arg - An object + * @params {string} arg.tagName - The name of the tag + * @params {string} arg.prefixValue - The name of a FontAwesome 5 icon. + * @params {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF". + */ + registerCustomTagSectionLinkPrefixIcon({ + tagName, + prefixValue, + prefixColor, + }) { + registerCustomTagSectionLinkPrefixIcon({ + tagName, + prefixValue, + prefixColor, + }); + } + /** * EXPERIMENTAL. Do not use. * Triggers a refresh of the counts for all category section links under the categories section for a logged in user. diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/base-tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/base-tag-section-link.js index 6a32ae90277..2c2c9967478 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/base-tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/base-tag-section-link.js @@ -1,6 +1,28 @@ +let customTagSectionLinkPrefixIcons = {}; + +export function registerCustomTagSectionLinkPrefixIcon({ + tagName, + prefixValue, + prefixColor, +}) { + customTagSectionLinkPrefixIcons[tagName] = { + prefixValue, + prefixColor, + }; +} + +export function resetCustomTagSectionLinkPrefixIcons() { + for (let key in customTagSectionLinkPrefixIcons) { + if (customTagSectionLinkPrefixIcons.hasOwnProperty(key)) { + delete customTagSectionLinkPrefixIcons[key]; + } + } +} + export default class BaseTagSectionLink { - constructor({ tagName }) { + constructor({ tagName, currentUser }) { this.tagName = tagName; + this.currentUser = currentUser; } get name() { @@ -16,6 +38,10 @@ export default class BaseTagSectionLink { } get prefixValue() { - return "tag"; + return customTagSectionLinkPrefixIcons[this.tagName]?.prefixValue || "tag"; + } + + get prefixColor() { + return customTagSectionLinkPrefixIcons[this.tagName]?.prefixColor; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/pm-tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/pm-tag-section-link.js index 7e10951d5b1..09d36e4d10c 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/pm-tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/pm-tag-section-link.js @@ -1,11 +1,6 @@ import BaseTagSectionLink from "discourse/lib/sidebar/user/tags-section/base-tag-section-link"; export default class PMTagSectionLink extends BaseTagSectionLink { - constructor({ currentUser }) { - super(...arguments); - this.currentUser = currentUser; - } - get models() { return [this.currentUser, this.tagName]; } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js index 179ee6486a0..5f05849ba05 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js @@ -12,10 +12,9 @@ export default class TagSectionLink extends BaseTagSectionLink { @tracked hideCount = this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; - constructor({ topicTrackingState, currentUser }) { + constructor({ topicTrackingState }) { super(...arguments); this.topicTrackingState = topicTrackingState; - this.currentUser = currentUser; this.refreshCounts(); } diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js index 2223c1191b1..3a42c83f95a 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js @@ -15,6 +15,7 @@ import { resetCustomCategorySectionLinkPrefix, resetCustomCountables, } from "discourse/lib/sidebar/user/categories-section/category-section-link"; +import { resetCustomTagSectionLinkPrefixIcons } from "discourse/lib/sidebar/user/tags-section/base-tag-section-link"; import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar"; import { bind } from "discourse-common/utils/decorators"; @@ -22,6 +23,7 @@ acceptance("Sidebar - Plugin API", function (needs) { needs.user({}); needs.settings({ + tagging_enabled: true, navigation_menu: "sidebar", }); @@ -820,4 +822,51 @@ acceptance("Sidebar - Plugin API", function (needs) { resetCustomCategorySectionLinkPrefix(); } }); + + test("Customizing the prefix icon used in a tag section link for a particular tag", async function (assert) { + try { + return await withPluginApi(PLUGIN_API_VERSION, async (api) => { + updateCurrentUser({ + display_sidebar_tags: true, + sidebar_tags: [ + { name: "tag2", pm_only: false }, + { name: "tag1", pm_only: false }, + { name: "tag3", pm_only: false }, + ], + }); + + api.registerCustomTagSectionLinkPrefixIcon({ + tagName: "tag1", + prefixValue: "wrench", + prefixColor: "#FF0000", // rgb(255, 0, 0) + }); + + await visit("/"); + + assert.ok( + exists( + `.sidebar-section-link[data-tag-name="tag1"] .prefix-icon.d-icon-wrench` + ), + "wrench icon is displayed for tag1 section link's prefix icon" + ); + + assert.strictEqual( + query( + `.sidebar-section-link[data-tag-name="tag1"] .sidebar-section-link-prefix` + ).style.color, + "rgb(255, 0, 0)", + "tag1 section link's prefix icon has the right color" + ); + + assert.ok( + exists( + `.sidebar-section-link[data-tag-name="tag2"] .prefix-icon.d-icon-tag` + ), + "default tag icon is displayed for tag2 section link's prefix icon" + ); + }); + } finally { + resetCustomTagSectionLinkPrefixIcons(); + } + }); });