mirror of
https://github.com/zulip/zulip.git
synced 2025-11-19 05:58:25 +00:00
help-beta: Introduce NavigationSteps component for settings links.
Fixes #31254. We are using `SHOW_RELATIVE_LINKS` as the env variable to set if we want to show relative settings link or non-linked markdown instructions. We are not trying to determine `SHOW_SETTINGS_LINK` by ourselves. See https://chat.zulip.org/#narrow/channel/49-development-help/topic/Passing.20sitename.20for.20astro.20project.20in.20production.2E for more details. Until the cutover happens, we would need to manually update the mapping in both the astro component and the python file, but since that mapping is not frequently changed, that is a tradeoff we can make. We had to add margin-bottom: 0 to icon styling since starlight was inserting a margin-bottom of 1.25 em for list items.
This commit is contained in:
committed by
Tim Abbott
parent
8e1f0c4bcf
commit
a0deeae80e
@@ -1,7 +1,7 @@
|
|||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
|
|
||||||
import starlight from "@astrojs/starlight";
|
import starlight from "@astrojs/starlight";
|
||||||
import {defineConfig} from "astro/config";
|
import {defineConfig, envField} from "astro/config";
|
||||||
import Icons from "unplugin-icons/vite";
|
import Icons from "unplugin-icons/vite";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
@@ -16,7 +16,7 @@ export default defineConfig({
|
|||||||
// It was setting the height to 1024 and 960 for some
|
// It was setting the height to 1024 and 960 for some
|
||||||
// icons. It is better to set the height explicitly.
|
// icons. It is better to set the height explicitly.
|
||||||
defaultStyle:
|
defaultStyle:
|
||||||
"display: inline; vertical-align: text-bottom; height: 1em; width: 1em;",
|
"display: inline; vertical-align: text-bottom; height: 1em; width: 1em; margin-bottom: 0;",
|
||||||
customCollections: {
|
customCollections: {
|
||||||
// unplugin-icons has a FileSystemIconLoader which is more
|
// unplugin-icons has a FileSystemIconLoader which is more
|
||||||
// versatile. But it only supports one directory path for
|
// versatile. But it only supports one directory path for
|
||||||
@@ -37,6 +37,16 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
env: {
|
||||||
|
schema: {
|
||||||
|
SHOW_RELATIVE_LINKS: envField.boolean({
|
||||||
|
context: "client",
|
||||||
|
access: "public",
|
||||||
|
optional: true,
|
||||||
|
default: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
starlight({
|
starlight({
|
||||||
title: "Zulip help center",
|
title: "Zulip help center",
|
||||||
|
|||||||
191
help-beta/src/components/NavigationSteps.astro
Normal file
191
help-beta/src/components/NavigationSteps.astro
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
---
|
||||||
|
import { SHOW_RELATIVE_LINKS } from "astro:env/client";
|
||||||
|
import RawZulipIconGear from "~icons/zulip-icon/gear?raw";
|
||||||
|
|
||||||
|
// This list has been transformed one-off from `help_settings_links.py`, we
|
||||||
|
// have added a comment in that file to update this list in case of any
|
||||||
|
// changes.
|
||||||
|
const setting_link_mapping: {
|
||||||
|
[key: string]: {
|
||||||
|
setting_type: string,
|
||||||
|
setting_name: string,
|
||||||
|
setting_link: string,
|
||||||
|
}
|
||||||
|
} = {
|
||||||
|
// a mapping from the setting identifier that is the same as the final URL
|
||||||
|
// breadcrumb to that setting to the name of its setting type, the setting
|
||||||
|
// name as it appears in the user interface, and a relative link that can
|
||||||
|
// be used to get to that setting
|
||||||
|
"profile": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Profile",
|
||||||
|
setting_link: "/#settings/profile"
|
||||||
|
},
|
||||||
|
"account-and-privacy": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Account & privacy",
|
||||||
|
setting_link: "/#settings/account-and-privacy"
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Preferences",
|
||||||
|
setting_link: "/#settings/preferences"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Notifications",
|
||||||
|
setting_link: "/#settings/notifications"
|
||||||
|
},
|
||||||
|
"your-bots": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Bots",
|
||||||
|
setting_link: "/#settings/your-bots"
|
||||||
|
},
|
||||||
|
"alert-words": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Alert words",
|
||||||
|
setting_link: "/#settings/alert-words"
|
||||||
|
},
|
||||||
|
"uploaded-files": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Uploaded files",
|
||||||
|
setting_link: "/#settings/uploaded-files"
|
||||||
|
},
|
||||||
|
"topics": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Topics",
|
||||||
|
setting_link: "/#settings/topics"
|
||||||
|
},
|
||||||
|
"muted-users": {
|
||||||
|
setting_type: "Personal settings",
|
||||||
|
setting_name: "Muted users",
|
||||||
|
setting_link: "/#settings/muted-users"
|
||||||
|
},
|
||||||
|
"organization-profile": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Organization profile",
|
||||||
|
setting_link: "/#organization/organization-profile"
|
||||||
|
},
|
||||||
|
"organization-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Organization settings",
|
||||||
|
setting_link: "/#organization/organization-settings"
|
||||||
|
},
|
||||||
|
"organization-permissions": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Organization permissions",
|
||||||
|
setting_link: "/#organization/organization-permissions"
|
||||||
|
},
|
||||||
|
"default-user-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Default user settings",
|
||||||
|
setting_link: "/#organization/organization-level-user-defaults"
|
||||||
|
},
|
||||||
|
"emoji-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Custom emoji",
|
||||||
|
setting_link: "/#organization/emoji-settings"
|
||||||
|
},
|
||||||
|
"auth-methods": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Authentication methods",
|
||||||
|
setting_link: "/#organization/auth-methods"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Users",
|
||||||
|
setting_link: "/#organization/users/active"
|
||||||
|
},
|
||||||
|
"deactivated": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Users",
|
||||||
|
setting_link: "/#organization/users/deactivated"
|
||||||
|
},
|
||||||
|
"invitations": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Users",
|
||||||
|
setting_link: "/#organization/users/invitations"
|
||||||
|
},
|
||||||
|
"bot-list-admin": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Bots",
|
||||||
|
setting_link: "/#organization/bot-list-admin"
|
||||||
|
},
|
||||||
|
"default-channels-list": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Default channels",
|
||||||
|
setting_link: "/#organization/default-channels-list"
|
||||||
|
},
|
||||||
|
"linkifier-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Linkifiers",
|
||||||
|
setting_link: "/#organization/linkifier-settings"
|
||||||
|
},
|
||||||
|
"playground-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Code playgrounds",
|
||||||
|
setting_link: "/#organization/playground-settings"
|
||||||
|
},
|
||||||
|
"profile-field-settings": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Custom profile fields",
|
||||||
|
setting_link: "/#organization/profile-field-settings"
|
||||||
|
},
|
||||||
|
"data-exports-admin": {
|
||||||
|
setting_type: "Organization settings",
|
||||||
|
setting_name: "Data exports",
|
||||||
|
setting_link: "/#organization/data-exports-admin"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSettingsMarkdown = (setting_type_name: string, setting_name: string) => `
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Click on the <b>gear</b> (${RawZulipIconGear}) icon in the upper
|
||||||
|
right corner of the web or desktop app.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Select <b>${setting_type_name}</b>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
On the left, click <b>${setting_name}</b>.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
`
|
||||||
|
|
||||||
|
const getSettingsHTML = (
|
||||||
|
setting_key: string,
|
||||||
|
SHOW_RELATIVE_LINKS: boolean
|
||||||
|
): string => {
|
||||||
|
const {
|
||||||
|
setting_type,
|
||||||
|
setting_name,
|
||||||
|
setting_link,
|
||||||
|
} = setting_link_mapping[setting_key]!;
|
||||||
|
|
||||||
|
if (!SHOW_RELATIVE_LINKS) {
|
||||||
|
return getSettingsMarkdown(setting_type, setting_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeLink = `<a href="${setting_link}">${setting_name}</a>`;
|
||||||
|
|
||||||
|
// The "Bots" label appears in both Personal and Organization settings
|
||||||
|
// in the user interface so we need special text for this setting.
|
||||||
|
const label = (setting_name === "Bots" || setting_name === "Users")
|
||||||
|
? `Navigate to the ${relativeLink} tab of the <b>${setting_type}</b> menu.`
|
||||||
|
: `Go to ${relativeLink}.`;
|
||||||
|
|
||||||
|
return `<ol>
|
||||||
|
<li>${label}</li>
|
||||||
|
</ol>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { identifier } = Astro.props;
|
||||||
|
const navigation_link_type = identifier.split("/")[0];
|
||||||
|
if (navigation_link_type !== "settings") {
|
||||||
|
throw new Error("Invalid navigation link type. Only `settings` is allowed.");
|
||||||
|
}
|
||||||
|
const resultHTML = getSettingsHTML(identifier.split("/")[1], SHOW_RELATIVE_LINKS);
|
||||||
|
|
||||||
|
---
|
||||||
|
<Fragment set:html={resultHTML} />
|
||||||
@@ -25,6 +25,7 @@ django.setup()
|
|||||||
from zerver.lib.markdown.tabbed_sections import generate_content_blocks, parse_tabs
|
from zerver.lib.markdown.tabbed_sections import generate_content_blocks, parse_tabs
|
||||||
|
|
||||||
INDENT_SPACES = " "
|
INDENT_SPACES = " "
|
||||||
|
SETTINGS_LINK_PATTERN = re.compile(r"{settings_tab\|(?P<setting_identifier>.*?)}")
|
||||||
|
|
||||||
# TODO list before cutover.
|
# TODO list before cutover.
|
||||||
#
|
#
|
||||||
@@ -69,6 +70,21 @@ def replace_emoticon_translation_table(markdown_string: str, import_statement_se
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def replace_setting_links(
|
||||||
|
markdown_string: str, import_statement_set: set[str], import_relative_base_path: str
|
||||||
|
) -> str:
|
||||||
|
setting_links_pattern = re.compile(r"{settings_tab\|(?P<setting_identifier>.*?)}")
|
||||||
|
|
||||||
|
def replace_setting_links_match(match: re.Match[str]) -> str:
|
||||||
|
import_statement_set.add(
|
||||||
|
f'import NavigationSteps from "{import_relative_base_path}/NavigationSteps.astro"'
|
||||||
|
)
|
||||||
|
setting_identifier = match.group("setting_identifier")
|
||||||
|
return f'<NavigationSteps identifier="settings/{setting_identifier}" />'
|
||||||
|
|
||||||
|
return setting_links_pattern.sub(replace_setting_links_match, markdown_string)
|
||||||
|
|
||||||
|
|
||||||
def replace_image_path(markdown_string: str, replacement_path: str) -> str:
|
def replace_image_path(markdown_string: str, replacement_path: str) -> str:
|
||||||
"""
|
"""
|
||||||
We will point to the existing image folder till
|
We will point to the existing image folder till
|
||||||
@@ -321,6 +337,10 @@ def insert_flattened_steps_component(
|
|||||||
if include_files_info[filename]["is_only_ordered_list"]:
|
if include_files_info[filename]["is_only_ordered_list"]:
|
||||||
index += step
|
index += step
|
||||||
continue
|
continue
|
||||||
|
settings_link_match = SETTINGS_LINK_PATTERN.match(line)
|
||||||
|
if settings_link_match:
|
||||||
|
index += step
|
||||||
|
continue
|
||||||
break
|
break
|
||||||
return index
|
return index
|
||||||
|
|
||||||
@@ -330,11 +350,8 @@ def insert_flattened_steps_component(
|
|||||||
# resulting in two opening <FlattenList> one after the other.
|
# resulting in two opening <FlattenList> one after the other.
|
||||||
# Using a set avoids this problem.
|
# Using a set avoids this problem.
|
||||||
insertions = set()
|
insertions = set()
|
||||||
for match in file_include_pattern.finditer(markdown_string):
|
|
||||||
filename = match.group(1)
|
|
||||||
if not include_files_info[filename]["is_only_ordered_list"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
def insert_flatten_list(match: re.Match[str]) -> None:
|
||||||
match_line_index = markdown_string[: match.start()].count("\n")
|
match_line_index = markdown_string[: match.start()].count("\n")
|
||||||
|
|
||||||
upper_bound = traverse_to_boundary(match_line_index - 1, step=-1)
|
upper_bound = traverse_to_boundary(match_line_index - 1, step=-1)
|
||||||
@@ -343,6 +360,15 @@ def insert_flattened_steps_component(
|
|||||||
lower_bound = traverse_to_boundary(match_line_index + 1, step=1)
|
lower_bound = traverse_to_boundary(match_line_index + 1, step=1)
|
||||||
insertions.add((lower_bound, "</FlattenList>"))
|
insertions.add((lower_bound, "</FlattenList>"))
|
||||||
|
|
||||||
|
for match in SETTINGS_LINK_PATTERN.finditer(markdown_string):
|
||||||
|
insert_flatten_list(match)
|
||||||
|
|
||||||
|
for match in file_include_pattern.finditer(markdown_string):
|
||||||
|
filename = match.group(1)
|
||||||
|
if not include_files_info[filename]["is_only_ordered_list"]:
|
||||||
|
continue
|
||||||
|
insert_flatten_list(match)
|
||||||
|
|
||||||
if insertions:
|
if insertions:
|
||||||
import_statement_set.add("import FlattenList from '../../components/FlattenList.astro';")
|
import_statement_set.add("import FlattenList from '../../components/FlattenList.astro';")
|
||||||
# Insert tags in reverse order to avoid index shifting
|
# Insert tags in reverse order to avoid index shifting
|
||||||
@@ -491,6 +517,7 @@ def convert_help_center_file_to_mdx(
|
|||||||
result = fix_file_imports(result, import_statement_set, "./include")
|
result = fix_file_imports(result, import_statement_set, "./include")
|
||||||
result = convert_admonitions_to_asides(result, import_statement_set, "../../components")
|
result = convert_admonitions_to_asides(result, import_statement_set, "../../components")
|
||||||
result = convert_tab_syntax(result, import_statement_set)
|
result = convert_tab_syntax(result, import_statement_set)
|
||||||
|
result = replace_setting_links(result, import_statement_set, "../../components")
|
||||||
result = escape_curly_braces(result)
|
result = escape_curly_braces(result)
|
||||||
result = fix_relative_path(result)
|
result = fix_relative_path(result)
|
||||||
result = replace_emoticon_translation_table(result, import_statement_set)
|
result = replace_emoticon_translation_table(result, import_statement_set)
|
||||||
@@ -527,6 +554,7 @@ def convert_include_file_to_mdx(
|
|||||||
result = fix_file_imports(result, import_statement_set, ".")
|
result = fix_file_imports(result, import_statement_set, ".")
|
||||||
result = convert_admonitions_to_asides(result, import_statement_set, "../../../components")
|
result = convert_admonitions_to_asides(result, import_statement_set, "../../../components")
|
||||||
result = convert_tab_syntax(result, import_statement_set)
|
result = convert_tab_syntax(result, import_statement_set)
|
||||||
|
result = replace_setting_links(result, import_statement_set, "../../../components")
|
||||||
result = escape_curly_braces(result)
|
result = escape_curly_braces(result)
|
||||||
result = fix_relative_path(result)
|
result = fix_relative_path(result)
|
||||||
result = replace_image_path(result, "../../../../../static/images/help")
|
result = replace_image_path(result, "../../../../../static/images/help")
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITIES
|
|||||||
|
|
||||||
REGEXP = re.compile(r"\{settings_tab\|(?P<setting_identifier>.*?)\}")
|
REGEXP = re.compile(r"\{settings_tab\|(?P<setting_identifier>.*?)\}")
|
||||||
|
|
||||||
|
|
||||||
|
# If any changes to this link mapping are made,
|
||||||
|
# `help-beta/src/components/NavigationSteps.astro` should be updated accordingly.
|
||||||
|
# This manual update mechanism will cease to exist once we have switched to the
|
||||||
|
# help-beta system.
|
||||||
link_mapping = {
|
link_mapping = {
|
||||||
# a mapping from the setting identifier that is the same as the final URL
|
# a mapping from the setting identifier that is the same as the final URL
|
||||||
# breadcrumb to that setting to the name of its setting type, the setting
|
# breadcrumb to that setting to the name of its setting type, the setting
|
||||||
|
|||||||
Reference in New Issue
Block a user