snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
109
zerver/lib/topic_link_util.py
Normal file
109
zerver/lib/topic_link_util.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# See the Zulip URL spec at https://zulip.com/api/zulip-urls
|
||||
#
|
||||
# Keep this synchronized with web/src/topic_link_util.ts
|
||||
|
||||
import re
|
||||
|
||||
from zerver.lib.url_encoding import encode_channel, encode_hash_component
|
||||
from zerver.models.messages import Message
|
||||
|
||||
invalid_stream_topic_regex = re.compile(r"[`>*&\[\]]|(\$\$)")
|
||||
|
||||
|
||||
def will_produce_broken_stream_topic_link(word: str) -> bool:
|
||||
return bool(invalid_stream_topic_regex.search(word))
|
||||
|
||||
|
||||
TOPIC_LINK_SYNTAX_FOR_DISPLAY = "#{channel_name} > {topic_name}"
|
||||
|
||||
escape_mapping = {
|
||||
"`": "`",
|
||||
">": ">",
|
||||
"*": "*",
|
||||
"&": "&",
|
||||
"$$": "$$",
|
||||
"[": "[",
|
||||
"]": "]",
|
||||
}
|
||||
|
||||
|
||||
def escape_invalid_stream_topic_characters(text: str) -> str:
|
||||
return re.sub(
|
||||
invalid_stream_topic_regex,
|
||||
lambda match: escape_mapping.get(match.group(0), match.group(0)),
|
||||
text,
|
||||
)
|
||||
|
||||
|
||||
def get_message_link_label(
|
||||
stream_name: str, topic_name: str | None = None, message_id: int | None = None
|
||||
) -> str:
|
||||
"""The text label for a channel/topic/message Markdown link, of the form
|
||||
"#channel > topic @ 💬" (matching the web app's "Copy link to message").
|
||||
|
||||
Characters that would break the surrounding Markdown link syntax are
|
||||
escaped, and an empty topic is shown using its fallback display name.
|
||||
"""
|
||||
escape = escape_invalid_stream_topic_characters
|
||||
text = f"#{escape(stream_name)}"
|
||||
if topic_name is not None:
|
||||
if topic_name == "":
|
||||
topic_name = Message.EMPTY_TOPIC_FALLBACK_NAME
|
||||
text += f" > {escape(topic_name)}"
|
||||
|
||||
if message_id is not None:
|
||||
text += " @ 💬"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def get_fallback_markdown_link(
|
||||
stream_id: int, stream_name: str, topic_name: str | None = None, message_id: int | None = None
|
||||
) -> str:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
link = f"#narrow/channel/{encode_channel(stream_id, stream_name)}"
|
||||
if topic_name is not None:
|
||||
link += f"/topic/{encode_hash_component(topic_name)}"
|
||||
|
||||
if message_id is not None:
|
||||
link += f"/near/{message_id}"
|
||||
|
||||
text = get_message_link_label(stream_name, topic_name, message_id)
|
||||
return f"[{text}]({link})"
|
||||
|
||||
|
||||
def get_message_link_syntax(
|
||||
stream_id: int, stream_name: str, topic_name: str, message_id: int
|
||||
) -> str:
|
||||
# If the stream/topic name is such that it will
|
||||
# generate an invalid #**stream>topic@message_id** syntax,
|
||||
# we revert to generating the normal markdown syntax for a link.
|
||||
if will_produce_broken_stream_topic_link(topic_name) or will_produce_broken_stream_topic_link(
|
||||
stream_name
|
||||
):
|
||||
return get_fallback_markdown_link(stream_id, stream_name, topic_name, message_id)
|
||||
return f"#**{stream_name}>{topic_name}@{message_id}**"
|
||||
|
||||
|
||||
def get_stream_topic_link_syntax(stream_id: int, stream_name: str, topic_name: str) -> str:
|
||||
# If the stream/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(topic_name) or will_produce_broken_stream_topic_link(
|
||||
stream_name
|
||||
):
|
||||
return get_fallback_markdown_link(stream_id, stream_name, topic_name)
|
||||
return f"#**{stream_name}>{topic_name}**"
|
||||
|
||||
|
||||
def get_stream_link_syntax(stream_id: int, stream_name: str) -> str:
|
||||
# If the stream name is such that it will generate an invalid #**stream** 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_id, stream_name)
|
||||
return f"#**{stream_name}**"
|
||||
Reference in New Issue
Block a user