matcornic / hugo-theme-learn

Porting Grav Learn theme to Hugo
https://learn.netlify.com/en/
MIT License
1.61k stars 1.28k forks source link

Does Hugo Learn support versioning pages? #360

Open kgilmer opened 4 years ago

kgilmer commented 4 years ago

Grav, the project that Hugo Learn is based on, has a feature in which documentation can be versioned, and there is a dropdown for the user to specify which version of the documentation to be used. Is there a way of enabling something like this in Hugo Learn?

image

Exadra37 commented 4 years ago

I use this bash script:

#!/bin/bash

set -eu

Trap_Exit()
{
  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    local exit_code=$?

    Undo_Git_Changes

    exit ${exit_code}
}

Undo_Git_Changes()
{
  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    {
      cd "${script_dir}/${docs_theme_path}"
      Undo_Work_In_Progress_Commit "${TEMPLATE_BRANCH}"
      git branch -D "${build_branch}"

      cd "${script_dir}"
      Undo_Work_In_Progress_Commit "${CONTENT_BRANCH}"
      git branch -D "${build_branch}"
    } &> /dev/null
}

Undo_Work_In_Progress_Commit()
{
  ######################################################################################################################
  # INPUT
  ######################################################################################################################

    local branch="${1? Missing branch to undo.}"

  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    git checkout "${branch}" || true

    git log -n 1 | grep -q -c "\-\-tempwip\-\-" && git reset HEAD~1

    return 0
}

Create_Work_In_Progress_Commit()
{
  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    #  creating a WIP commit
    git add -A
    git commit --no-verify --no-gpg-sign -m "--tempwip--" || true

    git checkout -b "${build_branch}" || true
}

Inject_Docs_Versions_Drop_Down_Menu_Into_Theme()
{
  ######################################################################################################################
  # VARS
  ######################################################################################################################

    local layouts_partials_dir="${version_dir}/layouts/partials"

    local versions_drop_down_menu_partial_file="${layouts_partials_dir}/auto-generated-versions-drop-down.html"

    local footer_js_file_path="${layouts_partials_dir}/footer-js.html"

  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    mkdir -pv "${layouts_partials_dir}"

    while read -r doc_version; do

      if [ -z "${doc_version}" ]; then
        continue
      fi

      # We will not include the current version in the drop down
      if [ "${doc_version}" = "${version}" ]; then
        continue
      fi

      local drop_down_versions="${drop_down_versions}<a href=\"{{ \"../${doc_version}\" | relURL }}\">${doc_version}</a>\n"

    done <<< "${versions}"

    ### INJECT RELEASE VERSIONS ###
    printf "<div id=\"latest-version\">(<strong>Latest: </strong><span>${latest_version}</span>)</div>" > "${versions_drop_down_menu_partial_file}"
    printf "<button class=\"dropbtn\">${version}</button>" >> "${versions_drop_down_menu_partial_file}"
    printf "<div class=\"dropdown-content\">${drop_down_versions}</div>" >> "${versions_drop_down_menu_partial_file}"
}

Main()
{
  ######################################################################################################################
  # DEFAULTS
  ######################################################################################################################

    local script_dir="${PWD}"
    local build_dir=build
    local local_build=false
    local dir_prefix="${PWD}"

    if [ -f .bash.vars ]; then
      . .bash.vars
    elif [ -f .bash.vars.example ]; then
      cp .bash.vars.example .bash.vars
      . .bash.vars
    else
      printf "Missing ${PWD}/.bash.vars file with CONTENT_REPO_GIT_URL and TEMPLATE_REPO_GIT_URL"
      echo
      exit 1
    fi

  ######################################################################################################################
  # INPUT
  ######################################################################################################################

    for input in "${@}"; do
      case "${input}" in
        --hfd | --hugo-from-docker )
          shift 1
          local dir_prefix="${1:-/home/developer/workspace}"

          if [ $# -gt 1 ]; then
            shift 1
          fi
        ;;

        --lb | --local-build )
          local local_build=true
          shift 1
        ;;
      esac
    done

  ######################################################################################################################
  # VARS
  ######################################################################################################################

    #local base_dir="${PWD}"
    local docs_theme_name=template-web
    local docs_theme_path="themes/${docs_theme_name}"

    local versions_dir="${build_dir}/versions"

    local releases_docs_home_dir="${build_dir}"/release/docs

    local site_dir="${build_dir}/site"
    local site_content_dir="${site_dir}/content"
    local site_theme_dir="${site_content_dir}/themes"

    local drop_down_versions=''

    local date_time=$(date +"%Y-%m-%d_%Hh%Mm")

    local build_branch="docs-build_${date_time}"
    trap "Trap_Exit" EXIT

  ######################################################################################################################
  # EXECUTION
  ######################################################################################################################

    rm -rf "./${build_dir}"
    mkdir -p "${site_dir}" "${site_content_dir}" "${versions_dir}" "${releases_docs_home_dir}"

    if [ "${local_build}" = "true" ]; then

      local tar_file_prefix="local"

      ### MANAGING CHANGES NOT COMMITTED IN OUR LOCAL REPOS ####
      # changes in the theme
      cd "${docs_theme_path}"
      local TEMPLATE_BRANCH="$(git rev-parse --abbrev-ref HEAD )"
      Create_Work_In_Progress_Commit
      cd -

      # changes in content
      local CONTENT_BRANCH="$(git rev-parse --abbrev-ref HEAD )"
      Create_Work_In_Progress_Commit

      ### CONTENT ###
      cd "${site_content_dir}"
      git clone --no-hardlinks --local ../../../ .

      ### THEME ###
      cd "${docs_theme_path}"
      git clone --no-hardlinks --local "../../../../../themes/${docs_theme_name}" .
      cd -

      ### TAGGING ###
      # Creating a development version to show up in the versions drop down menu
      git tag v_dev

      cd ../../../
    else

      ### CONTENT ###
      git clone "${CONTENT_REPO_GIT_URL}" "${site_content_dir}"

      ### THEME ###
      git clone --depth 1 "${TEMPLATE_REPO_GIT_URL}" "${site_theme_dir}/${docs_theme_name}"

      local tar_file_prefix="prod"
    fi

    ### LATEST VERSION ####
    cd "${site_content_dir}"

    # The `v*` match tags to be published to the docs website, because tags not prefixed with `v` are only for internal
    # reference.
    # The `--sort` option uses a `-` in `-v:refname` to sort in descendant order
    local versions=$(git tag --list 'v*' --sort -v:refname)
    local latest_version=$(git tag --list 'v*' --sort -v:refname | head -1)
    cd -

    local tar_file="${build_dir}/${tar_file_prefix}-release_${latest_version}_${date_time}.tar.gz"

    ### BUILD RELEASES FOR ALL APPROOV VERSIONS ###
    if [ -n "${versions}" ]; then

      while read -r version; do

        if [ -z "${version}" ]; then
          continue
        fi

        ### MAKE A LOCAL CLONE OF THE SITE CONTENT FOR A SPECIFIC DOCS VERSION ###
        local version_dir="${versions_dir}/${version}"
        local release_dir="${releases_docs_home_dir}/${version}"
        rm -rf "${version_dir}"

        git clone \
          --no-hardlinks \
          --local "${site_content_dir}" \
          --branch "${version}" \
          "${version_dir}"

        # Set the correct base url in the config.toml ensures that the relative
        #  links are properly generated by Hugo, like in the index.json file
        #  used indexing the content for search.
        sed -i "s|baseURL = \"/\"|baseURL = \"/docs/${version}/\"|g" "${version_dir}/config.toml"

        Inject_Docs_Versions_Drop_Down_Menu_Into_Theme

        ### BUID RELEASE ####

        hugo \
          --ignoreCache \
          --source "${dir_prefix}/${version_dir}" \
          --destination "${dir_prefix}/${release_dir}" \
          --themesDir "${dir_prefix}/${site_theme_dir}" \
          --theme "${docs_theme_name}"

        # Fix fonts folder location:
        # @TODO Maybe hugo configuration can be set to avoid this?
        mv "${release_dir}/fonts" "${release_dir}/assets"

      done <<< "${versions}"

      #### DOCS HOMEPAGE ###

      # Creates index.html for domain.tld/docs and fix all links to point to the
      #  latest version, like domain.tls/docs/v2.0/some-page
      cp "${releases_docs_home_dir}/${latest_version}/index.html" "${releases_docs_home_dir}"
      sed -i "s|=\"./|=\"./${latest_version}/|g" "${releases_docs_home_dir}/index.html"

      cp "${releases_docs_home_dir}/${latest_version}/sitemap.xml" "${releases_docs_home_dir}"
      sed -i "s|<loc>/|<loc>/${latest_version}/|g" "${releases_docs_home_dir}/sitemap.xml"

      # Add the search index.json file to the root of the project, but it's not
      #  needed to do the sed replace on this file, because the config.toml has
      #  now the correct relative path for the baseurl of each doc version site.
      # See above sed replace in the foreach for creating each doc version.
      cp "${releases_docs_home_dir}/${latest_version}/index.json" "${releases_docs_home_dir}"

      ### CREATING THE RELEASE TAR FILE ###
      tar zcf "${tar_file}" -C "${releases_docs_home_dir}/.." .

      printf "\n\nRELEASES: \n"
      printf "\n -> Compressed:   ${tar_file}"
      printf "\n -> Uncompressed: ${releases_docs_home_dir}\n\n"
    fi

    Undo_Git_Changes
    trap - EXIT
}

Main "${@}"

The bash script is tailored to deploy the docs site with all the versions into the docs path of a site, like example.com/docs.

So just save the script in the root of your content repo build-release.sh, and then invoke:

for testing:

./build-release.sh --local-build

output:

RELEASES: 

 -> Compressed:   build/local-release_v_dev_2020-05-15_18h06m.tar.gz
 -> Uncompressed: build/release/docs

for production:

./build-release

The output:

RELEASES: 

 -> Compressed:   build/prod-release_v2.3_2020-05-15_18h16m.tar.gz
 -> Uncompressed: build/release/docs

The content and template are in separated repos on purpose, and the content repo never overrides nay file in the template on purpose too, because this way when fixing a bug in the the template we can deploy the fix to all versions of the docs, same if you decide to upgrade the template with a brand new look.

The drop down html generated by the script into the file auto-generated-versions-drop-down.html will look like:

  <div id="latest-version">(<strong>Latest: </strong><span>v2.3</span>)</div>
  <button class="dropbtn">v2.3</button>
  <div class="dropdown-content">
    <a href="{{ .RelPermalink }}">v2.2</a>
    <a href="{{ .RelPermalink }}">v2.1</a>
    <a href="{{ .RelPermalink }}">v2.0</a>
  </div>

Now in your template menu.html you need to add:

  <div class="dropdown">
      {{ partial "auto-generated-versions-drop-down.html" . }}
  </div>

My css looks like this:

/* The container <div> - needed to position the dropdown content */
#sidebar .dropdown {
  position: relative;
  display: block;
  text-align: center;
  margin:0.5rem auto 1rem auto;
  padding: 0;
  width: 8rem;
  text-align: center;
}

#sidebar .dropdown #latest-version {
  position: relative;
  display: block;
  color: var(--MENU-SECTIONS-LINK-color);
  margin: auto;
}

/* Style The Dropdown Button */
#sidebar .dropdown button {
  background-color: var(--MENU-HEADER-BG-color);
  color: var(--MENU-SECTIONS-LINK-color);
  font-size: 1.2rem;
  margin: 0;
  padding: 0.5rem;
  width: 100%;
  border: none;
  cursor: pointer;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
}

/* Dropdown Content (Hidden by Default) */
#sidebar .dropdown-content {
  position: absolute;
  display: none;
  background-color: #f9f9f9;
  width: 100%;
  padding: 0.5rem;
  z-index: 1;
}

/* Links inside the dropdown */
#sidebar .dropdown-content a {
  color: #666666;
  padding: 0.5rem;
  text-decoration: none;
  display: block;
}

/* Change color of dropdown links on hover */
#sidebar .dropdown-content a:hover {
  background-color: var(--MENU-HEADER-BORDER-color);
  color: var(--MENU-SECTIONS-LINK-color);
}

/* Show the dropdown menu on hover */
#sidebar .dropdown:hover .dropdown-content {
  display: block;
}

/* Change the background color of the dropdown button when the dropdown content is shown */
#sidebar .dropdown:hover .dropbtn {
  background-color: var(--MENU-HEADER-BORDER-color);
}

In the end it looks like this:

Screenshot from 2020-05-15 18-48-07