import re from typing import Any, List, Match from markdown import Markdown from markdown.extensions import Extension from markdown.preprocessors import Preprocessor from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES # There is a lot of duplicated code between this file and # help_settings_links.py. So if you're making a change here consider making # it there as well. REGEXP = re.compile(r"\{relative\|(?P.*?)\|(?P.*?)\}") gear_info = { # The pattern is key: [name, link] # key is from REGEXP: `{relative|gear|key}` # name is what the item is called in the gear menu: `Select **name**.` # link is used for relative links: `Select [name](link).` "manage-streams": ["Manage streams", "/#streams/subscribed"], "settings": ["Personal Settings", "/#settings/profile"], "organization-settings": ["Organization settings", "/#organization/organization-profile"], "integrations": ["Integrations", "/integrations/"], "stats": ["Usage statistics", "/stats"], "plans": ["Plans and pricing", "/plans/"], "billing": ["Billing", "/billing/"], "invite": ["Invite users", "/#invite"], "about-zulip": ["About Zulip", "/#about-zulip"], } gear_instructions = """ 1. Click on the **gear** () icon in the upper right corner of the web or desktop app. 1. Select {item}. """ def gear_handle_match(key: str) -> str: if relative_help_links: item = f"[{gear_info[key][0]}]({gear_info[key][1]})" else: item = f"**{gear_info[key][0]}**" return gear_instructions.format(item=item) stream_info = { "all": ["All streams", "/#streams/all"], "subscribed": ["Subscribed streams", "/#streams/subscribed"], } stream_instructions_no_link = """ 1. Click on the **gear** () icon in the upper right corner of the web or desktop app. 1. Click **Manage streams**. """ def stream_handle_match(key: str) -> str: if relative_help_links: return f"1. Go to [{stream_info[key][0]}]({stream_info[key][1]})." if key == "all": return stream_instructions_no_link + "\n\n1. Click **All streams** in the upper left." return stream_instructions_no_link draft_instructions = """ 1. Click on **Drafts** in the left sidebar. """ scheduled_instructions = """ 1. Click on **Scheduled messages** in the left sidebar. If you do not see this link, you have no scheduled messages. """ message_info = { "drafts": ["Drafts", "/#drafts", draft_instructions], "scheduled": ["Scheduled messages", "/#scheduled", scheduled_instructions], } def message_handle_match(key: str) -> str: if relative_help_links: return f"1. Go to [{message_info[key][0]}]({message_info[key][1]})." else: return message_info[key][2] LINK_TYPE_HANDLERS = { "gear": gear_handle_match, "stream": stream_handle_match, "message": message_handle_match, } class RelativeLinksHelpExtension(Extension): def extendMarkdown(self, md: Markdown) -> None: """Add RelativeLinksHelpExtension to the Markdown instance.""" md.registerExtension(self) md.preprocessors.register( RelativeLinks(), "help_relative_links", PREPROCESSOR_PRIORITES["help_relative_links"] ) relative_help_links: bool = False def set_relative_help_links(value: bool) -> None: global relative_help_links relative_help_links = value class RelativeLinks(Preprocessor): def run(self, lines: List[str]) -> List[str]: done = False while not done: for line in lines: loc = lines.index(line) match = REGEXP.search(line) if match: text = [self.handleMatch(match)] # The line that contains the directive to include the macro # may be preceded or followed by text or tags, in that case # we need to make sure that any preceding or following text # stays the same. line_split = REGEXP.split(line, maxsplit=0) preceding = line_split[0] following = line_split[-1] text = [preceding, *text, following] lines = lines[:loc] + text + lines[loc + 1 :] break else: done = True return lines def handleMatch(self, match: Match[str]) -> str: return LINK_TYPE_HANDLERS[match.group("link_type")](match.group("key")) def makeExtension(*args: Any, **kwargs: Any) -> RelativeLinksHelpExtension: return RelativeLinksHelpExtension(*args, **kwargs)