#225 Redesign Docs front page layout
Opened 2 years ago by harshitajain. Modified 2 years ago
fedora-docs/ harshitajain/docs-fp-o prod  into  master

Add package maintainers
Otto Urpelainen • 2 years ago  
Move eln-docs to GitHub
Aleksandra Fedorova • 3 years ago  
Publish PgM docs to prod
Ben Cotton • 4 years ago  
file modified
+1
@@ -1,3 +1,4 @@ 

  build

  public

  cache

+ build-scripts/output

file modified
+3 -3
@@ -1,9 +1,9 @@ 

- FROM fedora:29

+ FROM fedora:34

  

- RUN dnf -y module install nodejs:10 && \

+ RUN dnf -y module install nodejs:16 && \

      dnf clean all

  

- RUN npm i -g @antora/cli@2.0 @antora/site-generator-default@2.0

+ RUN npm i -g @antora/cli@3.0.0 @antora/site-generator@3.0.0 @antora/lunr-extension@1.0.0-alpha.6

  

  WORKDIR /antora

  ENTRYPOINT [ "antora" ]

file modified
+6 -6
@@ -1,13 +1,13 @@ 

- FROM fedora:29

+ FROM fedora:34

  

- RUN dnf -y module install nodejs:10/default && \

-     dnf -y install git && \

+ RUN dnf -y module install nodejs:16 && \

+     dnf -y install git python3-yaml && \

      dnf clean all

  

- RUN npm i -g @antora/cli@2.0 @antora/site-generator-default@2.0

+ RUN npm i -g @antora/cli@3.0.0 @antora/site-generator@3.0.0 @antora/lunr-extension@1.0.0-alpha.6

  

- ADD rebuild-site.sh /antora/rebuild-site.sh

+ ADD rebuild-site.py /antora/rebuild-site.py

  

  WORKDIR /antora

  

- CMD /antora/rebuild-site.sh

+ CMD /antora/rebuild-site.py

file modified
+41 -2
@@ -1,7 +1,46 @@ 

  # Building the docs

  

+ This script is used in OpenShift to rebuild the whole site for Fedora.

+ 

+ 

+ ## Local testing

+ 

+ Test the build locally using podman.

+ 

+ First, make sure your in the right directory — it's the same one that has this *README.md* file. Then build the container on your system:

+ 

+ ```

+ $ podman build --no-cache -t fedora-docs-openshift-rebuild . 

+ ```

+ 

+ And then run it:

+ 

+ ```

+ $ mkdir output

+ $ podman run --rm -it -e "BUILD_ENV=prod" -v $(pwd)/output:/antora/output:Z fedora-docs-openshift-rebuild

+ ```

+ 

+ The output will be saved in `./output/`.

+ 

+ ### Changing configuration

+ 

+ You can also set different environment variables by setting `-e "VAR_NAME=value"` in the `podman run` command.

+ See a list of supported variables below.

+ 

  ```

- docker run --rm -it -e "BUILD_ENV=stg" -v $(pwd)/output:/antora/output antora-openshift-rebuild

+ # The script requires the following environment variables to be set:

+ # 

+ # Variable          Values      Effect

+ # -----------------|-----------------------------------------------------------

+ # BUILD_ENV         prod        Build the production version of the site

+ #                               using the "prod" branch. DEFAULT

+ #                   stg         Build the staging version of the site

+ #                               using the "stg" branch.

+ # -----------------|-----------------------------------------------------------

+ # BUILD_LANGS       english     Build the "en-US" version only. DEFAULT

+ #                   translated  Build only the translated versions.

+ #                   all         Build the "en-US" and the translated versions.

  ```

  

- The output will be in `./output/`.

+ Please note that rebuilding all the languages locally will take many hours and results in gigabytes of output.

+ 

@@ -0,0 +1,575 @@ 

+ #!/usr/bin/python3

+ 

+ 

+ # The script requires the following environment variables to be set:

+ # 

+ # Variable          Values      Effect

+ # -----------------|-----------------------------------------------------------

+ # BUILD_ENV         prod        Build the production version of the site

+ #                               using the "prod" branch. DEFAULT

+ #                   stg         Build the staging version of the site

+ #                               using the "stg" branch.

+ # -----------------|-----------------------------------------------------------

+ # BUILD_LANGS       english     Build the "en-US" version only. DEFAULT

+ #                   translated  Build only the translated versions.

+ #                   all         Build the "en-US" and the translated versions.

+ #

+ #

+ 

+ 

+ import tempfile

+ import yaml

+ import os

+ import errno

+ import subprocess

+ import copy

+ import datetime

+ import shutil

+ import sys

+ import glob

+ import re

+ 

+ 

+ def get_config():

+     config = {}

+ 

+     config["docs_repo_branch"] = os.getenv(

+         "BUILD_ENV",

+         "prod")

+ 

+     config["build_langs"] = os.getenv(

+         "BUILD_LANGS",

+         "english")

+ 

+     config["docs_repo_url"] = os.getenv(

+         "DOCS_REPO_URL",

+         "https://pagure.io/fedora-docs/docs-fp-o.git")

+ 

+     config["translated_sources_repo_url"] = os.getenv(

+         "TRANSLATED_SOURCES_REPO_URL",

+         "https://pagure.io/fedora-docs/translated-sources.git"

+     )

+ 

+     config["translated_adockeywords_repo_url"] = os.getenv(

+         "TRANSLATED_ADOCKEYWORDS_REPO_URL",

+         "https://pagure.io/fedora-docs-l10n/asciidoc-keywords.git")

+     

+     os.environ['CI'] = "true"

+ 

+     return config

+ 

+ 

+ def log(msg):

+     print(msg, flush=True)

+ 

+ 

+ def get_languages(config):

+     languages_dict = {}

+ 

+     with tempfile.TemporaryDirectory() as workdir:

+         translated_sources_repo = os.path.join(workdir, "translated_sources_repo")

+         subprocess.run(["git", "clone", "--depth=1", config["translated_sources_repo_url"], translated_sources_repo])

+ 

+         languages = []

+         filename_blacklist = [".git"]

+         for filename in os.listdir(translated_sources_repo):

+             filepath = os.path.join(translated_sources_repo, filename)

+             if os.path.isdir(filepath) and filename not in filename_blacklist:

+                 languages.append(filename)

+ 

+         languages.sort()

+ 

+         for lang in languages:

+             languages_dict[lang] = []

+ 

+             lang_dir = os.path.join(translated_sources_repo, lang)

+             for component in os.listdir(lang_dir):

+                 version_dir = os.path.join(lang_dir, component)

+                 for version in os.listdir(version_dir):

+                     start_path = "{lang}/{component}/{version}".format(

+                             lang=lang, component=component, version=version)

+ 

+                     # This is a workaround for cases when a component doesn't have

+                     # the ROOT module that should contain the antora.yml.

+                     # Without the ROOT module the translation scripts won't

+                     # pick up the antora.yml causing the docs build to fail.

+                     # So we're only including directories with the antora.yml file.

+                     if "antora.yml" in os.listdir(os.path.join(version_dir, version)):

+                         languages_dict[lang].append(start_path)

+ 

+     return languages_dict

+ 

+ 

+ def generate_lang_switch_ui(languages):

+     template_start = """

+         <div class="page-languages">

+             <button class="languages-menu-toggle" title="Show other languages of the site">

+                 {{{env.ANTORA_LANGUAGE}}}

+             </button>

+             <div class="languages-menu">

+     """

+     template_end = """

+             </div>

+         </div>

+     """

+ 

+     template_list = []

+     template_list.append(template_start)

+     for language in languages:

+         link = '<a class="language" href="{{{{siteRootPath}}}}/../{language}{{{{page.url}}}}">{language}</a>'.format(

+                     language=language)

+         template_list.append(link)

+     template_list.append(template_end)

+ 

+     return "\n".join(template_list)

+ 

+ def get_metadata_from_branch(branch, repodir):

+     cmd = f"""

+       for file in $(git ls-tree -r --name-only origin/{branch}|grep -E "adoc$"); do 

+         git log -1 --format="'$file':%n  refs: %S%n  hash: %h%n  date: %as" origin/{branch} -- $file;

+       done"""

+     s = subprocess.run(cmd, shell=True, cwd=repodir,  stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

+     return yaml.safe_load(s.stdout)

+ 

+ def prefetch_sources(original_sources, site_yml, config):

+     if not os.path.isdir(original_sources):

+         os.mkdir(original_sources)

+ 

+     default_branch = site_yml["content"].get("branches", ["master"])

+     if type(default_branch) is str:

+         default_branch = [default_branch]

+ 

+     metadata = {}

+ 

+     for repo_data in site_yml["content"]["sources"]:

+       gitdir = tempfile.TemporaryDirectory(dir=original_sources)

+       dirname = gitdir.name

+       gitdir.cleanup()

+       log(f"Fetching {repo_data['url']}...")

+       subprocess.run(["git", "clone", repo_data['url'], dirname])

+       repo_data['url'] = dirname

+ 

+       branches = repo_data.get("branches", default_branch)

+       if type(branches) is str:

+         branches = [branches]

+ 

+       for branch in branches:

+         y = get_metadata_from_branch(branch, repo_data['url'])

+         if dirname in metadata:

+           metadata[dirname][branch] = y

+         else:

+           metadata[dirname] = { branch: y }

+ 

+     if metadata:

+       with open("/tmp/metadata.yml", "w") as f:

+         yaml.dump(metadata, f)

+ 

+     return site_yml

+ 

+ def prepare_translated_sources(translated_sources, site_yml, languages, config):

+     with tempfile.TemporaryDirectory() as workdir:

+         # Location of the translated-sources repo

+         #   (This repo only holds content that has some translations done,

+         #   so it might be incomplete.)

+         translated_sources_original = os.path.join(workdir, "translated_sources_original")

+ 

+         # Location of the English sources in the same structure

+         # as translated-sources, so:

+         #    COMPONENT/VERSION/antora.yml

+         #    COMPONENT/VERSION/modules/MODULE1

+         #    COMPONENT/VERSION/modules/MODULE2

+         #    COMPONENT/VERSION/modules/...

+         en_sources = os.path.join(workdir, "en_sources")

+ 

+         # Clone the original translated-sources

+         subprocess.run(["git", "clone", "--depth=1", config["translated_sources_repo_url"], translated_sources_original])

+ 

+         # Get a list of the original English repos

+         repos = []

+         default_branch = site_yml["content"].get("branches", ["master"])

+         if type(default_branch) is str:

+             default_branch = [default_branch]

+ 

+         for repo_data in site_yml["content"]["sources"]:

+ 

+             branches = repo_data.get("branches", default_branch)

+             if type(branches) is str:

+                 branches = [branches]

+ 

+             start_path = repo_data.get("start_path", "")

+ 

+             for branch in branches:

+                 repo = {}

+                 repo["url"] = repo_data["url"]

+                 repo["branch"] = branch

+                 repo["start_path"] = start_path

+ 

+                 repos.append(repo)

+         

+         # Clome the original English sources and put them into the

+         # desired structure in en_sources (described above)

+         components = {}

+         for repo in repos:

+             with tempfile.TemporaryDirectory() as tmp_repo_root:

+                 log("")

+                 log("Cloning {url} {branch}".format(url=repo["url"], branch=repo["branch"]))

+                 subprocess.run(["git", "clone", "--branch", repo["branch"], "--depth=1", repo["url"], tmp_repo_root])

+ 

+                 repo_dir = os.path.join(tmp_repo_root, repo["start_path"])

+                 antora_yml_file = os.path.join(repo_dir, "antora.yml")

+                 with open(antora_yml_file, "r") as file:

+                     antora_yml = yaml.safe_load(file)

+ 

+                 component = antora_yml["name"]

+                 version = antora_yml["version"]

+                 # if component version is null (~), fallback to "master"

+                 if not version:

+                     version = "master"

+ 

+                 # Saving components and all their versions

+                 if component not in components:

+                     components[component] = set()

+                 components[component].add(version)

+ 

+                 for module in os.listdir(os.path.join(repo_dir, "modules")):

+                     # Now this is looping over modules accross all components,

+                     # so component, module, and version variables are available

+ 

+                     try:

+                         os.makedirs(os.path.join(en_sources, component, version, "modules"))

+                     except OSError as e:

+                         if e.errno != errno.EEXIST:

+                             raise

+                     

+                     original_module_dir = os.path.join(repo_dir, "modules", module)

+                     module_dir = os.path.join(en_sources, component, version, "modules", module)

+ 

+                     subprocess.run(["cp", "-a", original_module_dir, module_dir])

+ 

+                     if module == "ROOT" or "nav" in antora_yml:

+                         # if this is the main antora.yml file

+                         subprocess.run(["cp", "-a", antora_yml_file, os.path.join(en_sources, component, version, "antora.yml")])

+                         log("----- copying antora.yml for  {component} {module} {version}".format(

+                                 component=component, module=module, version=version))

+                     else:

+                         log("----- skipping antora.yml for  {component} {module} {version}".format(

+                                 component=component, module=module, version=version))

+ 

+         # Set up the language structure in translated_sources

+         for language in languages:

+             lang_dir = os.path.join(translated_sources, language)

+             try:

+                 os.makedirs(lang_dir)

+             except OSError as e:

+                 if e.errno != errno.EEXIST:

+                     raise

+         

+         # Copy the English sources in the translated_sources

+         for language in languages:

+             lang_dir = os.path.join(translated_sources, language)

+             for component in components:

+                 en_component_dir = os.path.join(en_sources, component)

+                 subprocess.run(["cp", "-a", en_component_dir, lang_dir])

+ 

+         # And finally copy the original translated sources

+         # into translated_sources

+         for language in languages:

+             src = os.path.join(translated_sources_original, language)

+             subprocess.run(["cp", "-a", src, translated_sources + "/"])

+ 

+     return components

+ 

+ 

+ def prepare_localized_admonitions(languages, config):

+     """ Asciidoc use keywords for admonitions and others items """

+     keywords = {}

+ 

+     with tempfile.TemporaryDirectory() as workdir:

+         translated_keywords_repo = os.path.join(workdir, "asciidoc-keywords")

+         subprocess.run(["git", "clone", "--depth=1", config["translated_adockeywords_repo_url"], translated_keywords_repo])

+ 

+         languages = []

+         log(translated_keywords_repo + "/langs/*/asciidoc-attributes.yml")

+         for filename in glob.glob(translated_keywords_repo + "/langs/*/asciidoc-attributes.yml"):

+             languages.append(filename.rsplit("/")[::-1][1])

+ 

+         for lang in languages:

+             file = translated_keywords_repo + "/langs/" + lang + "/asciidoc-attributes.yml"

+             with open(file, 'r') as stream:

+                 try:

+                     keywords[lang] = yaml.load(stream)

+                 except yaml.YAMLError as exc:

+                     print(exc)

+ 

+     return keywords

+ 

+ 

+ def init_git_repo(path):

+     try:

+         os.makedirs(path)

+     except OSError as e:

+         if e.errno != errno.EEXIST:

+             raise

+     subprocess.run(["git", "init"], cwd=path)

+     subprocess.run(["git", "config", "user.name", "Your Name"], cwd=path)

+     subprocess.run(["git", "config", "user.email", "you@example.com"], cwd=path)

+     subprocess.run(["git", "commit", "--allow-empty", "-m", "init"], cwd=path)

+ 

+ def get_version_from_file(index_file):

+     latest = None

+     if os.path.isfile(index_file):

+       with open(index_file, 'r') as f:

+         for l in f:

+           m = re.search(r'data-version="(f\d+)"', l)

+           if m:

+             latest = m[1]

+             break

+     return latest

+ 

+ 

+ def gen_redirect(lang, results_dir):

+     htaccess = os.path.join(results_dir, ".htaccess")

+     latest = get_version_from_file(f"{results_dir}/fedora/latest/index.html")

+     if latest:

+       htaccess_content = f"""

+         ErrorDocument 404 /{lang}/404.html

+         Redirect 302 /{lang}/fedora/{latest} /{lang}/fedora/latest

+         """

+       with open(htaccess, "w") as file:

+           file.write(htaccess_content)

+ 

+ def main():

+     config = get_config()

+     

+     with tempfile.TemporaryDirectory() as workdir:

+ 

+         #####--------------------------------------#####

+         #####  Preparation                         #####

+         #####--------------------------------------#####

+ 

+         # Location of the docs-fp-o repo

+         docs_repo = os.path.join(workdir, "docs_repo")

+ 

+         # Location of the translated sources used for the build

+         #   (That's the translated-sources repo with the missing files added

+         #   from the English sources.)

+         translated_sources = os.path.join(workdir, "translated_sources")

+         init_git_repo(translated_sources)

+ 

+         log("")

+         log("===== Getting the site definition (site.yml) =====")

+         subprocess.run(["git", "clone", "--branch", config["docs_repo_branch"], "--depth=1", config["docs_repo_url"], docs_repo])

+ 

+         with open(os.path.join(docs_repo, "site.yml"), "r") as file:

+             original_site_yml = yaml.safe_load(file)

+ 

+         log("")

+         log("===== Getting a list of languages =====")

+         languages = get_languages(config)

+         log("  Languages: {}".format(" ".join(languages)))

+ 

+         log("")

+         log("===== Generating the language switch UI =====")

+         lang_switch_ui = generate_lang_switch_ui(["en-US"] + list(languages.keys()))

+ 

+         ui_dir = os.path.join(docs_repo, "supplemental-ui", "partials")

+         try:

+             os.makedirs(ui_dir)

+         except OSError as e:

+             if e.errno != errno.EEXIST:

+                 raise

+ 

+         ui_file = os.path.join(ui_dir, "page-languages.hbs")

+         with open(ui_file, "w") as file:

+             file.write(lang_switch_ui)

+ 

+         # Timestamp to be included in the footer of the docs

+         timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')

+         antora_env = copy.deepcopy(os.environ)

+         antora_env["ANTORA_DATE"] = timestamp

+ 

+ 

+ 

+         #####--------------------------------------#####

+         #####  English site build                  #####

+         #####--------------------------------------#####

+ 

+         if config["build_langs"] == "english" or config["build_langs"] == "all":

+ 

+             log("")

+             log("===== Prefetching all components =====")

+             original_sources = os.path.join(workdir, "orginal_sources")

+             site_local = prefetch_sources(original_sources, original_site_yml, config)

+             log(original_site_yml["content"]["sources"][0]['url'])

+             log(site_local["content"]["sources"][0]['url'])

+ 

+             filename = os.path.join(docs_repo, "site-local.yml")

+             with open(filename, "w") as file:

+                 file.write(yaml.dump(site_local))

+ 

+             

+ 

+             log("")

+             log("===== Building the en-US site =====")

+             antora_env["ANTORA_LANGUAGE"] = "en-US"

+             # result = subprocess.run(["antora", "--html-url-extension-style=indexify", os.path.join(docs_repo, "site.yml")], env=antora_env)

+             result = subprocess.run(["antora", "--extension=lunr", filename], env=antora_env)

+ 

+             if result.returncode != 0:

+                 log("ERROR building the en-US site")

+                 sys.exit(1)

+                 

+             log("")

+             log("===== Copying the en-US site =====")

+             source_dir = os.path.join(docs_repo, "public")

+             target_dir_copying = "/antora/output/en-US.building"

+             target_dir_copied = "/antora/output/en-US.building/en-US"

+             target_dir_final = "/antora/output/en-US"

+ 

+             # Generate htaccess for redirect

+             gen_redirect("en-US", os.path.join(source_dir, "en-US"))

+ 

+             shutil.rmtree(target_dir_copying, ignore_errors=True)

+             subprocess.run(["cp", "-a", source_dir, target_dir_copying])

+             shutil.rmtree(target_dir_final, ignore_errors=True)

+             shutil.move(target_dir_copied, target_dir_final)

+             shutil.rmtree(target_dir_copying, ignore_errors=True)

+ 

+             log("")

+             log("===== Copying the index.html =====")

+             source_index_html = os.path.join(docs_repo, "static", "index.html")

+             target_index_html = "/antora/output/index.html"

+             shutil.copy(source_index_html, target_index_html)

+ 

+ 

+ 

+         #####--------------------------------------#####

+         #####  Translated site build               #####

+         #####--------------------------------------#####

+ 

+ 

+         if config["build_langs"] == "translated" or config["build_langs"] == "all":

+ 

+             log("")

+             log("===== Preparing the translated sources =====")

+             components = prepare_translated_sources(translated_sources, original_site_yml, languages, config)

+ 

+             log("")

+             log("===== Preparing the translated ascidoc keywords =====")

+             keywords = prepare_localized_admonitions(languages, config)

+ 

+             log("")

+             log("===== Generating site.lang.yml files =====")

+             for lang in languages:

+                 lang_site_yml = copy.deepcopy(original_site_yml)

+ 

+                 lang_site_yml["output"]["dir"] = "./public/{lang}".format(lang=lang)

+ 

+                 lang_site_yml["content"] = {}

+                 # Branches are set to HEAD because the script uses a locally-generated

+                 # content on top of the original translated-sources repo

+                 lang_site_yml["content"]["branches"] = "HEAD"

+                 # disable the "Edit this Page"

+                 lang_site_yml["content"]["edit_url"] = False

+                 lang_site_yml["content"]["sources"] = []

+ 

+                 for component, versions in components.items():

+                     for version in versions:

+                         source = {}

+                         source["url"] = translated_sources

+                         source["start_path"] = "{lang}/{component}/{version}".format(

+                                     lang=lang, component=component, version=version)

+ 

+                         lang_site_yml["content"]["sources"].append(source)

+ 

+                 if lang in keywords:

+                     if "attributes" in lang_site_yml["asciidoc"]:

+                         lang_site_yml["asciidoc"]["attributes"].append(keywords[lang])

+                     else:

+                         lang_site_yml["asciidoc"]["attributes"] = keywords[lang]

+ 

+                 # disable page-last-update extension 

+                 if "antora" in lang_site_yml and "extensions" in lang_site_yml["antora"]:

+                   for ext in lang_site_yml["antora"]["extensions"]:

+                     if 'id' in ext and ext['id'] == "page-last-update":

+                       ext['enabled'] = False

+ 

+                 # don't pull ui-bundle (use cache)

+                 lang_site_yml["runtime"]["fetch"] = False

+ 

+                 filename = os.path.join(docs_repo, "site-{lang}.yml".format(lang=lang))

+                 with open(filename, "w") as file:

+                     file.write(yaml.dump(lang_site_yml))

+ 

+                 log("  {lang} done".format(lang=lang))

+ 

+             # Building all the translated sites

+             lastlang = list(languages)[-1]

+             for lang in languages:

+                 log("")

+                 log("===== Building the {lang} site =====".format(lang=lang))

+                 filename = "site-{lang}.yml".format(lang=lang)

+                 antora_env["ANTORA_LANGUAGE"] = lang

+                 result = subprocess.run(["antora", "--extension=lunr", os.path.join(docs_repo, filename)], env=antora_env)

+ 

+                 if result.returncode != 0:

+                     log("ERROR building the {lang} site".format(lang=lang))

+                     sys.exit(1)

+ 

+                 # Copying the translated site

+                 log("")

+                 log(f"=== Copying the {lang} site ===")

+ 

+                 results_dir = os.path.join(docs_repo, "public", lang)

+                 publish_dir = f"/antora/output/{lang}"

+                 copying_dir = f"/antora/output/{lang}.tmp"

+                 lastlang_dir = f"/antora/output/{lastlang}"

+ 

+                 # I have:

+                 # docs_repo/public/xx         <- results_dir  (local partition)

+                 # /antora/output/xx.tmp       <- copying_dir  (mounted partition)

+                 # /antora/output/xx           <- publish_dir  (mounted partition)

+                 #

+                 # I need to:

+                 #   1/  copy from local to mounted

+                 #   2/  remove old in mounted

+                 #   3/  move new within mounted

+                 #   4/  remove the copying dir

+ 

+                 # Generate htaccess for redirect

+                 gen_redirect(lang, results_dir)

+ 

+                 # Make sure copying_dir doesn't exist

+                 shutil.rmtree(copying_dir, ignore_errors=True)

+ 

+                 # Copy results from local partition to a mounted partition

+                 log(f"Copying from {results_dir} to {copying_dir}")

+                 subprocess.run(["cp", "-a", results_dir, copying_dir])

+ 

+                 # Swap the old tree for the new one for each language

+                 log(f"Moving language to the final place: {copying_dir} to {publish_dir}")

+                 shutil.rmtree(publish_dir, ignore_errors=True)

+                 subprocess.run(["mv", copying_dir, publish_dir])

+ 

+                 # Recreate hardlinks as we go, in case the rsync job

+                 # start while we are still building

+                 if (lang != lastlang):

+                     log(f"hardlinking files: {publish_dir} - {lastlang_dir}")

+                     subprocess.run(["hardlink", "-cv", publish_dir, lastlang_dir])

+ 

+                 # Remove local build

+                 shutil.rmtree(results_dir, ignore_errors=True)

+ 

+             # End Building all the translated sites

+ 

+             # https://pagure.io/fedora-infrastructure/issue/8964

+             log("Consolidate files with hardlink...")

+             subprocess.run(["hardlink", "-cv", "/antora/output/"])

+ 

+             log("DONE!")

+     

+ 

+ 

+ if __name__ == "__main__":

+     main()

+ 

@@ -1,59 +0,0 @@ 

- #/bin/bash

- 

- echo ""

- echo "$(date +%G-%m-%d-%H%M%S): Building Fedora Docs..."

- echo ""

- 

- SOURCE_REPO="https://pagure.io/fedora-docs/docs-fp-o.git"

- BUILD_CMD="antora --html-url-extension-style=indexify site.yml"

- 

- target="/antora"

- 

- if [ -z ${BUILD_ENV+x} ]; then 

-     BUILD_ENV="master"   

- fi

- 

- 

- if ! workdir=$(mktemp -d) ; then

-     echo "Error creating a tempdir. Exiting."

-     exit 1

- fi

- 

- if ! git clone --single-branch --branch $BUILD_ENV  $SOURCE_REPO "$workdir/source" ; then

-     echo ""

-     echo "Error cloning the source repo."

-     rm -rf "$workdir"

-     exit 1

- else

-     echo "OK  cloned source repo"

- fi

- 

- if [ ! -f "$workdir/source/site.yml" ]; then

-     echo ""

-     echo "Error! There is no site.yml in the source."

-     rm -rf "$workdir"

-     exit 1

- fi

- 

- pushd "$workdir/source/" > /dev/null

- 

- 

- export ANTORA_DATE=$(date -u)

- 

- if ! $BUILD_CMD ; then

-     echo ""

-     echo "Error building the site."

-     rm -rf "$workdir"

-     exit 1

- fi

- 

- popd > /dev/null

- 

- cp -r "$workdir/source/public/en-US" "$target/output/en-US.building" && rm -rf "$target/output/en-US" && mv "$target/output/en-US.building" "$target/output/en-US" || exit 1

- cp "$workdir/source/static/index.html" "$target/output/index.html" || exit 1

- 

- rm -rf "$workdir"

- 

- echo ""

- echo "$(date +%G-%m-%d-%H%M%S): Building of Fedora Docs has finished successfully!"

- echo ""

file modified
+2 -2
@@ -1,7 +1,7 @@ 

  #!/bin/sh

  

- image="docker.io/antora/antora"

- cmd="--html-url-extension-style=indexify site.yml"

+ image="docker.io/antora/antora:3.0.0"

+ cmd="site.yml"

  

  if [ "$(uname)" == "Darwin" ]; then

      # Running on macOS.

@@ -0,0 +1,38 @@ 

+ 

+ module.exports.register = function () {

+   this.on('contentClassified', async ({ contentCatalog }) => {

+     const logger = this.require('@antora/logger').get('page-last-update')

+     const yaml = this.require('js-yaml')

+     const path = this.require('path')

+     const fs = this.require('fs')

+     const pages = contentCatalog.findBy({ family: 'page' })

+     const pageRx = new RegExp('modules/[^/]+/pages/(?:.+/)*[^.].*\.adoc$')

+ 

+     if (fs.existsSync("/tmp/metadata.yml")) {

+       let data = fs.readFileSync("/tmp/metadata.yml", "utf-8")

+       let y = yaml.loadAll(data)

+     

+       for (const page of pages) {

+         if (page.src.origin.type == "git" && pageRx.test(page.src.path)) {

+           const relpath = path.join(page.src.origin.startPath, page.src.path)

+           const abspath = page.src.origin.gitdir.replace(/\/\.git$/, "")

+           // logger.warn(abspath)

+           const contents = page.contents.toString('utf-8')

+           const branch = page.src.origin.branch

+           if (abspath in y[0] && branch in y[0][abspath] && relpath in y[0][abspath][branch]) {

+             let last_update = y[0][abspath][branch][relpath]["date"].toISOString().split('T')[0]

+             logger.warn({name: relpath, rev: branch, update: last_update})

+             

+             // Inject attribute in page content

+             const patchedContents = ":page-last-updated: " + last_update + "\n" + contents

+             page.contents = Buffer.from(patchedContents)

+           }

+         }

+       }

+ 

+     } else {

+       logger.warn("no git metadata found, skipping...")

+     }

+     this.updateVariables({ contentCatalog })

+   })

+ }

file modified
+99 -10
@@ -1,5 +1,5 @@ 

  site:

-   title: Fedora Docs Site

+   title: Fedora Docs

    url: https://docs.fedoraproject.org/en-US/

    start_page: docs::index.adoc

  content:
@@ -14,6 +14,11 @@ 

    - url: https://pagure.io/fedora-docs/release-docs-home.git

      branches:

      - master

+     - f35

+     - f34

+     - f33

+     - f32

+     - f31

      - f30

      - f29

      - f28
@@ -22,6 +27,11 @@ 

    - url: https://pagure.io/fedora-docs/install-guide.git

      branches:

      - master

+     - f35

+     - f34

+     - f33

+     - f32

+     - f31

      - f30

      - f29

      - f28
@@ -30,6 +40,11 @@ 

    - url: https://pagure.io/fedora-docs/system-administrators-guide.git

      branches:

      - master

+     - f35

+     - f34

+     - f33

+     - f32

+     - f31

      - f30

      - f29

      - f28
@@ -38,6 +53,11 @@ 

    - url: https://pagure.io/fedora-docs/release-notes.git

      branches:

      - master

+     - f35

+     - f34

+     - f33

+     - f32

+     - f31

      - f30

      - f29

      - f28
@@ -47,13 +67,26 @@ 

    - url: https://pagure.io/mentored-projects.git

    - url: https://pagure.io/fedora-commops.git

      start_path: docs

+     branches: main

+   - url: https://pagure.io/fedora-websites.git

+     start_path: docs

+     branches: main

    - url: https://pagure.io/Fedora-Council/council-docs.git

      start_path: council

+     branches: main

    - url: https://pagure.io/Fedora-Council/council-docs.git

      start_path: project

+     branches: main

+   - url: https://pagure.io/Fedora-Council/status_reports.git

+     branches: main

    - url: https://pagure.io/fedora-docs/modularity.git

+   - url: https://pagure.io/fedora-docs/package-maintainer-docs.git

+     branches: main

    - url: https://github.com/fedora-silverblue/silverblue-docs.git

-   - url: https://pagure.io/fedora-docs/documentation-contributors-guide.git

+   - url: https://pagure.io/fedora-kde/kinoite-docs

+     branches: main

+   - url: https://gitlab.com/fedora/docs/community-tools/documentation-contributors-guide.git

+     branches: main

    - url: https://pagure.io/fedora-docs/flatpak.git

    - url: https://pagure.io/fedora-diversity.git

    - url: https://pagure.io/packaging-committee.git
@@ -62,30 +95,86 @@ 

      start_path: website

    - url: https://pagure.io/fesco/fesco-docs.git

      start_path: fesco

-   - url: https://pagure.io/fedora-iot/iot-docs.git

-   - url: https://pagure.io/fedora-badges.git

-     start_path: docs

+   - url: https://github.com/fedora-iot/iot-docs.git

+     start_path: user-docs

+     branches: main

+   - url: https://github.com/fedora-iot/iot-docs.git

+     start_path: wg-docs

+     branches: main

+   - url: https://pagure.io/fedora-badges/docs.git

    - url: https://pagure.io/fedora-docs/remix-building.git

    - url: https://github.com/containers/docs.git

+     branches: main

    - url: https://pagure.io/neuro-sig/documentation.git

+     branches: main

    - url: https://pagure.io/sig-teleirc/infrastructure.git

      start_path: docs

    - url: https://pagure.io/fedora-ci/docs.git

    - url: https://pagure.io/java-packaging-howto.git

-   - url: https://pagure.io/fedora-docs/taiga-docs.git

+   - url: https://pagure.io/fedora-magazine.git

+   - url: https://pagure.io/minimization.git

+   - url: https://github.com/coreos/fedora-coreos-docs.git

+     branches: main

+   - url: https://pagure.io/cpe/rawhide-gating-docs.git

+   - url: https://pagure.io/cpe/docs.git

+   - url: https://pagure.io/fedora-qa/qa-docs.git

+   - url: https://pagure.io/fedora-pgm/pgm_docs.git

+     branches: main

+   - url: https://pagure.io/fedora-pgm/pgm_docs.git

+     branches: main

+     start_path: releases

+   - url: https://pagure.io/Ask-Fedora-SOP-docs.git

+   - url: https://pagure.io/fedora-join/fedora-join-docs.git

+   - url: https://pagure.io/fedora-docs/localization.git

+   - url: https://pagure.io/fedora-l10n/docs.git

+   - url: https://github.com/fedora-eln/eln-docs.git

+   - url: https://pagure.io/i3-sig/docs.git

+   - url: https://github.com/fedora-infra/fedora-accounts-docs.git

+     branches: main

+   - url: https://pagure.io/gaming/documentation.git

+     branches: main

+   - url: https://pagure.io/fedora-server.git

+     branches: main

+     start_path: docs

+   - url: https://pagure.io/fedora-server.git

+     branches: main

+     start_path: wg

+   - url: https://pagure.io/epel.git

+     branches: main

+   - url: https://pagure.io/infra-docs-fpo.git

+   - url: https://pagure.io/fedora-workstation/workstation-docs.git

+     branches: main

+   - url: https://gitlab.com/fedora/marketing/marketing-docs.git

+     branches: main

+   - url: https://pagure.io/defensive-coding-guide.git

  ui:

    bundle:

-     url: https://asamalik.fedorapeople.org/ui-bundle.zip

+     url: https://gitlab.com/fedora/docs/docs-website/ui-bundle/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable

      snapshot: true

    default_layout: with_menu

+   supplemental_files: ./supplemental-ui

  output:

    clean: true

    dir: ./public/en-US

-   destinations:

-   - provider: archive

  runtime:

-   pull: true

+   fetch: true

    cache_dir: ./cache

+   log:

+     format: pretty

+     level: fatal

  asciidoc:

    extensions:

    - ./lib/extensions/package-inline-macro.js

+ antora:

+   extensions:

+   - id: page-last-update

+     require: ./lib/extensions/page-last-update.js

+     enabled: true

+   - id: lunr

+     require: '@antora/lunr-extension'

+     index_latest_only: true

+     enabled: false

+ urls:

+   latest_version_segment: latest

+   html_extension_style: indexify

+ 

@@ -0,0 +1,75 @@ 

+ .search-result-dropdown-menu {

+   position: absolute;

+   z-index: 100;

+   display: block;

+   right: 0;

+   left: inherit;

+   top: 100%;

+   border-radius: 4px;

+   margin: 6px 0 0;

+   padding: 0;

+   text-align: left;

+   height: auto;

+   background: transparent;

+   border: none;

+   max-width: 600px;

+   min-width: 500px;

+   box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1);

+ }

+ 

+ @media screen and (max-width: 768px) {

+   .search-result-dropdown-menu {

+     min-width: calc(100vw - 3.75rem);

+   }

+ }

+ 

+ .search-result-dataset {

+   position: relative;

+   border: 1px solid #d9d9d9;

+   background: #fff;

+   border-radius: 4px;

+   overflow: auto;

+   padding: 0 8px;

+   max-height: calc(100vh - 5.25rem);

+   line-height: 1.5;

+ }

+ 

+ .search-result-item {

+   display: flex;

+   margin: 0.5rem 0;

+ }

+ 

+ .search-result-document-title {

+   width: 33%;

+   border-right: 1px solid #ddd;

+   color: #02060c;

+   font-weight: 500;

+   font-size: 0.8rem;

+   padding: 0.5rem 0.5rem 0.5rem 0;

+   text-align: right;

+   position: relative;

+   word-wrap: break-word;

+ }

+ 

+ .search-result-document-hit {

+   flex: 1;

+   font-size: 0.75rem;

+   color: #63676d;

+ }

+ 

+ .search-result-document-hit > a {

+   color: inherit;

+   display: block;

+   padding: 0.55rem 0.25rem 0.55rem 0.75rem;

+ }

+ 

+ .search-result-document-hit > a:hover {

+   background-color: rgba(69, 142, 225, 0.05);

+ }

+ 

+ .search-result-highlight {

+   color: #174d8c;

+   background: rgba(143, 187, 237, 0.1);

+   padding: 0.1em 0.05em;

+   font-weight: 500;

+ }

@@ -0,0 +1,248 @@ 

+ /* global CustomEvent */

+ ;(function (globalScope) {

+   /* eslint-disable no-var */

+   var config = document.getElementById('search-ui-script').dataset

+   var snippetLength = parseInt(config.snippetLength || 100, 10)

+   var siteRootPath = config.siteRootPath || ''

+   appendStylesheet(config.stylesheet)

+   var searchInput = document.getElementById('search-input')

+   var searchResult = document.createElement('div')

+   searchResult.classList.add('search-result-dropdown-menu')

+   searchInput.parentNode.appendChild(searchResult)

+ 

+   function appendStylesheet (href) {

+     if (!href) return

+     document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href }))

+   }

+ 

+   function highlightText (doc, position) {

+     var hits = []

+     var start = position[0]

+     var length = position[1]

+ 

+     var text = doc.text

+     var highlightSpan = document.createElement('span')

+     highlightSpan.classList.add('search-result-highlight')

+     highlightSpan.innerText = text.substr(start, length)

+ 

+     var end = start + length

+     var textEnd = text.length - 1

+     var contextAfter = end + snippetLength > textEnd ? textEnd : end + snippetLength

+     var contextBefore = start - snippetLength < 0 ? 0 : start - snippetLength

+     if (start === 0 && end === textEnd) {

+       hits.push(highlightSpan)

+     } else if (start === 0) {

+       hits.push(highlightSpan)

+       hits.push(document.createTextNode(text.substr(end, contextAfter)))

+     } else if (end === textEnd) {

+       hits.push(document.createTextNode(text.substr(0, start)))

+       hits.push(highlightSpan)

+     } else {

+       hits.push(document.createTextNode('...' + text.substr(contextBefore, start - contextBefore)))

+       hits.push(highlightSpan)

+       hits.push(document.createTextNode(text.substr(end, contextAfter - end) + '...'))

+     }

+     return hits

+   }

+ 

+   function highlightTitle (sectionTitle, doc, position) {

+     var hits = []

+     var start = position[0]

+     var length = position[1]

+ 

+     var highlightSpan = document.createElement('span')

+     highlightSpan.classList.add('search-result-highlight')

+     var title

+     if (sectionTitle) {

+       title = sectionTitle.text

+     } else {

+       title = doc.title

+     }

+     highlightSpan.innerText = title.substr(start, length)

+ 

+     var end = start + length

+     var titleEnd = title.length - 1

+     if (start === 0 && end === titleEnd) {

+       hits.push(highlightSpan)

+     } else if (start === 0) {

+       hits.push(highlightSpan)

+       hits.push(document.createTextNode(title.substr(length, titleEnd)))

+     } else if (end === titleEnd) {

+       hits.push(document.createTextNode(title.substr(0, start)))

+       hits.push(highlightSpan)

+     } else {

+       hits.push(document.createTextNode(title.substr(0, start)))

+       hits.push(highlightSpan)

+       hits.push(document.createTextNode(title.substr(end, titleEnd)))

+     }

+     return hits

+   }

+ 

+   function highlightHit (metadata, sectionTitle, doc) {

+     var hits = []

+     for (var token in metadata) {

+       var fields = metadata[token]

+       for (var field in fields) {

+         var positions = fields[field]

+         if (positions.position) {

+           var position = positions.position[0] // only higlight the first match

+           if (field === 'title') {

+             hits = highlightTitle(sectionTitle, doc, position)

+           } else if (field === 'text') {

+             hits = highlightText(doc, position)

+           }

+         }

+       }

+     }

+     return hits

+   }

+ 

+   function createSearchResult (result, store, searchResultDataset) {

+     result.forEach(function (item) {

+       var ids = item.ref.split('-')

+       var docId = ids[0]

+       var doc = store[docId]

+       var sectionTitle

+       if (ids.length > 1) {

+         var titleId = ids[1]

+         sectionTitle = doc.titles.filter(function (item) {

+           return String(item.id) === titleId

+         })[0]

+       }

+       var metadata = item.matchData.metadata

+       var hits = highlightHit(metadata, sectionTitle, doc)

+       searchResultDataset.appendChild(createSearchResultItem(doc, sectionTitle, item, hits))

+     })

+   }

+ 

+   function createSearchResultItem (doc, sectionTitle, item, hits) {

+     var documentTitle = document.createElement('div')

+     documentTitle.classList.add('search-result-document-title')

+     documentTitle.innerText = doc.title

+     var documentHit = document.createElement('div')

+     documentHit.classList.add('search-result-document-hit')

+     var documentHitLink = document.createElement('a')

+     documentHitLink.href = siteRootPath + doc.url + (sectionTitle ? '#' + sectionTitle.hash : '')

+     documentHit.appendChild(documentHitLink)

+     hits.forEach(function (hit) {

+       documentHitLink.appendChild(hit)

+     })

+     var searchResultItem = document.createElement('div')

+     searchResultItem.classList.add('search-result-item')

+     searchResultItem.appendChild(documentTitle)

+     searchResultItem.appendChild(documentHit)

+     searchResultItem.addEventListener('mousedown', function (e) {

+       e.preventDefault()

+     })

+     return searchResultItem

+   }

+ 

+   function createNoResult (text) {

+     var searchResultItem = document.createElement('div')

+     searchResultItem.classList.add('search-result-item')

+     var documentHit = document.createElement('div')

+     documentHit.classList.add('search-result-document-hit')

+     var message = document.createElement('strong')

+     message.innerText = 'No results found for query "' + text + '"'

+     documentHit.appendChild(message)

+     searchResultItem.appendChild(documentHit)

+     return searchResultItem

+   }

+ 

+   function clearSearchResults (reset) {

+     if (reset === true) searchInput.value = ''

+     searchResult.innerHTML = ''

+   }

+ 

+   function search (index, text) {

+     // execute an exact match search

+     var result = index.search(text)

+     if (result.length > 0) {

+       return result

+     }

+     // no result, use a begins with search

+     result = index.search(text + '*')

+     if (result.length > 0) {

+       return result

+     }

+     // no result, use a contains search

+     result = index.search('*' + text + '*')

+     return result

+   }

+ 

+   function searchIndex (index, store, text) {

+     clearSearchResults(false)

+     if (text.trim() === '') {

+       return

+     }

+     var result = search(index, text)

+     var searchResultDataset = document.createElement('div')

+     searchResultDataset.classList.add('search-result-dataset')

+     searchResult.appendChild(searchResultDataset)

+     if (result.length > 0) {

+       createSearchResult(result, store, searchResultDataset)

+     } else {

+       searchResultDataset.appendChild(createNoResult(text))

+     }

+   }

+ 

+   function confineEvent (e) {

+     e.stopPropagation()

+   }

+ 

+   function debounce (func, wait, immediate) {

+     var timeout

+     return function () {

+       var context = this

+       var args = arguments

+       var later = function () {

+         timeout = null

+         if (!immediate) func.apply(context, args)

+       }

+       var callNow = immediate && !timeout

+       clearTimeout(timeout)

+       timeout = setTimeout(later, wait)

+       if (callNow) func.apply(context, args)

+     }

+   }

+ 

+   function enableSearchInput (enabled) {

+     searchInput.disabled = !enabled

+     searchInput.title = enabled ? '' : 'Loading index...'

+   }

+ 

+   function initSearch (lunr, data) {

+     var start = performance.now()

+     var index = Object.assign({ index: lunr.Index.load(data.index), store: data.store })

+     enableSearchInput(true)

+     searchInput.dispatchEvent(

+       new CustomEvent('loadedindex', {

+         detail: {

+           took: performance.now() - start,

+         },

+       })

+     )

+     var debug = 'URLSearchParams' in globalScope && new URLSearchParams(globalScope.location.search).has('lunr-debug')

+     searchInput.addEventListener(

+       'keydown',

+       debounce(function (e) {

+         if (e.key === 'Escape' || e.key === 'Esc') return clearSearchResults(true)

+         try {

+           var query = searchInput.value

+           if (!query) return clearSearchResults()

+           searchIndex(index.index, index.store, searchInput.value)

+         } catch (err) {

+           if (debug) console.debug('Invalid search query: ' + query + ' (' + err.message + ')')

+         }

+       }, 100)

+     )

+     searchInput.addEventListener('click', confineEvent)

+     searchResult.addEventListener('click', confineEvent)

+     document.documentElement.addEventListener('click', clearSearchResults)

+   }

+ 

+   // disable the search input until the index is loaded

+   enableSearchInput(false)

+ 

+   globalScope.initSearch = initSearch

+ })(typeof globalThis !== 'undefined' ? globalThis : window)

@@ -0,0 +1,1 @@ 

+ <!-- Add any HTML code to show at the top of each page — like a banner to a survey, some announcement, etc. Leave this file empty to not show anything-->

When a user first opens the Fedora Docs website, there are a number of issues that he/she experiences that come in the way of a smooth and flawless user experience. Here is a detailed record of hitches in the Docs Front Page:-

  • The first visual of the front page is a grid of stark hued rectangular cards with white text in headings and subheadings. The similar appearance of all cards along with a lack of hierarchy in their placement, makes the website confusing to navigate.

  • The lack of an optimum classification of content on the website into suitable categories or headings and sub-headings further makes the navigation counterintuitive.

  • The three icons in the top right corner, namely, ‘Page history’, ‘Edit this page’ and ‘Report an issue’ are extremely small as compared to the rest of the elements on the page (i.e. blue cards) and thus can be missed by the eye of most users.

  • The language drop-down is counterintuitive. It is confusing for most users to figure out what the abbreviated language codes mean until they click on one of the options and try to switch languages using the same.

  • When a user lands on the website, there is no information regarding what the website is about. This lack of Introduction or About Section again breeds uncertainty regarding what to do after landing on the website.

  • The reason behind using a different shade of blue in some of the cards (bottom-most row of each section i.e. the User Documentation Section and Fedora Project and Community Section) is not apparent as the information in them is not severely different from the rest of the cards.

These are the set of improvements/solutions that can be offered to the problems mentioned above along with some additional features/functionalities to make the UX better:

  • In order to avoid the confusion caused by the similar appearance of all the cards, the front page can be made more intuitive in nature by the use of icons on the cards related to the heading. Let us say if the first category is ‘User Documentation’, a ‘documentation’ icon can be used on the card and if the second category is ‘Community’, an icon depicting the same can be used to make it easier for the user to figure out where to go next/how to proceed.

  • A proper hierarchy needs to be created with the context of information architecture to make navigation across the website easier.
    The four sections of classification could be:

    • About Fedora Project
    • User Documentation
    • Community
    • Code of Conduct

    Here is the link to information architecture classification as per my understanding of the same through documentation as well as the discussion thread:

    Figma Link of hierarchical classification of website content

  • The three icons in the top right corner, namely, ‘Page history’, ‘Edit this page’ and ‘Report an issue’ need to be made more prominent in the context of size and colour. Apart from just icons, the text also needs to be there because currently, a user can only determine the role of those buttons upon hovering over them. Even the text displayed on hover appears after delay and thus hinders a smooth user experience.

  • In the language drop-down, apart from the abbreviations, flag icons can also be used along to make the objective of that functionality simpler to understand.

  • Dark Mode functionality can be added to improve digital accessibility and to aid users with specific disabilities. Cataract sufferers, users with chronic migraines and users with low vision disorders will benefit from the dark mode functionality apart from users who generally work in low-light situations.

  • The negative space between the cards needs to be increased in order to create a more appealing visual look.

  • Brief information about each card can be displayed on hovering instead of being mentioned on the card itself. This would reduce the congestion on the front page and increase the ease of perception of information.

  • Additional UI specifications that have been included are:

    • Keeping the border radius of the search bar the same as the border radius of the cards for a more consistent design.
    • Making the colour scheme more appealing while at the same time following the Fedora Colour Palette.
  • A major change is the addition of a vertical navbar that clearly lists the hierarchy of information on the website, simplifies the classification and greatly improves the user experience because of the ease it offers.

View the prototype of redesigned Docs front page layout made on Figma

View the complete Figma file here

Gladly looking forward to feedback and suggestions.

I like the graphical design. I'm concerned that most users would take 3 clicks to get to the user docs they're looking for. Same for the teams docs. I'm not sure how to best collapse those, but three clicks is a lot.

Also, the Packaging Guidelines are not user documentation.

Your concern regarding the user taking 3 clicks to get to the User Docs and Teams Docs is extremely relevant and definitely needs an adequate amount of rethinking on my end.

However, as far as Packaging Guidelines are concerned, can we take it out of the tree and create a fifth section out of it along with the other four sections i.e. About Fedora Project, User Documentation, Community, Code of Conduct?

Similarly, how about removing the Teams subheading from the Community section and rather using Engineering Teams and Mindshare Teams as they are instead of using them as a sub-sub-heading. This would bring down the Teams Docs section to two clicks instead of three.

Representation: https://www.figma.com/file/dXU4fxY0wdhwKYJBSrVQqs/Hierarchy-Modified?node-id=0%3A1

However, as far as Packaging Guidelines are concerned, can we take it out of the tree and create a fifth section out of it along with the other four sections i.e. About Fedora Project, User Documentation, Community, Code of Conduct?

I don't think it needs to be a fifth section on its own. Maybe a group of policies from multiple sources could be one, although they'd also fit in About or Community.

Similarly, how about removing the Teams subheading from the Community section and rather using Engineering Teams and Mindshare Teams as they are instead of using them as a sub-sub-heading. This would bring down the Teams Docs section to two clicks instead of three.

That seems reasonable.

Since the concern is regarding 3 clicks being taken by users to get to the user docs, how about creating a hover effect on the cards.

  • Upon hovering on the card which contains the main heading (let's say User Documentation), the icons of subheadings (Variants, Quick Docs, EPEL, Older Releases etc.) appear. This would save a click and rather than clicking on User Documentation --> Variants --> F Linux 35, the user can just hover on User Documentation and click on Variants --> F Linux 35. This would bring down the number of clicks from 3 to 2.

  • Further, I've added the hover effect to the cards where there are no further sub-divisions as well. On these cards, brief one-liner information about the heading appears on hovering, which is shown in smaller font size on the blue cards on the current Docs Front Page.

View the prototype of redesigned Docs front page layout made on Figma

View the complete Figma file here

how about creating a hover effect on the cards.

How would that affect accessibility? My concern is that screen readers and other a11y tools would miss that context. But maybe they'd be able to handle it?

The point of accessibility needs a lot of consideration on my behalf. How about removing the hover effect completely away and rather using the layout as designed here?

If not, another option could be to make use of the hover effect on the Docs front page for PCs and laptops while for mobile phones and tablets where hover is not compatible, we can simply go with the earlier design (without the hover). The presence of the vertical navbar would make the navigation across the website on touch screen devices easier and would compensate for the multiple clicks being taken to navigate to a page.

However, I am not sure how reasonable the first alternative (displaying all icons on the home page instead of displaying them on hover) would look as far as the graphical design aspect is concerned.