mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	Compare commits
	
		
			17 Commits
		
	
	
		
			6.0-dev
			...
			shared-0.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3ff2bcf62a | ||
| 
						 | 
					7de1e7c477 | ||
| 
						 | 
					b7f670f5a0 | ||
| 
						 | 
					2a240d3e19 | ||
| 
						 | 
					efdf2c8fe3 | ||
| 
						 | 
					71c12e313c | ||
| 
						 | 
					da6d687215 | ||
| 
						 | 
					58799c6ca1 | ||
| 
						 | 
					8d9e6d6b87 | ||
| 
						 | 
					935cb605a5 | ||
| 
						 | 
					ca38b33346 | ||
| 
						 | 
					0008a76703 | ||
| 
						 | 
					d9bf8baca1 | ||
| 
						 | 
					46b19fe8bd | ||
| 
						 | 
					c1103e4c7b | ||
| 
						 | 
					c8dba33408 | ||
| 
						 | 
					6ea9947991 | 
							
								
								
									
										12
									
								
								.mailmap
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.mailmap
									
									
									
									
									
								
							@@ -12,6 +12,8 @@
 | 
			
		||||
#     # shows raw names/emails, filtered by mapped name:
 | 
			
		||||
#   $ 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> <github@chmrr.net>
 | 
			
		||||
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>
 | 
			
		||||
Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
 | 
			
		||||
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>
 | 
			
		||||
BIKI DAS <bikid475@gmail.com>
 | 
			
		||||
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>
 | 
			
		||||
Jessica McKellar <jesstess@mit.edu> <jesstess@humbughq.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@zulip.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>
 | 
			
		||||
Palash Raghuwanshi <singhpalash0@gmail.com>
 | 
			
		||||
Parth <mittalparth22@gmail.com>
 | 
			
		||||
Priyam Seth <sethpriyam1@gmail.com> <b19188@students.iitmandi.ac.in>
 | 
			
		||||
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@kandralabs.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>
 | 
			
		||||
Scott Feeney <scott@oceanbase.org> <scott@humbughq.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>
 | 
			
		||||
Yogesh Sirsat <yogeshsirsat56@gmail.com>
 | 
			
		||||
Zeeshan Equbal <equbalzeeshan@gmail.com> <54993043+zee-bit@users.noreply.github.com>
 | 
			
		||||
Zeeshan Equbal <equbalzeeshan@gmail.com>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# Version history
 | 
			
		||||
 | 
			
		||||
This page the release history for the Zulip server.  See also the
 | 
			
		||||
This page the release history for the Zulip server. See also the
 | 
			
		||||
[Zulip release lifecycle](../overview/release-lifecycle.md).
 | 
			
		||||
 | 
			
		||||
## Zulip 6.x series
 | 
			
		||||
@@ -8,7 +8,7 @@ This page the release history for the Zulip server.  See also the
 | 
			
		||||
### 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
 | 
			
		||||
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
 | 
			
		||||
@@ -1576,7 +1576,7 @@ Zulip installations; it has minimal changes for existing servers.
 | 
			
		||||
  disruption by running this migration first, before beginning the
 | 
			
		||||
  user-facing downtime. However, if you'd like to watch the downtime
 | 
			
		||||
  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.
 | 
			
		||||
 | 
			
		||||
#### Full feature changelog
 | 
			
		||||
@@ -1965,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
 | 
			
		||||
  user-facing downtime. However, if you'd like to watch the downtime
 | 
			
		||||
  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.
 | 
			
		||||
 | 
			
		||||
- We've removed support for an uncommon legacy deployment model where
 | 
			
		||||
@@ -2590,16 +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
 | 
			
		||||
easily read them all when upgrading across multiple releases.
 | 
			
		||||
 | 
			
		||||
* [Draft upgrade notes for 6.0](#upgrade-notes-for-6-0)
 | 
			
		||||
- [Upgrade notes for 5.0](#upgrade-notes-for-50)
 | 
			
		||||
- [Upgrade notes for 4.0](#upgrade-notes-for-40)
 | 
			
		||||
- [Upgrade notes for 3.0](#upgrade-notes-for-30)
 | 
			
		||||
- [Upgrade notes for 2.1.5](#upgrade-notes-for-215)
 | 
			
		||||
- [Upgrade notes for 2.1.0](#upgrade-notes-for-210)
 | 
			
		||||
- [Upgrade notes for 2.0.0](#upgrade-notes-for-200)
 | 
			
		||||
- [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)
 | 
			
		||||
- [Draft upgrade notes for 6.0](#upgrade-notes-for-60)
 | 
			
		||||
 | 
			
		||||
* [Upgrade notes for 5.0](#upgrade-notes-for-50)
 | 
			
		||||
* [Upgrade notes for 4.0](#upgrade-notes-for-40)
 | 
			
		||||
* [Upgrade notes for 3.0](#upgrade-notes-for-30)
 | 
			
		||||
* [Upgrade notes for 2.1.5](#upgrade-notes-for-215)
 | 
			
		||||
* [Upgrade notes for 2.1.0](#upgrade-notes-for-210)
 | 
			
		||||
* [Upgrade notes for 2.0.0](#upgrade-notes-for-200)
 | 
			
		||||
* [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
 | 
			
		||||
[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'] }
 | 
			
		||||
  service { 'chrony': ensure => 'running', require => Package['chrony'] }
 | 
			
		||||
  service { 'chrony': require => Package['chrony'] }
 | 
			
		||||
  package { $base_packages: ensure => 'installed' }
 | 
			
		||||
 | 
			
		||||
  group { 'zulip':
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,7 @@ export function build_page() {
 | 
			
		||||
            settings_config.create_web_public_stream_policy_values,
 | 
			
		||||
        disable_enable_spectator_access_setting: !page_params.server_web_public_streams_enabled,
 | 
			
		||||
        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") {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,10 @@
 | 
			
		||||
import marked from "../third/marked/lib/marked";
 | 
			
		||||
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as markdown from "./markdown";
 | 
			
		||||
 | 
			
		||||
const linkifier_map = new Map();
 | 
			
		||||
export let linkifier_list = [];
 | 
			
		||||
const linkifier_map = new Map(); // regex -> url
 | 
			
		||||
 | 
			
		||||
function handleLinkifier(pattern, matches) {
 | 
			
		||||
    let url = 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;
 | 
			
		||||
export function get_linkifier_map() {
 | 
			
		||||
    return linkifier_map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function python_to_js_linkifier(pattern, url) {
 | 
			
		||||
@@ -78,11 +66,7 @@ function python_to_js_linkifier(pattern, url) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function update_linkifier_rules(linkifiers) {
 | 
			
		||||
    // Update the marked parser with our particular set of linkifiers
 | 
			
		||||
    linkifier_map.clear();
 | 
			
		||||
    linkifier_list = [];
 | 
			
		||||
 | 
			
		||||
    const marked_rules = [];
 | 
			
		||||
 | 
			
		||||
    for (const linkifier of linkifiers) {
 | 
			
		||||
        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_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) {
 | 
			
		||||
    update_linkifier_rules(linkifiers);
 | 
			
		||||
 | 
			
		||||
    marked.setOptions({linkifierHandler: handleLinkifier});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,9 @@ import * as linkifiers from "./linkifiers";
 | 
			
		||||
// for example usage.
 | 
			
		||||
let helpers;
 | 
			
		||||
 | 
			
		||||
// Regexes that match some of our common backend-only Markdown syntax
 | 
			
		||||
const backend_only_markdown_re = [
 | 
			
		||||
// If we see preview-related syntax in our content, we will need the
 | 
			
		||||
// backend to render it.
 | 
			
		||||
const preview_regexes = [
 | 
			
		||||
    // 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
 | 
			
		||||
 | 
			
		||||
@@ -33,9 +34,13 @@ const backend_only_markdown_re = [
 | 
			
		||||
 | 
			
		||||
    // 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) {
 | 
			
		||||
    // Translates emoticons in a string to their colon syntax.
 | 
			
		||||
    let translated = text;
 | 
			
		||||
@@ -76,25 +81,36 @@ export function translate_emoticons_to_names(text) {
 | 
			
		||||
    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) {
 | 
			
		||||
    // Try to guess whether or not a message contains syntax that only the
 | 
			
		||||
    // backend Markdown processor can correctly handle.
 | 
			
		||||
    // 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));
 | 
			
		||||
 | 
			
		||||
    // 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;
 | 
			
		||||
    return contains_preview_link(content) || contains_problematic_linkifier(content);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_group = false;
 | 
			
		||||
    let mentioned_wildcard = false;
 | 
			
		||||
@@ -249,20 +265,20 @@ export function apply_markdown(message) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 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
 | 
			
		||||
    // message. Messages the user themselves sent via the browser are
 | 
			
		||||
    // always marked as read.
 | 
			
		||||
    message.flags = ["read"];
 | 
			
		||||
    const flags = ["read"];
 | 
			
		||||
    if (mentioned || mentioned_group) {
 | 
			
		||||
        message.flags.push("mentioned");
 | 
			
		||||
        flags.push("mentioned");
 | 
			
		||||
    }
 | 
			
		||||
    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) {
 | 
			
		||||
@@ -272,11 +288,8 @@ export function add_topic_links(message) {
 | 
			
		||||
    }
 | 
			
		||||
    const topic = message.topic;
 | 
			
		||||
    const links = [];
 | 
			
		||||
    const linkifier_list = linkifiers.linkifier_list;
 | 
			
		||||
 | 
			
		||||
    for (const linkifier of linkifier_list) {
 | 
			
		||||
        const pattern = linkifier.pattern;
 | 
			
		||||
        const url = linkifier.url_format;
 | 
			
		||||
    for (const [pattern, url] of linkifiers.get_linkifier_map().entries()) {
 | 
			
		||||
        let match;
 | 
			
		||||
        while ((match = pattern.exec(topic)) !== null) {
 | 
			
		||||
            let link_url = url;
 | 
			
		||||
@@ -360,6 +373,20 @@ function handleEmoji(emoji_name) {
 | 
			
		||||
    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) {
 | 
			
		||||
    let timeobject;
 | 
			
		||||
    if (Number.isNaN(Number(time))) {
 | 
			
		||||
@@ -422,8 +449,15 @@ function handleTex(tex, fullmatch) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initialize(helper_config) {
 | 
			
		||||
    helpers = helper_config;
 | 
			
		||||
export function set_linkifier_regexes(regexes) {
 | 
			
		||||
    // 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) {
 | 
			
		||||
        rules[name] = {
 | 
			
		||||
@@ -495,6 +529,7 @@ export function initialize(helper_config) {
 | 
			
		||||
        smartypants: false,
 | 
			
		||||
        zulip: true,
 | 
			
		||||
        emojiHandler: handleEmoji,
 | 
			
		||||
        linkifierHandler: handleLinkifier,
 | 
			
		||||
        unicodeEmojiHandler: handleUnicodeEmoji,
 | 
			
		||||
        streamHandler: handleStream,
 | 
			
		||||
        streamTopicHandler: handleStreamTopic,
 | 
			
		||||
@@ -504,3 +539,28 @@ export function initialize(helper_config) {
 | 
			
		||||
        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_role_text: people.get_user_type(page_params.user_id),
 | 
			
		||||
        default_language_name: settings_display.user_default_language_name,
 | 
			
		||||
        realm_push_notifications_enabled: page_params.realm_push_notifications_enabled,
 | 
			
		||||
        settings_object: user_settings,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@zulip/shared",
 | 
			
		||||
  "version": "0.0.9",
 | 
			
		||||
  "version": "0.0.10",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "katex": "^0.15.3",
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
                    <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}}">
 | 
			
		||||
                        {{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>
 | 
			
		||||
                        {{/if}}
 | 
			
		||||
                    </th>
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@
 | 
			
		||||
            <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="/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>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from typing import Dict, List
 | 
			
		||||
 | 
			
		||||
bot_commits = 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_log(committer_dict: Dict[str, int], input: List[str]) -> None:
 | 
			
		||||
    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]"):
 | 
			
		||||
            # Exclude dependabot[bot] and other GitHub bots.
 | 
			
		||||
            global bot_commits
 | 
			
		||||
            bot_commits += commit_count
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        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}"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
repository_dict: Dict[str, int] = defaultdict(int)
 | 
			
		||||
out_dict: Dict[str, int] = defaultdict(int)
 | 
			
		||||
subprocess.check_call(["git", "fetch"], cwd=find_path("zulip"))
 | 
			
		||||
zulip = retrieve_log("zulip", lower_zulip_version, upper_zulip_version)
 | 
			
		||||
print(f"Commit range for zulip/zulip: {lower_zulip_version[0:12]}..{upper_zulip_version[0:12]}")
 | 
			
		||||
add_log(out_dict, zulip)
 | 
			
		||||
commit_count = len(
 | 
			
		||||
    subprocess.check_output(
 | 
			
		||||
        ["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
 | 
			
		||||
# `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))
 | 
			
		||||
    lower_repo_version = find_last_commit_before_time(repository, branch, lower_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)
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
@@ -177,7 +198,8 @@ for committer_name, commit_count in sorted(
 | 
			
		||||
    print(str(commit_count) + "\t" + committer_name)
 | 
			
		||||
    grand_total += commit_count
 | 
			
		||||
 | 
			
		||||
print(f"Excluded {bot_commits} commits authored by bots.")
 | 
			
		||||
print(
 | 
			
		||||
    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}."
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -978,7 +978,6 @@ class StreamAdminTest(ZulipTestCase):
 | 
			
		||||
        self.assertTrue(attachment.is_realm_public)
 | 
			
		||||
 | 
			
		||||
        params = {
 | 
			
		||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
			
		||||
            "is_private": 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))
 | 
			
		||||
 | 
			
		||||
        params = {
 | 
			
		||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
			
		||||
            "is_private": orjson.dumps(False).decode(),
 | 
			
		||||
            "is_web_public": 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)
 | 
			
		||||
 | 
			
		||||
        params = {
 | 
			
		||||
            "stream_name": orjson.dumps("test_stream").decode(),
 | 
			
		||||
            "is_private": orjson.dumps(False).decode(),
 | 
			
		||||
            "is_web_public": orjson.dumps(False).decode(),
 | 
			
		||||
            "history_public_to_subscribers": orjson.dumps(True).decode(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user