mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	Adds tab for web-public streams in documentation for setting who can create new streams, as well as some text about why this is limited to certain roles. Removes list of actions that can be restricted to full members due to maintainability concerns for that type of list in the documentation and replaces it with a short descriptive text explaining that many settings in Zulip support this restriction.
		
			
				
	
	
		
			184 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			184 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import re
 | 
						|
from typing import Any, Dict, List, Mapping, Optional
 | 
						|
 | 
						|
import markdown
 | 
						|
from markdown.extensions import Extension
 | 
						|
from markdown.preprocessors import Preprocessor
 | 
						|
 | 
						|
from zerver.lib.markdown.preprocessor_priorities import PREPROCESSOR_PRIORITES
 | 
						|
 | 
						|
START_TABBED_SECTION_REGEX = re.compile(r"^\{start_tabs\}$")
 | 
						|
END_TABBED_SECTION_REGEX = re.compile(r"^\{end_tabs\}$")
 | 
						|
TAB_CONTENT_REGEX = re.compile(r"^\{tab\|\s*(.+?)\s*\}$")
 | 
						|
 | 
						|
CODE_SECTION_TEMPLATE = """
 | 
						|
<div class="code-section {tab_class}" markdown="1">
 | 
						|
{nav_bar}
 | 
						|
<div class="blocks">
 | 
						|
{blocks}
 | 
						|
</div>
 | 
						|
</div>
 | 
						|
""".strip()
 | 
						|
 | 
						|
NAV_BAR_TEMPLATE = """
 | 
						|
<ul class="nav">
 | 
						|
{tabs}
 | 
						|
</ul>
 | 
						|
""".strip()
 | 
						|
 | 
						|
NAV_LIST_ITEM_TEMPLATE = """
 | 
						|
<li data-language="{data_language}" tabindex="0">{label}</li>
 | 
						|
""".strip()
 | 
						|
 | 
						|
DIV_TAB_CONTENT_TEMPLATE = """
 | 
						|
<div data-language="{data_language}" markdown="1">
 | 
						|
{content}
 | 
						|
</div>
 | 
						|
""".strip()
 | 
						|
 | 
						|
# If adding new entries here, also check if you need to update
 | 
						|
# tabbed-instructions.js
 | 
						|
TAB_SECTION_LABELS = {
 | 
						|
    "desktop-web": "Desktop/Web",
 | 
						|
    "ios": "iOS",
 | 
						|
    "android": "Android",
 | 
						|
    "mac": "macOS",
 | 
						|
    "windows": "Windows",
 | 
						|
    "linux": "Linux",
 | 
						|
    "python": "Python",
 | 
						|
    "js": "JavaScript",
 | 
						|
    "curl": "curl",
 | 
						|
    "zulip-send": "zulip-send",
 | 
						|
    "web": "Web",
 | 
						|
    "desktop": "Desktop",
 | 
						|
    "mobile": "Mobile",
 | 
						|
    "mm-default": "Default installation",
 | 
						|
    "mm-docker": "Docker",
 | 
						|
    "mm-gitlab-omnibus": "GitLab Omnibus",
 | 
						|
    "send-email-invitations": "Send email invitations",
 | 
						|
    "share-an-invite-link": "Share an invite link",
 | 
						|
    "require-invitations": "Require invitations",
 | 
						|
    "allow-anyone-to-join": "Allow anyone to join",
 | 
						|
    "restrict-by-email-domain": "Restrict by email domain",
 | 
						|
    "zoom": "Zoom",
 | 
						|
    "jitsi-meet": "Jitsi Meet",
 | 
						|
    "bigbluebutton": "BigBlueButton",
 | 
						|
    "disable": "Disabled",
 | 
						|
    "chrome": "Chrome",
 | 
						|
    "firefox": "Firefox",
 | 
						|
    "desktop-app": "Desktop app",
 | 
						|
    "system-proxy-settings": "System proxy settings",
 | 
						|
    "custom-proxy-settings": "Custom proxy settings",
 | 
						|
    "stream": "From a stream view",
 | 
						|
    "not-stream": "From other views",
 | 
						|
    "via-recent-topics": "Via recent topics",
 | 
						|
    "via-left-sidebar": "Via left sidebar",
 | 
						|
    "instructions-for-all-platforms": "Instructions for all platforms",
 | 
						|
    "public-streams": "Public streams",
 | 
						|
    "private-streams": "Private streams",
 | 
						|
    "web-public-streams": "Web-public streams",
 | 
						|
    "via-user-profile": "Via the user's profile",
 | 
						|
    "via-organization-settings": "Via organization settings",
 | 
						|
    "via-browser-address-bar": "Via browser's address bar",
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
class TabbedSectionsGenerator(Extension):
 | 
						|
    def extendMarkdown(self, md: markdown.Markdown) -> None:
 | 
						|
        md.preprocessors.register(
 | 
						|
            TabbedSectionsPreprocessor(md, self.getConfigs()),
 | 
						|
            "tabbed_sections",
 | 
						|
            PREPROCESSOR_PRIORITES["tabbed_sections"],
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class TabbedSectionsPreprocessor(Preprocessor):
 | 
						|
    def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:
 | 
						|
        super().__init__(md)
 | 
						|
 | 
						|
    def run(self, lines: List[str]) -> List[str]:
 | 
						|
        tab_section = self.parse_tabs(lines)
 | 
						|
        while tab_section:
 | 
						|
            if "tabs" in tab_section:
 | 
						|
                tab_class = "has-tabs"
 | 
						|
            else:
 | 
						|
                tab_class = "no-tabs"
 | 
						|
                tab_section["tabs"] = [
 | 
						|
                    {
 | 
						|
                        "tab_name": "instructions-for-all-platforms",
 | 
						|
                        "start": tab_section["start_tabs_index"],
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            nav_bar = self.generate_nav_bar(tab_section)
 | 
						|
            content_blocks = self.generate_content_blocks(tab_section, lines)
 | 
						|
            rendered_tabs = CODE_SECTION_TEMPLATE.format(
 | 
						|
                tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks
 | 
						|
            )
 | 
						|
 | 
						|
            start = tab_section["start_tabs_index"]
 | 
						|
            end = tab_section["end_tabs_index"] + 1
 | 
						|
            lines = [*lines[:start], rendered_tabs, *lines[end:]]
 | 
						|
            tab_section = self.parse_tabs(lines)
 | 
						|
        return lines
 | 
						|
 | 
						|
    def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:
 | 
						|
        tab_content_blocks = []
 | 
						|
        for index, tab in enumerate(tab_section["tabs"]):
 | 
						|
            start_index = tab["start"] + 1
 | 
						|
            try:
 | 
						|
                # If there are more tabs, we can use the starting index
 | 
						|
                # of the next tab as the ending index of the previous one
 | 
						|
                end_index = tab_section["tabs"][index + 1]["start"]
 | 
						|
            except IndexError:
 | 
						|
                # Otherwise, just use the end of the entire section
 | 
						|
                end_index = tab_section["end_tabs_index"]
 | 
						|
 | 
						|
            content = "\n".join(lines[start_index:end_index]).strip()
 | 
						|
            tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(
 | 
						|
                data_language=tab["tab_name"],
 | 
						|
                # Wrapping the content in two newlines is necessary here.
 | 
						|
                # If we don't do this, the inner Markdown does not get
 | 
						|
                # rendered properly.
 | 
						|
                content=f"\n{content}\n",
 | 
						|
            )
 | 
						|
            tab_content_blocks.append(tab_content_block)
 | 
						|
        return "\n".join(tab_content_blocks)
 | 
						|
 | 
						|
    def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:
 | 
						|
        li_elements = []
 | 
						|
        for tab in tab_section["tabs"]:
 | 
						|
            tab_name = tab.get("tab_name")
 | 
						|
            tab_label = TAB_SECTION_LABELS.get(tab_name)
 | 
						|
            if tab_label is None:
 | 
						|
                raise ValueError(
 | 
						|
                    f"Tab '{tab_name}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py"
 | 
						|
                )
 | 
						|
 | 
						|
            li = NAV_LIST_ITEM_TEMPLATE.format(data_language=tab_name, label=tab_label)
 | 
						|
            li_elements.append(li)
 | 
						|
 | 
						|
        return NAV_BAR_TEMPLATE.format(tabs="\n".join(li_elements))
 | 
						|
 | 
						|
    def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:
 | 
						|
        block: Dict[str, Any] = {}
 | 
						|
        for index, line in enumerate(lines):
 | 
						|
            start_match = START_TABBED_SECTION_REGEX.search(line)
 | 
						|
            if start_match:
 | 
						|
                block["start_tabs_index"] = index
 | 
						|
 | 
						|
            tab_content_match = TAB_CONTENT_REGEX.search(line)
 | 
						|
            if tab_content_match:
 | 
						|
                block.setdefault("tabs", [])
 | 
						|
                tab = {"start": index, "tab_name": tab_content_match.group(1)}
 | 
						|
                block["tabs"].append(tab)
 | 
						|
 | 
						|
            end_match = END_TABBED_SECTION_REGEX.search(line)
 | 
						|
            if end_match:
 | 
						|
                block["end_tabs_index"] = index
 | 
						|
                break
 | 
						|
        return block
 | 
						|
 | 
						|
 | 
						|
def makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:
 | 
						|
    return TabbedSectionsGenerator(**kwargs)
 |