-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6694 from ehkasper/markdown-coffee
Convert markdown to js
- Loading branch information
Showing
8 changed files
with
225 additions
and
185 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Markdown is a named function that will parse markdown syntax | ||
* @param {Object} message - The message object | ||
*/ | ||
|
||
class MarkdownClass { | ||
parse(text) { | ||
return this.parseNotEscaped(_.escapeHTML(text)); | ||
} | ||
|
||
parseNotEscaped(msg) { | ||
const schemes = RocketChat.settings.get('Markdown_SupportSchemesForLink').split(',').join('|'); | ||
|
||
// Support ![alt text](http://image url) | ||
msg = msg.replace(new RegExp(`!\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\)]+)\\)`, 'gm'), function(match, title, url) { | ||
const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; | ||
return `<a href="${ _.escapeHTML(url) }" title="${ _.escapeHTML(title) }" target="${ _.escapeHTML(target) }"><div class="inline-image" style="background-image: url(${ _.escapeHTML(url) });"></div></a>`; | ||
}); | ||
|
||
// Support [Text](http://link) | ||
msg = msg.replace(new RegExp(`\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\)]+)\\)`, 'gm'), function(match, title, url) { | ||
const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; | ||
return `<a href="${ _.escapeHTML(url) }" target="${ _.escapeHTML(target) }">${ _.escapeHTML(title) }</a>`; | ||
}); | ||
|
||
// Support <http://link|Text> | ||
msg = msg.replace(new RegExp(`(?:<|<)((?:${ schemes }):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)`, 'gm'), (match, url, title) => { | ||
const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; | ||
return `<a href="${ _.escapeHTML(url) }" target="${ _.escapeHTML(target) }">${ _.escapeHTML(title) }</a>`; | ||
}); | ||
|
||
if (RocketChat.settings.get('Markdown_Headers')) { | ||
// Support # Text for h1 | ||
msg = msg.replace(/^# (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '<h1>$1</h1>'); | ||
|
||
// Support # Text for h2 | ||
msg = msg.replace(/^## (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '<h2>$1</h2>'); | ||
|
||
// Support # Text for h3 | ||
msg = msg.replace(/^### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '<h3>$1</h3>'); | ||
|
||
// Support # Text for h4 | ||
msg = msg.replace(/^#### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '<h4>$1</h4>'); | ||
} | ||
|
||
// Support *text* to make bold | ||
msg = msg.replace(/(^|>|[ >_~`])\*{1,2}([^\*\r\n]+)\*{1,2}([<_~`]|\B|\b|$)/gm, '$1<span class="copyonly">*</span><strong>$2</strong><span class="copyonly">*</span>$3'); | ||
|
||
// Support _text_ to make italics | ||
msg = msg.replace(/(^|>|[ >*~`])\_([^\_\r\n]+)\_([<*~`]|\B|\b|$)/gm, '$1<span class="copyonly">_</span><em>$2</em><span class="copyonly">_</span>$3'); | ||
|
||
// Support ~text~ to strike through text | ||
msg = msg.replace(/(^|>|[ >_*`])\~{1,2}([^~\r\n]+)\~{1,2}([<_*`]|\B|\b|$)/gm, '$1<span class="copyonly">~</span><strike>$2</strike><span class="copyonly">~</span>$3'); | ||
|
||
// Support for block quote | ||
// >>> | ||
// Text | ||
// <<< | ||
msg = msg.replace(/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g, '<blockquote class="background-transparent-darker-before"><span class="copyonly">>>></span>$1<span class="copyonly"><<<</span></blockquote>'); | ||
|
||
// Support >Text for quote | ||
msg = msg.replace(/^>(.*)$/gm, '<blockquote class="background-transparent-darker-before"><span class="copyonly">></span>$1</blockquote>'); | ||
|
||
// Remove white-space around blockquote (prevent <br>). Because blockquote is block element. | ||
msg = msg.replace(/\s*<blockquote class="background-transparent-darker-before">/gm, '<blockquote class="background-transparent-darker-before">'); | ||
msg = msg.replace(/<\/blockquote>\s*/gm, '</blockquote>'); | ||
|
||
// Remove new-line between blockquotes. | ||
msg = msg.replace(/<\/blockquote>\n<blockquote/gm, '</blockquote><blockquote'); | ||
|
||
if (typeof window !== 'undefined' && window !== null ? window.rocketDebug : undefined) { console.log('Markdown', msg); } | ||
|
||
return msg; | ||
} | ||
} | ||
|
||
const Markdown = new MarkdownClass; | ||
RocketChat.Markdown = Markdown; | ||
|
||
// renderMessage already did html escape | ||
const MarkdownMessage = (message) => { | ||
if (_.trim(message != null ? message.html : undefined)) { | ||
message.html = Markdown.parseNotEscaped(message.html); | ||
} | ||
|
||
return message; | ||
}; | ||
|
||
RocketChat.callbacks.add('renderMessage', MarkdownMessage, RocketChat.callbacks.priority.HIGH, 'markdown'); | ||
|
||
if (Meteor.isClient) { | ||
Blaze.registerHelper('RocketChatMarkdown', text => Markdown.parse(text)); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* MarkdownCode is a named function that will parse `inline code` and ```codeblock``` syntaxes | ||
* @param {Object} message - The message object | ||
*/ | ||
import hljs from 'highlight.js'; | ||
|
||
class MarkdownCode { | ||
constructor(message) { | ||
|
||
if (s.trim(message.html)) { | ||
if (message.tokens == null) { | ||
message.tokens = []; | ||
} | ||
|
||
MarkdownCode.handle_codeblocks(message); | ||
MarkdownCode.handle_inlinecode(message); | ||
|
||
if (window && window.rocketDebug) { | ||
console.log('Markdown', message); | ||
} | ||
} | ||
|
||
return message; | ||
} | ||
|
||
static handle_inlinecode(message) { | ||
// Support `text` | ||
return message.html = message.html.replace(/(^|>|[ >_*~])\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2, p3) => { | ||
const token = `=!=${ Random.id() }=!=`; | ||
|
||
message.tokens.push({ | ||
token, | ||
text: `${ p1 }<span class=\"copyonly\">\`</span><span><code class=\"code-colors inline\">${ p2 }</code></span><span class=\"copyonly\">\`</span>${ p3 }`, | ||
noHtml: match | ||
}); | ||
|
||
return token; | ||
}); | ||
} | ||
|
||
static handle_codeblocks(message) { | ||
// Count occurencies of ``` | ||
const count = (message.html.match(/```/g) || []).length; | ||
|
||
if (count) { | ||
|
||
// Check if we need to add a final ``` | ||
if ((count % 2) > 0) { | ||
message.html = `${ message.html }\n\`\`\``; | ||
message.msg = `${ message.msg }\n\`\`\``; | ||
} | ||
|
||
// Separate text in code blocks and non code blocks | ||
const msgParts = message.html.split(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(.*\n?)$/gm); | ||
|
||
for (let index = 0; index < msgParts.length; index++) { | ||
// Verify if this part is code | ||
const part = msgParts[index]; | ||
const codeMatch = part.match(/^```(.*[\n\ ]?)([\s\S]*?)```+?$/); | ||
|
||
if (codeMatch != null) { | ||
// Process highlight if this part is code | ||
let code; | ||
let lang; | ||
let result; | ||
const singleLine = codeMatch[0].indexOf('\n') === -1; | ||
|
||
if (singleLine) { | ||
lang = ''; | ||
code = _.unescapeHTML(codeMatch[1] + codeMatch[2]); | ||
} else { | ||
lang = codeMatch[1]; | ||
code = _.unescapeHTML(codeMatch[2]); | ||
} | ||
|
||
if (s.trim(lang) === '') { | ||
lang = ''; | ||
} | ||
|
||
if (!Array.from(hljs.listLanguages()).includes(s.trim(lang))) { | ||
result = hljs.highlightAuto((lang + code)); | ||
} else { | ||
result = hljs.highlight(s.trim(lang), code); | ||
} | ||
|
||
const token = `=!=${ Random.id() }=!=`; | ||
|
||
message.tokens.push({ | ||
highlight: true, | ||
token, | ||
text: `<pre><code class='code-colors hljs ${ result.language }'><span class='copyonly'>\`\`\`<br></span>${ result.value }<span class='copyonly'><br>\`\`\`</span></code></pre>`, | ||
noHtml: `\`\`\`\n${ s.stripTags(result.value) }\n\`\`\`` | ||
}); | ||
|
||
msgParts[index] = token; | ||
} else { | ||
msgParts[index] = part; | ||
} | ||
} | ||
|
||
// Re-mount message | ||
return message.html = msgParts.join(''); | ||
} | ||
} | ||
} | ||
|
||
RocketChat.MarkdownCode = MarkdownCode; | ||
|
||
const MarkdownCodeCB = (message) => new MarkdownCode(message); | ||
|
||
// MarkdownCode gets higher priority over Markdown so it's possible place a callback in between (katex for exmaple) | ||
RocketChat.callbacks.add('renderMessage', MarkdownCodeCB, RocketChat.callbacks.priority.HIGH - 2, 'markdowncode'); |
Oops, something went wrong.