mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	Add support for updating messages after they've been received.
Currently the interface for editing messages is limited to a command-line API tool; it's great for testing with e.g.: ./api/examples/edit-message --message=348135 --content="test $(date +%s)" --site=http://localhost:9991 --subject="test" The next commit will add a user interface for actually doing the editing. (imported from commit bdd408cec2946f31c2292e44f724f96ed5938791)
This commit is contained in:
		
							
								
								
									
										52
									
								
								api/examples/edit-message
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										52
									
								
								api/examples/edit-message
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copyright © 2012 Humbug, Inc.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					# of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					# in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					# copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					# furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					# all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
 | 
					# THE SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					from os import path
 | 
				
			||||||
 | 
					import optparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					usage = """edit-message [options] --message=<msg_id> --content=<new content> --subject=<new subject>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Edits a message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example: edit-message --message="348135" --subject="my subject" --message="test message"
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					parser = optparse.OptionParser(usage=usage)
 | 
				
			||||||
 | 
					parser.add_option('--subject', default="")
 | 
				
			||||||
 | 
					parser.add_option('--message', default="")
 | 
				
			||||||
 | 
					parser.add_option('--site',   default='https://humbughq.com')
 | 
				
			||||||
 | 
					parser.add_option('--content',   default="")
 | 
				
			||||||
 | 
					(options, args) = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sys.path.insert(0, path.join(path.dirname(__file__), '..'))
 | 
				
			||||||
 | 
					import humbug
 | 
				
			||||||
 | 
					client = humbug.Client(site=options.site)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message_data = {
 | 
				
			||||||
 | 
					    "message_id": options.message,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					if options.subject != "":
 | 
				
			||||||
 | 
					    message_data["subject"] = options.subject
 | 
				
			||||||
 | 
					if options.content != "":
 | 
				
			||||||
 | 
					    message_data["content"] = options.content
 | 
				
			||||||
 | 
					print client.update_message(message_data)
 | 
				
			||||||
@@ -272,6 +272,7 @@ def _mk_events(event_types=None):
 | 
				
			|||||||
    return dict(event_types=event_types)
 | 
					    return dict(event_types=event_types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Client._register('send_message', url='messages', make_request=(lambda request: request))
 | 
					Client._register('send_message', url='messages', make_request=(lambda request: request))
 | 
				
			||||||
 | 
					Client._register('update_message', method='PATCH', url='messages', make_request=(lambda request: request))
 | 
				
			||||||
Client._register('get_messages', method='GET', url='messages/latest', longpolling=True)
 | 
					Client._register('get_messages', method='GET', url='messages/latest', longpolling=True)
 | 
				
			||||||
Client._register('get_events', url='events', method='GET', longpolling=True, make_request=(lambda **kwargs: kwargs))
 | 
					Client._register('get_events', url='events', method='GET', longpolling=True, make_request=(lambda **kwargs: kwargs))
 | 
				
			||||||
Client._register('register', make_request=_mk_events)
 | 
					Client._register('register', make_request=_mk_events)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,6 +112,7 @@ urlpatterns += patterns('zephyr.views',
 | 
				
			|||||||
    url(r'^json/create_bot$',               'json_create_bot'),
 | 
					    url(r'^json/create_bot$',               'json_create_bot'),
 | 
				
			||||||
    url(r'^json/get_bots$',                 'json_get_bots'),
 | 
					    url(r'^json/get_bots$',                 'json_get_bots'),
 | 
				
			||||||
    url(r'^json/update_onboarding_steps$',  'json_update_onboarding_steps'),
 | 
					    url(r'^json/update_onboarding_steps$',  'json_update_onboarding_steps'),
 | 
				
			||||||
 | 
					    url(r'^json/update_message$',           'json_update_message'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # These are json format views used by the API.  They require an API key.
 | 
					    # These are json format views used by the API.  They require an API key.
 | 
				
			||||||
    url(r'^api/v1/get_profile$',            'api_get_profile'),
 | 
					    url(r'^api/v1/get_profile$',            'api_get_profile'),
 | 
				
			||||||
@@ -132,6 +133,7 @@ urlpatterns += patterns('zephyr.views',
 | 
				
			|||||||
    # GET returns messages, possibly filtered, POST sends a message
 | 
					    # GET returns messages, possibly filtered, POST sends a message
 | 
				
			||||||
    url(r'^api/v1/messages$', 'rest_dispatch',
 | 
					    url(r'^api/v1/messages$', 'rest_dispatch',
 | 
				
			||||||
            {'GET':  'get_old_messages_backend',
 | 
					            {'GET':  'get_old_messages_backend',
 | 
				
			||||||
 | 
					             'PATCH': 'update_message_backend',
 | 
				
			||||||
             'POST': 'send_message_backend'}),
 | 
					             'POST': 'send_message_backend'}),
 | 
				
			||||||
    url(r'^api/v1/streams$', 'rest_dispatch',
 | 
					    url(r'^api/v1/streams$', 'rest_dispatch',
 | 
				
			||||||
            {'GET':  'get_public_streams_backend'}),
 | 
					            {'GET':  'get_public_streams_backend'}),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,8 @@ from zephyr.models import Realm, Stream, UserProfile, UserActivity, \
 | 
				
			|||||||
    Subscription, Recipient, Message, UserMessage, valid_stream_name, \
 | 
					    Subscription, Recipient, Message, UserMessage, valid_stream_name, \
 | 
				
			||||||
    DefaultStream, UserPresence, MAX_SUBJECT_LENGTH, \
 | 
					    DefaultStream, UserPresence, MAX_SUBJECT_LENGTH, \
 | 
				
			||||||
    MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \
 | 
					    MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \
 | 
				
			||||||
    get_user_profile_by_id, PreregistrationUser, get_display_recipient
 | 
					    get_user_profile_by_id, PreregistrationUser, get_display_recipient, \
 | 
				
			||||||
 | 
					    to_dict_cache_key
 | 
				
			||||||
from django.db import transaction, IntegrityError
 | 
					from django.db import transaction, IntegrityError
 | 
				
			||||||
from django.db.models import F
 | 
					from django.db.models import F
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
@@ -27,7 +28,7 @@ from django.utils import timezone
 | 
				
			|||||||
from zephyr.lib.create_user import create_user
 | 
					from zephyr.lib.create_user import create_user
 | 
				
			||||||
from zephyr.lib import bugdown
 | 
					from zephyr.lib import bugdown
 | 
				
			||||||
from zephyr.lib.cache import cache_with_key, user_profile_by_id_cache_key, \
 | 
					from zephyr.lib.cache import cache_with_key, user_profile_by_id_cache_key, \
 | 
				
			||||||
    user_profile_by_email_cache_key, status_dict_cache_key
 | 
					    user_profile_by_email_cache_key, status_dict_cache_key, cache_set_many
 | 
				
			||||||
from zephyr.decorator import get_user_profile_by_email, json_to_list, JsonableError, \
 | 
					from zephyr.decorator import get_user_profile_by_email, json_to_list, JsonableError, \
 | 
				
			||||||
     statsd_increment
 | 
					     statsd_increment
 | 
				
			||||||
from zephyr.lib.event_queue import request_event_queue, get_user_events
 | 
					from zephyr.lib.event_queue import request_event_queue, get_user_events
 | 
				
			||||||
@@ -826,6 +827,57 @@ def do_update_onboarding_steps(user_profile, steps):
 | 
				
			|||||||
                  users=[user_profile.id])
 | 
					                  users=[user_profile.id])
 | 
				
			||||||
    tornado_callbacks.send_notification(notice)
 | 
					    tornado_callbacks.send_notification(notice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_update_message(user_profile, message_id, subject, content):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        message = Message.objects.select_related().get(id=message_id)
 | 
				
			||||||
 | 
					    except Message.DoesNotExist:
 | 
				
			||||||
 | 
					        raise JsonableError("Unknown message id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    event = {'type': 'update_message',
 | 
				
			||||||
 | 
					             'sender': user_profile.email,
 | 
				
			||||||
 | 
					             'message_id': message_id}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if message.sender != user_profile:
 | 
				
			||||||
 | 
					        raise JsonableError("Message was not sent by you")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if content is not None:
 | 
				
			||||||
 | 
					        rendered_content = bugdown.convert(content)
 | 
				
			||||||
 | 
					        if rendered_content is None:
 | 
				
			||||||
 | 
					            raise JsonableError("We were unable to render your updated message")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event['orig_content'] = message.content
 | 
				
			||||||
 | 
					        event['orig_rendered_content'] = message.rendered_content
 | 
				
			||||||
 | 
					        message.content = content
 | 
				
			||||||
 | 
					        message.rendered_content = rendered_content
 | 
				
			||||||
 | 
					        message.rendered_content_version = bugdown.version
 | 
				
			||||||
 | 
					        event["content"] = content
 | 
				
			||||||
 | 
					        event["rendered_content"] = rendered_content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if subject is not None:
 | 
				
			||||||
 | 
					        event["orig_subject"] = message.subject
 | 
				
			||||||
 | 
					        message.subject = subject
 | 
				
			||||||
 | 
					        event["subject"] = subject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log_event(event)
 | 
				
			||||||
 | 
					    message.save(update_fields=["subject", "content", "rendered_content",
 | 
				
			||||||
 | 
					                                "rendered_content_version"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Update the message as stored in both the (deprecated) message
 | 
				
			||||||
 | 
					    # cache (for shunting the message over to Tornado in the old
 | 
				
			||||||
 | 
					    # get_messages API) and also the to_dict caches.
 | 
				
			||||||
 | 
					    cache_save_message(message)
 | 
				
			||||||
 | 
					    items_for_memcached = {}
 | 
				
			||||||
 | 
					    items_for_memcached[to_dict_cache_key(message, True)] = \
 | 
				
			||||||
 | 
					        (message.to_dict_uncached(apply_markdown=True,
 | 
				
			||||||
 | 
					                                  rendered_content=message.rendered_content),)
 | 
				
			||||||
 | 
					    items_for_memcached[to_dict_cache_key(message, False)] = \
 | 
				
			||||||
 | 
					        (message.to_dict_uncached(apply_markdown=False),)
 | 
				
			||||||
 | 
					    cache_set_many(items_for_memcached)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    recipients = [um.user_profile_id for um in UserMessage.objects.filter(message=message_id)]
 | 
				
			||||||
 | 
					    notice = dict(event=event, users=recipients)
 | 
				
			||||||
 | 
					    tornado_callbacks.send_notification(notice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def do_finish_tutorial(user_profile):
 | 
					def do_finish_tutorial(user_profile):
 | 
				
			||||||
    user_profile.tutorial_status = UserProfile.TUTORIAL_FINISHED
 | 
					    user_profile.tutorial_status = UserProfile.TUTORIAL_FINISHED
 | 
				
			||||||
    user_profile.save()
 | 
					    user_profile.save()
 | 
				
			||||||
@@ -950,6 +1002,11 @@ def do_events_register(user_profile, user_client, apply_markdown=True,
 | 
				
			|||||||
                                              ret['subscriptions'])
 | 
					                                              ret['subscriptions'])
 | 
				
			||||||
        elif event['type'] == "presence":
 | 
					        elif event['type'] == "presence":
 | 
				
			||||||
                ret['presences'][event['email']] = event['presence']
 | 
					                ret['presences'][event['email']] = event['presence']
 | 
				
			||||||
 | 
					        elif event['type'] == "update_message":
 | 
				
			||||||
 | 
					            # The client will get the updated message directly
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise ValueError("Unexpected event type %s" % (event['type'],))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if events:
 | 
					    if events:
 | 
				
			||||||
        ret['last_event_id'] = events[-1]['id']
 | 
					        ret['last_event_id'] = events[-1]['id']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -213,6 +213,11 @@ MessageList.prototype = {
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this._rerender_preserving_scrolltop();
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _rerender_preserving_scrolltop: function MessageList__rerender_preserving_scrolltop() {
 | 
				
			||||||
        // scrolltop_offset is the number of pixels between the top of the
 | 
					        // scrolltop_offset is the number of pixels between the top of the
 | 
				
			||||||
        // viewable window and the newly selected message
 | 
					        // viewable window and the newly selected message
 | 
				
			||||||
        var scrolltop_offset;
 | 
					        var scrolltop_offset;
 | 
				
			||||||
@@ -233,8 +238,6 @@ MessageList.prototype = {
 | 
				
			|||||||
        if (selected_in_view) {
 | 
					        if (selected_in_view) {
 | 
				
			||||||
            viewport.scrollTop(rows.get(this._selected_id, this.table_name).offset().top + scrolltop_offset);
 | 
					            viewport.scrollTop(rows.get(this._selected_id, this.table_name).offset().top + scrolltop_offset);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _render: function MessageList__render(messages, where) {
 | 
					    _render: function MessageList__render(messages, where) {
 | 
				
			||||||
@@ -505,7 +508,7 @@ MessageList.prototype = {
 | 
				
			|||||||
        this._render_win_end += messages.length;
 | 
					        this._render_win_end += messages.length;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    add_and_rerender: function MessageList_interior(messages) {
 | 
					    add_and_rerender: function MessageList_add_and_rerender(messages) {
 | 
				
			||||||
        // To add messages that might be in the interior of our
 | 
					        // To add messages that might be in the interior of our
 | 
				
			||||||
        // existing messages list, we just add the new messages and
 | 
					        // existing messages list, we just add the new messages and
 | 
				
			||||||
        // then rerender the whole thing.
 | 
					        // then rerender the whole thing.
 | 
				
			||||||
@@ -521,6 +524,15 @@ MessageList.prototype = {
 | 
				
			|||||||
                                       this._render_win_end), 'bottom');
 | 
					                                       this._render_win_end), 'bottom');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rerender: function MessageList_rerender() {
 | 
				
			||||||
 | 
					        // We need to clear the rendering state, rather than just
 | 
				
			||||||
 | 
					        // doing _clear_table, since we want to potentially recollapse
 | 
				
			||||||
 | 
					        // things.
 | 
				
			||||||
 | 
					        this._clear_rendering_state();
 | 
				
			||||||
 | 
					        this._rerender_preserving_scrolltop();
 | 
				
			||||||
 | 
					        this.select_id(this._selected_id);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    all: function MessageList_all() {
 | 
					    all: function MessageList_all() {
 | 
				
			||||||
        return this._items;
 | 
					        return this._items;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -545,9 +545,10 @@ function case_insensitive_find(term, array) {
 | 
				
			|||||||
    }).length !== 0;
 | 
					    }).length !== 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function process_message_for_recent_subjects(message) {
 | 
					function process_message_for_recent_subjects(message, remove_message) {
 | 
				
			||||||
    var current_timestamp = 0;
 | 
					    var current_timestamp = 0;
 | 
				
			||||||
    var max_subjects = 5;
 | 
					    var max_subjects = 5;
 | 
				
			||||||
 | 
					    var count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (! recent_subjects.hasOwnProperty(message.stream)) {
 | 
					    if (! recent_subjects.hasOwnProperty(message.stream)) {
 | 
				
			||||||
        recent_subjects[message.stream] = [];
 | 
					        recent_subjects[message.stream] = [];
 | 
				
			||||||
@@ -557,6 +558,7 @@ function process_message_for_recent_subjects(message) {
 | 
				
			|||||||
                var is_duplicate = (item.subject.toLowerCase() === message.subject.toLowerCase());
 | 
					                var is_duplicate = (item.subject.toLowerCase() === message.subject.toLowerCase());
 | 
				
			||||||
                if (is_duplicate) {
 | 
					                if (is_duplicate) {
 | 
				
			||||||
                    current_timestamp = item.timestamp;
 | 
					                    current_timestamp = item.timestamp;
 | 
				
			||||||
 | 
					                    count = item.count;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return !is_duplicate;
 | 
					                return !is_duplicate;
 | 
				
			||||||
@@ -564,8 +566,18 @@ function process_message_for_recent_subjects(message) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var recents = recent_subjects[message.stream];
 | 
					    var recents = recent_subjects[message.stream];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (remove_message !== undefined) {
 | 
				
			||||||
 | 
					        count = count - 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        count = count + 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (count !== 0) {
 | 
				
			||||||
        recents.push({subject: message.subject,
 | 
					        recents.push({subject: message.subject,
 | 
				
			||||||
 | 
					                      count: count,
 | 
				
			||||||
                      timestamp: Math.max(message.timestamp, current_timestamp)});
 | 
					                      timestamp: Math.max(message.timestamp, current_timestamp)});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    recents.sort(function (a, b) {
 | 
					    recents.sort(function (a, b) {
 | 
				
			||||||
        return b.timestamp - a.timestamp;
 | 
					        return b.timestamp - a.timestamp;
 | 
				
			||||||
@@ -788,6 +800,54 @@ function maybe_add_narrowed_messages(messages, msg_list) {
 | 
				
			|||||||
        }});
 | 
					        }});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function update_message_unread_subjects(msg, event) {
 | 
				
			||||||
 | 
					    if (event.subject !== undefined &&
 | 
				
			||||||
 | 
					        unread_subjects[msg.stream] !== undefined &&
 | 
				
			||||||
 | 
					        unread_subjects[msg.stream][msg.subject] !== undefined &&
 | 
				
			||||||
 | 
					        unread_subjects[msg.stream][msg.subject][msg.id]) {
 | 
				
			||||||
 | 
					        // Move the unread subject count to the new subject
 | 
				
			||||||
 | 
					        delete unread_subjects[msg.stream][msg.subject][msg.id];
 | 
				
			||||||
 | 
					        if (unread_subjects[msg.stream][msg.subject].length === 0) {
 | 
				
			||||||
 | 
					            delete unread_subjects[msg.stream][msg.subject];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (unread_subjects[msg.stream][event.subject] === undefined) {
 | 
				
			||||||
 | 
					            unread_subjects[msg.stream][event.subject] = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        unread_subjects[msg.stream][event.subject][msg.id] = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function update_messages(events) {
 | 
				
			||||||
 | 
					    $.each(events, function (idx, event) {
 | 
				
			||||||
 | 
					        var msg = all_msg_list.get(event.message_id);
 | 
				
			||||||
 | 
					        if (event.rendered_content !== undefined) {
 | 
				
			||||||
 | 
					            msg.content = event.rendered_content;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (event.subject !== undefined) {
 | 
				
			||||||
 | 
					            // Remove the recent subjects entry for the old subject;
 | 
				
			||||||
 | 
					            // must be called before we update msg.subject
 | 
				
			||||||
 | 
					            process_message_for_recent_subjects(msg, true);
 | 
				
			||||||
 | 
					            // Update the unread counts; again, this must be called
 | 
				
			||||||
 | 
					            // before we update msg.subject
 | 
				
			||||||
 | 
					            update_message_unread_subjects(msg, event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            msg.subject = event.subject;
 | 
				
			||||||
 | 
					            // Add the recent subjects entry for the new subject; must
 | 
				
			||||||
 | 
					            // be called after we update msg.subject
 | 
				
			||||||
 | 
					            process_message_for_recent_subjects(msg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    home_msg_list.rerender();
 | 
				
			||||||
 | 
					    if (current_msg_list === narrowed_msg_list) {
 | 
				
			||||||
 | 
					        narrowed_msg_list.rerender();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    compose.update_faded_messages();
 | 
				
			||||||
 | 
					    update_unread_counts();
 | 
				
			||||||
 | 
					    stream_list.update_streams_sidebar();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var get_updates_xhr;
 | 
					var get_updates_xhr;
 | 
				
			||||||
var get_updates_timeout;
 | 
					var get_updates_timeout;
 | 
				
			||||||
function get_updates(options) {
 | 
					function get_updates(options) {
 | 
				
			||||||
@@ -820,6 +880,7 @@ function get_updates(options) {
 | 
				
			|||||||
            $('#connection-error').hide();
 | 
					            $('#connection-error').hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var messages = [];
 | 
					            var messages = [];
 | 
				
			||||||
 | 
					            var messages_to_update = [];
 | 
				
			||||||
            var new_pointer;
 | 
					            var new_pointer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $.each(data.events, function (idx, event) {
 | 
					            $.each(data.events, function (idx, event) {
 | 
				
			||||||
@@ -839,6 +900,9 @@ function get_updates(options) {
 | 
				
			|||||||
                case 'onboarding_steps':
 | 
					                case 'onboarding_steps':
 | 
				
			||||||
                    onboarding.set_step_info(event.steps);
 | 
					                    onboarding.set_step_info(event.steps);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'update_message':
 | 
				
			||||||
 | 
					                    messages_to_update.push(event);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                case 'realm_user':
 | 
					                case 'realm_user':
 | 
				
			||||||
                    if (event.op === 'add') {
 | 
					                    if (event.op === 'add') {
 | 
				
			||||||
                        add_person(event.person);
 | 
					                        add_person(event.person);
 | 
				
			||||||
@@ -912,6 +976,10 @@ function get_updates(options) {
 | 
				
			|||||||
                home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
 | 
					                home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (messages_to_update.length !== 0) {
 | 
				
			||||||
 | 
					                update_messages(messages_to_update);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            get_updates_timeout = setTimeout(get_updates, 0);
 | 
					            get_updates_timeout = setTimeout(get_updates, 0);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        error: function (xhr, error_type, exn) {
 | 
					        error: function (xhr, error_type, exn) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,7 @@ from zephyr.lib.actions import do_add_subscription, do_remove_subscription, \
 | 
				
			|||||||
    update_user_presence, bulk_add_subscriptions, update_message_flags, \
 | 
					    update_user_presence, bulk_add_subscriptions, update_message_flags, \
 | 
				
			||||||
    recipient_for_emails, extract_recipients, do_events_register, do_finish_tutorial, \
 | 
					    recipient_for_emails, extract_recipients, do_events_register, do_finish_tutorial, \
 | 
				
			||||||
    get_status_dict, do_change_enable_offline_email_notifications, \
 | 
					    get_status_dict, do_change_enable_offline_email_notifications, \
 | 
				
			||||||
    do_update_onboarding_steps
 | 
					    do_update_onboarding_steps, do_update_message
 | 
				
			||||||
from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, CreateBotForm, \
 | 
					from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, CreateBotForm, \
 | 
				
			||||||
    is_unique, is_inactive, isnt_mit
 | 
					    is_unique, is_inactive, isnt_mit
 | 
				
			||||||
from django.views.decorators.csrf import csrf_exempt
 | 
					from django.views.decorators.csrf import csrf_exempt
 | 
				
			||||||
@@ -1034,6 +1034,20 @@ def json_tutorial_status(request, user_profile, status=REQ('status')):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return json_success()
 | 
					    return json_success()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@authenticated_json_post_view
 | 
				
			||||||
 | 
					def json_update_message(request, user_profile):
 | 
				
			||||||
 | 
					    return update_message_backend(request, user_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@has_request_variables
 | 
				
			||||||
 | 
					def update_message_backend(request, user_profile,
 | 
				
			||||||
 | 
					                           message_id=REQ(converter=to_non_negative_int),
 | 
				
			||||||
 | 
					                           subject=REQ(default=None),
 | 
				
			||||||
 | 
					                           content=REQ(default=None)):
 | 
				
			||||||
 | 
					    if subject is None and content is None:
 | 
				
			||||||
 | 
					        return json_error("Nothing to change")
 | 
				
			||||||
 | 
					    do_update_message(user_profile, message_id, subject, content)
 | 
				
			||||||
 | 
					    return json_success()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# We do not @require_login for send_message_backend, since it is used
 | 
					# We do not @require_login for send_message_backend, since it is used
 | 
				
			||||||
# both from the API and the web service.  Code calling
 | 
					# both from the API and the web service.  Code calling
 | 
				
			||||||
# send_message_backend should either check the API key or check that
 | 
					# send_message_backend should either check the API key or check that
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user