mirror of https://github.com/zulip/zulip.git
Add feature to mark all in stream/topic as read with mouse.
Fixes #736.
This commit is contained in:
parent
34fb276b7b
commit
635828069f
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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\'')
|
||||||
|
|
||||||
|
|
|
@ -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': ''})
|
||||||
|
|
Loading…
Reference in New Issue