From 4a04a77f6483f4ce09d41ce4be843ea1dff985b2 Mon Sep 17 00:00:00 2001 From: crimx Date: Sat, 3 Oct 2020 11:10:42 +0800 Subject: [PATCH] refactor: simplify implementation --- README.md | 92 ++++++++++++-------- index.js | 245 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 203 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 171352f..097603b 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ A Hexo plugin that adds emoji support, using [Github Emojis API][ghemojis]. Check out the [Emoji Cheat Sheet](http://www.webpagefx.com/tools/emoji-cheat-sheet/) for all the emojis it supports. -V2 is not compatible with [V1](https://github.com/crimx/hexo-filter-github-emojis/tree/e52ceb8b18a7b06916b6cb0a887b218d49a7ab92). V1 replaces codepoints with `` tags. While V2 makes the font transparent and displays emojis with `background-image`. - ## Installation ``` bash @@ -30,43 +28,71 @@ githubEmojis: customEmojis: ``` -- **className** - Image class name. For example :sparkles: `:sparkles:` the filter will generate something like this: +- **enable** `boolean=true` - Enable `::` emoji parsing. If off the [tag](#tag) and [helper](#helper) still work. + +- **className** `string="github-emoji"` - Emoji class name. + For example :sparkles: `:sparkles:` the filter will generate something like this: ```html - + ``` -- **inject** - If true, the filter will inject proper inline styles and a script to fallback when image loading fails. If you can modify script files and style files, you may turn this off and add them yourself. - - ```html - - ``` +- **inject** `boolean=true` - Inject emoji styles and fallback script. + If `true`, the filter will inject a ` ``` -- **styles** - inline styles. For example: +- **styles** `object={}` - inline styles. For example: ```yaml githubEmojis: @@ -78,10 +104,10 @@ githubEmojis: outputs: ```html - + ``` -- **customEmojis** - You can specify your own list. An object or JSON string is valid. The filter will first check the `customEmojis` then fallback to the [Github Emojis][ghemojis] list. +- **customEmojis** `object={}` - You can specify your own list. An object or JSON string is valid. The filter will first check the `customEmojis` then fallback to the [Github Emojis][ghemojis] list. For example: @@ -129,7 +155,7 @@ no-emoji: true You can also render a GitHub emoji from a template using the `github_emoji` helper: ```html -

<% github_emoji('octocat') %>

+

<%- github_emoji('octocat') %>

``` [ghemojis]: https://api.github.com/emojis diff --git a/index.js b/index.js index cbf098d..39a35f3 100644 --- a/index.js +++ b/index.js @@ -1,144 +1,187 @@ -'use strict' +"use strict"; /* global hexo */ -const _ = require('lodash') -const path = require('path') -const fs = require('fs') -const { JSDOM } = require('jsdom') +const _ = require("lodash"); +const { JSDOM } = require("jsdom"); -var options = _.assign({ - enable: true, - inject: true, - version: 'latest', - className: 'github-emoji', -}, hexo.config.githubEmojis) +const options = Object.assign( + { + enable: true, + inject: true, + version: "latest", + className: "github-emoji", + }, + hexo.theme.githubEmojis || {}, + hexo.config.githubEmojis || {} +); -if (options.enable !== false) { - const emojis = _.assign( - {}, - require('./emojis.json'), - loadCustomEmojis(options.customEmojis || options.localEmojis), - ) +const emojis = Object.assign( + {}, + require("./emojis.json"), + loadCustomEmojis(options.customEmojis || options.localEmojis) +); - fs.writeFile( - path.join(__dirname, 'emojis.json'), - JSON.stringify(emojis, null, ' '), - function (err) { err && console.warn(err) }, - ) +hexo.extend.helper.register("github_emoji", (name) => + renderEmoji(emojis, name) +); - hexo.extend.filter.register('after_post_render', data => { - if (!options.inject && data['no-emoji']) { return data } +hexo.extend.tag.register("github_emoji", (args) => + renderEmoji(emojis, args[0]) +); - const $content = new JSDOM(data.content) - const $excerpt = new JSDOM(data.excerpt) +hexo.extend.filter.register("after_post_render", (data) => { + if (data["no-emoji"]) { + if (options.inject !== false) { + data.content = `` + data.content; + } + } else { + const $content = new JSDOM(data.content); + const $excerpt = new JSDOM(data.excerpt); - if (options.inject) { - const $script = $content.window.document.createElement('script') - $script.innerHTML = ` - document.querySelectorAll('.${options.className}') - .forEach(el => { - if (!el.dataset.src) { return; } - const img = document.createElement('img'); - img.style = 'display:none !important;'; - img.src = el.dataset.src; - img.addEventListener('error', () => { - img.remove(); - el.style.color = 'inherit'; - el.style.backgroundImage = 'none'; - el.style.background = 'none'; - }); - img.addEventListener('load', () => { - img.remove(); - }); - document.body.appendChild(img); - }); - ` - $content.window.document.body.appendChild($script) + replaceColons($content.window.document.body, emojis); + replaceColons($excerpt.window.document.body, emojis); + + if (options.inject !== false) { + const style = $content.window.document.createElement("style"); + style.innerHTML = getEmojiStyles(); + $content.window.document.body.insertBefore( + style, + $content.window.document.body.firstElementChild + ); } - if (!data['no-emoji']) { - replaceColons($content.window.document.body, emojis) - replaceColons($excerpt.window.document.body, emojis) - } + data.content = $content.window.document.body.innerHTML; + data.excerpt = $excerpt.window.document.body.innerHTML; + } - data.content = $content.window.document.body.innerHTML - data.excerpt = $excerpt.window.document.body.innerHTML - return data - }) + return data; +}); - hexo.extend.helper.register('github_emoji', name => renderEmoji(emojis, name)) - hexo.extend.tag.register('github_emoji', args => renderEmoji(emojis, args[0])) -} - -function replaceColons (node, emojis) { - if (!node || !node.childNodes) { return } +function replaceColons(node, emojis) { + if (!node || !node.childNodes) { + return; + } for (let i = node.childNodes.length - 1; i >= 0; i--) { - const child = node.childNodes[i] - if (child.tagName === 'PRE' || child.tagName === 'CODE') { return } + const child = node.childNodes[i]; + if (child.tagName === "PRE" || child.tagName === "CODE") { + return; + } if (child.nodeType === 3) { - const content = child.data.replace( - /:(\w+):/ig, - (match, p1) => emojis[p1] ? renderEmoji(emojis, p1) : match, - ) + const content = child.data.replace(/:(\w+):/gi, (match, p1) => + emojis[p1] ? renderEmoji(emojis, p1) : match + ); if (content !== child.data) { - child.replaceWith(JSDOM.fragment(content)) + child.replaceWith(JSDOM.fragment(content)); } } else { - replaceColons(child, emojis) + replaceColons(child, emojis); } } } -function loadCustomEmojis (customEmojis) { +function loadCustomEmojis(customEmojis) { // JSON string if (_.isString(customEmojis)) { try { - customEmojis = JSON.parse(customEmojis) - Object.keys(customEmojis).forEach(name => { + customEmojis = JSON.parse(customEmojis); + Object.keys(customEmojis).forEach((name) => { if (_.isString(customEmojis[name])) { customEmojis[name] = { src: customEmojis[name], - } + }; } - }) + }); } catch (err) { - customEmojis = {} - console.warn('hexo-filter-github-emojis: Custom emojis not valid. Skipped.') + customEmojis = {}; + console.warn( + "hexo-filter-github-emojis: Custom emojis not valid. Skipped." + ); } } if (!_.isObject(customEmojis)) { - customEmojis = {} + customEmojis = {}; } - Object.values(customEmojis).forEach(emoji => { - if (emoji.codepoints && !_.isArray(emoji.codepoints)) { - emoji.codepoints = emoji.codepoints.split(' ') - } - }) + return Object.keys(customEmojis).reduce((emojis, name) => { + const emoji = customEmojis[name]; + emojis[name] = Object.assign( + {}, + emoji, + emoji.codepoints && !_.isArray(emoji.codepoints) + ? { codepoints: emoji.codepoints.split(" ") } + : {} + ); + return emojis; + }, {}); } -function renderEmoji (emojis, name) { - if (!emojis[name]) { return name } +function renderEmoji(emojis, name) { + if (!emojis[name]) return name; const styles = _.isObject(options.styles) - ? Object.keys(options.styles) - .filter(k => _.isString(options.styles[k])) - .map(k => k + ':' + options.styles[k]) - : [] - - if (options.inject) { - styles.push( - 'color: transparent', - `background:no-repeat url(${emojis[name].src}) center/contain`, - ) - } else { - styles.push(`background-image:url(${emojis[name].src})`) - } + ? ` style="${Object.keys(options.styles) + .map((k) => k + ":" + options.styles[k]) + .join(";")}"` + : ""; const codepoints = emojis[name].codepoints - ? emojis[name].codepoints.map(c => `&#x${c};`).join('') - : ' ' + ? emojis[name].codepoints.map((c) => `&#x${c};`).join("") + : " "; - return `${codepoints}` + return ( + `` + ); +} + +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; + }`; + + return rules.replace(/^ +/gm, ' ').replace(/\n/g, '') }