Skip to content

Commit 370b20c

Browse files
authored
feat: add backgroundColor option for the tags, and style them every where possible (#46)
1 parent 75d7f1d commit 370b20c

9 files changed

Lines changed: 134 additions & 90 deletions

File tree

common/.DS_Store

-6 KB
Binary file not shown.

common/common.scss

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,48 @@
66
color: inherit;
77
vertical-align: middle;
88
}
9+
10+
.hashtag-cooked .hashtag-tag-icon .d-icon {
11+
color: var(--color1, var(--primary));
12+
}
13+
14+
.discourse-tag--tag-icons-style .tag-icon {
15+
color: var(--color1, var(--tag-text-color));
16+
}
17+
18+
@if $enable_colors_for_tag_labels == "true" {
19+
.discourse-tag--tag-icons-style {
20+
&.simple {
21+
color: var(--color1, var(--tag-text-color));
22+
}
23+
24+
&.bullet {
25+
color: var(--color1, var(--tag-text-color));
26+
}
27+
28+
&.box {
29+
color: var(--color2, var(--tag-text-color));
30+
background-color: var(--color1, var(--primary-low));
31+
32+
.tag-icon {
33+
color: inherit;
34+
}
35+
}
36+
37+
.select-kit .select-kit-row &,
38+
.select-kit .select-kit-row &:visited,
39+
.select-kit .select-kit-row &:hover {
40+
&.simple {
41+
color: var(--color1, var(--primary-high));
42+
}
43+
44+
&.bullet {
45+
color: var(--color1, var(--primary-high));
46+
}
47+
48+
&.box {
49+
color: var(--color2, var(--primary-high));
50+
}
51+
}
52+
}
53+
}

javascripts/discourse/initializers/tag-icons.js

Lines changed: 44 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,44 @@
1-
import escape from "discourse/lib/escape";
2-
import getURL from "discourse/lib/get-url";
31
import TagHashtagType from "discourse/lib/hashtag-types/tag";
4-
import { helperContext } from "discourse/lib/helpers";
52
import { iconHTML } from "discourse/lib/icon-library";
63
import { withPluginApi } from "discourse/lib/plugin-api";
7-
import { escapeExpression } from "discourse/lib/utilities";
4+
import { defaultRenderTag } from "discourse/lib/render-tag";
5+
import { contrastColor } from "../lib/colors";
86

97
function iconTagRenderer(tag, params) {
10-
let { siteSettings, currentUser } = helperContext();
11-
let tagIconList = settings.tag_icon_list.split("|");
12-
13-
params = params || {};
14-
// TODO(https://github.com/discourse/discourse/pull/36678): The string check can be
15-
// removed using .discourse-compatibility once the PR is merged.
16-
const tagStr = typeof tag === "string" ? tag : tag.name;
17-
const visibleName = escapeExpression(tagStr);
18-
tag = visibleName.toLowerCase();
19-
20-
const classes = ["discourse-tag"];
21-
const htmlTagName = params.tagName || "a";
22-
let path;
23-
if (htmlTagName === "a" && !params.noHref) {
24-
if ((params.isPrivateMessage || params.pmOnly) && currentUser) {
25-
const username = params.tagsForUser
26-
? params.tagsForUser
27-
: currentUser.username;
28-
path = `/u/${username}/messages/tags/${tag}`;
29-
} else {
30-
path = `/tag/${tag}`;
31-
}
32-
}
33-
const href = path ? ` href='${getURL(path)}' ` : "";
34-
if (siteSettings.tag_style || params.style) {
35-
classes.push(params.style || siteSettings.tag_style);
36-
}
37-
38-
if (params.extraClass) {
39-
classes.push(params.extraClass);
40-
}
41-
42-
if (params.size) {
43-
classes.push(params.size);
44-
}
8+
// Get the rendered default tag markup.
9+
const renderedTag = defaultRenderTag(tag, params);
4510

46-
// remove all html tags from hover text
47-
const hoverDescription =
48-
params.description && params.description.replace(/<.+?>/g, "");
11+
// Get the tag configuration list from the settings.
12+
const tagIconList = settings.tag_icon_list.split("|");
4913

50-
/// Add custom tag icon from theme settings
51-
let tagIconItem = tagIconList.find((str) => {
52-
return str.indexOf(",") > -1
53-
? tag === str.substr(0, str.indexOf(",")).toLowerCase()
54-
: "";
55-
});
14+
// Returns the tag configuration if found.
15+
const tagIconItem = tagIconList.find(
16+
(line) =>
17+
line.indexOf(",") > -1 &&
18+
tag.toLowerCase() === line.substr(0, line.indexOf(",")).toLowerCase()
19+
);
5620

57-
let tagIconHTML = "";
21+
// Update the tag markup with an SVG icon, and inline-styles for the colors.
5822
if (tagIconItem) {
59-
let tagIcon = tagIconItem.split(",");
60-
61-
let itemColor = tagIcon[2] ? `style="color: ${tagIcon[2]}"` : "";
62-
tagIconHTML = `<span ${itemColor} class="tag-icon">${iconHTML(
63-
tagIcon[1]
64-
)}</span>`;
65-
}
66-
/// End custom tag icon
67-
68-
let val =
69-
"<" +
70-
htmlTagName +
71-
href +
72-
" data-tag-name=" +
73-
tag +
74-
(params.description ? ' title="' + escape(hoverDescription) + '" ' : "") +
75-
" class='" +
76-
classes.join(" ") +
77-
"'>" +
78-
tagIconHTML + // inject tag Icon in html
79-
(params.displayName ? escape(params.displayName) : visibleName) +
80-
"</" +
81-
htmlTagName +
82-
">";
83-
84-
if (params.count) {
85-
val += " <span class='discourse-tag-count'>x" + params.count + "</span>";
23+
const [, iconName, color] = tagIconItem.split(",");
24+
25+
const parser = new DOMParser();
26+
const tagElement = parser.parseFromString(renderedTag, "text/html").body
27+
.firstChild;
28+
const iconElement = parser.parseFromString(
29+
`<span class="tag-icon">${iconHTML(iconName)}</span>`,
30+
"text/html"
31+
).body.firstChild;
32+
33+
tagElement.prepend(iconElement);
34+
tagElement.classList.add("discourse-tag--tag-icons-style");
35+
tagElement.style.setProperty("--color1", color ?? "");
36+
tagElement.style.setProperty("--color2", color ? contrastColor(color) : "");
37+
38+
return tagElement.outerHTML;
8639
}
8740

88-
return val;
41+
return renderedTag;
8942
}
9043

9144
class TagHashtagTypeWithIcon extends TagHashtagType {
@@ -97,27 +50,36 @@ class TagHashtagTypeWithIcon extends TagHashtagType {
9750
generateIconHTML(hashtag) {
9851
const opt = hashtag.slug && this.dict[hashtag.slug];
9952
if (opt) {
53+
const svgIcon = iconHTML(opt.icon, {
54+
class: `hashtag-color--${this.type}-${hashtag.id}`,
55+
});
10056
const newIcon = document.createElement("span");
10157
newIcon.classList.add("hashtag-tag-icon");
102-
newIcon.innerHTML = iconHTML(opt.icon);
58+
newIcon.innerHTML = svgIcon;
10359
if (opt.color) {
104-
newIcon.style.color = opt.color;
60+
newIcon.style.setProperty("--color1", opt.color ?? "");
61+
newIcon.style.setProperty(
62+
"--color2",
63+
opt.color ? contrastColor(opt.color) : ""
64+
);
10565
}
10666
return newIcon.outerHTML;
107-
} else {
108-
return super.generateIconHTML(hashtag);
10967
}
68+
69+
return super.generateIconHTML(hashtag);
11070
}
11171
}
11272

11373
export default {
11474
name: "tag-icons",
11575

76+
before: "hashtag-css-generator",
77+
11678
initialize(owner) {
11779
withPluginApi((api) => {
11880
api.replaceTagRenderer(iconTagRenderer);
11981

120-
/** @type {Record<string, {icon: string, color: string?}?>} */
82+
/** @type {Record<string, { icon: string, color?: string }>} */
12183
const tagsMap = {};
12284

12385
const tagIconList = settings.tag_icon_list.split("|");
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const hexToRgb = (hex) => {
2+
if (typeof hex !== "string") {
3+
throw new TypeError("Hex color must be a string");
4+
}
5+
6+
let value = hex.trim().replace(/^#/, "");
7+
8+
if (value.length === 3) {
9+
value = value
10+
.split("")
11+
.map((char) => char + char)
12+
.join("");
13+
}
14+
15+
if (!/^[0-9a-fA-F]{6}$/.test(value)) {
16+
throw new Error(`Invalid hex color: ${hex}`);
17+
}
18+
19+
const num = parseInt(value, 16);
20+
21+
// eslint-disable-next-line no-bitwise
22+
return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
23+
};
24+
25+
const luminance = (rgb) => {
26+
const c = [rgb[0], rgb[1], rgb[2]].map((v) => {
27+
v /= 255;
28+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
29+
});
30+
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
31+
};
32+
33+
export const contrastColor = (hexColor) => {
34+
const rgb = hexToRgb(hexColor);
35+
return luminance(rgb) >= 0.45 ? "#000d" : "#fffd";
36+
};

locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ en:
22
theme_metadata:
33
settings:
44
tag_icon_list: 'Enter comma-delimited configuration for tags, in the format "tag-slug,icon,iconColor". Icon color is optional.'
5+
enable_colors_for_tag_labels: "Use colors for both tag icon and the tag label based on the configuration set above."
56
svg_icons: "List of FontAwesome 6 icons used in this theme component"

settings.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
tag_icon_list:
22
default: "tag1,circle-question,#CC0000|"
33
type: "list"
4+
enable_colors_for_tag_labels:
5+
default: false
6+
type: "bool"
47
svg_icons:
58
default: "circle-question"
69
type: "list"

spec/system/page_objects/components/tag_icon.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ module PageObjects
44
module Components
55
class TagIcon < PageObjects::Components::Base
66
def has_icon_for_tag?(tag_name:, icon:, color: nil)
7-
selector = ".discourse-tag[data-tag-name='#{tag_name}'] .tag-icon"
8-
selector += "[style*='color: #{color}']" if color
9-
selector += " .d-icon-#{icon}"
7+
selector = ".discourse-tag[data-tag-name='#{tag_name}']"
8+
selector += "[style*='--color1: #{color}; --color2: #fffd;']" if color
9+
selector += " .tag-icon .d-icon-#{icon}"
1010
page.has_css?(selector)
1111
end
1212

test/acceptance/post-body-tag-icons-test.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,7 @@ acceptance("Post body - Tag icons", function (needs) {
5757

5858
assert
5959
.dom(`.cooked .hashtag-cooked[data-id="1"] .hashtag-tag-icon`)
60-
.hasStyle(
61-
{ color: "rgb(255, 0, 0)" },
62-
"tag-1 's icon has the right color"
63-
);
60+
.hasStyle({ "--color1": "#FF0000" }, "tag-1 's icon has the right color");
6461

6562
assert
6663
.dom(

test/acceptance/tag-icons-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ acceptance("Topic with tags", function (needs) {
2525

2626
assert.equal(
2727
window.getComputedStyle(el).color,
28-
"rgb(204, 0, 0)",
28+
"rgb(100, 100, 100)",
2929
"tag icon color matches default value"
3030
);
3131
});

0 commit comments

Comments
 (0)