markdown: Fix stream link handler in corner cases.

* Fixes handling of multiple stream links and invalid stream names.
* Fixes text regex so it handle hash sign the right way.
* Adds tests for these stream link cases.
This commit is contained in:
paxapy 2016-11-10 22:20:40 +03:00 committed by Tim Abbott
parent 852bc6b491
commit ade3bda025
3 changed files with 57 additions and 20 deletions

View File

@ -1,6 +1,8 @@
/*global Dict */ /*global Dict */
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
global.stub_out_jquery(); global.stub_out_jquery();
@ -30,6 +32,8 @@ add_dependencies({
marked: 'third/marked/lib/marked.js', marked: 'third/marked/lib/marked.js',
emoji: 'js/emoji.js', emoji: 'js/emoji.js',
people: 'js/people.js', people: 'js/people.js',
stream_data: 'js/stream_data.js',
hashchange: 'js/hashchange',
fenced_code: 'js/fenced_code.js' fenced_code: 'js/fenced_code.js'
}); });
@ -49,6 +53,9 @@ set_global('$', function (obj) {
set_global('feature_flags', {local_echo: true}); set_global('feature_flags', {local_echo: true});
jsdom.changeURL(window, 'http://zulip.zulipdev.com');
set_global('window', window);
var people = global.people; var people = global.people;
people.add({ people.add({
@ -57,6 +64,25 @@ people.add({
email: 'cordelia@zulip.com' email: 'cordelia@zulip.com'
}); });
var stream_data = global.stream_data;
var denmark = {
subscribed: false,
color: 'blue',
name: 'Denmark',
stream_id: 1,
in_home_view: false
};
var social = {
subscribed: true,
color: 'red',
name: 'social',
stream_id: 2,
in_home_view: true,
invite_only: true
};
stream_data.add_sub('Denmark', denmark);
stream_data.add_sub('social', social);
var echo = require('js/echo.js'); var echo = require('js/echo.js');
var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver/fixtures/bugdown-data.json'), 'utf8', 'r')); var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver/fixtures/bugdown-data.json'), 'utf8', 'r'));
@ -79,6 +105,7 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
"User Mention @**leo**", "User Mention @**leo**",
"User Mention @**leo f**", "User Mention @**leo f**",
"User Mention @**leo with some name**", "User Mention @**leo with some name**",
"Stream #**Verona**",
"This contains !gravatar(leo@zulip.com)", "This contains !gravatar(leo@zulip.com)",
"And an avatar !avatar(leo@zulip.com) is here" "And an avatar !avatar(leo@zulip.com) is here"
]; ];
@ -138,6 +165,12 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
expected: '<blockquote>\n<p>quote this for me</p>\n</blockquote>\n<p>thanks</p>'}, expected: '<blockquote>\n<p>quote this for me</p>\n</blockquote>\n<p>thanks</p>'},
{input: 'This is a @**Cordelia Lear** mention', {input: 'This is a @**Cordelia Lear** mention',
expected: '<p>This is a <span class="user-mention" data-user-email="cordelia@zulip.com">@Cordelia Lear</span> mention</p>'}, expected: '<p>This is a <span class="user-mention" data-user-email="cordelia@zulip.com">@Cordelia Lear</span> mention</p>'},
{input: 'This is a #**Denmark** stream link',
expected: '<p>This is a <a class="stream" data-stream-id="1" href="http://zulip.zulipdev.com/#narrow/stream/Denmark">#Denmark</a> stream link</p>'},
{input: 'This is #**Denmark** and #**social** stream links',
expected: '<p>This is <a class="stream" data-stream-id="1" href="http://zulip.zulipdev.com/#narrow/stream/Denmark">#Denmark</a> and <a class="stream" data-stream-id="2" href="http://zulip.zulipdev.com/#narrow/stream/social">#social</a> stream links</p>'},
{input: 'And this is a #**wrong** stream link',
expected: '<p>And this is a #**wrong** stream link</p>'},
{input: 'mmm...:burrito:s', {input: 'mmm...:burrito:s',
expected: '<p>mmm...<img alt=\":burrito:\" class=\"emoji\" src=\"/static/third/gemoji/images/emoji/burrito.png\" title=\":burrito:\">s</p>'}, expected: '<p>mmm...<img alt=\":burrito:\" class=\"emoji\" src=\"/static/third/gemoji/images/emoji/burrito.png\" title=\":burrito:\">s</p>'},
{input: 'This is an :poop: message', {input: 'This is an :poop: message',

View File

@ -327,6 +327,18 @@ function handleUserMentions(username) {
} }
} }
function handleStream(streamName) {
var stream = stream_data.get_sub(streamName);
if (stream === undefined) {
return undefined;
}
return '<a class="stream" data-stream-id="' + stream.stream_id + '" ' +
'href="' + window.location.origin + '/#narrow/stream/' +
hashchange.encodeHashComponent(stream.name) + '"' +
'>' + '#' + stream.name + '</a>';
}
function handleRealmFilter(pattern, matches) { function handleRealmFilter(pattern, matches) {
var url = realm_filter_map[pattern]; var url = realm_filter_map[pattern];
@ -340,10 +352,6 @@ function handleRealmFilter(pattern, matches) {
return url; return url;
} }
function handleStreamLinks(stream_name) {
return '<a href="' + window.location.origin + '#narrow/stream/' + hashchange.encodeHashComponent(stream_name) + '">#' + stream_name + '</a>';
}
function python_to_js_filter(pattern, url) { function python_to_js_filter(pattern, url) {
// Converts a python named-group regex to a javascript-compatible numbered // Converts a python named-group regex to a javascript-compatible numbered
// group regex... with a regex! // group regex... with a regex!
@ -487,7 +495,7 @@ $(function () {
avatarHandler: handleAvatar, avatarHandler: handleAvatar,
unicodeEmojiHandler: handleUnicodeEmoji, unicodeEmojiHandler: handleUnicodeEmoji,
userMentionHandler: handleUserMentions, userMentionHandler: handleUserMentions,
streamLinkHandler: handleStreamLinks, streamHandler: handleStream,
realmFilterHandler: handleRealmFilter, realmFilterHandler: handleRealmFilter,
renderer: r, renderer: r,
preprocessors: [preprocess_code_blocks] preprocessors: [preprocess_code_blocks]

View File

@ -477,6 +477,7 @@ var inline = {
emoji: noop, emoji: noop,
unicodeemoji: noop, unicodeemoji: noop,
usermention: noop, usermention: noop,
stream: noop,
avatar: noop, avatar: noop,
gravatar: noop, gravatar: noop,
realm_filters: [], realm_filters: [],
@ -537,13 +538,13 @@ inline.zulip = merge({}, inline.breaks, {
emoji: /^:([A-Za-z0-9_\-\+]+?):/, emoji: /^:([A-Za-z0-9_\-\+]+?):/,
unicodeemoji: /^(\ud83c[\udf00-\udfff]|\ud83d[\udc00-\ude4f]|\ud83d[\ude80-\udeff])/, unicodeemoji: /^(\ud83c[\udf00-\udfff]|\ud83d[\udc00-\ude4f]|\ud83d[\ude80-\udeff])/,
usermention: /^(@(?:\*\*([^\*]+)?\*\*|(\w+)?))/m, // Match multi-word string between @** ** or match any one-word usermention: /^(@(?:\*\*([^\*]+)?\*\*|(\w+)?))/m, // Match multi-word string between @** ** or match any one-word
streamlink: /^(#\*\*([^\*]+)\*\*)/, stream: /^#\*\*([^\*]+)\*\*/m,
avatar: /^!avatar\(([^)]+)\)/, avatar: /^!avatar\(([^)]+)\)/,
gravatar: /^!gravatar\(([^)]+)\)/, gravatar: /^!gravatar\(([^)]+)\)/,
realm_filters: [], realm_filters: [],
text: replace(inline.breaks.text) text: replace(inline.breaks.text)
('|', '|(\ud83c[\udf00-\udfff]|\ud83d[\udc00-\ude4f]|\ud83d[\ude80-\udeff])|') ('|', '|(\ud83c[\udf00-\udfff]|\ud83d[\udc00-\ude4f]|\ud83d[\ude80-\udeff])|')
(']|', '@:]|') (']|', '#@:]|')
() ()
}); });
@ -725,10 +726,11 @@ InlineLexer.prototype.output = function(src) {
out += this.usermention(cap[2] || cap[3], cap[1]); out += this.usermention(cap[2] || cap[3], cap[1]);
continue; continue;
} }
// streamlink (zulip)
if (cap = this.rules.streamlink.exec(src)) { // stream (zulip)
if (cap = this.rules.stream.exec(src)) {
src = src.substring(cap[0].length); src = src.substring(cap[0].length);
out += this.streamlink(cap[2], cap[1]); out += this.stream(cap[1], cap[0]);
continue; continue;
} }
@ -870,22 +872,16 @@ InlineLexer.prototype.usermention = function (username, orig) {
return orig; return orig;
}; };
InlineLexer.prototype.streamlink = function(stream_name, orig) { InlineLexer.prototype.stream = function (streamName, orig) {
if (stream_data.get_sub(stream_name) === undefined) { if (typeof this.options.streamHandler !== 'function')
return orig; return orig;
}
if (typeof this.options.streamLinkHandler !== 'function')
{
return orig;
}
var handled = this.options.streamLinkHandler(stream_name); var handled = this.options.streamHandler(streamName);
if (handled !== undefined) { if (handled !== undefined) {
return handled; return handled;
} }
return orig; return orig;
} };
/** /**
* Smartypants Transformations * Smartypants Transformations