/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/src/topic_link_util.ts
177 строк
7 KB
Anders Kaseorg
eslint: Fix unicorn/no-unsafe-string-replacement.
29 июл 2026, 02:35
29 июл 2026, 02:35
a2f20bb
Код
Авторство
О чём код?
// See the Zulip URL spec at https://zulip.com/api/zulip-urls // // Keep this synchronized with zerver/lib/topic_link_util.py import assert from "minimalistic-assert"; import * as hash_util from "./hash_util.ts"; import * as stream_data from "./stream_data.ts"; import type {StreamSubscription} from "./sub_store.ts"; import * as util from "./util.ts"; const invalid_stream_topic_regex = /[`>*&[\]]|(\$\$)/g; export function will_produce_broken_stream_topic_link(word: string): boolean { return invalid_stream_topic_regex.test(word); } export function escape_invalid_stream_topic_characters(text: string): string { switch (text) { case "`": return "`"; case ">": return ">"; case "*": return "*"; case "&": return "&"; case "$$": return "$$"; case "[": return "["; case "]": return "]"; default: return text; } } // This record should be kept in sync with the // escape_invalid_stream_topic_characters function. const escaped_to_original_mapping: Record<string, string> = { "`": "`", ">": ">", "*": "*", "&": "&", "$$": "$$", "[": "[", "]": "]", }; export function html_unescape_invalid_stream_topic_characters(text: string): string { const unescape_regex = new RegExp(Object.keys(escaped_to_original_mapping).join("|"), "g"); return text.replaceAll(unescape_regex, (match) => escaped_to_original_mapping[match] ?? match); } export function html_escape_markdown_syntax_characters(text: string): string { return text.replaceAll(invalid_stream_topic_regex, (text) => escape_invalid_stream_topic_characters(text), ); } export function get_topic_link_content_with_stream_name(opts: { stream_name: string; topic_name: string | undefined; message_id: string | undefined; }): {label_text_markdown: string; label_text_plain: string; url: string} { const stream = stream_data.get_sub(opts.stream_name); assert(stream !== undefined); return _get_topic_link_content({stream, ...opts}); } export function get_topic_link_content_with_stream_id(opts: { stream_id: number; topic_name: string | undefined; message_id: string | undefined; }): {label_text_markdown: string; label_text_plain: string; url: string} { const stream = stream_data.get_sub_by_id(opts.stream_id); assert(stream !== undefined); return _get_topic_link_content({stream, ...opts}); } function _get_topic_link_content(opts: { stream: StreamSubscription; topic_name: string | undefined; message_id: string | undefined; }): {label_text_markdown: string; label_text_plain: string; url: string} { const {stream, topic_name, message_id} = opts; const stream_name = stream.name; const stream_id = stream.stream_id; const escape = html_escape_markdown_syntax_characters; if (topic_name !== undefined) { // This URL is relative, unlike the absolute URLs we use in quoting a message. // See discussion: // https://chat.zulip.org/#narrow/channel/101-design/topic/.E2.9C.94.20.22quote.20message.22.20uses.20absolute.20URL.20instead.20of.20realm-rela.2E.2E.2E/near/2325588 const stream_topic_url = hash_util.by_stream_topic_url(stream_id, topic_name); const topic_display_name = util.get_final_topic_display_name(topic_name); if (message_id !== undefined) { return { label_text_markdown: `#${escape(stream_name)} > ${escape(topic_display_name)} @ 💬`, label_text_plain: `#${stream_name} > ${topic_display_name} @ 💬`, url: `${stream_topic_url}/near/${message_id}`, }; } return { label_text_markdown: `#${escape(stream_name)} > ${escape(topic_display_name)}`, label_text_plain: `#${stream_name} > ${topic_display_name}`, url: stream_topic_url, }; } return { label_text_markdown: `#${escape(stream_name)}`, label_text_plain: `#${stream_name}`, url: hash_util.channel_url_by_user_setting(stream_id), }; } export function as_markdown_link_syntax(text: string, url: string): string { return `[${text}](${url})`; } export function as_html_link_syntax_unsafe(text: string, url: string): string { // The caller is responsible for making sure that the `text` // parameter is properly escaped. return `<a href="${url}">${text}</a>`; } export function get_fallback_markdown_link( stream_name: string, topic_name?: string, message_id?: string, ): string { // Helper that should only be called by other methods in this file. // Generates the vanilla markdown link syntax for a stream/topic/message link, as // a fallback for cases where the nicer Zulip link syntax would not // render properly due to special characters in the channel or topic name. const {label_text_markdown, url} = get_topic_link_content_with_stream_name({ stream_name, topic_name, message_id, }); return as_markdown_link_syntax(label_text_markdown, url); } export function get_stream_topic_link_syntax( stream_name: string, topic_name: string, message_id?: string, ): string { // If the topic/stream name would produce an invalid #**stream>topic** syntax, fall back // to markdown link syntax. if ( will_produce_broken_stream_topic_link(topic_name) || will_produce_broken_stream_topic_link(stream_name) ) { if (message_id !== undefined) { const stream = stream_data.get_sub(stream_name); assert(stream !== undefined); const stream_topic_url = hash_util.by_stream_topic_url(stream.stream_id, topic_name); const topic_display_name = util.get_final_topic_display_name(topic_name); const escape = html_escape_markdown_syntax_characters; const label = `#${escape(stream_name)} > ${escape(topic_display_name)}`; return as_markdown_link_syntax(label, `${stream_topic_url}/with/${message_id}`); } return get_fallback_markdown_link(stream_name, topic_name); } return `#**${stream_name}>${topic_name}**`; } export function get_stream_link_syntax(stream_name: string): string { // If the topic name is such that it will generate an invalid #**stream>topic** syntax, // we revert to generating the normal markdown syntax for a link. if (will_produce_broken_stream_topic_link(stream_name)) { return get_fallback_markdown_link(stream_name); } return `#**${stream_name}**`; }