mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
This commit removes the current help center markdown files and any logic that was used to host those files at help/. We also remove a bunch of tests, we should the equivalent of those tests for the new help center. Issues to track: #35649, #35647. These issues track adding back tests for redirects and broken links. We had a symlink from templates/zerver/integrations/email.md pointing to help/using-zulip-via-email.md. We can no longer have that symlink since the latter has been converted to an MDX file. We have deleted the symlink and put a markdown file in it's place. Both the files have comments to edit the other in case of changes. This commit also makes changes in astro config, astro component paths and other places to move the starlight help center docs base path from /starlight_help to /help. The change to rename /starlight_help/ to /help/ in MDX files is done in the next commit. If we squash these commits, this line should be removed. `./tools/build-help-center` no longer does the conversion step. We also remove some dead code related to the old help center in documentation.py.
228 lines
8.4 KiB
Python
228 lines
8.4 KiB
Python
import time
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import markdown
|
|
import markdown.extensions.admonition
|
|
import markdown.extensions.codehilite
|
|
import markdown.extensions.extra
|
|
import markdown.extensions.toc
|
|
import orjson
|
|
from django.conf import settings
|
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
|
from django.template import Library, engines
|
|
from django.template.backends.jinja2 import Jinja2
|
|
from django.utils.safestring import mark_safe
|
|
from markupsafe import Markup
|
|
|
|
import zerver.lib.markdown.api_arguments_table_generator
|
|
import zerver.lib.markdown.api_return_values_table_generator
|
|
import zerver.lib.markdown.fenced_code
|
|
import zerver.lib.markdown.help_relative_links
|
|
import zerver.lib.markdown.help_settings_links
|
|
import zerver.lib.markdown.include
|
|
import zerver.lib.markdown.nested_code_blocks
|
|
import zerver.lib.markdown.static
|
|
import zerver.lib.markdown.tabbed_sections
|
|
import zerver.openapi.markdown_extension
|
|
from zerver.lib.cache import dict_to_items_tuple, ignore_unhashable_lru_cache, items_tuple_to_dict
|
|
from zerver.openapi.merge_api_changelogs import (
|
|
get_feature_level,
|
|
get_unmerged_changelogs,
|
|
merge_changelogs,
|
|
)
|
|
|
|
register = Library()
|
|
|
|
|
|
def and_n_others(values: list[str | Markup], limit: int) -> str | Markup:
|
|
# A helper for the commonly appended "and N other(s)" string, with
|
|
# the appropriate pluralization.
|
|
return " and {} other{}".format(
|
|
len(values) - limit,
|
|
"" if len(values) == limit + 1 else "s",
|
|
)
|
|
|
|
|
|
def get_updated_changelog() -> str | None:
|
|
unmerged_changelogs = get_unmerged_changelogs(verbose=False)
|
|
if not unmerged_changelogs:
|
|
return None
|
|
|
|
new_feature_level = get_feature_level(update_feature_level=False)
|
|
return merge_changelogs(unmerged_changelogs, new_feature_level, False)
|
|
|
|
|
|
@register.filter(name="display_list", is_safe=True)
|
|
def display_list(values: list[str | Markup], display_limit: int) -> str | Markup:
|
|
"""
|
|
Given a list of values, return a string nicely formatting those values,
|
|
summarizing when you have more than `display_limit`. Eg, for a
|
|
`display_limit` of 3 we get the following possible cases:
|
|
|
|
Jessica
|
|
Jessica and Waseem
|
|
Jessica, Waseem, and Tim
|
|
Jessica, Waseem, Tim, and 1 other
|
|
Jessica, Waseem, Tim, and 2 others
|
|
"""
|
|
sep = Markup(", ") if any(isinstance(value, Markup) for value in values) else ", "
|
|
if len(values) == 1:
|
|
# One value, show it.
|
|
display_string = values[0]
|
|
elif len(values) <= display_limit:
|
|
# Fewer than `display_limit` values, show all of them.
|
|
display_string = sep.join(value for value in values[:-1])
|
|
display_string += " and " + values[-1]
|
|
else:
|
|
# More than `display_limit` values, only mention a few.
|
|
display_string = sep.join(value for value in values[:display_limit])
|
|
display_string += and_n_others(values, display_limit)
|
|
|
|
return display_string
|
|
|
|
|
|
md_extensions: list[markdown.Extension] | None = None
|
|
md_macro_extension: markdown.Extension | None = None
|
|
# Prevent the automatic substitution of macros in these docs. If
|
|
# they contain a macro, it is always used literally for documenting
|
|
# the macro system.
|
|
docs_without_macros = [
|
|
"incoming-webhooks-walkthrough.md",
|
|
]
|
|
|
|
|
|
# render_markdown_path is passed a context dictionary (unhashable), which
|
|
# results in the calls not being cached. To work around this, we convert the
|
|
# dict to a tuple of dict items to cache the results.
|
|
@dict_to_items_tuple
|
|
@ignore_unhashable_lru_cache(512)
|
|
@items_tuple_to_dict
|
|
@register.filter(name="render_markdown_path", is_safe=True)
|
|
def render_markdown_path(
|
|
markdown_file_path: str,
|
|
context: dict[str, Any] | None = None,
|
|
integration_doc: bool = False,
|
|
help_center: bool = False,
|
|
) -> str:
|
|
"""Given a path to a Markdown file, return the rendered HTML.
|
|
|
|
Note that this assumes that any HTML in the Markdown file is
|
|
trusted; it is intended to be used for documentation, not user
|
|
data."""
|
|
|
|
# We set this global hackishly
|
|
from zerver.lib.markdown.help_settings_links import set_relative_settings_links
|
|
|
|
relative_links = bool(context is not None and context.get("html_settings_links"))
|
|
set_relative_settings_links(relative_links)
|
|
from zerver.lib.markdown.help_relative_links import set_relative_help_links
|
|
|
|
billing_relative_links = bool(context is not None and context.get("corporate_enabled"))
|
|
set_relative_help_links(relative_links, billing_relative_links)
|
|
|
|
global md_extensions, md_macro_extension
|
|
if md_extensions is None:
|
|
md_extensions = [
|
|
markdown.extensions.extra.makeExtension(),
|
|
markdown.extensions.toc.makeExtension(),
|
|
markdown.extensions.admonition.makeExtension(),
|
|
markdown.extensions.codehilite.makeExtension(
|
|
linenums=False,
|
|
guess_lang=False,
|
|
),
|
|
zerver.lib.markdown.fenced_code.makeExtension(
|
|
run_content_validators=bool(
|
|
context is not None and context.get("run_content_validators", False)
|
|
),
|
|
),
|
|
zerver.lib.markdown.api_arguments_table_generator.makeExtension(),
|
|
zerver.lib.markdown.api_return_values_table_generator.makeExtension(),
|
|
zerver.lib.markdown.nested_code_blocks.makeExtension(),
|
|
zerver.lib.markdown.tabbed_sections.makeExtension(),
|
|
zerver.lib.markdown.help_settings_links.makeExtension(),
|
|
zerver.lib.markdown.help_relative_links.makeExtension(),
|
|
zerver.lib.markdown.static.makeExtension(),
|
|
]
|
|
if context is not None and "api_url" in context:
|
|
# We need to generate the API code examples extension each
|
|
# time so the `api_url` config parameter can be set dynamically.
|
|
#
|
|
# TODO: Convert this to something more efficient involving
|
|
# passing the API URL as a direct parameter.
|
|
extensions = [
|
|
zerver.openapi.markdown_extension.makeExtension(
|
|
api_url=context["api_url"],
|
|
),
|
|
*md_extensions,
|
|
]
|
|
else:
|
|
extensions = md_extensions
|
|
|
|
if integration_doc:
|
|
md_macro_extension = zerver.lib.markdown.include.makeExtension(
|
|
base_path="templates/zerver/integrations/include/"
|
|
)
|
|
elif help_center:
|
|
md_macro_extension = zerver.lib.markdown.include.makeExtension(base_path="help/include/")
|
|
else:
|
|
md_macro_extension = zerver.lib.markdown.include.makeExtension(
|
|
base_path="api_docs/include/"
|
|
)
|
|
if not any(doc in markdown_file_path for doc in docs_without_macros):
|
|
extensions = [md_macro_extension, *extensions]
|
|
|
|
md_engine = markdown.Markdown(extensions=extensions)
|
|
md_engine.reset()
|
|
|
|
jinja = engines["Jinja2"]
|
|
assert isinstance(jinja, Jinja2)
|
|
if markdown_file_path.startswith("/"):
|
|
with open(markdown_file_path) as fp:
|
|
markdown_string = fp.read()
|
|
else:
|
|
markdown_string = jinja.env.loader.get_source(jinja.env, markdown_file_path)[0]
|
|
|
|
if (
|
|
settings.DEVELOPMENT
|
|
and Path(markdown_file_path).resolve() == Path("api_docs/changelog.md").resolve()
|
|
):
|
|
updated_changelog = get_updated_changelog()
|
|
if updated_changelog is not None:
|
|
markdown_string = updated_changelog
|
|
|
|
API_ENDPOINT_NAME = context.get("API_ENDPOINT_NAME", "") if context is not None else ""
|
|
markdown_string = markdown_string.replace("API_ENDPOINT_NAME", API_ENDPOINT_NAME)
|
|
|
|
html = md_engine.convert(markdown_string)
|
|
if context is None:
|
|
return mark_safe(html) # noqa: S308
|
|
|
|
return mark_safe(jinja.from_string(html).render(context)) # noqa: S308
|
|
|
|
|
|
def webpack_entry(entrypoint: str) -> list[str]:
|
|
while True:
|
|
with open(settings.WEBPACK_STATS_FILE, "rb") as f:
|
|
stats = orjson.loads(f.read())
|
|
status = stats["status"]
|
|
if not settings.DEBUG or status != "compile":
|
|
break
|
|
time.sleep(0.2)
|
|
|
|
if status != "done":
|
|
raise RuntimeError("Webpack compilation was not successful")
|
|
|
|
try:
|
|
files_from_entrypoints = [
|
|
staticfiles_storage.url(settings.WEBPACK_BUNDLES + filename)
|
|
for filename in stats["chunks"][entrypoint]
|
|
if filename.endswith((".css", ".js")) and not filename.endswith(".hot-update.js")
|
|
]
|
|
except KeyError:
|
|
raise KeyError(
|
|
f"'{entrypoint}' entrypoint could not be found. Please define it in web/webpack.assets.json."
|
|
)
|
|
|
|
return files_from_entrypoints
|