From 5db33c4e7c1a5a4426ed652ff313490bcbedf4de Mon Sep 17 00:00:00 2001 From: brecert <11599528+Brecert@users.noreply.github.com> Date: Fri, 26 Jul 2019 00:18:37 -0400 Subject: [PATCH] replace the main futoji message formatter with markdown-it plugins --- package.json | 2 + src/components/app/MessageTemplate.vue | 7 + .../markdown-it-plugins/replaceCustomEmoji.js | 92 ++++++++++++ src/utils/messageFormatter.js | 132 ++++-------------- yarn.lock | 57 +++++++- 5 files changed, 178 insertions(+), 112 deletions(-) create mode 100644 src/utils/markdown-it-plugins/replaceCustomEmoji.js diff --git a/package.json b/package.json index 81cd83a..1c2cadc 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "futoji": "^0.5.0", "highlight.js": "^9.15.8", "jquery": "^3.4.0", + "markdown-it": "^9.0.1", + "markdown-it-chat-formatter": "^0.1.1", "match-sorter": "^2.3.0", "particles.js": "^2.0.0", "socket.io": "^2.2.0", diff --git a/src/components/app/MessageTemplate.vue b/src/components/app/MessageTemplate.vue index b6b30b0..2fa8fb0 100644 --- a/src/components/app/MessageTemplate.vue +++ b/src/components/app/MessageTemplate.vue @@ -452,11 +452,18 @@ export default { .msg-link { color: rgb(86, 159, 253); } + +pre { + padding: 0; + margin: 0; +} + .codeblock { background-color: rgba(0, 0, 0, 0.397); padding: 5px; border-radius: 5px; } + img.emoji { height: 1.7em; width: auto; diff --git a/src/utils/markdown-it-plugins/replaceCustomEmoji.js b/src/utils/markdown-it-plugins/replaceCustomEmoji.js new file mode 100644 index 0000000..b810514 --- /dev/null +++ b/src/utils/markdown-it-plugins/replaceCustomEmoji.js @@ -0,0 +1,92 @@ +import config from "@/config.js"; + +/* 58: ':' */ +/* 60: '<' */ +/* 62: '>' */ + +function render_custom_emoji(tokens, idx) { + return ':3' +} + +function parseUntil(state, fromPos, until) { + let max = state.posMax + let found = false + + let oldPos = state.pos + // let start = nameStart + 1 + state.pos = fromPos + let end = -1 + + while(state.pos++ < max) { + let marker = state.src.charCodeAt(state.pos) + if(marker === until) { + found = true; + break; + } + } + + if(found) { + end = state.pos + } + + state.pos = oldPos + + return end +} + +function parseEmojiName(state, nameStart) { + return parseUntil(state, nameStart, 58) +} + +function replace_custom_emoji(state, silent) { + let pos = state.pos + let max = state.posMax + + // if begins with < + if (state.src.charCodeAt(pos) !== 60) { return false; } + pos += 1 + + // if the next character is not ':' then it's not a custom emoji + if(state.src.charCodeAt(pos) !== 58) { return false; } + + // parse the emoji name + let nameStart = pos + 1 + let nameEnd = parseEmojiName(state, nameStart) + + // parser failed to find another ':', so it's not a valid emoji + if(nameEnd < 0 || nameEnd - nameStart <= 1) { return false; } + + let emojiName = state.src.slice(nameStart, nameEnd) + + pos = nameEnd + 1 + + // parse until '>' + let idStart = pos + let idEnd = parseUntil(state, idStart, 62) + + if(idEnd < 0 || idEnd - idStart <= 1) { return false; } + + let emojiID = state.src.slice(idStart, idEnd) + + if(!silent) { + state.pos = idStart + state.posMax = idEnd + + let token = state.push('custom_emoji_open', 'img', 1); + token.attrs = [[ 'src', `${config.domain}/files/${emojiID}` ]] + + // state.md.inline.tokenize(state) + } + + state.pos = idEnd + 1 + state.posMax = max + return true +} +export default function custom_emoji_plugin(md, opts) { + md.renderer.rules.custom_emoji_open = (tokens, idx) => { + let token = tokens[idx] + return `<${md.utils.escapeHtml(token.tag)} class="emoji" src=${md.utils.escapeHtml(token.attrs.find(([name]) => name === 'src')[1])} />` + } + + md.inline.ruler.push('custom_emoji', replace_custom_emoji) +} \ No newline at end of file diff --git a/src/utils/messageFormatter.js b/src/utils/messageFormatter.js index 1f3d7ef..cd42c9b 100644 --- a/src/utils/messageFormatter.js +++ b/src/utils/messageFormatter.js @@ -3,7 +3,30 @@ import twemoji from 'twemoji' import emojiParser from '@/utils/emojiParser'; import config from "@/config.js"; -const futoji = new Formatter(); +import customEmoji from './markdown-it-plugins/replaceCustomEmoji' + +import hljs from 'highlight.js' + +import MarkdownIt from 'markdown-it' +import chatPlugin from 'markdown-it-chat-formatter/dist-src/plugin' + +const markdown = new MarkdownIt({ + highlight: function (str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return '
' + + hljs.highlight(lang, str, true).value + + '
'; + } catch (err) { + console.error(err) + } + } + + return '
' + markdown.utils.escapeHtml(str) + '
'; + } +}).use(chatPlugin) + .use(customEmoji); + const emojiFormatter = new Formatter(); emojiFormatter.addTransformer({ @@ -23,116 +46,11 @@ function owo (text) { } -futoji.addTransformer({ - name: 'custom emoji', - symbol: ':', - padding: false, - recursive: false, - validate: text => /.+?&(.+?)/.test(text), - transformer: text => { - const formattedInner = emojiFormatter.format(text); - return owo(formattedInner); - - } -}) - - - -futoji.addTransformer({ - name: 'url', - open: 'http', - close: ' ', - recursive: false, - validate: text => /(https?:\/\/[^\s]+)/g.test('http' + text), - transformer: text => 'http' + text + ' ' -}) - - -futoji.addTransformer({ - name: 'bold-and-italic', - symbol: '***', - transformer: text => `${text}` -}) - -futoji.addTransformer({ - name: 'bold', - symbol: '**', - transformer: text => `${text}` -}) - -futoji.addTransformer({ - name: 'italic', - symbol: '*', - transformer: text => `${text}` -}) - -futoji.addTransformer({ - name: 'underline', - symbol: '__', - transformer: text => `${text}` -}) -futoji.addTransformer({ - name: 'italic', - symbol: '_', - transformer: text => `${text}` -}) -futoji.addTransformer({ - name: 'srike', - symbol: '~~', - transformer: text => `${text.trim()}` -}) - -futoji.addTransformer({ - name: 'code-block', - symbol: '```', - recursive: false, - transformer: text => `
${formatCode(text).trim()}
`, -}) - -futoji.addTransformer({ - name: 'code', - symbol: '`', - recursive: false, - transformer: text => `${text}`, -}) - export default (message) => { - message = futoji.format(escapeHtml(message + ' ')).trim(); + message = markdown.render(message).trim(); message = emojiParser.replaceEmojis(message); return message; -} - - - - -/** - * format code to add syntax highlighting - */ -function formatCode(text) { - // matches if word until newline - // if spaces then it won't match - let nameRegex = new RegExp('^(\\w+)\\s') - - if (nameRegex.test(text)) { - let language = nameRegex.exec(text)[1] - let newText = text.replace(nameRegex, '') - - // TODO: format newText with language - - return newText - } - - return text -} - -function escapeHtml(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1c9001c..ca59661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1552,10 +1552,10 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -axios@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" - integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== +axios@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" + integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== dependencies: follow-redirects "1.5.10" is-buffer "^2.0.2" @@ -3171,7 +3171,7 @@ enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" -entities@^1.1.1: +entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -4990,6 +4990,13 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +linkify-it@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" + integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + dependencies: + uc.micro "^1.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5180,6 +5187,36 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it-chat-formatter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/markdown-it-chat-formatter/-/markdown-it-chat-formatter-0.1.1.tgz#76a24a6599925ae22cdbf179301c6152867b1848" + integrity sha512-RIGKubGe3wjvizsTqeeU+RIVS7b1bqAwZE0Xv7H15uxZxf3pdLQjpX+ATcInPkf1L2SY4Vl2do8nSliLBCutag== + dependencies: + markdown-it "^9.0.1" + markdown-it-underline "^1.0.1" + marked "^0.7.0" + +markdown-it-underline@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/markdown-it-underline/-/markdown-it-underline-1.0.1.tgz#43a54a541d95d739b43157701f1da306d7c300b2" + integrity sha1-Q6VKVB2V1zm0MVdwHx2jBtfDALI= + +markdown-it@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.0.1.tgz#aafe363c43718720b6575fd10625cde6e4ff2d47" + integrity sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw== + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +marked@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" + integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== + match-sorter@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-2.3.0.tgz#99eaf386689f75bf976f6bbf7f49afb9a7ffecc8" @@ -5201,6 +5238,11 @@ mdn-data@~1.1.0: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8151,6 +8193,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + uglify-js@3.4.x: version "3.4.10" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"