Add feature to mark all in stream/topic as read with mouse.

Fixes #736.
This commit is contained in:
Preston Hansen 2016-05-08 08:20:51 -05:00 committed by Tim Abbott
parent 34fb276b7b
commit 635828069f
9 changed files with 190 additions and 9 deletions

View File

@ -3,6 +3,7 @@
All notable changes to the Zulip server are documented in this file. All notable changes to the Zulip server are documented in this file.
### Unreleased ### Unreleased
- Added UI for marking all messages in a stream or topic as read.
- Added new Attachment model to keep track of uploaded files. - Added new Attachment model to keep track of uploaded files.
- Added caching of virtualenvs in development. - Added caching of virtualenvs in development.
- Fixed missing helper scripts for RabbitMQ Nagios plugins. - Fixed missing helper scripts for RabbitMQ Nagios plugins.

View File

@ -383,6 +383,13 @@ exports.register_click_handlers = function () {
e.preventDefault(); e.preventDefault();
}); });
$('body').on('click', '.sidebar-popover-mark-topic-read', function (e) {
var topic = $(e.currentTarget).attr('data-topic-name');
var stream = $(e.currentTarget).attr('data-stream-name');
popovers.hide_topic_sidebar_popover();
unread.mark_topic_as_read(stream,topic);
e.stopPropagation();
});
$('#stream_filters').on('click', '.stream-sidebar-arrow', function (e) { $('#stream_filters').on('click', '.stream-sidebar-arrow', function (e) {
var elt = e.target; var elt = e.target;
@ -547,6 +554,13 @@ exports.register_click_handlers = function () {
e.stopPropagation(); e.stopPropagation();
}); });
$('body').on('click', '.mark_stream_as_read', function (e) {
var stream = $(e.currentTarget).parents('ul').attr('data-name');
popovers.hide_stream_sidebar_popover();
unread.mark_stream_as_read(stream);
e.stopPropagation();
});
$('body').on('click', '.open_stream_settings', function (e) { $('body').on('click', '.open_stream_settings', function (e) {
var stream = $(e.currentTarget).parents('ul').attr('data-name'); var stream = $(e.currentTarget).parents('ul').attr('data-name');
popovers.hide_stream_sidebar_popover(); popovers.hide_stream_sidebar_popover();

View File

@ -272,6 +272,33 @@ exports.mark_current_list_as_read = function mark_current_list_as_read(options)
exports.mark_messages_as_read(current_msg_list.all_messages(), options); exports.mark_messages_as_read(current_msg_list.all_messages(), options);
}; };
exports.mark_stream_as_read = function mark_stream_as_read(stream, cont) {
channel.post({
url: '/json/messages/flags',
idempotent: true,
data: {messages: JSON.stringify([]),
all: false,
op: 'add',
flag: 'read',
stream_name: stream
},
success: cont});
};
exports.mark_topic_as_read = function mark_topic_as_read(stream, topic, cont) {
channel.post({
url: '/json/messages/flags',
idempotent: true,
data: {messages: JSON.stringify([]),
all: false,
op: 'add',
flag: 'read',
topic_name: topic,
stream_name: stream
},
success: cont});
};
return exports; return exports;
}()); }());
if (typeof module !== 'undefined') { if (typeof module !== 'undefined') {

View File

@ -23,6 +23,12 @@
Compose a message to stream <b>{{stream.name}}</b> Compose a message to stream <b>{{stream.name}}</b>
</a> </a>
</li> </li>
<li>
<a class="mark_stream_as_read">
<i class="icon-vector-book"></i>
Mark all messages in <b>{{stream.name}}</b> as read
</a>
</li>
<li> <li>
<a class="open_stream_settings"> <a class="open_stream_settings">
<i class="icon-vector-cog"></i> <i class="icon-vector-cog"></i>

View File

@ -24,5 +24,12 @@
</a> </a>
</li> </li>
{{/if}} {{/if}}
<li>
<a class="sidebar-popover-mark-topic-read" data-stream-name="{{ stream_name }}" data-topic-name="{{ topic_name }}">
<i class="icon-vector-book"></i>
Mark all messages in <b>{{topic_name}}</b> as read
</a>
</li>
</ul> </ul>

View File

@ -2050,12 +2050,20 @@ def do_update_pointer(user_profile, pointer, update_flags=False):
event = dict(type='pointer', pointer=pointer) event = dict(type='pointer', pointer=pointer)
send_event(event, [user_profile.id]) send_event(event, [user_profile.id])
def do_update_message_flags(user_profile, operation, flag, messages, all): def do_update_message_flags(user_profile, operation, flag, messages, all, stream_obj, topic_name):
flagattr = getattr(UserMessage.flags, flag) flagattr = getattr(UserMessage.flags, flag)
if all: if all:
log_statsd_event('bankruptcy') log_statsd_event('bankruptcy')
msgs = UserMessage.objects.filter(user_profile=user_profile) msgs = UserMessage.objects.filter(user_profile=user_profile)
elif stream_obj is not None:
recipient = get_recipient(Recipient.STREAM, stream_obj.id)
if topic_name:
msgs = UserMessage.objects.filter(message__recipient=recipient,
user_profile=user_profile,
message__subject__iexact=topic_name)
else:
msgs = UserMessage.objects.filter(message__recipient=recipient, user_profile=user_profile)
else: else:
msgs = UserMessage.objects.filter(user_profile=user_profile, msgs = UserMessage.objects.filter(user_profile=user_profile,
message__id__in=messages) message__id__in=messages)
@ -2092,9 +2100,13 @@ def do_update_message_flags(user_profile, operation, flag, messages, all):
# are kind of magical; they are actually just testing the one bit. # are kind of magical; they are actually just testing the one bit.
if operation == 'add': if operation == 'add':
msgs = msgs.filter(flags=~flagattr) msgs = msgs.filter(flags=~flagattr)
if stream_obj:
messages = list(msgs.values_list('message__id', flat=True))
count = msgs.update(flags=F('flags').bitor(flagattr)) count = msgs.update(flags=F('flags').bitor(flagattr))
elif operation == 'remove': elif operation == 'remove':
msgs = msgs.filter(flags=flagattr) msgs = msgs.filter(flags=flagattr)
if stream_obj:
messages = list(msgs.values_list('message__id', flat=True))
count = msgs.update(flags=F('flags').bitand(~flagattr)) count = msgs.update(flags=F('flags').bitand(~flagattr))
event = {'type': 'update_message_flags', event = {'type': 'update_message_flags',

View File

@ -21,7 +21,7 @@ class Command(BaseCommand):
print("e-mail %s doesn't exist in the system, skipping" % (email,)) print("e-mail %s doesn't exist in the system, skipping" % (email,))
continue continue
do_update_message_flags(user_profile, "add", "read", None, True) do_update_message_flags(user_profile, "add", "read", None, True, None, None)
messages = Message.objects.filter( messages = Message.objects.filter(
usermessage__user_profile=user_profile).order_by('-id')[:1] usermessage__user_profile=user_profile).order_by('-id')[:1]

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-AA
from __future__ import absolute_import from __future__ import absolute_import
from zerver.models import ( from zerver.models import (
get_user_profile_by_email, Recipient, UserMessage, get_user_profile_by_email, Recipient, UserMessage
) )
from zerver.lib.test_helpers import AuthedTestCase from zerver.lib.test_helpers import AuthedTestCase, tornado_redirected_to_list
import ujson import ujson
class PointerTest(AuthedTestCase): class PointerTest(AuthedTestCase):
@ -137,3 +137,102 @@ class UnreadCountTests(AuthedTestCase):
for msg in self.get_old_messages(): for msg in self.get_old_messages():
self.assertEqual(msg['flags'], []) self.assertEqual(msg['flags'], [])
def test_mark_all_in_stream_read(self):
self.login("hamlet@zulip.com")
user_profile = get_user_profile_by_email("hamlet@zulip.com")
self.subscribe_to_stream(user_profile.email, "test_stream", user_profile.realm)
message_id = self.send_message("hamlet@zulip.com", "test_stream", Recipient.STREAM, "hello")
unrelated_message_id = self.send_message("hamlet@zulip.com", "Denmark", Recipient.STREAM, "hello")
events = []
with tornado_redirected_to_list(events):
result = self.client.post("/json/messages/flags", {"messages": ujson.dumps([]),
"op": "add",
"flag": "read",
"stream_name": "test_stream"})
self.assert_json_success(result)
self.assertTrue(len(events) == 1)
event = events[0]['event']
expected = dict(operation='add',
messages=[message_id],
flag='read',
type='update_message_flags',
all=False)
differences = [key for key in expected if expected[key] != event[key]]
self.assertTrue(len(differences) == 0)
um = list(UserMessage.objects.filter(message=message_id))
for msg in um:
if msg.user_profile.email == "hamlet@zulip.com":
self.assertTrue(msg.flags.read)
else:
self.assertFalse(msg.flags.read)
unrelated_messages = list(UserMessage.objects.filter(message=unrelated_message_id))
for msg in unrelated_messages:
if msg.user_profile.email == "hamlet@zulip.com":
self.assertFalse(msg.flags.read)
def test_mark_all_in_invalid_stream_read(self):
self.login("hamlet@zulip.com")
invalid_stream_name = ""
result = self.client.post("/json/messages/flags", {"messages": ujson.dumps([]),
"op": "add",
"flag": "read",
"stream_name": invalid_stream_name})
self.assert_json_error(result, 'No such stream \'\'')
def test_mark_all_in_stream_topic_read(self):
self.login("hamlet@zulip.com")
user_profile = get_user_profile_by_email("hamlet@zulip.com")
self.subscribe_to_stream(user_profile.email, "test_stream", user_profile.realm)
message_id = self.send_message("hamlet@zulip.com", "test_stream", Recipient.STREAM, "hello", "test_topic")
unrelated_message_id = self.send_message("hamlet@zulip.com", "Denmark", Recipient.STREAM, "hello", "Denmark2")
events = []
with tornado_redirected_to_list(events):
result = self.client.post("/json/messages/flags", {"messages": ujson.dumps([]),
"op": "add",
"flag": "read",
"topic_name": "test_topic",
"stream_name": "test_stream"})
self.assert_json_success(result)
self.assertTrue(len(events) == 1)
event = events[0]['event']
expected = dict(operation='add',
messages=[message_id],
flag='read',
type='update_message_flags',
all=False)
differences = [key for key in expected if expected[key] != event[key]]
self.assertTrue(len(differences) == 0)
um = list(UserMessage.objects.filter(message=message_id))
for msg in um:
if msg.user_profile.email == "hamlet@zulip.com":
self.assertTrue(msg.flags.read)
unrelated_messages = list(UserMessage.objects.filter(message=unrelated_message_id))
for msg in unrelated_messages:
if msg.user_profile.email == "hamlet@zulip.com":
self.assertFalse(msg.flags.read)
def test_mark_all_in_invalid_topic_read(self):
self.login("hamlet@zulip.com")
invalid_topic_name = "abc"
result = self.client.post("/json/messages/flags", {"messages": ujson.dumps([]),
"op": "add",
"flag": "read",
"topic_name": invalid_topic_name,
"stream_name": "Denmark"})
self.assert_json_error(result, 'No such topic \'abc\'')

View File

@ -603,11 +603,26 @@ def get_old_messages_backend(request, user_profile,
@has_request_variables @has_request_variables
def update_message_flags(request, user_profile, def update_message_flags(request, user_profile,
messages=REQ('messages', validator=check_list(check_int)), messages=REQ('messages', validator=check_list(check_int)),
operation=REQ('op'), flag=REQ('flag'), operation=REQ('op'), flag=REQ('flag'),
all=REQ('all', validator=check_bool, default=False)): all=REQ('all', validator=check_bool, default=False),
stream_name=REQ('stream_name', default=None),
topic_name=REQ('topic_name', default=None)):
request._log_data["extra"] = "[%s %s]" % (operation, flag) request._log_data["extra"] = "[%s %s]" % (operation, flag)
do_update_message_flags(user_profile, operation, flag, messages, all) stream = None
if stream_name is not None:
stream = get_stream(stream_name, user_profile.realm)
if not stream:
raise JsonableError('No such stream \'%s\'' % (stream_name,))
if topic_name:
topic_exists = UserMessage.objects.filter(user_profile=user_profile,
message__recipient__type_id=stream.id,
message__recipient__type=Recipient.STREAM,
message__subject__iexact=topic_name).exists()
if not topic_exists:
raise JsonableError('No such topic \'%s\'' % (topic_name,))
do_update_message_flags(user_profile, operation, flag, messages, all, stream, topic_name)
return json_success({'result': 'success', return json_success({'result': 'success',
'messages': messages, 'messages': messages,
'msg': ''}) 'msg': ''})