around our code blocks instead a codehilite and disable
// class-specific highlighting.
renderer.code = (code) => fenced_code.wrap_code(code) + "\n\n";
// Prohibit empty links for some reason.
const old_link = renderer.link;
renderer.link = (href, title, text) =>
old_link.call(renderer, href, title, text.trim() ? text : href);
// Put a newline after a
in the generated HTML to match Markdown
renderer.br = function () {
return "
\n";
};
function preprocess_code_blocks(src) {
return fenced_code.process_fenced_code(src);
}
function preprocess_translate_emoticons(src) {
if (!helper_config.should_translate_emoticons()) {
return src;
}
// In this scenario, the message has to be from the user, so the only
// requirement should be that they have the setting on.
return translate_emoticons_to_names({
src,
get_emoticon_translations: helper_config.get_emoticon_translations,
});
}
// Disable headings
// We only keep the # Heading format.
disable_markdown_regex(marked.Lexer.rules.tables, "lheading");
// Disable __strong__ (keeping **strong**)
marked.InlineLexer.rules.zulip.strong = /^\*\*([\S\s]+?)\*\*(?!\*)/;
// Make sure syntax matches the backend processor
marked.InlineLexer.rules.zulip.del = /^(?!<~)~~([^~]+)~~(?!~)/;
// Disable _emphasis_ (keeping *emphasis*)
// Text inside ** must start and end with a word character
// to prevent mis-parsing things like "char **x = (char **)y"
marked.InlineLexer.rules.zulip.em = /^\*(?!\s+)((?:\*\*|[\S\s])+?)(\S)\*(?!\*)/;
// Disable autolink as (a) it is not used in our backend and (b) it interferes with @mentions
disable_markdown_regex(marked.InlineLexer.rules.zulip, "autolink");
// Tell our fenced code preprocessor how to insert arbitrary
// HTML into the output. This generated HTML is safe to not escape
fenced_code.set_stash_func((html) => marked.stashHtml(html, true));
function streamHandler(stream_name) {
return handleStream({
stream_name,
get_stream_by_name: helper_config.get_stream_by_name,
stream_hash: helper_config.stream_hash,
});
}
function streamTopicHandler(stream_name, topic) {
return handleStreamTopic({
stream_name,
topic,
get_stream_by_name: helper_config.get_stream_by_name,
stream_topic_hash: helper_config.stream_topic_hash,
});
}
function emojiHandler(emoji_name) {
return handleEmoji({
emoji_name,
get_realm_emoji_url: helper_config.get_realm_emoji_url,
get_emoji_codepoint: helper_config.get_emoji_codepoint,
});
}
function unicodeEmojiHandler(unicode_emoji) {
return handleUnicodeEmoji({
unicode_emoji,
get_emoji_name: helper_config.get_emoji_name,
});
}
function linkifierHandler(pattern, matches) {
return handleLinkifier({
pattern,
matches,
get_linkifier_map: helper_config.get_linkifier_map,
});
}
const options = {
get_linkifier_regexes,
linkifierHandler,
emojiHandler,
unicodeEmojiHandler,
streamHandler,
streamTopicHandler,
texHandler: handleTex,
timestampHandler: handleTimestamp,
gfm: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false,
zulip: true,
renderer,
preprocessors: [preprocess_code_blocks, preprocess_translate_emoticons],
};
return parse_with_options({raw_content, helper_config, options});
}
// NOTE: Everything below this line is likely to be webapp-specific
// and won't be used by future platforms such as mobile.
// We may eventually move this code to a new file, but we want
// to wait till the dust settles a bit on some other changes first.
let webapp_helpers;
export function initialize(helper_config) {
// This is generally only intended to be called by the webapp. Most
// other platforms should call setup().
webapp_helpers = helper_config;
}
export function apply_markdown(message) {
// This is generally only intended to be called by the webapp. Most
// other platforms should call parse().
const raw_content = message.raw_content;
const {content, flags} = parse({raw_content, helper_config: webapp_helpers});
message.content = content;
message.flags = flags;
message.is_me_message = is_status_message(raw_content);
}
export function add_topic_links(message) {
if (message.type !== "stream") {
message.topic_links = [];
return;
}
message.topic_links = get_topic_links({
topic: message.topic,
get_linkifier_map: webapp_helpers.get_linkifier_map,
});
}
export function contains_backend_only_syntax(content) {
return content_contains_backend_only_syntax({
content,
get_linkifier_map: webapp_helpers.get_linkifier_map,
});
}
export function parse_non_message(raw_content) {
// Occasionally we get markdown from the server that is not technically
// a message, but we want to convert it to HTML. Note that we parse
// raw_content exactly as if it were a Zulip message, so we will
// handle things like mentions, stream links, and linkifiers.
return parse({raw_content, helper_config: webapp_helpers}).content;
}