mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			5.3
			...
			shared-0.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3ff2bcf62a | ||
| 
						 | 
					7de1e7c477 | ||
| 
						 | 
					b7f670f5a0 | ||
| 
						 | 
					2a240d3e19 | ||
| 
						 | 
					efdf2c8fe3 | ||
| 
						 | 
					71c12e313c | ||
| 
						 | 
					da6d687215 | ||
| 
						 | 
					58799c6ca1 | ||
| 
						 | 
					8d9e6d6b87 | ||
| 
						 | 
					935cb605a5 | ||
| 
						 | 
					ca38b33346 | ||
| 
						 | 
					0008a76703 | ||
| 
						 | 
					d9bf8baca1 | ||
| 
						 | 
					46b19fe8bd | ||
| 
						 | 
					c1103e4c7b | ||
| 
						 | 
					c8dba33408 | ||
| 
						 | 
					6ea9947991 | ||
| 
						 | 
					12e8f0f5ea | 
							
								
								
									
										12
									
								
								.mailmap
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.mailmap
									
									
									
									
									
								
							@@ -12,6 +12,8 @@
 | 
				
			|||||||
#     # shows raw names/emails, filtered by mapped name:
 | 
					#     # shows raw names/emails, filtered by mapped name:
 | 
				
			||||||
#   $ git log --format='%an %ae' --author=$NAME | uniq -c
 | 
					#   $ git log --format='%an %ae' --author=$NAME | uniq -c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adam Benesh <Adam.Benesh@gmail.com> <Adam-Daniel.Benesh@t-systems.com>
 | 
				
			||||||
 | 
					Adam Benesh <Adam.Benesh@gmail.com>
 | 
				
			||||||
Alex Vandiver <alexmv@zulip.com> <alex@chmrr.net>
 | 
					Alex Vandiver <alexmv@zulip.com> <alex@chmrr.net>
 | 
				
			||||||
Alex Vandiver <alexmv@zulip.com> <github@chmrr.net>
 | 
					Alex Vandiver <alexmv@zulip.com> <github@chmrr.net>
 | 
				
			||||||
Allen Rabinovich <allenrabinovich@yahoo.com> <allenr@humbughq.com>
 | 
					Allen Rabinovich <allenrabinovich@yahoo.com> <allenr@humbughq.com>
 | 
				
			||||||
@@ -20,6 +22,9 @@ Alya Abbott <alya@zulip.com> <2090066+alya@users.noreply.github.com>
 | 
				
			|||||||
Aman Agrawal <amanagr@zulip.com> <f2016561@pilani.bits-pilani.ac.in>
 | 
					Aman Agrawal <amanagr@zulip.com> <f2016561@pilani.bits-pilani.ac.in>
 | 
				
			||||||
Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
 | 
					Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
 | 
				
			||||||
Anders Kaseorg <anders@zulip.com> <andersk@mit.edu>
 | 
					Anders Kaseorg <anders@zulip.com> <andersk@mit.edu>
 | 
				
			||||||
 | 
					Aryan Shridhar <aryanshridhar7@gmail.com> <53977614+aryanshridhar@users.noreply.github.com>
 | 
				
			||||||
 | 
					Aryan Shridhar <aryanshridhar7@gmail.com>
 | 
				
			||||||
 | 
					Ashwat Kumar Singh <ashwat.kumarsingh.met20@itbhu.ac.in>
 | 
				
			||||||
Austin Riba <austin@zulip.com> <austin@m51.io>
 | 
					Austin Riba <austin@zulip.com> <austin@m51.io>
 | 
				
			||||||
BIKI DAS <bikid475@gmail.com>
 | 
					BIKI DAS <bikid475@gmail.com>
 | 
				
			||||||
Brock Whittaker <brock@zulipchat.com> <bjwhitta@asu.edu>
 | 
					Brock Whittaker <brock@zulipchat.com> <bjwhitta@asu.edu>
 | 
				
			||||||
@@ -37,6 +42,7 @@ Jeff Arnold <jbarnold@gmail.com> <jbarnold@humbughq.com>
 | 
				
			|||||||
Jeff Arnold <jbarnold@gmail.com> <jbarnold@zulip.com>
 | 
					Jeff Arnold <jbarnold@gmail.com> <jbarnold@zulip.com>
 | 
				
			||||||
Jessica McKellar <jesstess@mit.edu> <jesstess@humbughq.com>
 | 
					Jessica McKellar <jesstess@mit.edu> <jesstess@humbughq.com>
 | 
				
			||||||
Jessica McKellar <jesstess@mit.edu> <jesstess@zulip.com>
 | 
					Jessica McKellar <jesstess@mit.edu> <jesstess@zulip.com>
 | 
				
			||||||
 | 
					Julia Bichler <julia.bichler@tum.de> <74348920+juliaBichler01@users.noreply.github.com>
 | 
				
			||||||
Kevin Mehall <km@kevinmehall.net> <kevin@humbughq.com>
 | 
					Kevin Mehall <km@kevinmehall.net> <kevin@humbughq.com>
 | 
				
			||||||
Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com>
 | 
					Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com>
 | 
				
			||||||
Kevin Scott <kevin.scott.98@gmail.com>
 | 
					Kevin Scott <kevin.scott.98@gmail.com>
 | 
				
			||||||
@@ -45,11 +51,13 @@ Mateusz Mandera <mateusz.mandera@zulip.com> <mateusz.mandera@protonmail.com>
 | 
				
			|||||||
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in>
 | 
					m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in>
 | 
				
			||||||
Palash Raghuwanshi <singhpalash0@gmail.com>
 | 
					Palash Raghuwanshi <singhpalash0@gmail.com>
 | 
				
			||||||
Parth <mittalparth22@gmail.com>
 | 
					Parth <mittalparth22@gmail.com>
 | 
				
			||||||
 | 
					Priyam Seth <sethpriyam1@gmail.com> <b19188@students.iitmandi.ac.in>
 | 
				
			||||||
Ray Kraesig <rkraesig@zulip.com> <rkraesig@zulipchat.com>
 | 
					Ray Kraesig <rkraesig@zulip.com> <rkraesig@zulipchat.com>
 | 
				
			||||||
 | 
					Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com>
 | 
				
			||||||
Rishi Gupta <rishig@zulipchat.com> <rishig+git@mit.edu>
 | 
					Rishi Gupta <rishig@zulipchat.com> <rishig+git@mit.edu>
 | 
				
			||||||
Rishi Gupta <rishig@zulipchat.com> <rishig@kandralabs.com>
 | 
					Rishi Gupta <rishig@zulipchat.com> <rishig@kandralabs.com>
 | 
				
			||||||
Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com>
 | 
					Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com>
 | 
				
			||||||
Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com>
 | 
					Rishabh Maheshwari <b20063@students.iitmandi.ac.in>
 | 
				
			||||||
Sayam Samal <samal.sayam@gmail.com>
 | 
					Sayam Samal <samal.sayam@gmail.com>
 | 
				
			||||||
Scott Feeney <scott@oceanbase.org> <scott@humbughq.com>
 | 
					Scott Feeney <scott@oceanbase.org> <scott@humbughq.com>
 | 
				
			||||||
Scott Feeney <scott@oceanbase.org> <scott@zulip.com>
 | 
					Scott Feeney <scott@oceanbase.org> <scott@zulip.com>
 | 
				
			||||||
@@ -71,3 +79,5 @@ Sahil Batra <sahil@zulip.com> <sahilbatra839@gmail.com>
 | 
				
			|||||||
Yash RE <33805964+YashRE42@users.noreply.github.com> <YashRE42@github.com>
 | 
					Yash RE <33805964+YashRE42@users.noreply.github.com> <YashRE42@github.com>
 | 
				
			||||||
Yash RE <33805964+YashRE42@users.noreply.github.com>
 | 
					Yash RE <33805964+YashRE42@users.noreply.github.com>
 | 
				
			||||||
Yogesh Sirsat <yogeshsirsat56@gmail.com>
 | 
					Yogesh Sirsat <yogeshsirsat56@gmail.com>
 | 
				
			||||||
 | 
					Zeeshan Equbal <equbalzeeshan@gmail.com> <54993043+zee-bit@users.noreply.github.com>
 | 
				
			||||||
 | 
					Zeeshan Equbal <equbalzeeshan@gmail.com>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,19 @@
 | 
				
			|||||||
# Version history
 | 
					# Version history
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This page contains the release history for the Zulip 5.x stable
 | 
					This page the release history for the Zulip server. See also the
 | 
				
			||||||
release series. See the [current Zulip changelog][latest-changelog]
 | 
					[Zulip release lifecycle](../overview/release-lifecycle.md).
 | 
				
			||||||
for newer release series, or the [commit log][commit-log] for an
 | 
					
 | 
				
			||||||
up-to-date list of raw changes.
 | 
					## Zulip 6.x series
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 6.0 -- unreleased
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This section is an incomplete draft of the release notes for the next
 | 
				
			||||||
 | 
					major release, and is only updated occasionally. See the [commit
 | 
				
			||||||
 | 
					log][commit-log] for an up-to-date list of raw changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Upgrade notes for 6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- None yet.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Zulip 5.x series
 | 
					## Zulip 5.x series
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1566,7 +1576,7 @@ Zulip installations; it has minimal changes for existing servers.
 | 
				
			|||||||
  disruption by running this migration first, before beginning the
 | 
					  disruption by running this migration first, before beginning the
 | 
				
			||||||
  user-facing downtime. However, if you'd like to watch the downtime
 | 
					  user-facing downtime. However, if you'd like to watch the downtime
 | 
				
			||||||
  phase of the upgrade closely, we recommend
 | 
					  phase of the upgrade closely, we recommend
 | 
				
			||||||
  [running them first manually](https://zulip.readthedocs.io/en/1.9.0/production/expensive-migrations.html)
 | 
					  running them first manually
 | 
				
			||||||
  as well as the usual trick of doing an apt upgrade first.
 | 
					  as well as the usual trick of doing an apt upgrade first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Full feature changelog
 | 
					#### Full feature changelog
 | 
				
			||||||
@@ -1955,7 +1965,7 @@ running a version from before 1.7 should upgrade directly to 1.7.1.
 | 
				
			|||||||
  minimizes disruption by running these first, before beginning the
 | 
					  minimizes disruption by running these first, before beginning the
 | 
				
			||||||
  user-facing downtime. However, if you'd like to watch the downtime
 | 
					  user-facing downtime. However, if you'd like to watch the downtime
 | 
				
			||||||
  phase of the upgrade closely, we recommend
 | 
					  phase of the upgrade closely, we recommend
 | 
				
			||||||
  [running them first manually](https://zulip.readthedocs.io/en/1.9.0/production/expensive-migrations.html)
 | 
					  running them first manually
 | 
				
			||||||
  as well as the usual trick of doing an apt upgrade first.
 | 
					  as well as the usual trick of doing an apt upgrade first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- We've removed support for an uncommon legacy deployment model where
 | 
					- We've removed support for an uncommon legacy deployment model where
 | 
				
			||||||
@@ -2580,15 +2590,17 @@ running a version from before 1.7 should upgrade directly to 1.7.1.
 | 
				
			|||||||
This section links to the upgrade notes from past releases, so you can
 | 
					This section links to the upgrade notes from past releases, so you can
 | 
				
			||||||
easily read them all when upgrading across multiple releases.
 | 
					easily read them all when upgrading across multiple releases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [Upgrade notes for 5.0](#upgrade-notes-for-50)
 | 
					- [Draft upgrade notes for 6.0](#upgrade-notes-for-60)
 | 
				
			||||||
- [Upgrade notes for 4.0](#upgrade-notes-for-40)
 | 
					
 | 
				
			||||||
- [Upgrade notes for 3.0](#upgrade-notes-for-30)
 | 
					* [Upgrade notes for 5.0](#upgrade-notes-for-50)
 | 
				
			||||||
- [Upgrade notes for 2.1.5](#upgrade-notes-for-215)
 | 
					* [Upgrade notes for 4.0](#upgrade-notes-for-40)
 | 
				
			||||||
- [Upgrade notes for 2.1.0](#upgrade-notes-for-210)
 | 
					* [Upgrade notes for 3.0](#upgrade-notes-for-30)
 | 
				
			||||||
- [Upgrade notes for 2.0.0](#upgrade-notes-for-200)
 | 
					* [Upgrade notes for 2.1.5](#upgrade-notes-for-215)
 | 
				
			||||||
- [Upgrade notes for 1.9.0](#upgrade-notes-for-190)
 | 
					* [Upgrade notes for 2.1.0](#upgrade-notes-for-210)
 | 
				
			||||||
- [Upgrade notes for 1.8.0](#upgrade-notes-for-180)
 | 
					* [Upgrade notes for 2.0.0](#upgrade-notes-for-200)
 | 
				
			||||||
- [Upgrade notes for 1.7.0](#upgrade-notes-for-170)
 | 
					* [Upgrade notes for 1.9.0](#upgrade-notes-for-190)
 | 
				
			||||||
 | 
					* [Upgrade notes for 1.8.0](#upgrade-notes-for-180)
 | 
				
			||||||
 | 
					* [Upgrade notes for 1.7.0](#upgrade-notes-for-170)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[docker-zulip]: https://github.com/zulip/docker-zulip
 | 
					[docker-zulip]: https://github.com/zulip/docker-zulip
 | 
				
			||||||
[commit-log]: https://github.com/zulip/zulip/commits/main
 | 
					[commit-log]: https://github.com/zulip/zulip/commits/main
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										149
									
								
								frontend_tests/node_tests/markdown_parse.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								frontend_tests/node_tests/markdown_parse.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {strict: assert} = require("assert");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {zrequire} = require("../zjsunit/namespace");
 | 
				
			||||||
 | 
					const {run_test} = require("../zjsunit/test");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const markdown = zrequire("markdown");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const my_id = 101;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const user_map = new Map();
 | 
				
			||||||
 | 
					user_map.set(my_id, "Me Myself");
 | 
				
			||||||
 | 
					user_map.set(105, "greg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_actual_name_from_user_id(user_id) {
 | 
				
			||||||
 | 
					    return user_map.get(user_id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_user_id_from_name(name) {
 | 
				
			||||||
 | 
					    for (const [user_id, _name] of user_map.entries()) {
 | 
				
			||||||
 | 
					        if (name === _name) {
 | 
				
			||||||
 | 
					            return user_id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function is_valid_full_name_and_user_id(name, user_id) {
 | 
				
			||||||
 | 
					    return user_map.has(user_id) && user_map.get(user_id) === name;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function my_user_id() {
 | 
				
			||||||
 | 
					    return my_id;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function is_valid_user_id(user_id) {
 | 
				
			||||||
 | 
					    return user_map.has(user_id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const staff_group = {
 | 
				
			||||||
 | 
					    id: 201,
 | 
				
			||||||
 | 
					    name: "Staff",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const user_group_map = new Map();
 | 
				
			||||||
 | 
					user_group_map.set(staff_group.name, staff_group);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_user_group_from_name(name) {
 | 
				
			||||||
 | 
					    return user_group_map.get(name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function is_member_of_user_group(user_group_id, user_id) {
 | 
				
			||||||
 | 
					    assert.equal(user_group_id, staff_group.id);
 | 
				
			||||||
 | 
					    assert.equal(user_id, my_id);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const social = {
 | 
				
			||||||
 | 
					    stream_id: 301,
 | 
				
			||||||
 | 
					    name: "social",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sub_map = new Map();
 | 
				
			||||||
 | 
					sub_map.set(social.name, social);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_stream_by_name(name) {
 | 
				
			||||||
 | 
					    return sub_map.get(name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function stream_hash(stream_id) {
 | 
				
			||||||
 | 
					    return `stream-${stream_id}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function stream_topic_hash(stream_id, topic) {
 | 
				
			||||||
 | 
					    return `stream-${stream_id}-topic-${topic}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const helper_config = {
 | 
				
			||||||
 | 
					    // user stuff
 | 
				
			||||||
 | 
					    get_actual_name_from_user_id,
 | 
				
			||||||
 | 
					    get_user_id_from_name,
 | 
				
			||||||
 | 
					    is_valid_full_name_and_user_id,
 | 
				
			||||||
 | 
					    is_valid_user_id,
 | 
				
			||||||
 | 
					    my_user_id,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // user groups
 | 
				
			||||||
 | 
					    get_user_group_from_name,
 | 
				
			||||||
 | 
					    is_member_of_user_group,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // stream hashes
 | 
				
			||||||
 | 
					    get_stream_by_name,
 | 
				
			||||||
 | 
					    stream_hash,
 | 
				
			||||||
 | 
					    stream_topic_hash,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // settings
 | 
				
			||||||
 | 
					    should_translate_emoticons: () => false,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function assert_parse(raw_content, expected_content) {
 | 
				
			||||||
 | 
					    const {content} = markdown.parse({raw_content, helper_config});
 | 
				
			||||||
 | 
					    assert.equal(content, expected_content);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function test(label, f) {
 | 
				
			||||||
 | 
					    markdown.setup();
 | 
				
			||||||
 | 
					    run_test(label, f);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("basics", () => {
 | 
				
			||||||
 | 
					    assert_parse("boring", "<p>boring</p>");
 | 
				
			||||||
 | 
					    assert_parse("**bold**", "<p><strong>bold</strong></p>");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("user mentions", () => {
 | 
				
			||||||
 | 
					    assert_parse("@**greg**", '<p><span class="user-mention" data-user-id="105">@greg</span></p>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert_parse("@**|105**", '<p><span class="user-mention" data-user-id="105">@greg</span></p>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert_parse(
 | 
				
			||||||
 | 
					        "@**greg|105**",
 | 
				
			||||||
 | 
					        '<p><span class="user-mention" data-user-id="105">@greg</span></p>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert_parse(
 | 
				
			||||||
 | 
					        "@**Me Myself|101**",
 | 
				
			||||||
 | 
					        '<p><span class="user-mention" data-user-id="101">@Me Myself</span></p>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("user group mentions", () => {
 | 
				
			||||||
 | 
					    assert_parse(
 | 
				
			||||||
 | 
					        "@*Staff*",
 | 
				
			||||||
 | 
					        '<p><span class="user-group-mention" data-user-group-id="201">@Staff</span></p>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("stream links", () => {
 | 
				
			||||||
 | 
					    assert_parse(
 | 
				
			||||||
 | 
					        "#**social**",
 | 
				
			||||||
 | 
					        '<p><a class="stream" data-stream-id="301" href="/stream-301">#social</a></p>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert_parse(
 | 
				
			||||||
 | 
					        "#**social>lunch**",
 | 
				
			||||||
 | 
					        '<p><a class="stream-topic" data-stream-id="301" href="/stream-301-topic-lunch">#social > lunch</a></p>',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -61,7 +61,7 @@ class zulip::profile::base {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  package { 'ntp': ensure => 'purged', before => Package['chrony'] }
 | 
					  package { 'ntp': ensure => 'purged', before => Package['chrony'] }
 | 
				
			||||||
  service { 'chrony': ensure => 'running', require => Package['chrony'] }
 | 
					  service { 'chrony': require => Package['chrony'] }
 | 
				
			||||||
  package { $base_packages: ensure => 'installed' }
 | 
					  package { $base_packages: ensure => 'installed' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  group { 'zulip':
 | 
					  group { 'zulip':
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -161,6 +161,7 @@ export function build_page() {
 | 
				
			|||||||
            settings_config.create_web_public_stream_policy_values,
 | 
					            settings_config.create_web_public_stream_policy_values,
 | 
				
			||||||
        disable_enable_spectator_access_setting: !page_params.server_web_public_streams_enabled,
 | 
					        disable_enable_spectator_access_setting: !page_params.server_web_public_streams_enabled,
 | 
				
			||||||
        can_sort_by_email: settings_data.show_email(),
 | 
					        can_sort_by_email: settings_data.show_email(),
 | 
				
			||||||
 | 
					        realm_push_notifications_enabled: page_params.realm_push_notifications_enabled,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options.realm_logo_source !== "D" && options.realm_night_logo_source === "D") {
 | 
					    if (options.realm_logo_source !== "D" && options.realm_night_logo_source === "D") {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +1,10 @@
 | 
				
			|||||||
import marked from "../third/marked/lib/marked";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import * as blueslip from "./blueslip";
 | 
					import * as blueslip from "./blueslip";
 | 
				
			||||||
 | 
					import * as markdown from "./markdown";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const linkifier_map = new Map();
 | 
					const linkifier_map = new Map(); // regex -> url
 | 
				
			||||||
export let linkifier_list = [];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleLinkifier(pattern, matches) {
 | 
					export function get_linkifier_map() {
 | 
				
			||||||
    let url = linkifier_map.get(pattern);
 | 
					    return linkifier_map;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let current_group = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const match of matches) {
 | 
					 | 
				
			||||||
        const back_ref = "\\" + current_group;
 | 
					 | 
				
			||||||
        url = url.replace(back_ref, match);
 | 
					 | 
				
			||||||
        current_group += 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return url;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function python_to_js_linkifier(pattern, url) {
 | 
					function python_to_js_linkifier(pattern, url) {
 | 
				
			||||||
@@ -78,11 +66,7 @@ function python_to_js_linkifier(pattern, url) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function update_linkifier_rules(linkifiers) {
 | 
					export function update_linkifier_rules(linkifiers) {
 | 
				
			||||||
    // Update the marked parser with our particular set of linkifiers
 | 
					 | 
				
			||||||
    linkifier_map.clear();
 | 
					    linkifier_map.clear();
 | 
				
			||||||
    linkifier_list = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const marked_rules = [];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const linkifier of linkifiers) {
 | 
					    for (const linkifier of linkifiers) {
 | 
				
			||||||
        const [regex, final_url] = python_to_js_linkifier(linkifier.pattern, linkifier.url_format);
 | 
					        const [regex, final_url] = python_to_js_linkifier(linkifier.pattern, linkifier.url_format);
 | 
				
			||||||
@@ -92,18 +76,12 @@ export function update_linkifier_rules(linkifiers) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        linkifier_map.set(regex, final_url);
 | 
					        linkifier_map.set(regex, final_url);
 | 
				
			||||||
        linkifier_list.push({
 | 
					 | 
				
			||||||
            pattern: regex,
 | 
					 | 
				
			||||||
            url_format: final_url,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        marked_rules.push(regex);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    marked.InlineLexer.rules.zulip.linkifiers = marked_rules;
 | 
					    // Update our parser with our particular set of linkifiers.
 | 
				
			||||||
 | 
					    markdown.set_linkifier_regexes(Array.from(linkifier_map.keys()));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initialize(linkifiers) {
 | 
					export function initialize(linkifiers) {
 | 
				
			||||||
    update_linkifier_rules(linkifiers);
 | 
					    update_linkifier_rules(linkifiers);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    marked.setOptions({linkifierHandler: handleLinkifier});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,8 +23,9 @@ import * as linkifiers from "./linkifiers";
 | 
				
			|||||||
// for example usage.
 | 
					// for example usage.
 | 
				
			||||||
let helpers;
 | 
					let helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Regexes that match some of our common backend-only Markdown syntax
 | 
					// If we see preview-related syntax in our content, we will need the
 | 
				
			||||||
const backend_only_markdown_re = [
 | 
					// backend to render it.
 | 
				
			||||||
 | 
					const preview_regexes = [
 | 
				
			||||||
    // Inline image previews, check for contiguous chars ending in image suffix
 | 
					    // Inline image previews, check for contiguous chars ending in image suffix
 | 
				
			||||||
    // To keep the below regexes simple, split them out for the end-of-message case
 | 
					    // To keep the below regexes simple, split them out for the end-of-message case
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,9 +34,13 @@ const backend_only_markdown_re = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Twitter and youtube links are given previews
 | 
					    // Twitter and youtube links are given previews
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /\S*(?:twitter|youtube).com\/\S*/,
 | 
					    /\S*(?:twitter|youtube)\.com\/\S*/,
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function contains_preview_link(content) {
 | 
				
			||||||
 | 
					    return preview_regexes.some((re) => re.test(content));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function translate_emoticons_to_names(text) {
 | 
					export function translate_emoticons_to_names(text) {
 | 
				
			||||||
    // Translates emoticons in a string to their colon syntax.
 | 
					    // Translates emoticons in a string to their colon syntax.
 | 
				
			||||||
    let translated = text;
 | 
					    let translated = text;
 | 
				
			||||||
@@ -76,25 +81,36 @@ export function translate_emoticons_to_names(text) {
 | 
				
			|||||||
    return translated;
 | 
					    return translated;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function contains_problematic_linkifier(content) {
 | 
				
			||||||
 | 
					    // If a linkifier doesn't start with some specified characters
 | 
				
			||||||
 | 
					    // then don't render it locally. It is workaround for the fact that
 | 
				
			||||||
 | 
					    // javascript regex doesn't support lookbehind.
 | 
				
			||||||
 | 
					    for (const re of linkifiers.get_linkifier_map().keys()) {
 | 
				
			||||||
 | 
					        const pattern = /[^\s"'(,:<]/.source + re.source + /(?!\w)/.source;
 | 
				
			||||||
 | 
					        const regex = new RegExp(pattern);
 | 
				
			||||||
 | 
					        if (regex.test(content)) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function contains_backend_only_syntax(content) {
 | 
					export function contains_backend_only_syntax(content) {
 | 
				
			||||||
    // Try to guess whether or not a message contains syntax that only the
 | 
					    // Try to guess whether or not a message contains syntax that only the
 | 
				
			||||||
    // backend Markdown processor can correctly handle.
 | 
					    // backend Markdown processor can correctly handle.
 | 
				
			||||||
    // If it doesn't, we can immediately render it client-side for local echo.
 | 
					    // If it doesn't, we can immediately render it client-side for local echo.
 | 
				
			||||||
    const markedup = backend_only_markdown_re.find((re) => re.test(content));
 | 
					    return contains_preview_link(content) || contains_problematic_linkifier(content);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If a linkifier doesn't start with some specified characters
 | 
					 | 
				
			||||||
    // then don't render it locally. It is workaround for the fact that
 | 
					 | 
				
			||||||
    // javascript regex doesn't support lookbehind.
 | 
					 | 
				
			||||||
    const linkifier_list = linkifiers.linkifier_list;
 | 
					 | 
				
			||||||
    const false_linkifier_match = linkifier_list.find((re) => {
 | 
					 | 
				
			||||||
        const pattern = /[^\s"'(,:<]/.source + re.pattern.source + /(?!\w)/.source;
 | 
					 | 
				
			||||||
        const regex = new RegExp(pattern);
 | 
					 | 
				
			||||||
        return regex.test(content);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return markedup !== undefined || false_linkifier_match !== undefined;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function apply_markdown(message) {
 | 
					export function parse({raw_content, helper_config}) {
 | 
				
			||||||
 | 
					    // Given the raw markdown content of a message (raw_content)
 | 
				
			||||||
 | 
					    // we return the HTML content (content) and flags.
 | 
				
			||||||
 | 
					    // Our caller passes a helper_config object that has several
 | 
				
			||||||
 | 
					    // helper functions for getting info about users, streams, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    helpers = helper_config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mentioned = false;
 | 
					    let mentioned = false;
 | 
				
			||||||
    let mentioned_group = false;
 | 
					    let mentioned_group = false;
 | 
				
			||||||
    let mentioned_wildcard = false;
 | 
					    let mentioned_wildcard = false;
 | 
				
			||||||
@@ -249,20 +265,20 @@ export function apply_markdown(message) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Our Python-Markdown processor appends two \n\n to input
 | 
					    // Our Python-Markdown processor appends two \n\n to input
 | 
				
			||||||
    message.content = marked(message.raw_content + "\n\n", options).trim();
 | 
					    const content = marked(raw_content + "\n\n", options).trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Simulate message flags for our locally rendered
 | 
					    // Simulate message flags for our locally rendered
 | 
				
			||||||
    // message. Messages the user themselves sent via the browser are
 | 
					    // message. Messages the user themselves sent via the browser are
 | 
				
			||||||
    // always marked as read.
 | 
					    // always marked as read.
 | 
				
			||||||
    message.flags = ["read"];
 | 
					    const flags = ["read"];
 | 
				
			||||||
    if (mentioned || mentioned_group) {
 | 
					    if (mentioned || mentioned_group) {
 | 
				
			||||||
        message.flags.push("mentioned");
 | 
					        flags.push("mentioned");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (mentioned_wildcard) {
 | 
					    if (mentioned_wildcard) {
 | 
				
			||||||
        message.flags.push("wildcard_mentioned");
 | 
					        flags.push("wildcard_mentioned");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    message.is_me_message = is_status_message(message.raw_content);
 | 
					    return {content, flags};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function add_topic_links(message) {
 | 
					export function add_topic_links(message) {
 | 
				
			||||||
@@ -272,11 +288,8 @@ export function add_topic_links(message) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    const topic = message.topic;
 | 
					    const topic = message.topic;
 | 
				
			||||||
    const links = [];
 | 
					    const links = [];
 | 
				
			||||||
    const linkifier_list = linkifiers.linkifier_list;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const linkifier of linkifier_list) {
 | 
					    for (const [pattern, url] of linkifiers.get_linkifier_map().entries()) {
 | 
				
			||||||
        const pattern = linkifier.pattern;
 | 
					 | 
				
			||||||
        const url = linkifier.url_format;
 | 
					 | 
				
			||||||
        let match;
 | 
					        let match;
 | 
				
			||||||
        while ((match = pattern.exec(topic)) !== null) {
 | 
					        while ((match = pattern.exec(topic)) !== null) {
 | 
				
			||||||
            let link_url = url;
 | 
					            let link_url = url;
 | 
				
			||||||
@@ -360,6 +373,20 @@ function handleEmoji(emoji_name) {
 | 
				
			|||||||
    return alt_text;
 | 
					    return alt_text;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleLinkifier(pattern, matches) {
 | 
				
			||||||
 | 
					    let url = linkifiers.get_linkifier_map().get(pattern);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let current_group = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const match of matches) {
 | 
				
			||||||
 | 
					        const back_ref = "\\" + current_group;
 | 
				
			||||||
 | 
					        url = url.replace(back_ref, match);
 | 
				
			||||||
 | 
					        current_group += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return url;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleTimestamp(time) {
 | 
					function handleTimestamp(time) {
 | 
				
			||||||
    let timeobject;
 | 
					    let timeobject;
 | 
				
			||||||
    if (Number.isNaN(Number(time))) {
 | 
					    if (Number.isNaN(Number(time))) {
 | 
				
			||||||
@@ -422,8 +449,15 @@ function handleTex(tex, fullmatch) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initialize(helper_config) {
 | 
					export function set_linkifier_regexes(regexes) {
 | 
				
			||||||
    helpers = helper_config;
 | 
					    // This needs to be called any time we modify our linkifier regexes,
 | 
				
			||||||
 | 
					    // until we find a less clumsy way to handle this.
 | 
				
			||||||
 | 
					    marked.InlineLexer.rules.zulip.linkifiers = regexes;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setup() {
 | 
				
			||||||
 | 
					    // Once we focus on supporting other platforms such as mobile,
 | 
				
			||||||
 | 
					    // we will export this function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function disable_markdown_regex(rules, name) {
 | 
					    function disable_markdown_regex(rules, name) {
 | 
				
			||||||
        rules[name] = {
 | 
					        rules[name] = {
 | 
				
			||||||
@@ -495,6 +529,7 @@ export function initialize(helper_config) {
 | 
				
			|||||||
        smartypants: false,
 | 
					        smartypants: false,
 | 
				
			||||||
        zulip: true,
 | 
					        zulip: true,
 | 
				
			||||||
        emojiHandler: handleEmoji,
 | 
					        emojiHandler: handleEmoji,
 | 
				
			||||||
 | 
					        linkifierHandler: handleLinkifier,
 | 
				
			||||||
        unicodeEmojiHandler: handleUnicodeEmoji,
 | 
					        unicodeEmojiHandler: handleUnicodeEmoji,
 | 
				
			||||||
        streamHandler: handleStream,
 | 
					        streamHandler: handleStream,
 | 
				
			||||||
        streamTopicHandler: handleStreamTopic,
 | 
					        streamTopicHandler: handleStreamTopic,
 | 
				
			||||||
@@ -504,3 +539,28 @@ export function initialize(helper_config) {
 | 
				
			|||||||
        preprocessors: [preprocess_code_blocks, preprocess_translate_emoticons],
 | 
					        preprocessors: [preprocess_code_blocks, preprocess_translate_emoticons],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE: Everything below this line is likely to be webapp-specific
 | 
				
			||||||
 | 
					//       and won't be used by future platforms such as mobile.
 | 
				
			||||||
 | 
					//       We may eventually move this code to a new file, but we want
 | 
				
			||||||
 | 
					//       to wait till the dust settles a bit on some other changes first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let webapp_helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function initialize(helper_config) {
 | 
				
			||||||
 | 
					    // This is generally only intended to be called by the webapp. Most
 | 
				
			||||||
 | 
					    // other platforms should call setup().
 | 
				
			||||||
 | 
					    webapp_helpers = helper_config;
 | 
				
			||||||
 | 
					    helpers = helper_config;
 | 
				
			||||||
 | 
					    setup();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function apply_markdown(message) {
 | 
				
			||||||
 | 
					    // This is generally only intended to be called by the webapp. Most
 | 
				
			||||||
 | 
					    // other platforms should call parse().
 | 
				
			||||||
 | 
					    const raw_content = message.raw_content;
 | 
				
			||||||
 | 
					    const {content, flags} = parse({raw_content, helper_config: webapp_helpers});
 | 
				
			||||||
 | 
					    message.content = content;
 | 
				
			||||||
 | 
					    message.flags = flags;
 | 
				
			||||||
 | 
					    message.is_me_message = is_status_message(raw_content);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,6 +98,7 @@ export function build_page() {
 | 
				
			|||||||
        user_can_change_avatar: settings_data.user_can_change_avatar(),
 | 
					        user_can_change_avatar: settings_data.user_can_change_avatar(),
 | 
				
			||||||
        user_role_text: people.get_user_type(page_params.user_id),
 | 
					        user_role_text: people.get_user_type(page_params.user_id),
 | 
				
			||||||
        default_language_name: settings_display.user_default_language_name,
 | 
					        default_language_name: settings_display.user_default_language_name,
 | 
				
			||||||
 | 
					        realm_push_notifications_enabled: page_params.realm_push_notifications_enabled,
 | 
				
			||||||
        settings_object: user_settings,
 | 
					        settings_object: user_settings,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@zulip/shared",
 | 
					  "name": "@zulip/shared",
 | 
				
			||||||
  "version": "0.0.9",
 | 
					  "version": "0.0.10",
 | 
				
			||||||
  "license": "Apache-2.0",
 | 
					  "license": "Apache-2.0",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "katex": "^0.15.3",
 | 
					    "katex": "^0.15.3",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
                    <th colspan="2" width="28%">{{t "Desktop"}}</th>
 | 
					                    <th colspan="2" width="28%">{{t "Desktop"}}</th>
 | 
				
			||||||
                    <th rowspan="2" width="14%" class="{{#if show_push_notifications_tooltip.push_notifications}}control-label-disabled{{/if}}">
 | 
					                    <th rowspan="2" width="14%" class="{{#if show_push_notifications_tooltip.push_notifications}}control-label-disabled{{/if}}">
 | 
				
			||||||
                        {{t "Mobile"}}
 | 
					                        {{t "Mobile"}}
 | 
				
			||||||
                        {{#if (not page_params.realm_push_notifications_enabled) }}
 | 
					                        {{#if (not realm_push_notifications_enabled) }}
 | 
				
			||||||
                        <i class="fa fa-question-circle settings-info-icon tippy-zulip-tooltip" data-tippy-content="{{t 'Mobile push notifications are not configured on this server.' }}"></i>
 | 
					                        <i class="fa fa-question-circle settings-info-icon tippy-zulip-tooltip" data-tippy-content="{{t 'Mobile push notifications are not configured on this server.' }}"></i>
 | 
				
			||||||
                        {{/if}}
 | 
					                        {{/if}}
 | 
				
			||||||
                    </th>
 | 
					                    </th>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,11 @@ clients should check the `zulip_feature_level` field, present in the
 | 
				
			|||||||
/register`](/api/register-queue) responses, to determine the API
 | 
					/register`](/api/register-queue) responses, to determine the API
 | 
				
			||||||
format used by the Zulip server that they are interacting with.
 | 
					format used by the Zulip server that they are interacting with.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Changes in Zulip 6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Feature levels 123-124 are reserved for future use in 5.x maintenance
 | 
				
			||||||
 | 
					releases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Changes in Zulip 5.0
 | 
					## Changes in Zulip 5.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Feature level 122**
 | 
					**Feature level 122**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -143,7 +143,7 @@
 | 
				
			|||||||
            <li><a href="/team/">{{ _("Team") }}</a> & <a href="/history/">{{ _("History") }}</a></li>
 | 
					            <li><a href="/team/">{{ _("Team") }}</a> & <a href="/history/">{{ _("History") }}</a></li>
 | 
				
			||||||
            <li><a href="https://twitter.com/zulip/">Twitter</a></li>
 | 
					            <li><a href="https://twitter.com/zulip/">Twitter</a></li>
 | 
				
			||||||
            <li><a href="/jobs/">{{ _("Jobs") }}</a></li>
 | 
					            <li><a href="/jobs/">{{ _("Jobs") }}</a></li>
 | 
				
			||||||
            <li><a href="/attribution">Website attributions</a></li>
 | 
					            <li><a href="/attribution">{{ _("Website attributions") }}</a></li>
 | 
				
			||||||
            <li><a href="https://github.com/sponsors/zulip">{{ _("Sponsor Zulip") }}</a></li>
 | 
					            <li><a href="https://github.com/sponsors/zulip">{{ _("Sponsor Zulip") }}</a></li>
 | 
				
			||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,8 @@ import sys
 | 
				
			|||||||
from collections import defaultdict
 | 
					from collections import defaultdict
 | 
				
			||||||
from typing import Dict, List
 | 
					from typing import Dict, List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bot_commits = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
 | 
					def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
 | 
				
			||||||
    for dataset in input:
 | 
					    for dataset in input:
 | 
				
			||||||
@@ -15,6 +17,8 @@ def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if committer_name.endswith("[bot]"):
 | 
					        if committer_name.endswith("[bot]"):
 | 
				
			||||||
            # Exclude dependabot[bot] and other GitHub bots.
 | 
					            # Exclude dependabot[bot] and other GitHub bots.
 | 
				
			||||||
 | 
					            global bot_commits
 | 
				
			||||||
 | 
					            bot_commits += commit_count
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        committer_dict[committer_name] += commit_count
 | 
					        committer_dict[committer_name] += commit_count
 | 
				
			||||||
@@ -131,11 +135,21 @@ print(
 | 
				
			|||||||
    f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}"
 | 
					    f"Commit range {lower_zulip_version}..{upper_zulip_version} corresponds to {lower_time} to {upper_time}"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					repository_dict: Dict[str, int] = defaultdict(int)
 | 
				
			||||||
out_dict: Dict[str, int] = defaultdict(int)
 | 
					out_dict: Dict[str, int] = defaultdict(int)
 | 
				
			||||||
subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
 | 
					subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
 | 
				
			||||||
zulip = retrieve_log("zulip", lower_zulip_version, upper_zulip_version)
 | 
					commit_count = len(
 | 
				
			||||||
print(f"Commit range for zulip/zulip: {lower_zulip_version[0:12]}..{upper_zulip_version[0:12]}")
 | 
					    subprocess.check_output(
 | 
				
			||||||
add_log(out_dict, zulip)
 | 
					        ["git", "log", "--pretty=oneline", f"{lower_zulip_version}..{upper_zulip_version}"],
 | 
				
			||||||
 | 
					        cwd=find_path("zulip"),
 | 
				
			||||||
 | 
					        text=True,
 | 
				
			||||||
 | 
					    ).splitlines()
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					repo_log = retrieve_log("zulip", lower_zulip_version, upper_zulip_version)
 | 
				
			||||||
 | 
					print(
 | 
				
			||||||
 | 
					    f"{commit_count} commits from zulip/zulip: {lower_zulip_version[0:12]}..{upper_zulip_version[0:12]}"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					add_log(out_dict, repo_log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: We should migrate the last couple repositories to use the
 | 
					# TODO: We should migrate the last couple repositories to use the
 | 
				
			||||||
# `main` default branch name and then simplify this.
 | 
					# `main` default branch name and then simplify this.
 | 
				
			||||||
@@ -163,9 +177,16 @@ for (full_repository, branch) in [
 | 
				
			|||||||
    subprocess.check_call(["git", "fetch"], cwd=find_path(repository))
 | 
					    subprocess.check_call(["git", "fetch"], cwd=find_path(repository))
 | 
				
			||||||
    lower_repo_version = find_last_commit_before_time(repository, branch, lower_time)
 | 
					    lower_repo_version = find_last_commit_before_time(repository, branch, lower_time)
 | 
				
			||||||
    upper_repo_version = find_last_commit_before_time(repository, branch, upper_time)
 | 
					    upper_repo_version = find_last_commit_before_time(repository, branch, upper_time)
 | 
				
			||||||
 | 
					    commit_count = len(
 | 
				
			||||||
 | 
					        subprocess.check_output(
 | 
				
			||||||
 | 
					            ["git", "log", "--pretty=oneline", f"{lower_repo_version}..{upper_repo_version}"],
 | 
				
			||||||
 | 
					            cwd=find_path(repository),
 | 
				
			||||||
 | 
					            text=True,
 | 
				
			||||||
 | 
					        ).splitlines()
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    repo_log = retrieve_log(repository, lower_repo_version, upper_repo_version)
 | 
					    repo_log = retrieve_log(repository, lower_repo_version, upper_repo_version)
 | 
				
			||||||
    print(
 | 
					    print(
 | 
				
			||||||
        f"Commit range for {full_repository}: {lower_repo_version[0:12]}..{upper_repo_version[0:12]}"
 | 
					        f"{commit_count} commits from {full_repository}: {lower_repo_version[0:12]}..{upper_repo_version[0:12]}"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    add_log(out_dict, repo_log)
 | 
					    add_log(out_dict, repo_log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -177,7 +198,8 @@ for committer_name, commit_count in sorted(
 | 
				
			|||||||
    print(str(commit_count) + "\t" + committer_name)
 | 
					    print(str(commit_count) + "\t" + committer_name)
 | 
				
			||||||
    grand_total += commit_count
 | 
					    grand_total += commit_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(f"Excluded {bot_commits} commits authored by bots.")
 | 
				
			||||||
print(
 | 
					print(
 | 
				
			||||||
    f"{grand_total} total commits by {len(out_dict)} contributors between "
 | 
					    f"{grand_total} total commits by {len(out_dict)} contributors between "
 | 
				
			||||||
    f"{lower_zulip_version} and {upper_repo_version}."
 | 
					    f"{lower_zulip_version} and {upper_zulip_version}."
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ZULIP_VERSION = "5.0"
 | 
					ZULIP_VERSION = "6.0-dev+git"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add information on number of commits and commit hash to version, if available
 | 
					# Add information on number of commits and commit hash to version, if available
 | 
				
			||||||
zulip_git_version_file = os.path.join(
 | 
					zulip_git_version_file = os.path.join(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -978,7 +978,6 @@ class StreamAdminTest(ZulipTestCase):
 | 
				
			|||||||
        self.assertTrue(attachment.is_realm_public)
 | 
					        self.assertTrue(attachment.is_realm_public)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        params = {
 | 
					        params = {
 | 
				
			||||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
					 | 
				
			||||||
            "is_private": orjson.dumps(True).decode(),
 | 
					            "is_private": orjson.dumps(True).decode(),
 | 
				
			||||||
            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
					            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -1000,7 +999,6 @@ class StreamAdminTest(ZulipTestCase):
 | 
				
			|||||||
        self.assertFalse(validate_attachment_request_for_spectator_access(realm, attachment))
 | 
					        self.assertFalse(validate_attachment_request_for_spectator_access(realm, attachment))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        params = {
 | 
					        params = {
 | 
				
			||||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
					 | 
				
			||||||
            "is_private": orjson.dumps(False).decode(),
 | 
					            "is_private": orjson.dumps(False).decode(),
 | 
				
			||||||
            "is_web_public": orjson.dumps(True).decode(),
 | 
					            "is_web_public": orjson.dumps(True).decode(),
 | 
				
			||||||
            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
					            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
				
			||||||
@@ -1025,7 +1023,6 @@ class StreamAdminTest(ZulipTestCase):
 | 
				
			|||||||
        self.assertTrue(attachment.is_realm_public)
 | 
					        self.assertTrue(attachment.is_realm_public)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        params = {
 | 
					        params = {
 | 
				
			||||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
					 | 
				
			||||||
            "is_private": orjson.dumps(False).decode(),
 | 
					            "is_private": orjson.dumps(False).decode(),
 | 
				
			||||||
            "is_web_public": orjson.dumps(False).decode(),
 | 
					            "is_web_public": orjson.dumps(False).decode(),
 | 
				
			||||||
            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
					            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user