refactor: Rename filter to linkifier in frontend code and docs.

This only leaves `page_params.realm_filters`, which
will be changed in further commits along with the
API change.
This commit is contained in:
Abhijeet Prasad Bodas 2021-03-13 22:45:14 +05:30 committed by Tim Abbott
parent c6d1fbd051
commit 9223dced3b
23 changed files with 188 additions and 181 deletions

View File

@ -484,11 +484,11 @@ run_test("realm_emoji", (override) => {
}
});
run_test("realm_filters", (override) => {
run_test("linkifier", (override) => {
const event = event_fixtures.realm_filters;
page_params.realm_filters = [];
override(settings_linkifiers, "populate_filters", noop);
override(markdown, "update_realm_filter_rules", noop);
override(settings_linkifiers, "populate_linkifiers", noop);
override(markdown, "update_linkifier_rules", noop);
dispatch(event);
assert_same(page_params.realm_filters, event.realm_filters);
});

View File

@ -390,23 +390,23 @@ run_test("marked", () => {
expected:
'<blockquote>\n<p>Mention in quote: <span class="user-mention silent" data-user-id="101">Cordelia Lear</span></p>\n</blockquote>\n<p>Mention outside quote: <span class="user-mention" data-user-id="101">@Cordelia Lear</span></p>',
},
// Test only those realm filters which don't return True for
// Test only those linkifiers which don't return True for
// `contains_backend_only_syntax()`. Those which return True
// are tested separately.
{
input: "This is a realm filter #1234 with text after it",
input: "This is a linkifier #1234 with text after it",
expected:
'<p>This is a realm filter <a href="https://trac.example.com/ticket/1234" title="https://trac.example.com/ticket/1234">#1234</a> with text after it</p>',
'<p>This is a linkifier <a href="https://trac.example.com/ticket/1234" title="https://trac.example.com/ticket/1234">#1234</a> with text after it</p>',
},
{input: "#1234is not a realm filter.", expected: "<p>#1234is not a realm filter.</p>"},
{input: "#1234is not a linkifier.", expected: "<p>#1234is not a linkifier.</p>"},
{
input: "A pattern written as #1234is not a realm filter.",
expected: "<p>A pattern written as #1234is not a realm filter.</p>",
input: "A pattern written as #1234is not a linkifier.",
expected: "<p>A pattern written as #1234is not a linkifier.</p>",
},
{
input: "This is a realm filter with ZGROUP_123:45 groups",
input: "This is a linkifier with ZGROUP_123:45 groups",
expected:
'<p>This is a realm filter with <a href="https://zone_45.zulip.net/ticket/123" title="https://zone_45.zulip.net/ticket/123">ZGROUP_123:45</a> groups</p>',
'<p>This is a linkifier with <a href="https://zone_45.zulip.net/ticket/123" title="https://zone_45.zulip.net/ticket/123">ZGROUP_123:45</a> groups</p>',
},
{input: "Test *italic*", expected: "<p>Test <em>italic</em></p>"},
{
@ -453,8 +453,8 @@ run_test("marked", () => {
},
{input: "@*notagroup*", expected: "<p>@*notagroup*</p>"},
{
input: "This is a realm filter `hello` with text after it",
expected: "<p>This is a realm filter <code>hello</code> with text after it</p>",
input: "This is a linkifier `hello` with text after it",
expected: "<p>This is a linkifier <code>hello</code> with text after it</p>",
},
// Test the emoticon conversion
{input: ":)", expected: "<p>:)</p>"},
@ -650,39 +650,39 @@ run_test("message_flags", () => {
assert.equal(message.mentioned, false);
});
run_test("backend_only_realm_filters", () => {
markdown.update_realm_filter_rules(page_params.realm_filters);
run_test("backend_only_linkifiers", () => {
markdown.update_linkifier_rules(page_params.realm_filters);
const backend_only_realm_filters = [
const backend_only_linkifiers = [
"Here is the PR-#123.",
"Function abc() was introduced in (PR)#123.",
];
for (const content of backend_only_realm_filters) {
for (const content of backend_only_linkifiers) {
assert.equal(markdown.contains_backend_only_syntax(content), true);
}
});
run_test("python_to_js_filter", () => {
// The only way to reach python_to_js_filter is indirectly, hence the call
// to update_realm_filter_rules.
markdown.update_realm_filter_rules([["/a(?im)a/g"], ["/a(?L)a/g"]]);
let actual_value = marked.InlineLexer.rules.zulip.realm_filters;
run_test("python_to_js_linkifier", () => {
// The only way to reach python_to_js_linkifier is indirectly, hence the call
// to update_linkifier_rules.
markdown.update_linkifier_rules([["/a(?im)a/g"], ["/a(?L)a/g"]]);
let actual_value = marked.InlineLexer.rules.zulip.linkifiers;
let expected_value = [/\/aa\/g(?!\w)/gim, /\/aa\/g(?!\w)/g];
assert.deepEqual(actual_value, expected_value);
// Test case with multiple replacements.
markdown.update_realm_filter_rules([
markdown.update_linkifier_rules([
["#cf(?P<contest>\\d+)(?P<problem>[A-Z][\\dA-Z]*)", "http://google.com"],
]);
actual_value = marked.InlineLexer.rules.zulip.realm_filters;
actual_value = marked.InlineLexer.rules.zulip.linkifiers;
expected_value = [/#cf(\d+)([A-Z][\dA-Z]*)(?!\w)/g];
assert.deepEqual(actual_value, expected_value);
// Test incorrect syntax.
blueslip.expect(
"error",
"python_to_js_filter: Invalid regular expression: /!@#@(!#&((!&(@#((?!\\w)/: Unterminated group",
"python_to_js_linkifier: Invalid regular expression: /!@#@(!#&((!&(@#((?!\\w)/: Unterminated group",
);
markdown.update_realm_filter_rules([["!@#@(!#&((!&(@#(", "http://google.com"]]);
actual_value = marked.InlineLexer.rules.zulip.realm_filters;
markdown.update_linkifier_rules([["!@#@(!#&((!&(@#(", "http://google.com"]]);
actual_value = marked.InlineLexer.rules.zulip.linkifiers;
expected_value = [];
assert.deepEqual(actual_value, expected_value);
});

View File

@ -5,56 +5,59 @@ import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common";
async function test_add_linkifier(page: Page): Promise<void> {
await page.waitForSelector(".admin-filter-form", {visible: true});
await common.fill_form(page, "form.admin-filter-form", {
await page.waitForSelector(".admin-linkifier-form", {visible: true});
await common.fill_form(page, "form.admin-linkifier-form", {
pattern: "#(?P<id>[0-9]+)",
url_format_string: "https://trac.example.com/ticket/%(id)s",
});
await page.click("form.admin-filter-form button.button");
await page.click("form.admin-linkifier-form button.button");
const admin_filter_status_selector = "div#admin-filter-status";
await page.waitForSelector(admin_filter_status_selector, {visible: true});
const admin_filter_status = await common.get_text_from_selector(
const admin_linkifier_status_selector = "div#admin-linkifier-status";
await page.waitForSelector(admin_linkifier_status_selector, {visible: true});
const admin_linkifier_status = await common.get_text_from_selector(
page,
admin_filter_status_selector,
admin_linkifier_status_selector,
);
assert.strictEqual(admin_filter_status, "Custom filter added!");
assert.strictEqual(admin_linkifier_status, "Custom linkifier added!");
await page.waitForSelector(".filter_row", {visible: true});
await page.waitForSelector(".linkifier_row", {visible: true});
assert.strictEqual(
await common.get_text_from_selector(page, ".filter_row span.filter_pattern"),
await common.get_text_from_selector(page, ".linkifier_row span.linkifier_pattern"),
"#(?P<id>[0-9]+)",
);
assert.strictEqual(
await common.get_text_from_selector(page, ".filter_row span.filter_url_format_string"),
await common.get_text_from_selector(
page,
".linkifier_row span.linkifier_url_format_string",
),
"https://trac.example.com/ticket/%(id)s",
);
}
async function test_delete_linkifier(page: Page): Promise<void> {
await page.click(".filter_row button");
await page.waitForSelector(".filter_row", {hidden: true});
await page.click(".linkifier_row button");
await page.waitForSelector(".linkifier_row", {hidden: true});
}
async function test_invalid_linkifier_pattern(page: Page): Promise<void> {
await page.waitForSelector(".admin-filter-form", {visible: true});
await common.fill_form(page, "form.admin-filter-form", {
await page.waitForSelector(".admin-linkifier-form", {visible: true});
await common.fill_form(page, "form.admin-linkifier-form", {
pattern: "a$",
url_format_string: "https://trac.example.com/ticket/%(id)s",
});
await page.click("form.admin-filter-form button.button");
await page.click("form.admin-linkifier-form button.button");
await page.waitForSelector("div#admin-filter-pattern-status", {visible: true});
await page.waitForSelector("div#admin-linkifier-pattern-status", {visible: true});
assert.strictEqual(
await common.get_text_from_selector(page, "div#admin-filter-pattern-status"),
await common.get_text_from_selector(page, "div#admin-linkifier-pattern-status"),
"Failed: Invalid filter pattern. Valid characters are [ a-zA-Z_#=/:+!-].",
);
}
async function realm_linkifier_test(page: Page): Promise<void> {
async function linkifier_test(page: Page): Promise<void> {
await common.log_in(page);
await common.manage_organization(page);
await page.click("li[data-section='filter-settings']");
await page.click("li[data-section='linkifier-settings']");
await test_add_linkifier(page);
await test_delete_linkifier(page);
@ -63,4 +66,4 @@ async function realm_linkifier_test(page: Page): Promise<void> {
await common.log_out(page);
}
common.run_test(realm_linkifier_test);
common.run_test(linkifier_test);

View File

@ -22,8 +22,8 @@ import * as message_store from "./message_store";
// for example usage.
let helpers;
const realm_filter_map = new Map();
let realm_filter_list = [];
const linkifier_map = new Map();
let linkifier_list = [];
// Regexes that match some of our common backend-only Markdown syntax
const backend_only_markdown_re = [
@ -84,15 +84,15 @@ export function contains_backend_only_syntax(content) {
// 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 realm filter doesn't start with some specified characters
// 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 false_filter_match = realm_filter_list.find((re) => {
const false_linkifier_match = linkifier_list.find((re) => {
const pattern = /[^\s"'(,:<]/.source + re[0].source + /(?!\w)/.source;
const regex = new RegExp(pattern);
return regex.test(content);
});
return markedup !== undefined || false_filter_match !== undefined;
return markedup !== undefined || false_linkifier_match !== undefined;
}
export function apply_markdown(message) {
@ -212,9 +212,9 @@ export function add_topic_links(message) {
const topic = message.topic;
let links = [];
for (const realm_filter of realm_filter_list) {
const pattern = realm_filter[0];
const url = realm_filter[1];
for (const linkifier of linkifier_list) {
const pattern = linkifier[0];
const url = linkifier[1];
let match;
while ((match = pattern.exec(topic)) !== null) {
let link_url = url;
@ -340,8 +340,8 @@ function handleStreamTopic(stream_name, topic) {
)}" href="/${_.escape(href)}">${_.escape(text)}</a>`;
}
function handleRealmFilter(pattern, matches) {
let url = realm_filter_map.get(pattern);
function handleLinkifier(pattern, matches) {
let url = linkifier_map.get(pattern);
let current_group = 1;
@ -367,7 +367,7 @@ function handleTex(tex, fullmatch) {
}
}
function python_to_js_filter(pattern, url) {
function python_to_js_linkifier(pattern, url) {
// Converts a python named-group regex to a javascript-compatible numbered
// group regex... with a regex!
const named_group_re = /\(?P<([^>]+?)>/g;
@ -404,7 +404,7 @@ function python_to_js_filter(pattern, url) {
pattern = pattern.replace(inline_flag_re, "");
}
// Ideally we should have been checking that realm filters
// Ideally we should have been checking that linkifiers
// begin with certain characters but since there is no
// support for negative lookbehind in javascript, we check
// for this condition in `contains_backend_only_syntax()`
@ -418,36 +418,36 @@ function python_to_js_filter(pattern, url) {
final_regex = new RegExp(pattern, js_flags);
} catch (error) {
// We have an error computing the generated regex syntax.
// We'll ignore this realm filter for now, but log this
// We'll ignore this linkifier for now, but log this
// failure for debugging later.
blueslip.error("python_to_js_filter: " + error.message);
blueslip.error("python_to_js_linkifier: " + error.message);
}
return [final_regex, url];
}
export function update_realm_filter_rules(realm_filters) {
// Update the marked parser with our particular set of realm filters
realm_filter_map.clear();
realm_filter_list = [];
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 [pattern, url] of realm_filters) {
const [regex, final_url] = python_to_js_filter(pattern, url);
for (const [pattern, url] of linkifiers) {
const [regex, final_url] = python_to_js_linkifier(pattern, url);
if (!regex) {
// Skip any realm filters that could not be converted
// Skip any linkifiers that could not be converted
continue;
}
realm_filter_map.set(regex, final_url);
realm_filter_list.push([regex, final_url]);
linkifier_map.set(regex, final_url);
linkifier_list.push([regex, final_url]);
marked_rules.push(regex);
}
marked.InlineLexer.rules.zulip.realm_filters = marked_rules;
marked.InlineLexer.rules.zulip.linkifiers = marked_rules;
}
export function initialize(realm_filters, helper_config) {
export function initialize(linkifiers, helper_config) {
helpers = helper_config;
function disable_markdown_regex(rules, name) {
@ -506,7 +506,7 @@ export function initialize(realm_filters, helper_config) {
// Disable autolink as (a) it is not used in our backend and (b) it interferes with @mentions
disable_markdown_regex(marked.InlineLexer.rules.zulip, "autolink");
update_realm_filter_rules(realm_filters);
update_linkifier_rules(linkifiers);
// Tell our fenced code preprocessor how to insert arbitrary
// HTML into the output. This generated HTML is safe to not escape
@ -525,7 +525,7 @@ export function initialize(realm_filters, helper_config) {
unicodeEmojiHandler: handleUnicodeEmoji,
streamHandler: handleStream,
streamTopicHandler: handleStreamTopic,
realmFilterHandler: handleRealmFilter,
linkifierHandler: handleLinkifier,
texHandler: handleTex,
timestampHandler: handleTimestamp,
renderer: r,

View File

@ -286,8 +286,8 @@ export function dispatch_normal_event(event) {
case "realm_filters":
page_params.realm_filters = event.realm_filters;
markdown.update_realm_filter_rules(page_params.realm_filters);
settings_linkifiers.populate_filters(page_params.realm_filters);
markdown.update_linkifier_rules(page_params.realm_filters);
settings_linkifiers.populate_linkifiers(page_params.realm_filters);
break;
case "realm_domains": {

View File

@ -1,6 +1,6 @@
import $ from "jquery";
import render_admin_filter_list from "../templates/admin_filter_list.hbs";
import render_admin_linkifier_list from "../templates/admin_linkifier_list.hbs";
import * as channel from "./channel";
import * as ListWidget from "./list_widget";
@ -39,42 +39,42 @@ function sort_url(a, b) {
return compare_by_index(a, b, 1);
}
export function populate_filters(filters_data) {
export function populate_linkifiers(linkifiers_data) {
if (!meta.loaded) {
return;
}
const filters_table = $("#admin_filters_table").expectOne();
ListWidget.create(filters_table, filters_data, {
const linkifiers_table = $("#admin_linkifiers_table").expectOne();
ListWidget.create(linkifiers_table, linkifiers_data, {
name: "linkifiers_list",
modifier(filter) {
return render_admin_filter_list({
filter: {
pattern: filter[0],
url_format_string: filter[1],
id: filter[2],
modifier(linkifier) {
return render_admin_linkifier_list({
linkifier: {
pattern: linkifier[0],
url_format_string: linkifier[1],
id: linkifier[2],
},
can_modify: page_params.is_admin,
});
},
filter: {
element: filters_table.closest(".settings-section").find(".search"),
element: linkifiers_table.closest(".settings-section").find(".search"),
predicate(item, value) {
return (
item[0].toLowerCase().includes(value) || item[1].toLowerCase().includes(value)
);
},
onupdate() {
ui.reset_scrollbar(filters_table);
ui.reset_scrollbar(linkifiers_table);
},
},
parent_container: $("#filter-settings").expectOne(),
parent_container: $("#linkifier-settings").expectOne(),
init_sort: [sort_pattern],
sort_fields: {
pattern: sort_pattern,
url: sort_url,
},
simplebar_container: $("#filter-settings .progressive-table-wrapper"),
simplebar_container: $("#linkifier-settings .progressive-table-wrapper"),
});
loading.destroy_indicator($("#admin_page_filters_loading_indicator"));
@ -91,16 +91,16 @@ export function build_page() {
// create loading indicators
loading.make_indicator($("#admin_page_filters_loading_indicator"));
// Populate filters table
populate_filters(page_params.realm_filters);
// Populate linkifiers table
populate_linkifiers(page_params.realm_filters);
$(".admin_filters_table").on("click", ".delete", function (e) {
$(".admin_linkifiers_table").on("click", ".delete", function (e) {
e.preventDefault();
e.stopPropagation();
const btn = $(this);
channel.del({
url: "/json/realm/filters/" + encodeURIComponent(btn.attr("data-filter-id")),
url: "/json/realm/filters/" + encodeURIComponent(btn.attr("data-linkifier-id")),
error(xhr) {
ui_report.generic_row_button_error(xhr, btn);
},
@ -111,38 +111,38 @@ export function build_page() {
});
});
$(".organization form.admin-filter-form")
$(".organization form.admin-linkifier-form")
.off("submit")
.on("submit", function (e) {
e.preventDefault();
e.stopPropagation();
const filter_status = $("#admin-filter-status");
const pattern_status = $("#admin-filter-pattern-status");
const format_status = $("#admin-filter-format-status");
const add_filter_button = $(".new-filter-form button");
add_filter_button.prop("disabled", true);
filter_status.hide();
const linkifier_status = $("#admin-linkifier-status");
const pattern_status = $("#admin-linkifier-pattern-status");
const format_status = $("#admin-linkifier-format-status");
const add_linkifier_button = $(".new-linkifier-form button");
add_linkifier_button.prop("disabled", true);
linkifier_status.hide();
pattern_status.hide();
format_status.hide();
const filter = {};
const linkifier = {};
for (const obj of $(this).serializeArray()) {
filter[obj.name] = obj.value;
linkifier[obj.name] = obj.value;
}
channel.post({
url: "/json/realm/filters",
data: $(this).serialize(),
success(data) {
$("#filter_pattern").val("");
$("#filter_format_string").val("");
add_filter_button.prop("disabled", false);
filter.id = data.id;
ui_report.success(i18n.t("Custom filter added!"), filter_status);
$("#linkifier_pattern").val("");
$("#linkifier_format_string").val("");
add_linkifier_button.prop("disabled", false);
linkifier.id = data.id;
ui_report.success(i18n.t("Custom linkifier added!"), linkifier_status);
},
error(xhr) {
const errors = JSON.parse(xhr.responseText).errors;
add_filter_button.prop("disabled", false);
add_linkifier_button.prop("disabled", false);
if (errors.pattern !== undefined) {
xhr.responseText = JSON.stringify({msg: errors.pattern});
ui_report.error(i18n.t("Failed"), xhr, pattern_status);
@ -153,7 +153,7 @@ export function build_page() {
}
if (errors.__all__ !== undefined) {
xhr.responseText = JSON.stringify({msg: errors.__all__});
ui_report.error(i18n.t("Failed"), xhr, filter_status);
ui_report.error(i18n.t("Failed"), xhr, linkifier_status);
}
},
});

View File

@ -56,7 +56,7 @@ export function initialize() {
load_func_dict.set("org_users", settings_users.set_up_humans);
load_func_dict.set("emoji-settings", settings_emoji.set_up);
load_func_dict.set("default-streams-list", settings_streams.set_up);
load_func_dict.set("filter-settings", settings_linkifiers.set_up);
load_func_dict.set("linkifier-settings", settings_linkifiers.set_up);
load_func_dict.set("invites-list-admin", settings_invites.set_up);
load_func_dict.set("user-groups-admin", settings_user_groups.set_up);
load_func_dict.set("profile-field-settings", settings_profile_fields.set_up);

View File

@ -185,9 +185,9 @@ h3 .fa-question-circle-o {
word-break: break-all;
}
#filter-settings {
#filter_pattern,
#filter_format_string {
#linkifier-settings {
#linkifier_pattern,
#linkifier_format_string {
width: calc(100% - 10em - 6em);
}
}
@ -657,7 +657,7 @@ input[type="checkbox"] {
}
.add-new-profile-field-box,
.add-new-filter-box {
.add-new-linkifier-box {
button {
margin-left: calc(10em + 20px) !important;
}
@ -682,8 +682,8 @@ input[type="checkbox"] {
}
}
#admin-filter-pattern-status,
#admin-filter-format-status {
#admin-linkifier-pattern-status,
#admin-linkifier-format-status {
margin: 20px 0 0 0;
}
@ -870,7 +870,7 @@ input[type="checkbox"] {
#create_bot_form,
#create_alert_word_form,
.admin-emoji-form,
.admin-filter-form,
.admin-linkifier-form,
.admin-profile-field-form,
.edit_bot_form {
.control-label {
@ -1825,12 +1825,12 @@ body:not(.night-mode) #settings_page .custom_user_field .datepicker {
margin: auto;
}
#filter-settings .new-filter-form,
#linkifier-settings .new-linkifier-form,
#profile-field-settings .new-profile-field-form {
width: 100%;
}
#filter-settings .new-filter-form .control-label,
#linkifier-settings .new-linkifier-form .control-label,
#profile-field-settings .new-profile-field-form .control-label {
display: block;
width: 120px;
@ -1841,7 +1841,7 @@ body:not(.night-mode) #settings_page .custom_user_field .datepicker {
float: none;
}
#filter-settings .new-filter-form .controls,
#linkifier-settings .new-linkifier-form .controls,
#profile-field-settings .new-profile-field-form .controls {
margin: auto;
text-align: center;

View File

@ -1,17 +0,0 @@
{{#with filter}}
<tr class="filter_row">
<td>
<span class="filter_pattern">{{pattern}}</span>
</td>
<td>
<span class="filter_url_format_string">{{url_format_string}}</span>
</td>
{{#if ../can_modify}}
<td class="no-select actions">
<button class="button small delete btn-danger" data-filter-id="{{id}}">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</td>
{{/if}}
</tr>
{{/with}}

View File

@ -0,0 +1,17 @@
{{#with linkifier}}
<tr class="linkifier_row">
<td>
<span class="linkifier_pattern">{{pattern}}</span>
</td>
<td>
<span class="linkifier_url_format_string">{{url_format_string}}</span>
</td>
{{#if ../can_modify}}
<td class="no-select actions">
<button class="button small delete btn-danger" data-linkifier-id="{{id}}">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</td>
{{/if}}
</tr>
{{/with}}

View File

@ -1,4 +1,4 @@
<div id="filter-settings" class="settings-section" data-name="filter-settings">
<div id="linkifier-settings" class="settings-section" data-name="linkifier-settings">
<div class="admin-table-wrapper">
<p>
@ -36,25 +36,25 @@
</ul>
<p>
{{#tr this}}
More details are available <a href="/help/add-a-custom-linkification-filter" target="_blank" rel="noopener noreferrer">in the Help Center article</a>.
More details are available <a href="/help/add-a-custom-linkifier" target="_blank" rel="noopener noreferrer">in the Help Center article</a>.
{{/tr}}
</p>
{{#if is_admin}}
<form class="form-horizontal admin-filter-form">
<div class="add-new-filter-box grey-box">
<div class="new-filter-form wrapper">
<div class="settings-section-title new-filter-section-title">{{t "Add a new linkifier" }}</div>
<div class="alert" id="admin-filter-status"></div>
<form class="form-horizontal admin-linkifier-form">
<div class="add-new-linkifier-box grey-box">
<div class="new-linkifier-form wrapper">
<div class="settings-section-title new-linkifier-section-title">{{t "Add a new linkifier" }}</div>
<div class="alert" id="admin-linkifier-status"></div>
<div class="control-group">
<label for="filter_pattern" class="control-label">{{t "Pattern" }}</label>
<input type="text" id="filter_pattern" name="pattern" placeholder="#(?P<id>[0-9]+)" />
<div class="alert" id="admin-filter-pattern-status"></div>
<label for="linkifier_pattern" class="control-label">{{t "Pattern" }}</label>
<input type="text" id="linkifier_pattern" name="pattern" placeholder="#(?P<id>[0-9]+)" />
<div class="alert" id="admin-linkifier-pattern-status"></div>
</div>
<div class="control-group">
<label for="filter_format_string" class="control-label">{{t "URL format string" }}</label>
<input type="text" id="filter_format_string" name="url_format_string" placeholder="https://github.com/zulip/zulip/issues/%(id)s" />
<div class="alert" id="admin-filter-format-status"></div>
<label for="linkifier_format_string" class="control-label">{{t "URL format string" }}</label>
<input type="text" id="linkifier_format_string" name="url_format_string" placeholder="https://github.com/zulip/zulip/issues/%(id)s" />
<div class="alert" id="admin-linkifier-format-status"></div>
</div>
<button type="submit" class="button rounded sea-green">
{{t 'Add linkifier' }}
@ -66,7 +66,7 @@
<input type="text" class="search" placeholder="{{t 'Filter linkifiers' }}" aria-label="{{t 'Filter linkifiers' }}"/>
<div class="progressive-table-wrapper" data-simplebar>
<table class="table table-condensed table-striped wrapped-table admin_filters_table">
<table class="table table-condensed table-striped wrapped-table admin_linkifiers_table">
<thead>
<th class="active" data-sort="pattern">{{t "Pattern" }}</th>
<th data-sort="url">{{t "URL format string" }}</th>
@ -74,7 +74,7 @@
<th class="actions">{{t "Actions" }}</th>
{{/if}}
</thead>
<tbody id="admin_filters_table" {{#unless is_admin}}class="required-text" data-empty="{{t 'No linkifiers set.' }}"{{/unless}}></tbody>
<tbody id="admin_linkifiers_table" {{#unless is_admin}}class="required-text" data-empty="{{t 'No linkifiers set.' }}"{{/unless}}></tbody>
</table>
</div>
</div>

View File

@ -484,7 +484,7 @@ var inline = {
stream: noop,
tex: noop,
timestamp: noop,
realm_filters: [],
linkifiers: [],
text: /^[\s\S]+?(?=[\\<!\[_*`$]| {2,}\n|$)/
};
@ -550,7 +550,7 @@ inline.zulip = merge({}, inline.breaks, {
stream: /^#\*\*([^\*]+)\*\*/,
tex: /^(\$\$([^\n_$](\\\$|[^\n$])*)\$\$(?!\$))\B/,
timestamp: /^<time:([^>]+)>/,
realm_filters: [],
linkifiers: [],
text: replace(inline.breaks.text)
('|', '|(\ud83c[\udd00-\udfff]|\ud83d[\udc00-\ude4f]|' +
'\ud83d[\ude80-\udeff]|\ud83e[\udd00-\uddff]|' +
@ -645,12 +645,12 @@ InlineLexer.prototype.output = function(src) {
continue;
}
// realm_filters (Zulip)
// linkifier (Zulip)
var self = this;
this.rules.realm_filters.forEach(function (realm_filter) {
var ret = self.inlineReplacement(realm_filter, src, function(regex, groups, match) {
this.rules.linkifiers.forEach(function (linkifier) {
var ret = self.inlineReplacement(linkifier, src, function(regex, groups, match) {
// Insert the created URL
href = self.realm_filter(regex, groups, match);
href = self.linkifier(regex, groups, match);
if (href !== undefined) {
href = escape(href);
return self.renderer.link(href, href, match);
@ -890,11 +890,11 @@ InlineLexer.prototype.timestamp = function (time) {
return this.options.timestampHandler(time);
};
InlineLexer.prototype.realm_filter = function (filter, matches, orig) {
if (typeof this.options.realmFilterHandler !== 'function')
InlineLexer.prototype.linkifier = function (linkifier, matches, orig) {
if (typeof this.options.linkifierHandler !== 'function')
return;
return this.options.realmFilterHandler(filter, matches);
return this.options.linkifierHandler(linkifier, matches);
};
InlineLexer.prototype.usermention = function (username, orig, silent) {

View File

@ -42,5 +42,5 @@ A typical successful JSON response may look like:
{generate_code_example|/messages:get|fixture(200)}
[status-messages]: /help/format-your-message-using-markdown#status-messages
[linkification-filters]: /help/add-a-custom-linkification-filter
[linkifiers]: /help/add-a-custom-linkifier
[message-flags]: /api/update-message-flags#available-flags

View File

@ -127,7 +127,7 @@
{% endif %}
</li>
{% endif %}
<li tabindex="0" data-section="filter-settings">
<li tabindex="0" data-section="linkifier-settings">
<i class="icon fa fa-font" aria-hidden="true"></i>
<div class="text">{{ _('Linkifiers') }}</div>
{% if not is_admin %}

View File

@ -57,7 +57,7 @@ Get events from GitHub, Stripe, Travis CI, JIRA, and
[hundreds of other tools](/integrations) right in Zulip. Use topics to give
each issue or decision its own place for discussion. Link to tickets in
external sites with
[custom linkification filters](/help/add-a-custom-linkification-filter) like
[custom linkification filters](/help/add-a-custom-linkifier) like
`Z1234` for Zendesk ticket #1234.
### Rich message formatting.

View File

@ -147,7 +147,7 @@ so they are useful for posterity.
Efficiently refer to issues or code reviews with notation like `#1234` or
`T1234`. You can set up any regex as a
[custom linkification filter](/help/add-a-custom-linkification-filter) for
[custom linkification filter](/help/add-a-custom-linkifier) for
your organization.
### Hundreds of integrations.

View File

@ -1,20 +1,20 @@
# Add a custom linkification filter
# Add a custom linkifier
{!admin-only.md!}
Linkifiers make it easy to refer to issues or tickets in third
party issue trackers, like GitHub, Salesforce, Zendesk, and others.
For instance, you can add a filter that automatically turns `#2468`
For instance, you can add a linkifier that automatically turns `#2468`
into a link to `https://github.com/zulip/zulip/issues/2468`.
If the pattern appears in a message topic, Zulip adds a little button to the
right of the topic that links to the appropriate URL.
### Add a custom linkification filter
### Add a custom linkifier
{start_tabs}
{settings_tab|filter-settings}
{settings_tab|linkifier-settings}
1. Under **Add a new linkifier**, enter a **Pattern** and
**URL format string**.

View File

@ -62,7 +62,7 @@ Numbered lists
## Links
Zulip auto-linkifies URLs and valid stream names. You can also add a
[custom linkifier](/help/add-a-custom-linkification-filter) to link
[custom linkifier](/help/add-a-custom-linkifier) to link
patterns like `#1234` to your ticketing system.
```

View File

@ -109,7 +109,7 @@ expert teaching other users how to use Zulip.
## Bonus things to set up
* [Add custom profile fields](/help/add-custom-profile-fields).
- [Automatically linkify](/help/add-a-custom-linkification-filter)
- [Automatically linkify](/help/add-a-custom-linkifier)
issue numbers.
- [Write custom integrations](/api/integrations-overview)
for your teams workflow.

View File

@ -137,7 +137,7 @@
* [Require topics in stream messages](/help/require-topics)
* [Add custom emoji](/help/add-custom-emoji)
* [Configure authentication methods](/help/configure-authentication-methods)
* [Add a custom linkification filter](/help/add-a-custom-linkification-filter)
* [Add a custom linkifier](/help/add-a-custom-linkifier)
* [Message retention policy](/help/message-retention-policy)
* [SAML authentication](/help/saml-authentication)

View File

@ -58,7 +58,11 @@ link_mapping = {
"Default streams",
"/#organization/default-streams-list",
],
"filter-settings": ["Manage organization", "Linkifiers", "/#organization/filter-settings"],
"linkifier-settings": [
"Manage organization",
"Linkifiers",
"/#organization/linkifier-settings",
],
"profile-field-settings": [
"Manage organization",
"Custom profile fields",

View File

@ -2321,7 +2321,7 @@ paths:
additionalProperties: false
description: |
Event sent to all users in a Zulip organization when the
set of configured [linkifiers](/help/add-a-custom-linkification-filter)
set of configured [linkifiers](/help/add-a-custom-linkifier)
for the organization has changed.
Processing this event is important to doing Markdown local echo
@ -5779,7 +5779,7 @@ paths:
tags: ["server_and_organizations"]
description: |
List all of an organization's configured
[linkifiers](/help/add-a-custom-linkification-filter), regular
[linkifiers](/help/add-a-custom-linkifier), regular
expression patterns that are automatically linkified when they appear
in messages and topics.
@ -5829,7 +5829,7 @@ paths:
operationId: add_linkifier
tags: ["server_and_organizations"]
description: |
Configure [linkifiers](/help/add-a-custom-linkification-filter),
Configure [linkifiers](/help/add-a-custom-linkifier),
regular expression patterns that are automatically linkified when they
appear in messages and topics.
@ -5877,7 +5877,7 @@ paths:
operationId: remove_linkifier
tags: ["server_and_organizations"]
description: |
Remove [linkifiers](/help/add-a-custom-linkification-filter), regular
Remove [linkifiers](/help/add-a-custom-linkifier), regular
expression patterns that are automatically linkified when they appear
in messages and topics.
@ -6164,7 +6164,7 @@ paths:
Present if `realm_filters` is present in `fetch_event_types`.
An array of tuples (fixed-length arrays) where each tuple describes
a single realm filter ([linkifier](/help/add-a-custom-linkification-filter).
a single realm filter ([linkifier](/help/add-a-custom-linkifier).
The first element of the tuple is a string regex pattern which represents
the pattern that should be linkified on matching.

View File

@ -12,7 +12,7 @@ Get Zulip notifications for Stripe events!
**Add endpoint**.
1. [Optional] In Zulip, add a
[linkification filter](/help/add-a-custom-linkification-filter) with
[linkification filter](/help/add-a-custom-linkifier) with
**Pattern** `(?P<id>cus_[0-9a-zA-Z]+)` and **URL format string**
`https://dashboard.stripe.com/customers/%(id)s`.