hexo-filter-github-emojis/index.js

177 lines
4.3 KiB
JavaScript
Raw Normal View History

2020-10-03 11:10:42 +08:00
"use strict";
2016-12-22 19:11:28 +08:00
/* global hexo */
2020-10-03 11:10:42 +08:00
const { JSDOM } = require("jsdom");
const options = Object.assign(
{
enable: true,
inject: true,
version: "latest",
className: "github-emoji",
},
hexo.theme.githubEmojis || {},
hexo.config.githubEmojis || {}
);
const emojis = Object.assign(
{},
require("./emojis.json"),
loadCustomEmojis(options.customEmojis || options.localEmojis)
);
2020-10-03 13:28:34 +08:00
hexo.extend.helper.register("github_emoji", (name) => renderEmoji(name));
hexo.extend.tag.register("github_emoji", (args) => renderEmoji(args[0]));
2020-10-03 11:10:42 +08:00
2020-10-03 13:28:34 +08:00
if (options.inject !== false) {
hexo.extend.filter.register("after_render:html", (str) =>
str.replace("</head>", `\n<style>${getEmojiStyles()}</style>\n</head>`)
);
}
2020-10-03 11:10:42 +08:00
2020-10-03 13:28:34 +08:00
if (options.enable) {
hexo.extend.filter.register("after_post_render", (data) => {
if (!data["no-emoji"]) {
const $content = new JSDOM(data.content);
const $excerpt = new JSDOM(data.excerpt);
2018-10-04 19:07:24 +08:00
2020-10-03 13:28:34 +08:00
replaceColons($content.window.document.body);
replaceColons($excerpt.window.document.body);
2018-10-04 19:07:24 +08:00
2020-10-03 13:28:34 +08:00
data.content = $content.window.document.body.innerHTML;
data.excerpt = $excerpt.window.document.body.innerHTML;
}
2020-10-03 11:34:27 +08:00
2020-10-03 13:28:34 +08:00
return data;
});
}
2018-10-04 19:07:24 +08:00
2020-10-03 13:28:34 +08:00
function replaceColons(node) {
2020-10-03 11:10:42 +08:00
if (!node || !node.childNodes) {
return;
}
2019-08-15 01:42:50 +08:00
for (let i = node.childNodes.length - 1; i >= 0; i--) {
2020-10-03 11:10:42 +08:00
const child = node.childNodes[i];
if (child.tagName === "PRE" || child.tagName === "CODE") {
2020-10-03 11:41:24 +08:00
continue;
2020-10-03 11:10:42 +08:00
}
2019-08-15 01:42:50 +08:00
if (child.nodeType === 3) {
2020-10-03 11:10:42 +08:00
const content = child.data.replace(/:(\w+):/gi, (match, p1) =>
2020-10-03 13:28:34 +08:00
emojis[p1] ? renderEmoji(p1) : match
2020-10-03 11:10:42 +08:00
);
2018-10-04 19:07:24 +08:00
if (content !== child.data) {
2020-10-03 11:10:42 +08:00
child.replaceWith(JSDOM.fragment(content));
2018-10-04 19:07:24 +08:00
}
2019-08-15 01:42:50 +08:00
} else {
2020-10-03 13:28:34 +08:00
replaceColons(child);
2018-10-04 19:07:24 +08:00
}
2019-08-15 01:42:50 +08:00
}
2018-10-04 19:07:24 +08:00
}
2020-10-03 11:10:42 +08:00
function loadCustomEmojis(customEmojis) {
2016-12-23 18:46:01 +08:00
// JSON string
2022-04-26 01:01:10 +08:00
if (typeof customEmojis === "string") {
2016-12-22 19:11:28 +08:00
try {
2020-10-03 11:10:42 +08:00
customEmojis = JSON.parse(customEmojis);
Object.keys(customEmojis).forEach((name) => {
2022-04-26 01:01:10 +08:00
if (typeof customEmojis[name] === "string") {
2018-10-04 19:07:24 +08:00
customEmojis[name] = {
src: customEmojis[name],
2020-10-03 11:10:42 +08:00
};
2016-12-23 18:46:01 +08:00
}
2020-10-03 11:10:42 +08:00
});
2018-10-04 19:07:24 +08:00
} catch (err) {
2020-10-03 11:10:42 +08:00
customEmojis = {};
console.warn(
"hexo-filter-github-emojis: Custom emojis not valid. Skipped."
);
2016-12-22 19:11:28 +08:00
}
}
2018-10-04 19:07:24 +08:00
2022-04-26 01:01:10 +08:00
if (typeof customEmojis !== "object" || customEmojis === null) {
2020-10-03 11:10:42 +08:00
customEmojis = {};
}
2016-12-22 19:11:28 +08:00
2020-10-03 11:10:42 +08:00
return Object.keys(customEmojis).reduce((emojis, name) => {
const emoji = customEmojis[name];
emojis[name] = Object.assign(
{},
emoji,
2022-04-26 01:01:10 +08:00
emoji.codepoints && !Array.isArray(emoji.codepoints)
2020-10-03 11:10:42 +08:00
? { codepoints: emoji.codepoints.split(" ") }
: {}
);
return emojis;
}, {});
2018-10-04 19:07:24 +08:00
}
2016-12-22 19:11:28 +08:00
2020-10-03 13:28:34 +08:00
function renderEmoji(name) {
2020-10-03 11:10:42 +08:00
if (!emojis[name]) return name;
2022-04-26 01:01:10 +08:00
const styles = typeof options.styles === "object" && options.styles !== null
2020-10-03 11:10:42 +08:00
? ` style="${Object.keys(options.styles)
.map((k) => k + ":" + options.styles[k])
.join(";")}"`
: "";
2017-06-26 15:52:12 +08:00
2018-10-04 19:07:24 +08:00
const codepoints = emojis[name].codepoints
2020-10-03 11:10:42 +08:00
? emojis[name].codepoints.map((c) => `&#x${c};`).join("")
: " ";
return (
2020-10-03 13:59:08 +08:00
`<span class="${options.className}"${styles}>` +
2020-10-03 11:10:42 +08:00
`<span>${codepoints}</span>` +
2020-10-03 13:59:08 +08:00
`<img src="${emojis[name].src}" aria-hidden="true" onerror="this.parent.classList.add('${options.className}-fallback')">` +
2020-10-03 11:10:42 +08:00
`</span>`
);
}
function getEmojiStyles() {
const rules = `.${options.className} {
position: relative;
display: inline-block;
width: 1.2em;
min-height: 1.2em;
overflow: hidden;
vertical-align: top;
color: transparent;
}
.${options.className} > span {
position: relative;
z-index: 10;
}
.${options.className} img,
.${options.className} .fancybox {
margin: 0 !important;
padding: 0 !important;
border: none !important;
outline: none !important;
text-decoration: none !important;
user-select: none !important;
cursor: auto !important;
}
.${options.className} img {
height: 1.2em !important;
width: 1.2em !important;
position: absolute !important;
left: 50% !important;
top: 50% !important;
transform: translate(-50%, -50%) !important;
user-select: none !important;
cursor: auto !important;
}
.${options.className}-fallback {
color: inherit;
}
.${options.className}-fallback img {
opacity: 0 !important;
}`;
2017-06-26 15:51:51 +08:00
2020-10-03 11:41:24 +08:00
return rules.replace(/^ +/gm, " ").replace(/\n/g, "");
2016-12-22 19:11:28 +08:00
}