Add custom markdown tag to render a stream subscribe button

When new streams are created we now send a message with a custom
markdown tag that renders a subscribe button.

(imported from commit 9dfba280b3b4ff4f32f6431ef9227867c8bf4b40)
This commit is contained in:
Jason Michalski 2014-01-17 14:00:04 -05:00
parent 033e0e5969
commit c30a411c10
3 changed files with 140 additions and 5 deletions

View File

@ -507,6 +507,26 @@ class Emoji(markdown.inlinepatterns.Pattern):
else: else:
return None return None
class StreamSubscribeButton(markdown.inlinepatterns.Pattern):
# This markdown extension has required javascript in
# static/js/custom_markdown.js
def handleMatch(self, match):
stream_name = match.group('stream_name')
stream_name = stream_name.replace('\\)', ')').replace('\\\\', '\\')
span = markdown.util.etree.Element('span')
span.set('class', 'inline-subscribe')
span.set('data-stream-name', stream_name)
button = markdown.util.etree.SubElement(span, 'button')
button.text = 'Subscribe to ' + stream_name
button.set('class', 'inline-subscribe-button zulip-button')
error = markdown.util.etree.SubElement(span, 'span')
error.set('class', 'inline-subscribe-error')
return span
upload_re = re.compile(r"^(?:https://%s.s3.amazonaws.com|/user_uploads/\d+)/[^/]*/([^/]*)$" % (settings.S3_BUCKET,)) upload_re = re.compile(r"^(?:https://%s.s3.amazonaws.com|/user_uploads/\d+)/[^/]*/([^/]*)$" % (settings.S3_BUCKET,))
def url_filename(url): def url_filename(url):
"""Extract the filename if a URL is an uploaded file, or return the original URL""" """Extract the filename if a URL is an uploaded file, or return the original URL"""
@ -791,6 +811,8 @@ class Bugdown(markdown.Extension):
md.inlinePatterns.add('avatar', Avatar(r'!avatar\((?P<email>[^)]*)\)'), '_begin') md.inlinePatterns.add('avatar', Avatar(r'!avatar\((?P<email>[^)]*)\)'), '_begin')
md.inlinePatterns.add('gravatar', Avatar(r'!gravatar\((?P<email>[^)]*)\)'), '_begin') md.inlinePatterns.add('gravatar', Avatar(r'!gravatar\((?P<email>[^)]*)\)'), '_begin')
md.inlinePatterns.add('stream_subscribe_button', StreamSubscribeButton(r'!_stream_subscribe_button\((?P<stream_name>(?:[^)\\]|\\\)|\\)*)\)'), '_begin')
md.inlinePatterns.add('usermention', UserMentionPattern(mention.find_mentions), '>backtick') md.inlinePatterns.add('usermention', UserMentionPattern(mention.find_mentions), '>backtick')
md.inlinePatterns.add('emoji', Emoji(r'(?<!\w)(?P<syntax>:[^:\s]+:)(?!\w)'), '_end') md.inlinePatterns.add('emoji', Emoji(r'(?<!\w)(?P<syntax>:[^:\s]+:)(?!\w)'), '_end')
md.inlinePatterns.add('link', AtomicLinkPattern(markdown.inlinepatterns.LINK_RE, md), '>backtick') md.inlinePatterns.add('link', AtomicLinkPattern(markdown.inlinepatterns.LINK_RE, md), '>backtick')

View File

@ -2043,6 +2043,61 @@ class SubscriptionAPITest(AuthedTestCase):
add_streams, self.streams, self.test_email, self.streams + add_streams) add_streams, self.streams, self.test_email, self.streams + add_streams)
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)
def test_successful_subscriptions_notifies(self):
"""
Calling /json/subscriptions/add should notify when a new stream is created.
"""
invitee = "iago@zulip.com"
invitee_full_name = 'Iago'
current_stream = self.get_streams(invitee)[0]
invite_streams = self.make_random_stream_names(current_stream)[:1]
result = self.common_subscribe_to_streams(
invitee,
invite_streams,
extra_post_data={
'announce': 'true',
'principals': '["%s"]' % (self.user_profile.email,)
},
)
self.assert_json_success(result)
msg = Message.objects.latest('id')
self.assertEqual(msg.sender_id,
get_user_profile_by_email('notification-bot@zulip.com').id)
expected_msg = "Hi there! %s just created a new stream '%s'. " \
"!_stream_subscribe_button(%s)" % (invitee_full_name,
invite_streams[0],
invite_streams[0])
self.assertEqual(msg.content, expected_msg)
def test_successful_subscriptions_notifies_with_escaping(self):
"""
Calling /json/subscriptions/add should notify when a new stream is created.
"""
invitee = "iago@zulip.com"
invitee_full_name = 'Iago'
invite_streams = ['strange ) \\ test']
result = self.common_subscribe_to_streams(
invitee,
invite_streams,
extra_post_data={
'announce': 'true',
'principals': '["%s"]' % (self.user_profile.email,)
},
)
self.assert_json_success(result)
msg = Message.objects.latest('id')
self.assertEqual(msg.sender_id,
get_user_profile_by_email('notification-bot@zulip.com').id)
expected_msg = "Hi there! %s just created a new stream '%s'. " \
"!_stream_subscribe_button(strange \\) \\\\ test)" % (
invitee_full_name,
invite_streams[0])
self.assertEqual(msg.content, expected_msg)
def test_non_ascii_stream_subscription(self): def test_non_ascii_stream_subscription(self):
""" """
Subscribing to a stream name with non-ASCII characters succeeds. Subscribing to a stream name with non-ASCII characters succeeds.
@ -4825,6 +4880,58 @@ Content Cell | Content Cell
self.assertEqual(converted, expected) self.assertEqual(converted, expected)
def test_stream_subscribe_button_simple(self):
msg = '!_stream_subscribe_button(simple)'
converted = bugdown_convert(msg)
self.assertEqual(
converted,
'<p>'
'<span class="inline-subscribe" data-stream-name="simple">'
'<button class="inline-subscribe-button zulip-button">Subscribe to simple</button>'
'<span class="inline-subscribe-error"></span>'
'</span>'
'</p>'
)
def test_stream_subscribe_button_in_name(self):
msg = '!_stream_subscribe_button(simple (not\\))'
converted = bugdown_convert(msg)
self.assertEqual(
converted,
'<p>'
'<span class="inline-subscribe" data-stream-name="simple (not)">'
'<button class="inline-subscribe-button zulip-button">Subscribe to simple (not)</button>'
'<span class="inline-subscribe-error"></span>'
'</span>'
'</p>'
)
def test_stream_subscribe_button_after_name(self):
msg = '!_stream_subscribe_button(simple) (not)'
converted = bugdown_convert(msg)
self.assertEqual(
converted,
'<p>'
'<span class="inline-subscribe" data-stream-name="simple">'
'<button class="inline-subscribe-button zulip-button">Subscribe to simple</button>'
'<span class="inline-subscribe-error"></span>'
'</span>'
' (not)</p>'
)
def test_stream_subscribe_button_slash(self):
msg = '!_stream_subscribe_button(simple\\\\)'
converted = bugdown_convert(msg)
self.assertEqual(
converted,
'<p>'
'<span class="inline-subscribe" data-stream-name="simple\\">'
'<button class="inline-subscribe-button zulip-button">Subscribe to simple\\</button>'
'<span class="inline-subscribe-error"></span>'
'</span>'
'</p>'
)
class UserPresenceTests(AuthedTestCase): class UserPresenceTests(AuthedTestCase):
def test_get_empty(self): def test_get_empty(self):
self.login("hamlet@zulip.com") self.login("hamlet@zulip.com")

View File

@ -1145,6 +1145,11 @@ def stream_link(stream_name):
"Escapes a stream name to make a #narrow/stream/stream_name link" "Escapes a stream name to make a #narrow/stream/stream_name link"
return "#narrow/stream/%s" % (urllib.quote(stream_name.encode('utf-8')),) return "#narrow/stream/%s" % (urllib.quote(stream_name.encode('utf-8')),)
def stream_button(stream_name):
stream_name = stream_name.replace('\\', '\\\\')
stream_name = stream_name.replace(')', '\\)')
return '!_stream_subscribe_button(%s)' % (stream_name,)
@has_request_variables @has_request_variables
def add_subscriptions_backend(request, user_profile, def add_subscriptions_backend(request, user_profile,
streams_raw = REQ("subscriptions", streams_raw = REQ("subscriptions",
@ -1236,16 +1241,17 @@ def add_subscriptions_backend(request, user_profile,
(", ".join('`%s`' % (s.name,) for s in created_streams),) (", ".join('`%s`' % (s.name,) for s in created_streams),)
else: else:
stream_msg = "a new stream `%s`" % (created_streams[0].name) stream_msg = "a new stream `%s`" % (created_streams[0].name)
msg = ("%s just created %s. To join, visit your [Streams page](#subscriptions)."
% (user_profile.full_name, stream_msg)) stream_buttons = ' '.join(stream_button(s.name for s in created_streams))
msg = ("%s just created %s. %s" % (user_profile.full_name,
stream_msg, stream_buttons))
notifications.append(internal_prep_message(settings.NOTIFICATION_BOT, notifications.append(internal_prep_message(settings.NOTIFICATION_BOT,
"stream", "stream",
notifications_stream.name, "Streams", msg, notifications_stream.name, "Streams", msg,
realm=notifications_stream.realm)) realm=notifications_stream.realm))
else: else:
msg = ("Hi there! %s just created a new stream '%s'. " msg = ("Hi there! %s just created a new stream '%s'. %s"
"To join, click the gear in the left-side streams list." % (user_profile.full_name, created_streams[0].name, stream_button(created_streams[0].name)))
% (user_profile.full_name, created_streams[0].name))
for realm_user_dict in get_active_user_dicts_in_realm(user_profile.realm): for realm_user_dict in get_active_user_dicts_in_realm(user_profile.realm):
# Don't announce to yourself or to people you explicitly added # Don't announce to yourself or to people you explicitly added
# (who will get the notification above instead). # (who will get the notification above instead).