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 +
+ '' + markdown.utils.escapeHtml(str) + '${formatCode(text).trim()}${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"