Files
zulip/zerver/lib/topic_link_util.py
PieterCK 48e33eed31 url_encoding: Standardize to use encode_hash_component.
Previously we use `hash_util_encode` to encode channel and topic names
to be URL compatible. This uses the more capable `encode_hash_component`
from the recently added `topic_link_utils.py` module. It also moves the
function to `url_encoding.py`
2025-07-03 14:40:49 -07:00

89 lines
3.1 KiB
Python

# 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))
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_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.
"""
escape = escape_invalid_stream_topic_characters
link = f"#narrow/channel/{encode_channel(stream_id, stream_name)}"
text = f"#{escape(stream_name)}"
if topic_name is not None:
link += f"/topic/{encode_hash_component(topic_name)}"
if topic_name == "":
topic_name = Message.EMPTY_TOPIC_FALLBACK_NAME
text += f" > {escape(topic_name)}"
if message_id is not None:
link += f"/near/{message_id}"
text += " @ 💬"
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}**"