mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 06:23:38 +00:00
Fixes #31248. For cases with zero tabs in our current help center, we were using the tab syntax just to put a border around the instructions without any tab label. We do not want that border anymore since Tabs also don't have any borders. So, we just remove the old tab syntax in that case during our conversion. We also move the admonition to asides conversion before the tabs syntax conversion since the tabs conversion happening first was messing with the asides conversion and was resulting into some missing closing tags. We can modify things to work regardless of order, but doesn't seem worth digging into for a conversion script.
241 lines
8.4 KiB
Python
241 lines
8.4 KiB
Python
import re
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
import markdown
|
|
from markdown.extensions import Extension
|
|
from markdown.preprocessors import Preprocessor
|
|
from typing_extensions import override
|
|
|
|
from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITIES
|
|
|
|
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\|([^}]+)\}$")
|
|
|
|
TABBED_SECTION_TEMPLATE = """
|
|
<div class="tabbed-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-tab-key="{data_tab_key}" tabindex="0">{label}</li>
|
|
""".strip()
|
|
|
|
DIV_TAB_CONTENT_TEMPLATE = """
|
|
<div class="tab-content" data-tab-key="{data_tab_key}" 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",
|
|
"most-systems": "Most systems",
|
|
"linux-with-apt": "Linux with APT",
|
|
"python": "Python",
|
|
"js": "JavaScript",
|
|
"curl": "curl",
|
|
"zulip-send": "zulip-send",
|
|
"web": "Web",
|
|
"desktop": "Desktop",
|
|
"mobile": "Mobile",
|
|
"mm-default": "Default installation",
|
|
"mm-cloud": "Cloud instance",
|
|
"mm-docker": "Docker",
|
|
"mm-gitlab-omnibus": "GitLab Omnibus",
|
|
"mm-self-hosting-cloud-export": "Self hosting (cloud export)",
|
|
"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-conversations": "Via recent conversations",
|
|
"via-inbox-view": "Via inbox view",
|
|
"via-left-sidebar": "Via left sidebar",
|
|
"via-right-sidebar": "Via right sidebar",
|
|
"instructions-for-all-platforms": "Instructions for all platforms",
|
|
"public-channels": "Public channels",
|
|
"private-channels": "Private channels",
|
|
"web-public-channels": "Web-public channels",
|
|
"via-user-card": "Via user card",
|
|
"via-user-profile": "Via user profile",
|
|
"via-organization-settings": "Via organization settings",
|
|
"via-personal-settings": "Via personal settings",
|
|
"via-channel-settings": "Via channel settings",
|
|
"via-group-settings": "Via group settings",
|
|
"via-group-card": "Via group card",
|
|
"via-compose-box": "Via compose box",
|
|
"via-search-box": "Via search box",
|
|
"default-subdomain": "Default subdomain",
|
|
"custom-subdomain": "Custom subdomain",
|
|
"zulip-cloud-standard": "Zulip Cloud Standard",
|
|
"zulip-cloud-plus": "Zulip Cloud Plus",
|
|
"request-sponsorship": "Request sponsorship",
|
|
"request-education-pricing": "Request education pricing",
|
|
"zulip-cloud": "Zulip Cloud",
|
|
"self-hosting": "Self hosting",
|
|
"okta": "Okta",
|
|
"onelogin": "OneLogin",
|
|
"azuread": "Entra ID (AzureAD)",
|
|
"entraid": "Microsoft Entra ID",
|
|
"keycloak": "Keycloak",
|
|
"auth0": "Auth0",
|
|
"logged-in": "If you are logged in",
|
|
"logged-out": "If you are logged out",
|
|
"user": "User",
|
|
"bot": "Bot",
|
|
"on-sign-up": "On sign-up",
|
|
"via-paste": "Via paste",
|
|
"via-drag-and-drop": "Via drag-and-drop",
|
|
"via-markdown": "Via Markdown",
|
|
"via-compose-box-buttons": "Via compose box button",
|
|
"channel-compose": "Compose to a channel",
|
|
"dm-compose": "Compose a DM",
|
|
"v6": "Zulip Server 6.0+",
|
|
"v4": "Zulip Server 4.0+",
|
|
"organization-billing": "Organization-level billing",
|
|
"server-billing": "Server-level billing",
|
|
"by-card": "Pay by credit card",
|
|
"by-invoice": "Pay by invoice",
|
|
"for-a-bot": "For a bot",
|
|
"for-yourself": "For yourself",
|
|
"new-organizations": "New organizations",
|
|
"imported-organizations": "Imported organizations",
|
|
}
|
|
|
|
|
|
class TabbedSectionsGenerator(Extension):
|
|
@override
|
|
def extendMarkdown(self, md: markdown.Markdown) -> None:
|
|
md.preprocessors.register(
|
|
TabbedSectionsPreprocessor(md, self.getConfigs()),
|
|
"tabbed_sections",
|
|
PREPROCESSOR_PRIORITIES["tabbed_sections"],
|
|
)
|
|
|
|
|
|
def parse_tabs(lines: list[str]) -> dict[str, Any] | None:
|
|
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_key": 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 generate_content_blocks(
|
|
tab_section: dict[str, Any], lines: list[str], tab_content_template: 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 = tab_content_template.format(
|
|
data_tab_key=tab["tab_key"],
|
|
# This attribute is not used directly in this file here,
|
|
# we need this for the current conversion script in for
|
|
# help-beta where this function is being imported.
|
|
tab_label=TAB_SECTION_LABELS[tab["tab_key"]],
|
|
# 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)
|
|
|
|
|
|
class TabbedSectionsPreprocessor(Preprocessor):
|
|
def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:
|
|
super().__init__(md)
|
|
|
|
@override
|
|
def run(self, lines: list[str]) -> list[str]:
|
|
tab_section = parse_tabs(lines)
|
|
while tab_section:
|
|
if "tabs" in tab_section:
|
|
tab_class = "has-tabs"
|
|
else:
|
|
tab_class = "no-tabs"
|
|
tab_section["tabs"] = [
|
|
{
|
|
"tab_key": "instructions-for-all-platforms",
|
|
"start": tab_section["start_tabs_index"],
|
|
}
|
|
]
|
|
nav_bar = self.generate_nav_bar(tab_section)
|
|
content_blocks = generate_content_blocks(tab_section, lines, DIV_TAB_CONTENT_TEMPLATE)
|
|
rendered_tabs = TABBED_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 = parse_tabs(lines)
|
|
return lines
|
|
|
|
def generate_nav_bar(self, tab_section: dict[str, Any]) -> str:
|
|
li_elements = []
|
|
for tab in tab_section["tabs"]:
|
|
tab_key = tab.get("tab_key")
|
|
tab_label = TAB_SECTION_LABELS.get(tab_key)
|
|
if tab_label is None:
|
|
raise ValueError(
|
|
f"Tab '{tab_key}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py"
|
|
)
|
|
|
|
li = NAV_LIST_ITEM_TEMPLATE.format(data_tab_key=tab_key, label=tab_label)
|
|
li_elements.append(li)
|
|
|
|
return NAV_BAR_TEMPLATE.format(tabs="\n".join(li_elements))
|
|
|
|
|
|
def makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:
|
|
return TabbedSectionsGenerator(**kwargs)
|