mirror of https://github.com/zulip/zulip.git
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:
parent
c6d1fbd051
commit
9223dced3b
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}}
|
|
@ -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}}
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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**.
|
|
@ -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.
|
||||
|
||||
```
|
||||
|
|
|
@ -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 team’s workflow.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
|
Loading…
Reference in New Issue