Browse Source

Add documentation/courses/tutorials layout

- Close #405
- See #681 (improve consistency after #571 Bootstrap 4 migration)
- Add `search_results` to language file
- example site: Add example `tutorial` folder
- example site: Set privacy page to hidden (draft)
George Cushen 6 năm trước cách đây
mục cha
commit
674bb67364

+ 4 - 0
data/assets.toml

@@ -59,6 +59,10 @@
   version = "2.9.0"
   sri = "sha256-cJXigylnJlxvAdfFNHeS+IiMcKWS3Rf/cy3bl9bb0ng="
   url = "https://cdnjs.cloudflare.com/ajax/libs/instantsearch.js/%s/instantsearch.min.js"
+[js.anchor]
+  version = "4.1.1"
+  sri = "sha256-pB/deHc9CGfFpJRjC43imB29Rse8tak+5eXqntO94ck="
+  url = "https://cdnjs.cloudflare.com/ajax/libs/anchor-js/%s/anchor.min.js"
 
 # CSS
 

+ 2 - 2
exampleSite/config.toml

@@ -280,8 +280,8 @@ enableGitInfo = false
   weight = 4
 
 [[menu.main]]
-  name = "Teaching"
-  url = "#teaching"
+  name = "Tutorials"
+  url = "/tutorial/"
   weight = 5
 
 [[menu.main]]

+ 1 - 1
exampleSite/content/privacy.md

@@ -2,7 +2,7 @@
 title = "Privacy Policy"
 
 date = 2018-06-28T00:00:00
-draft = false
+draft = true
 
 # [header]
 # image = ""

+ 33 - 0
exampleSite/content/tutorial/_index.md

@@ -0,0 +1,33 @@
++++
+title = "Overview"
+
+date = 2018-09-09T00:00:00
+# lastmod = 2018-09-09T00:00:00
+
+draft = false  # Is this a draft? true/false
+toc = true  # Show table of contents? true/false
+type = "docs"  # Do not modify.
+
+# Add menu entry to sidebar.
+[menu.docs]
+  name = "Overview"
+  weight = 1
+
+# Featured image.
+# Uncomment below lines to use.
+# [header]
+# image = "headers/getting-started.png"
+# caption = "Image credit: [**Academic**](https://github.com/gcushen/hugo-academic/)"
++++
+
+This feature can be used for publishing content such as:
+
+* **Project or software documentation**
+* **Online courses**
+* **Tutorials**
+
+The parent folder may be renamed, for example, to `docs` for project documentation or `course` for creating an online course.
+
+To disable this feature, either delete the parent folder, or set `draft = true` in the front matter of all its pages. 
+
+After renaming or deleting the parent folder, you may wish to update any `[[menu.main]]` menu links to it in the `config.toml`. 

+ 32 - 0
exampleSite/content/tutorial/example.md

@@ -0,0 +1,32 @@
++++
+title = "Example Page"
+
+date = 2018-09-09T00:00:00
+# lastmod = 2018-09-09T00:00:00
+
+draft = false  # Is this a draft? true/false
+toc = true  # Show table of contents? true/false
+type = "docs"  # Do not modify.
+
+# Add menu entry to sidebar.
+linktitle = "Example Page"
+[menu.docs]
+  parent = "Example Topic"
+  weight = 1
+
+# Featured image.
+# Uncomment below lines to use.
+# [header]
+# image = "headers/getting-started.png"
+# caption = "Image credit: [**Academic**](https://github.com/gcushen/hugo-academic/)"
++++
+
+In this tutorial, I'll share my top 10 tips for getting started with Academic:
+
+## Tip 1
+
+...
+
+## Tip 2
+
+...

+ 3 - 0
i18n/en.yaml

@@ -145,6 +145,9 @@
 - id: search_placeholder
   translation: Search...
 
+- id: search_results
+  translation: Search Results
+
 - id: search_no_results
   translation: No results found
 

+ 2 - 4
layouts/_default/list.html

@@ -9,9 +9,8 @@
 
 {{ partial "header_image.html" . }}
 
-<div class="article-container">
-
-  {{ with .Title }}<h1 class="my-4">{{ . }}</h1>{{ end }}
+<div class="universal-wrapper">
+  {{ with .Title }}<h1 class="pt-3">{{ . }}</h1>{{ end }}
 
   {{ with .Content }}
   <div class="article-style" itemprop="articleBody">{{ . }}</div>
@@ -34,7 +33,6 @@
   {{ end }}
 
   {{ partial "pagination" . }}
-
 </div>
 {{ partial "footer_container.html" . }}
 {{ partial "footer.html" . }}

+ 1 - 0
layouts/docs/list.html

@@ -0,0 +1 @@
+{{ partial "docs_layout.html" . }}

+ 1 - 0
layouts/docs/single.html

@@ -0,0 +1 @@
+{{ partial "docs_layout.html" . }}

+ 237 - 7
layouts/partials/css/academic.css

@@ -268,7 +268,7 @@ small,
   overflow-x: hidden;
 }
 
-#search-query {
+#search-box #search-query {
   border: 1px solid #dedede;
   border-radius: 1rem;
   padding: 1rem 1rem 1rem 2rem; /* Wider left padding for search icon to fit in. */
@@ -299,6 +299,11 @@ small,
   display: none;
 }
 
+.form-control:focus {
+  border-color: {{ .Get "primary" }};
+  box-shadow: 0 0 0 .2rem {{ .Get "primary_light" }};
+}
+
 /*************************************************
  *  Modals.
  **************************************************/
@@ -1500,21 +1505,246 @@ div.alert a:hover {
 
 
 /*************************************************
- *  Dark themed components
+ *  Documentation layout
  **************************************************/
 
-body.dark {
-  color: rgb(248, 248, 242);
+.docs-article-container {
+  max-width: 760px;
 }
 
-.dark input {
-  color: rgb(248, 248, 242);
+/* Documentation: article footer. */
+
+.docs .body-footer {
+  border-top: 1px solid #e8e8e8;
+  margin-top: 30px;
+  padding-top: 10px;
+  font-size: 14px;
+  color: #707070;
+}
+
+/* Docs content. */
+
+.docs-content {
+  order: 1;
+  position: relative;
+}
+
+.docs-content>h2[id],
+.docs-content>h3[id],
+.docs-content>h4[id] {
+  pointer-events: none;
+}
+
+.docs-content>ol li,
+.docs-content>ul li {
+  margin-bottom: .25rem;
+}
+
+/* Docs search. */
+
+.docs-search {
+  position: relative;
+  padding: 1rem 15px;
+  margin-right: -15px;
+  margin-left: -15px;
+  border-bottom: 1px solid rgba(0, 0, 0, .05);
+}
+
+.docs-search .form-control:focus {
+  border-color: {{ .Get "primary" }};
+  box-shadow: 0 0 0 3px {{ .Get "primary_light" }};
+}
+
+/* Docs sidebar. */
+
+.docs-sidebar {
+  order: 0;
+  border-bottom: 1px solid rgba(0, 0, 0, .1)
+}
+
+@media (min-width:768px) {
+  .docs-sidebar {
+    border-right: 1px solid rgba(0, 0, 0, .1)
+  }
+  @supports ((position:-webkit-sticky) or (position:sticky)) {
+    .docs-sidebar {
+      position: -webkit-sticky;
+      position: sticky;
+      top: 71px;
+      z-index: 1000;
+      height: calc(100vh - 71px)
+    }
+  }
+}
+
+@media (min-width:1200px) {
+  .docs-sidebar {
+    flex: 0 1 320px
+  }
+}
+
+/* Docs sidebar li>a. */
+
+.docs-sidebar .nav>li>a {
+  display: block;
+  padding: .25rem 1.5rem;
+  font-size: 90%;
+  color: rgba(0, 0, 0, .65);
+}
+
+.docs-sidebar .nav>li>a:hover {
+  color: rgba(0, 0, 0, .85);
+  text-decoration: none;
+  background-color: transparent;
+}
+
+.docs-sidebar .docs-toc-item.active a,
+.docs-sidebar .nav>.active:hover>a,
+.docs-sidebar .nav>.active>a {
+  font-weight: bold;
+  color: {{ .Get "primary" }};
+  background-color: transparent;
+}
+
+/* Docs links. */
+
+.docs-links {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+@media (min-width:768px) {
+  @supports (position: sticky) {
+    .docs-links {
+      max-height: calc(100vh - 5rem - 71px);
+      overflow-y: auto;
+    }
+  }
+}
+
+@media (min-width:768px) {
+  .docs-links {
+    display: block!important;
+  }
+}
+
+/* Docs TOC. */
+
+.docs-toc {
+  order: 2;
+  padding-top: 1.5rem;
+  padding-bottom: 1.5rem;
+  font-size: .875rem
 }
 
-.dark .modal button.close {
+@supports ((position:-webkit-sticky) or (position:sticky)) {
+  .docs-toc {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 4rem;
+    height: calc(100vh - 4rem);
+    overflow-y: auto
+  }
+}
+
+/* Docs TOC item links. */
+
+.docs-toc-link {
+  display: block;
+  padding: .25rem 1.5rem;
+  font-weight: bold;
+  color: rgba(0, 0, 0, .65);
+}
+
+.docs-toc-link:hover {
+  color: rgba(0, 0, 0, .85);
+  text-decoration: none;
+}
+
+.docs-toc-item.active {
+  margin-bottom: 1rem;
+}
+
+.docs-toc-item.active:not(:first-child) {
+  margin-top: 1rem;
+}
+
+.docs-toc-item.active>.docs-toc-link {
+  color: rgba(0, 0, 0, .85);
+}
+
+.docs-toc-item.active>.docs-toc-link:hover {
+  background-color: transparent;
+}
+
+.docs-toc-item.active>.docs-sidenav {
+  display: block;
+}
+
+/* Docs TOC nav. */
+
+#TableOfContents {
+  padding-left: 0;
+  border-left: 1px solid #eee;
+}
+
+#TableOfContents ul {
+  padding-left: 1rem;
+}
+
+#TableOfContents ul ul {
+  display: none;
+}
+
+#TableOfContents li {
+  display: block;
+}
+
+#TableOfContents li a {
+  display: block;
+  padding: .125rem 1.5rem;
+  color: #99979c;
+}
+
+#TableOfContents li a:hover {
+  color: {{ .Get "primary" }};
+  text-decoration: none;
+}
+
+/* Docs achnorjs links. */
+
+.anchorjs-link {
+  font-weight: 400;
+  color: {{ .Get "primary_dark" }};
+  transition: color .16s linear;
+}
+
+.anchorjs-link:hover {
+  color: {{ .Get "primary" }};
+  text-decoration: none;
+}
+
+/*************************************************
+ *  Dark themed components
+ **************************************************/
+
+body.dark,
+.dark .docs-toc-link,
+.dark .docs-sidebar .nav > li:not(.active) > a,
+.dark .modal button.close,
+.dark input,
+.dark .form-control:focus {
   color: rgb(248, 248, 242);
 }
 
+.dark .form-control:focus {
+  background-color: rgb(68, 71, 90);
+  border-color: {{ .Get "primary" }};
+  box-shadow: 0 0 0 .2rem {{ .Get "primary_dark" }};
+}
+
 .dark h1,
 .dark h2,
 .dark h3,

+ 47 - 0
layouts/partials/docs_layout.html

@@ -0,0 +1,47 @@
+{{ partial "header.html" . }}
+{{ partial "navbar.html" . }}
+{{ $current_page := . }}
+
+<div class="container-fluid docs">
+  <div class="row flex-xl-nowrap">
+    <div class="col-12 col-md-3 col-xl-2 docs-sidebar">
+      {{ partial "docs_sidebar.html" . }}
+    </div>
+
+    {{ if .Params.toc }}
+    <div class="d-none d-xl-block col-xl-2 docs-toc">
+      {{ .TableOfContents }}
+    </div>
+    {{ end }}
+
+    <main class="col-12 col-md-9 col-xl-8 py-md-3 pl-md-5 docs-content" role="main">
+      <div id="search-hits">
+        <!-- Search results will appear here -->
+      </div>
+      <article class="article" itemscope itemtype="http://schema.org/Article">
+
+        {{ partial "header_image.html" . }}
+
+        <div class="docs-article-container">
+          <h1 itemprop="name">{{ .Title }}</h1>
+
+          <div class="article-style" itemprop="articleBody">
+            {{ .Content }}
+          </div>
+
+          {{ partial "tags.html" . }}
+        </div>
+
+        <div class="body-footer">
+          {{ i18n "last_updated" }} {{ $.Lastmod.Format $.Site.Params.date_format }}
+        </div>
+
+      </article>
+
+      {{ partial "footer_section.html" . }}
+
+    </main>
+  </div>
+</div>
+
+{{ partial "footer.html" . }}

+ 26 - 0
layouts/partials/docs_sidebar.html

@@ -0,0 +1,26 @@
+{{ $current_page := . }}
+
+{{ if eq $.Site.Params.search.engine 1 }}
+<form class="docs-search d-flex align-items-center">
+  <input name="q" type="search" class="form-control" id="search-query" placeholder="{{ i18n "search_placeholder" }}" autocomplete="off">
+</form>
+{{ end }}
+
+<nav class="docs-links" id="docs-nav">
+  {{ range .Site.Menus.docs.ByWeight }}
+  <div class="docs-toc-item{{ if $current_page.IsMenuCurrent "docs" . }} active{{ end }}">
+    <a class="docs-toc-link" {{ if .URL }}href="{{ .URL }}"{{else if .HasChildren }}href="{{ (index .Children 0).URL }}"{{end}}>{{ .Name }}</a>
+
+    {{- if .HasChildren }}
+    <ul class="nav docs-sidenav">
+      {{ range .Children }}
+      <li {{ if $current_page.IsMenuCurrent "docs" . }}class="active"{{ end }}>
+        <a href="{{ .URL }}">{{ .Name }}</a>
+      </li>
+      {{ end }}
+    </ul>
+  {{ end }}
+
+  </div>
+  {{ end }}
+</nav>

+ 37 - 0
layouts/partials/footer.html

@@ -69,6 +69,7 @@
       const search_index_filename = {{ "/index.json" | relURL }};
       const i18n = {
         'placeholder': {{ i18n "search_placeholder" }},
+        'results': {{ i18n "search_results" }},
         'no_results': {{ i18n "search_no_results" }}
       };
       const content_type = {
@@ -80,6 +81,14 @@
     </script>
     {{ end }}
 
+    {{/* Load hash anchors for documentation pages. */}}
+    {{ if eq .Type "docs" }}
+    {{ printf "<script src=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\"></script>" (printf $js.anchor.url $js.anchor.version) $js.anchor.sri | safeHTML }}
+    <script>
+      anchors.add();
+    </script>
+    {{ end }}
+
     {{/* Fuse search engine. */}}
     {{ if eq .Site.Params.search.engine 1 }}
     {{ printf "<script src=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\"></script>" (printf $js.fuse.url $js.fuse.version) $js.fuse.sri | safeHTML }}
@@ -160,5 +169,33 @@
     </script>
     {{ end }}
 
+    {{ if eq $.Site.Params.search.engine 1 }}
+    {{/* Fuse search result template. */}}
+    <script id="search-hit-fuse-template" type="text/x-template">
+      <div class="search-hit" id="summary-{{"{{key}}"}}">
+      <div class="search-hit-content">
+        <div class="search-hit-name">
+          {{ printf "<a href=\"%s\">%s</a>" "{{relpermalink}}" "{{title}}" | safeHTML }}
+          <div class="article-metadata search-hit-type">{{"{{type}}"}}</div>
+          <p class="search-hit-description">{{"{{snippet}}"}}</p>
+        </div>
+      </div>
+      </div>
+    </script>
+    {{ else if eq $.Site.Params.search.engine 2 }}
+    {{/* Algolia search result template. */}}
+    <script id="search-hit-algolia-template" type="text/html">
+      <div class="search-hit">
+        <div class="search-hit-content">
+          <div class="search-hit-name">
+            {{ printf "<a href=\"%s\">{{{_highlightResult.title.value}}}</a>" "{{relpermalink}}" | safeHTML }}
+          </div>
+          <div class="article-metadata search-hit-type">{{"{{type}}"}}</div>
+          <p class="search-hit-description">{{ safeHTML "{{{_highlightResult.summary.value}}}" }}</p>
+        </div>
+      </div>
+    </script>
+    {{ end }}
+
   </body>
 </html>

+ 3 - 28
layouts/partials/footer_container.html

@@ -1,31 +1,6 @@
-<footer class="site-footer">
-  <div class="container">
-
-    {{ with .Site.GetPage "privacy.md" }}
-    <p class="powered-by">
-      {{ printf "<a href=\"%s\">%s</a>" .Permalink .Title | safeHTML }}
-    </p>
-    {{ end }}
-
-    <p class="powered-by">
-
-      {{ with .Site.Copyright }}{{ . | markdownify}} &middot; {{ end }}
-
-      Powered by the
-      <a href="https://sourcethemes.com/academic/" target="_blank" rel="noopener">Academic theme</a> for
-      <a href="https://gohugo.io" target="_blank" rel="noopener">Hugo</a>.
-
-      <span class="float-right" aria-hidden="true">
-        <a href="#" id="back_to_top">
-          <span class="button_icon">
-            <i class="fas fa-chevron-up fa-2x"></i>
-          </span>
-        </a>
-      </span>
-
-    </p>
-  </div>
-</footer>
+<div class="container">
+  {{ partial "footer_section.html" . }}
+</div>
 
 <!-- Citation modal -->
 <div id="modal" class="modal fade" role="dialog">

+ 23 - 0
layouts/partials/footer_section.html

@@ -0,0 +1,23 @@
+<footer class="site-footer">
+  {{ with .Site.GetPage "privacy.md" }}
+  <p class="powered-by">
+    {{ printf "<a href=\"%s\">%s</a>" .Permalink .Title | safeHTML }}
+  </p>
+  {{ end }}
+
+  <p class="powered-by">
+    {{ with .Site.Copyright }}{{ . | markdownify}} &middot; {{ end }}
+
+    Powered by the
+    <a href="https://sourcethemes.com/academic/" target="_blank" rel="noopener">Academic theme</a> for
+    <a href="https://gohugo.io" target="_blank" rel="noopener">Hugo</a>.
+
+    <span class="float-right" aria-hidden="true">
+      <a href="#" id="back_to_top">
+        <span class="button_icon">
+          <i class="fas fa-chevron-up fa-2x"></i>
+        </span>
+      </a>
+    </span>
+  </p>
+</footer>

+ 0 - 28
layouts/partials/widgets/search.html

@@ -23,31 +23,3 @@
 
   </div>
 </div>
-
-{{ if eq $.Site.Params.search.engine 1 }}
-{{/* Fuse search result template. */}}
-<script id="search-hit-fuse-template" type="text/x-template">
-  <div class="search-hit" id="summary-{{"{{key}}"}}">
-    <div class="search-hit-content">
-      <div class="search-hit-name">
-        {{ printf "<a href=\"%s\">%s</a>" "{{relpermalink}}" "{{title}}" | safeHTML }}
-        <div class="article-metadata search-hit-type">{{"{{type}}"}}</div>
-        <p class="search-hit-description">{{"{{snippet}}"}}</p>
-      </div>
-    </div>
-  </div>
-</script>
-{{ else }}
-{{/* Algolia search result template. */}}
-<script id="search-hit-algolia-template" type="text/html">
-  <div class="search-hit">
-    <div class="search-hit-content">
-      <div class="search-hit-name">
-        {{ printf "<a href=\"%s\">{{{_highlightResult.title.value}}}</a>" "{{relpermalink}}" | safeHTML }}
-      </div>
-      <div class="article-metadata search-hit-type">{{"{{type}}"}}</div>
-      <p class="search-hit-description">{{ safeHTML "{{{_highlightResult.summary.value}}}" }}</p>
-    </div>
-  </div>
-</script>
-{{ end }}

+ 1 - 1
layouts/section/post.html

@@ -5,7 +5,7 @@
 
 <div class="universal-wrapper">
 
-  <h1 class="my-4">{{ .Title | default (i18n "posts") }}</h1>
+  <h1 class="pt-3">{{ .Title | default (i18n "posts") }}</h1>
 
   {{ with .Content }}
   <div class="article-style" itemprop="articleBody">{{ . }}</div>

+ 6 - 6
layouts/section/publication.html

@@ -3,10 +3,10 @@
 
 {{ partial "header_image.html" . }}
 
-<div class="container">
-  <div class="row">
+<div class="universal-wrapper">
+  <div class="row pt-3">
     <div class="col-lg-12">
-      <h1 class="my-4">{{ .Title | default (i18n "publications") }}</h1>
+      <h1>{{ .Title | default (i18n "publications") }}</h1>
 
       {{ with .Content }}
       <div class="article-style" itemprop="articleBody">{{ . }}</div>
@@ -18,11 +18,11 @@
         {{ $.Scratch.SetInMap "years" $year $year }}
       {{ end }}
 
-      <div class="row">
+      <div class="form-row mb-3">
         <div class="col-auto">
           {{ i18n "filter_by_type" }}:
         </div>
-        <div class="form-group col-auto">
+        <div class="col-auto">
           <select class="pub-filters pubtype-select form-control form-control-sm" data-filter-group="pubtype">
             <option value="*">{{ i18n "filter_all" }}</option>
             {{ range $index, $taxonomy := .Site.Taxonomies.publication_types }}
@@ -32,7 +32,7 @@
             {{ end }}
           </select>
         </div>
-        <div class="form-group col-auto">
+        <div class="col-auto">
           <select class="pub-filters form-control form-control-sm" data-filter-group="year">
             <option value="*">{{ i18n "filter_all" }}</option>
             {{ $years_sorted := $.Scratch.GetSortedMapValues "years" }}

+ 3 - 3
layouts/section/talk.html

@@ -3,10 +3,10 @@
 
 {{ partial "header_image.html" . }}
 
-<div class="container">
-  <div class="row">
+<div class="universal-wrapper">
+  <div class="row pt-3">
     <div class="col-lg-12">
-      <h1 class="my-4">{{ .Title | default (i18n "talks") }}</h1>
+      <h1>{{ .Title | default (i18n "talks") }}</h1>
 
       {{ with .Content }}
       <div class="article-style" itemprop="articleBody">{{ . }}</div>

+ 3 - 0
static/js/hugo-academic.js

@@ -330,6 +330,9 @@
 
     // Initialise Google Maps if necessary.
     initMap();
+
+    // Fix Hugo's inbuilt Table of Contents.
+    $('#TableOfContents > ul > li > ul').unwrap().unwrap();
   });
 
 })(jQuery);

+ 10 - 2
static/js/search.js

@@ -49,8 +49,12 @@ function initSearch(force, fuse) {
   let query = $("#search-query").val();
 
   // If query deleted, clear results.
-  if ( query.length < 1)
+  if ( query.length < 1) {
     $('#search-hits').empty();
+    $('.docs-content .article').show();
+  } else {
+    $('.docs-content .article').hide();
+  }
 
   // Check for timer event (enter key not pressed) and query less than minimum length required.
   if (!force && query.length < fuseOptions.minMatchCharLength)
@@ -67,6 +71,10 @@ function initSearch(force, fuse) {
 function searchAcademic(query, fuse) {
   let results = fuse.search(query);
   // console.log({"results": results});
+
+  if ($('.docs-content').length > 0)
+    $('#search-hits').append('<h1>' + i18n.results + '</h1>');
+
   if (results.length > 0) {
     parseResults(query, results);
   } else {
@@ -102,7 +110,7 @@ function parseResults(query, results) {
     var template = $('#search-hit-fuse-template').html();
 
     // Localize content types.
-    let content_key = value.item.type;
+    let content_key = value.item.section;
     if (content_key in content_type) {
       content_key = content_type[content_key];
     }