discourse/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
Krzysztof Kotlarek aa3a9b6fea
FEATURE: Differentiate notification type for directly vs indirectly watched topic (#19433)
When user is watching category or tag (watching or watching first post) notifications are moved to other tab.

To achieve that and distinguish between post create to directly watched topics and indirectly watched topics, new notification type called `watching_category_or_tag` was introduced.
2022-12-14 10:22:26 +11:00

221 lines
5.9 KiB
JavaScript

import I18n from "I18n";
import attributeHook from "discourse-common/lib/attribute-hook";
import { h } from "virtual-dom";
import { isDevelopment } from "discourse-common/config/environment";
import escape from "discourse-common/lib/escape";
import deprecated from "discourse-common/lib/deprecated";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let _renderers = [];
let warnMissingIcons = true;
let _iconList;
const REPLACEMENTS = {
"d-tracking": "bell",
"d-muted": "discourse-bell-slash",
"d-regular": "far-bell",
"d-watching": "discourse-bell-exclamation",
"d-watching-first": "discourse-bell-one",
"d-drop-expanded": "caret-down",
"d-drop-collapsed": "caret-right",
"d-unliked": "far-heart",
"d-liked": "heart",
"d-post-share": "link",
"d-topic-share": "link",
"notification.mentioned": "at",
"notification.group_mentioned": "users",
"notification.quoted": "quote-right",
"notification.replied": "reply",
"notification.posted": "discourse-bell-exclamation",
"notification.watching_category_or_tag": "discourse-bell-exclamation",
"notification.edited": "pencil-alt",
"notification.bookmark_reminder": "discourse-bookmark-clock",
"notification.liked": "heart",
"notification.liked_2": "heart",
"notification.liked_many": "heart",
"notification.liked_consolidated": "heart",
"notification.private_message": "envelope",
"notification.invited_to_private_message": "envelope",
"notification.invited_to_topic": "hand-point-right",
"notification.invitee_accepted": "user",
"notification.moved_post": "sign-out-alt",
"notification.linked": "link",
"notification.granted_badge": "certificate",
"notification.topic_reminder": "far-clock",
"notification.watching_first_post": "discourse-bell-one",
"notification.group_message_summary": "users",
"notification.post_approved": "check",
"notification.membership_request_accepted": "user-plus",
"notification.membership_request_consolidated": "users",
"notification.reaction": "bell",
"notification.votes_released": "plus",
"notification.chat_quoted": "quote-right",
};
export function replaceIcon(source, destination) {
REPLACEMENTS[source] = destination;
}
export function disableMissingIconWarning() {
warnMissingIcons = false;
}
export function enableMissingIconWarning() {
warnMissingIcons = false;
}
export function renderIcon(renderType, id, params) {
params ||= {};
for (const renderer of _renderers) {
const rendererForType = renderer[renderType];
if (!rendererForType) {
continue;
}
const icon = { id, replacementId: REPLACEMENTS[id] };
const result = rendererForType(icon, params);
if (result) {
return result;
}
}
}
export function iconHTML(id, params) {
return renderIcon("string", id, params);
}
export function iconNode(id, params) {
return renderIcon("node", id, params);
}
export function convertIconClass(icon) {
return icon
.replace("far fa-", "far-")
.replace("fab fa-", "fab-")
.replace("fas fa-", "")
.replace("fa-", "")
.trim();
}
export function registerIconRenderer(renderer) {
_renderers.unshift(renderer);
}
function iconClasses(icon, params) {
// "notification." is invalid syntax for classes, use replacement instead
const dClass =
icon.replacementId && icon.id.includes("notification.")
? icon.replacementId
: icon.id;
let classNames = `fa d-icon d-icon-${dClass} svg-icon`;
if (params && params["class"]) {
classNames += " " + params["class"];
}
return classNames;
}
export function setIconList(iconList) {
_iconList = iconList;
}
export function isExistingIconId(id) {
return _iconList?.includes(id);
}
function warnIfMissing(id) {
if (warnMissingIcons && isDevelopment() && !isExistingIconId(id)) {
console.warn(`The icon "${id}" is missing from the SVG subset.`); // eslint-disable-line no-console
}
}
function handleIconId(icon) {
let id = icon.replacementId || icon.id || "";
// TODO: clean up "thumbtack unpinned" at source instead of here
id = id.replace(" unpinned", "");
warnIfMissing(id);
return id;
}
// default resolver is font awesome
registerIconRenderer({
name: "font-awesome",
string(icon, params) {
const id = escape(handleIconId(icon));
let html = `<svg class='${escape(iconClasses(icon, params))} svg-string'`;
if (params.label) {
html += " aria-hidden='true'";
} else if (params["aria-label"]) {
html += ` aria-hidden='false' aria-label='${escape(
params["aria-label"]
)}'`;
}
html += ` xmlns="${SVG_NAMESPACE}"><use href="#${id}" /></svg>`;
if (params.label) {
html += `<span class='sr-only'>${escape(params.label)}</span>`;
}
if (params.title) {
html = `<span class="svg-icon-title" title='${escape(
I18n.t(params.title)
)}'>${html}</span>`;
}
if (params.translatedtitle) {
deprecated(`use 'translatedTitle' option instead of 'translatedtitle'`, {
since: "2.9.0.beta6",
dropFrom: "2.10.0.beta1",
id: "discourse.icon-renderer-translatedtitle",
});
params.translatedTitle = params.translatedtitle;
}
if (params.translatedTitle) {
html = `<span class="svg-icon-title" title='${escape(
params.translatedTitle
)}'>${html}</span>`;
}
return html;
},
node(icon, params) {
const id = handleIconId(icon);
const classes = iconClasses(icon, params) + " svg-node";
const svg = h(
"svg",
{
attributes: { class: classes, "aria-hidden": true },
namespace: SVG_NAMESPACE,
},
[
h("use", {
href: attributeHook("http://www.w3.org/1999/xlink", `#${escape(id)}`),
namespace: SVG_NAMESPACE,
}),
]
);
if (params.title) {
return h(
"span",
{
title: params.title,
attributes: { class: "svg-icon-title" },
},
[svg]
);
} else {
return svg;
}
},
});