Browse Source

search fixes, add more shortcodes, css edit, bug fixes, new param, blog ui update,

search fixes
- don't include fuse.js file when search disabled
- search indexing
- search url
shortcode
- codetab
- tab
css edit
- tab border
bug fixes
- toggle wide mode
new param
- enableWideBlogSwitch
- wideViewAsDefault
blog
- add tag cloud
- ui changed
- wide button added
- image header support
zzossig 5 years ago
parent
commit
4e0ff9dba4
38 changed files with 635 additions and 224 deletions
  1. 1 1
      README.md
  2. 2 0
      assets/sass/components/_breadcrumb.scss
  3. 1 1
      assets/sass/components/_button.scss
  4. 59 0
      assets/sass/components/_codetab.scss
  5. 2 2
      assets/sass/components/_summary.scss
  6. 40 37
      assets/sass/components/_switch.scss
  7. 57 0
      assets/sass/components/_tab.scss
  8. 29 5
      assets/sass/components/_tag.scss
  9. 8 10
      assets/sass/layout/_grid.scss
  10. 2 0
      assets/sass/main.scss
  11. 2 10
      assets/sass/pages/_list.scss
  12. 1 9
      assets/sass/pages/_single.scss
  13. 0 0
      exampleSite/resources/_gen/assets/scss/sass/main.scss_b4f67ac5085b89b62b54c1923e5a9145.content
  14. 1 1
      i18n/en.toml
  15. 1 1
      i18n/ko.toml
  16. 0 8
      layouts/_default/list.searchindex.json
  17. 0 8
      layouts/_default/single.searchindex.json
  18. 26 30
      layouts/_default/taxonomy.html
  19. 0 8
      layouts/_default/terms.searchindex.json
  20. 24 29
      layouts/blog/section.html
  21. 31 13
      layouts/blog/single.html
  22. 107 5
      layouts/partials/head/scripts.html
  23. 4 4
      layouts/partials/header/blog-header-img.html
  24. 9 9
      layouts/partials/header/blog-header-text.html
  25. 0 5
      layouts/partials/header/taxo-header.html
  26. 3 0
      layouts/partials/main/component/breadcrumb.html
  27. 18 0
      layouts/partials/main/component/tag-cloud.html
  28. 25 0
      layouts/partials/main/component/toggle-menu.html
  29. 21 8
      layouts/partials/main/component/toggle-sidebar.html
  30. 1 0
      layouts/partials/main/header.html
  31. 2 2
      layouts/partials/main/landing/section-card.html
  32. 6 2
      layouts/partials/main/list.html
  33. 49 16
      layouts/partials/main/single.html
  34. 1 0
      layouts/shortcodes/boxmd.html
  35. 4 0
      layouts/shortcodes/code.html
  36. 47 0
      layouts/shortcodes/codes.html
  37. 4 0
      layouts/shortcodes/tab.html
  38. 47 0
      layouts/shortcodes/tabs.html

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 ## Zdoc theme for Hugo
 
-Thank you for click me!. Zdoc theme is a simple documentation theme powered by Hugo
+I'll document all the updates soon.
 
 ## Table of contents
 

+ 2 - 0
assets/sass/components/_breadcrumb.scss

@@ -1,4 +1,6 @@
 .breadcrumb {
+  position: relative;
+  
   &[data-is-blog="true"] {
     margin: 1.5rem 1rem 0 1rem;
     padding-bottom: 1.5rem;

+ 1 - 1
assets/sass/components/_button.scss

@@ -41,7 +41,7 @@
   border: none;
   outline: none;
   padding: 0.75rem;
-  margin: 0 0.95rem;
+  margin: 0 0.5rem;
   text-decoration: none;
   cursor: pointer;
   font-family: $title-font;

+ 59 - 0
assets/sass/components/_codetab.scss

@@ -0,0 +1,59 @@
+.codetab {
+  position: relative;
+
+  &__links {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 27px;
+    z-index: z('clipboard');
+    border-top-left-radius: 0.25rem;
+
+    @include flexbox();
+    @include align-items(center);
+    @include justify-content(flex-start);
+    @include themify($codeblock) {
+      background-color: themed('content-pre-header-background-color');
+    }
+  }
+
+  &__link {
+    height: 27px;
+    border: none;
+    outline: none;
+    cursor: pointer;
+    font-size: 13.8px;
+    font-family: $title-font;
+    text-transform: capitalize;
+    padding: 0.25rem 0.5rem;
+
+    &:first-child {
+      border-top-left-radius: 0.25rem;
+    }
+    
+    @include transition(all, 0.2s, ease-in);
+    @include themify($themes) {
+      color: themed('navbar-title-color');
+      border-top: 1px solid transparent;
+      &.active {
+        border-top: 1px solid themed('content-pre-header-background-color');
+      }
+    }
+
+    @include themify($codeblock) {
+      background-color: themed('content-pre-header-background-color');
+
+      &.active {
+        background-color: themed('content-pre-background-color');
+      }
+
+      @include on-event {
+        background-color: themed('content-pre-background-color');
+      }
+    }
+  }
+
+  &__content {
+    display: none;
+  }
+}

+ 2 - 2
assets/sass/components/_summary.scss

@@ -1,12 +1,11 @@
 .summary-card {
   padding: 1rem;
   padding-bottom: 0;
-  margin: 1.5rem 0;
+  margin: 2.5rem 0 2rem 0;
   position: relative;
 
   @include themify($themes) {
     background-color: themed('body-background-color');
-    border-bottom: 1px solid themed('hr-color');
   }
 
   .title {
@@ -43,6 +42,7 @@
     @include flex-wrap(wrap);
     @include themify($themes) {
       background-color: themed('body-background-color');
+      border-bottom: 1px solid themed('hr-color');
     }
   }
 

+ 40 - 37
assets/sass/components/_switch.scss

@@ -1,12 +1,41 @@
 /* The switch - the box around the slider */
 .switch {
-  position: absolute;
-  right: -2rem;
-  top: 1.75rem;
-  display: inline-block;
-  width: 30px;
-  height: 16px;
-  z-index: z('gtt');
+  cursor: pointer;
+
+  @include flexbox();
+  @include align-items(center);
+  @include justify-content(center);
+
+  &__rel {
+    position: relative;
+    height: auto;
+    padding: 0 6px;
+    
+    @include themify($themes) {
+      color: themed('landing-button-default');
+      @include on-event {
+        color: themed('body-color');
+        background-color: themed('dropdown-hover-background-color');
+      }
+    }
+  }
+
+  &__abs {
+    position: absolute;
+    left: -75px;
+    top: -16px;
+    z-index: z('menu');
+    width: 50px;
+    height: 50px;
+    margin: auto 0;
+
+    @include themify($themes) {
+      color: themed('landing-button-default');
+      @include on-event {
+        color: themed('body-color');
+      }
+    }
+  }
 }
 
 /* Hide default HTML checkbox */
@@ -18,44 +47,18 @@
 
 /* The slider */
 .slider {
-  position: absolute;
-  cursor: pointer;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
   background-color: transparent;
   -webkit-transition: .2s;
   transition: .2s;
 
-  &:before {
-    position: absolute;
-    content: "";
-    height: 12px;
-    width: 13px;
-    left: 2px;
-    bottom: 2px;
-    -webkit-transition: .2s;
-    transition: .2s;
-  }
-
   &__icon {
-    padding: 0.5rem;
+    svg {
+      @include flexbox();
+      @include align-items(center);
+    }
   }
 }
 
-// input:checked + .slider {
-//   @include themify($themes) {
-//     background-color: transparent;
-//   }
-// }
-
-// input:focus + .slider {
-//   @include themify($themes) {
-//     background-color: transparent;
-//   }
-// }
-
 input:checked + .slider:before {
   -webkit-transform: translateX(13px);
   -ms-transform: translateX(13px);

+ 57 - 0
assets/sass/components/_tab.scss

@@ -0,0 +1,57 @@
+.tab {
+  position: relative;
+  padding: 0.5rem 0;
+  margin: 2rem 0;
+
+  &__links {
+    height: 30px;
+    border-top-left-radius: 0.25rem;
+
+    @include flexbox();
+    @include align-items(center);
+    @include justify-content(flex-start);
+    @include flex-wrap(wrap);
+  }
+
+  &__link {
+    outline: none;
+    border: none;
+    cursor: pointer;
+    font-size: 13.8px;
+    font-family: $title-font;
+    text-transform: capitalize;
+    padding: 0.5rem 0.75rem;
+    border-top-left-radius: 0.25rem;
+    border-top-right-radius: 0.25rem;
+    
+    @include themify($themes) {
+      color: themed('navbar-title-color');
+      background-color: themed('body-background-color');
+      border: none;
+      border-bottom: 1px solid themed('hr-color');
+
+      &.active {
+        background-color: themed('body-background-color');
+        border: 1px solid themed('hr-color');
+        border-bottom: none;
+      }
+
+      @include on-event {
+        color: themed('navbar-title-hover-color');
+        background-color: themed('content-pre-header-background-color');
+      }
+    }
+  }
+
+  &__content {
+    display: none;
+    padding: 0 0.5rem;
+    border-radius: 0.25rem;
+    border-top-left-radius: 0;
+
+    @include themify($themes) {
+      border: 1px solid themed('hr-color');
+      background-color: themed('body-background-color');
+    }
+  }
+}

+ 29 - 5
assets/sass/components/_tag.scss

@@ -1,6 +1,7 @@
 .tag {
   display: inline-block;
-  margin: 0.25rem 0.4rem;
+  padding: 0.2rem 0;
+  width: 100%;
   text-decoration: none !important;
 
   @include themify($themes) {
@@ -16,14 +17,37 @@
   }
 
   &__text {
-    font-size: 0.95rem;
-    font-weight: bold;
     color: inherit;
   }
 
   &__num {
-    font-size: 0.75rem;
-    font-weight: bold;
     color: inherit;
   }
+
+  &-cloud {
+    position: relative;
+
+    &__label {
+      font-family: $title-font;
+      font-size: 1rem;
+      margin: 3.5rem 0 0 0;
+      margin-left: 1.25rem;
+      padding-left: 2.5rem;
+
+      @include themify($themes) {
+        color: themed('toc-label-color');
+      }
+    }
+
+    &__tags {
+      // position: -webkit-sticky;
+      // position: sticky;
+      top: $grid-nav-height;
+      padding: 1rem;
+      padding-left: 3rem;
+      height: calc(100vh - $grid-nav-height);
+      overflow-y: auto;
+      z-index: z('menu');
+    }
+  }
 }

+ 8 - 10
assets/sass/layout/_grid.scss

@@ -56,20 +56,18 @@
 
 .sv { // single view
     display: grid;
+    grid-template-columns: minmax(630px, 1fr) minmax(125px, 325px);
     grid-template-rows: 1fr;
     grid-column-gap: 0px;
     grid-row-gap: 0px;
 
-    &[data-view="full"] {
-        grid-template-columns: 769px;
-    }
-
-    &[data-view="mobile"] {
-        width: 100%;
-        grid-template-columns: 1fr;
+    @media only screen and (max-width: 769px) {
+        grid-template-columns: minmax(200px, 1fr);
     }
 }
 
-.blog {
-    grid-area: 1 / 1 / 2 / 2;
-}
+.blog-post { grid-area: 1 / 1 / 2 / 2; } // left
+
+.blog-tags { grid-area: 1 / 2 / 2 / 3; } // right
+
+.blog-total { grid-area: 1 / 1 / 2 / 3; } // left right

+ 2 - 0
assets/sass/main.scss

@@ -49,6 +49,8 @@ $light-nav-icon-color: {{ .Site.Data.color.light_nav_icon_color }};
 @import 'components/switch';
 @import 'components/notice';
 @import 'components/alert';
+@import 'components/tab';
+@import 'components/codetab';
 
 @import 'layout/grid';
 @import 'layout/footer';

+ 2 - 10
assets/sass/pages/_list.scss

@@ -8,26 +8,18 @@
 
     &[data-dir="ltr"] {
       border-right: 1px solid themed('border-line-color');
+      border-left: 1px solid themed('border-line-color');
     }
 
     &[data-dir="rtl"] {
       border-left: 1px solid themed('border-line-color');
+      border-right: 1px solid themed('border-line-color');
     }
   }
 }
 
 #list-menu {
   position: relative;
-  
-  @include themify($themes) {
-    &[data-dir="ltr"] {
-      border-right: 1px solid themed('border-line-color');
-    }
-
-    &[data-dir="rtl"] {
-      border-left: 1px solid themed('border-line-color');
-    }
-  }
 }
 
 #list-side {

+ 1 - 9
assets/sass/pages/_single.scss

@@ -1,13 +1,5 @@
 #single-menu {
-  @include themify($themes) {
-    &[data-dir="ltr"] {
-      border-right: 1px solid themed('border-line-color');
-    }
-
-    &[data-dir="rtl"] {
-      border-left: 1px solid themed('border-line-color');
-    }
-  }
+  position: relative;
 }
 
 .single {

File diff suppressed because it is too large
+ 0 - 0
exampleSite/resources/_gen/assets/scss/sass/main.scss_b4f67ac5085b89b62b54c1923e5a9145.content


+ 1 - 1
i18n/en.toml

@@ -20,4 +20,4 @@ other = "min read"
 other = "WRITTEN BY"
 
 [edit-this-page]
-other = "EDIT THIS PAGE"
+other = "EDIT THIS PAGE"

+ 1 - 1
i18n/ko.toml

@@ -20,4 +20,4 @@ other = "min read"
 other = "글쓴이"
 
 [edit-this-page]
-other = "이 페이지 수정하기"
+other = "이 페이지 수정하기"

+ 0 - 8
layouts/_default/list.searchindex.json

@@ -1,8 +0,0 @@
-{{ .Scratch.Delete "searchindex" }}
-{{- $.Scratch.Add "searchindex" slice -}}
-{{- range $index, $element := (where .Site.Pages "Kind" "page") -}}
-  {{ with $element.Plain }}
-    {{- $.Scratch.Add "searchindex" (dict "id" $index "title" $element.Title "uri" $element.Permalink "tags" $element.Params.tags "section" $element.Section "content" $element.Plain "description" $element.Description) -}}
-  {{ end }}
-{{- end -}}
-{{- $.Scratch.Get "searchindex" | jsonify -}}

+ 0 - 8
layouts/_default/single.searchindex.json

@@ -1,8 +0,0 @@
-{{ .Scratch.Delete "searchindex" }}
-{{- $.Scratch.Add "searchindex" slice -}}
-{{- range $index, $element := (where .Site.Pages "Kind" "page") -}}
-  {{ with $element.Plain }}
-    {{- $.Scratch.Add "searchindex" (dict "id" $index "title" $element.Title "uri" $element.Permalink "tags" $element.Params.tags "section" $element.Section "content" $element.Plain "description" $element.Description) -}}
-  {{ end }}
-{{- end -}}
-{{- $.Scratch.Get "searchindex" | jsonify -}}

+ 26 - 30
layouts/_default/taxonomy.html

@@ -1,42 +1,32 @@
 {{ define "main" }}
 <div class="top">
   <header class="header__wrapper bgcolor__header">
-    <div class="divider">
-      <div class="lmr">
-        {{ partial "header/taxo-header.html" . }}
-      </div>
-    </div>
+    {{ with .Site.GetPage "section" "blog" }}
+      {{ if eq .Params.blogHeaderType "img" }}
+        {{ partial "header/blog-header.html" . }}
+      {{ else }}
+        <div class="divider">
+          <div class="lmr">
+            {{ partial "header/blog-header.html" . }}
+          </div>
+        </div>
+      {{ end }}
+    {{ end }}
   </header>
-
-  <div class="header__wrapper bgcolor__breadcrumb">
-    <div class="divider">
-      <div class="lmr flexbox jc-center flex-wrap tag__wrapper">
-        {{ range $index, $element := .Site.Taxonomies.tags }}
-          {{ if $index }}
-            <a href="{{ $element.Page.RelPermalink }}" class="tag">
-              <span class="tag__text">
-                {{ $element.Page.Title }}
-              </span>
-              <span class="tag__num" dir="auto">
-                {{ printf "%#v" (len $element) }}
-              </span>
-            </a>
-          {{ end }}
-        {{ end }}
-      </div>
-    </div>
-  </div>
 </div>
 
 <div class="mid blog__bg">
   <main class="main blog__bg">
-    <div class="sv" data-view="full">
-      <div class="blog">
+    <div class="sv">
+      <div class="blog-post">
         {{ range .RegularPages }}
           {{ .Render "summary" }}
         {{ end }}
         {{ partial "main/component/pagination.html" . }}
       </div>
+      <div class="blog-tags">
+        {{ partial "main/component/tag-cloud" . }}
+      </div>
     </div>
 
   </main>
@@ -44,27 +34,33 @@
 
 <script>
   var singleViewElem = document.querySelectorAll('.sv');
+  var postElem = document.querySelector('.blog-post');
+  var tagsElem = document.querySelector('.blog-tags');
 
   enquire.register("screen and (max-width:1280px)", {
     match: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
     unmatch: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
   }).register("screen and (max-width:769px)", {
     match: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'mobile');
+        postElem.className = "blog-total";
+        tagsElem.className = "hide";
       });
     },
     unmatch: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
   });

+ 0 - 8
layouts/_default/terms.searchindex.json

@@ -1,8 +0,0 @@
-{{ .Scratch.Delete "searchindex" }}
-{{- $.Scratch.Add "searchindex" slice -}}
-{{- range $index, $element := (where .Site.Pages "Kind" "page") -}}
-  {{ with $element.Plain }}
-    {{- $.Scratch.Add "searchindex" (dict "id" $index "title" $element.Title "uri" $element.Permalink "tags" $element.Params.tags "section" $element.Section "content" $element.Plain "description" $element.Description) -}}
-  {{ end }}
-{{- end -}}
-{{- $.Scratch.Get "searchindex" | jsonify -}}

+ 24 - 29
layouts/blog/section.html

@@ -1,69 +1,64 @@
 {{ define "main" }}
 <div class="top">
   <header class="header__wrapper bgcolor__header">
-    <div class="divider">
-      <div class="lmr">
-        {{ partial "header/blog-header.html" . }}
+    {{ if eq .Params.blogHeaderType "img" }}
+      {{ partial "header/blog-header.html" . }}
+    {{ else }}
+      <div class="divider">
+        <div class="lmr">
+          {{ partial "header/blog-header.html" . }}
+        </div>
       </div>
-    </div>
+    {{ end }}
   </header>
-  
-  <div class="header__wrapper bgcolor__breadcrumb">
-    <div class="divider">
-      <div class="lmr flexbox jc-center flex-wrap tag__wrapper">
-        {{ range $index, $element := .Site.Taxonomies.tags }}
-          {{ if $index }}
-            <a href="{{ $element.Page.RelPermalink }}" class="tag">
-              <span class="tag__text">
-                {{ $element.Page.Title }}
-              </span>
-              <span class="tag__num" dir="auto">
-                {{ printf "%#v" (len $element) }}
-              </span>
-            </a>
-          {{ end }}
-        {{ end }}
-      </div>
-    </div>
-  </div>
 </div>
+
 <div class="mid blog__bg">
   <main class="main blog__bg">
-    <div class="sv" data-view="full">
-      <div class="blog">
+    <div class="sv">
+      <div class="blog-post">
         {{ $paginator := .Paginate (where .RegularPages "Type" .Type) }}
         {{ range $paginator.Pages }}
           {{ .Render "summary" }}
         {{ end }}
         {{ partial "main/component/pagination.html" . }}
       </div>
+      <div class="blog-tags">
+        {{ partial "main/component/tag-cloud" . }}
+      </div>
     </div>
   </main>
 </div>
 
 <script>
   var singleViewElem = document.querySelectorAll('.sv');
+  var postElem = document.querySelector('.blog-post');
+  var tagsElem = document.querySelector('.blog-tags');
 
   enquire.register("screen and (max-width:1280px)", {
     match: function () {
       singleViewElem.forEach(function(elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
     unmatch: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
   }).register("screen and (max-width:769px)", {
     match: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'mobile');
+        postElem.className = "blog-total";
+        tagsElem.className = "hide";
       });
     },
     unmatch: function () {
       singleViewElem.forEach(function (elem) {
-        elem.setAttribute('data-view', 'full');
+        postElem.className = "blog-post";
+        tagsElem.className = "blog-tags";
       });
     },
   });

+ 31 - 13
layouts/blog/single.html

@@ -1,15 +1,15 @@
 {{ define "main" }}
+{{ $wideViewAsDefault := ($.Param "wideViewAsDefault") }}
 <div class="mid">
   <div class="divider">
     
-    <nav id="single-menu" class="l" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
+    <nav id="single-menu" class="{{ if $wideViewAsDefault }}hide{{ else }}l{{ end }}" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
     </nav>
 
-    <article id="list-main" class="m" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
+    <article id="list-main" class="{{ if $wideViewAsDefault }}lm{{ else }}m{{ end }}" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
       {{ if $.Param "enableBlogBreadcrumb" }}
         {{ partial "main/component/breadcrumb.html" . }}
       {{ end }}
-      {{ partial "main/component/toggle-sidebar.html" . }}
       {{ partial "main/sections/list-main.html" . }}
       {{ partial "main/component/pagination-single.html" . }}
     </article>
@@ -28,17 +28,30 @@
   var listSide = document.getElementById('list-side');
   var listMain = document.getElementById('list-main');
   var singleMenu = document.getElementById('single-menu');
+  var wideViewAsDefault = JSON.parse({{ $wideViewAsDefault | jsonify }});
 
   enquire.register("screen and (max-width:1280px)", {
     match: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        singleMenu.className = 'hide';
+        listMain.className = 'lm';
+        listSide.className = 'r';
+      } else {
+        singleMenu.className = 'l';
+        listMain.className = 'm';
+        listSide.className = 'r';
+      }
     },
     unmatch: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        singleMenu.className = 'hide';
+        listMain.className = 'lm';
+        listSide.className = 'r';
+      } else {
+        singleMenu.className = 'l';
+        listMain.className = 'm';
+        listSide.className = 'r';
+      }
     },
   }).register("screen and (max-width:960px)", {
     match: function () {
@@ -47,9 +60,15 @@
       singleMenu.className = 'hide';
     },
     unmatch: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        singleMenu.className = 'hide';
+        listMain.className = 'lm';
+        listSide.className = 'r';
+      } else {
+        singleMenu.className = 'l';
+        listMain.className = 'm';
+        listSide.className = 'r';
+      }
     },
   }).register("screen and (max-width:600px)", {
     match: function () {
@@ -61,7 +80,6 @@
       listSide.className = 'r';
       listMain.className = 'lm';
       singleMenu.className = 'hide';
-      
     },
   });
 </script>

+ 107 - 5
layouts/partials/head/scripts.html

@@ -8,10 +8,13 @@
 {{ end }}
 {{ end }}
 
+{{ if $.Param "enableSearch" }}
+  {{ $fuse := resources.Get "js/fuse.min.js" | resources.Fingerprint }}
+  <script defer src="{{ $fuse.RelPermalink }}"></script>
+{{ end }}
+
 {{ $enquire := resources.Get "js/enquire.min.js" | resources.Fingerprint }}
 <script src="{{ $enquire.RelPermalink }}"></script>
-{{ $fuse := resources.Get "js/fuse.min.js" | resources.Fingerprint }}
-<script defer src="{{ $fuse.RelPermalink }}"></script>
 {{ $lazysizes := resources.Get "js/lazysizes.min.js" | resources.Fingerprint }}
 <script defer src="{{ $lazysizes.RelPermalink }}"></script>
 {{ $getParents := resources.Get "js/helper/getParents.js" | resources.Minify | resources.Fingerprint }}
@@ -29,10 +32,12 @@
 
     // ==================== toc visibility ========================
     var toggleSidebarElem = document.getElementById("toggle-sidebar");
+    var toggleMenuElem = document.getElementById("toggle-menu");
     var tocBodyElem = document.querySelector('.toc__body');
     var tocLabelElem = document.querySelector('.toc__label');
     var listMainElem = document.getElementById('list-main');
     var listSideElem = document.getElementById('list-side');
+    var singleMenuElem = document.getElementById('single-menu');
     var sliderIcons = document.querySelectorAll('.slider__icon');
 
     toggleSidebarElem ?
@@ -69,6 +74,38 @@
             listSideElem.className = 'hide';
           }
 
+          sliderIcons && sliderIcons.forEach(function (elem) {
+            if (elem.classList.contains('hide')) {
+              elem.classList.remove('hide');
+            } else {
+              elem.classList.add('hide');
+            }
+          });
+        }
+      }) : null;
+
+    toggleMenuElem ?
+      toggleMenuElem.addEventListener('change', function (e) {
+        if (e.target.checked) {
+          if (listMainElem && singleMenuElem) {
+            listMainElem.className = 'm';
+            singleMenuElem.className = 'l';
+          }
+
+          sliderIcons && sliderIcons.forEach(function (elem) {
+            if (elem.classList.contains('hide')) {
+              elem.classList.remove('hide');
+            } else {
+              elem.classList.add('hide');
+            }
+          });
+
+        } else {
+          if (listMainElem && singleMenuElem) {
+            listMainElem.className = 'lm';
+            singleMenuElem.className = 'hide';
+          }
+
           sliderIcons && sliderIcons.forEach(function (elem) {
             if (elem.classList.contains('hide')) {
               elem.classList.remove('hide');
@@ -80,6 +117,7 @@
       }) : null;
     // ============================================================
 
+
     // ===================== navbar collapse ======================
     var navCollapseBtn = document.getElementById('navCollapseBtn');
     navCollapseBtn ? navCollapseBtn.addEventListener('click', function(e) {
@@ -119,6 +157,66 @@
     }
     // ============================================================
 
+
+
+    // ============================ tab ============================
+    document.querySelectorAll('.tab') ? 
+    document.querySelectorAll('.tab').forEach(function(elem, idx) {
+      var containerId = elem.getAttribute('id');
+      var containerElem = elem;
+      var tabLinks = elem.querySelectorAll('.tab__link');
+      var tabContents = elem.querySelectorAll('.tab__content');
+      var ids = [];
+
+      tabLinks && tabLinks.length > 0 ?
+      tabLinks.forEach(function(link, index, self) {
+        link.onclick = function(e) {
+          for (var i = 0; i < self.length; i++) {
+            if (index === parseInt(i, 10)) {
+              if (!self[i].classList.contains('active')) {
+                self[i].classList.add('active');
+                tabContents[i].style.display = 'block';
+              }
+            } else {
+              self[i].classList.remove('active');
+              tabContents[i].style.display = 'none';
+            }
+          }
+        }
+      }) : null;
+    }) : null;
+    // =============================================================
+
+
+    // ========================== codetab ==========================
+    document.querySelectorAll('.codetab') ? 
+    document.querySelectorAll('.codetab').forEach(function(elem, idx) {
+      var containerId = elem.getAttribute('id');
+      var containerElem = elem;
+      var codetabLinks = elem.querySelectorAll('.codetab__link');
+      var codetabContents = elem.querySelectorAll('.codetab__content');
+      var ids = [];
+
+      codetabLinks && codetabLinks.length > 0 ?
+      codetabLinks.forEach(function(link, index, self) {
+        link.onclick = function(e) {
+          for (var i = 0; i < self.length; i++) {
+            if (index === parseInt(i, 10)) {
+              if (!self[i].classList.contains('active')) {
+                self[i].classList.add('active');
+                codetabContents[i].style.display = 'block';
+              }
+            } else {
+              self[i].classList.remove('active');
+              codetabContents[i].style.display = 'none';
+            }
+          }
+        }
+      }) : null;
+    }) : null;
+    // =============================================================
+
+
     
     // ======================= toggle theme =======================
     var root = document.getElementById('root');
@@ -456,8 +554,12 @@
 
 
   // ========================== search ==========================
-    {{ $searchURL:= ("/" | absLangURL) }}
-    var searchURL = JSON.parse({{ $searchURL | jsonify }});
+    {{ $baseurl := $.Site.BaseURL }}
+    var baseurl = JSON.parse({{ $baseurl | jsonify }});
+    {{ $permalink := .Permalink }}
+    var permalink = JSON.parse({{ $permalink | jsonify }});
+    {{ $langprefix := $.Site.LanguagePrefix }}
+    var langprefix = JSON.parse({{ $langprefix | jsonify }});
     var searchResults = null;
     var searchMenu = null;
     var searchText = null;
@@ -472,7 +574,7 @@
     if (enableSearch) {
       (function initFuse() {
         var xhr = new XMLHttpRequest();
-        xhr.open('GET', searchURL + "index.json");
+        xhr.open('GET', baseurl + langprefix + "/index.json");
         xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
         xhr.onload = function () {
           if (xhr.status === 200) {

+ 4 - 4
layouts/partials/header/blog-header-img.html

@@ -1,12 +1,12 @@
 {{ if .Params.header }}
   {{ range .Params.header }}
     {{ if eq .type "img" }}
-    <div class="site-header site-header__align-{{ .align }}" style="background-image: url('{{ .imageSrc | relURL }}'); background-size: {{ .imageSize }};
+    <div class="blog-header blog-header__align-{{ .align }}" style="background-image: url('{{ .imageSrc | relURL }}'); background-size: {{ .imageSize }};
     background-repeat: {{ .imageRepeat }}; background-position: {{ .imagePosition }}; width: 100%; height: {{ .height }}px; padding: {{ .paddingY }}px {{ .paddingX }}px;">
       {{ $header := . }}
       {{ if .title }}
         {{ range .title }}
-        <div class="site-header__title {{ if $header.titleShadow }}site-header__title--shadow{{ end }}" style="font-size: {{ $header.titleFontSize }}px; {{ with $header.titleColor }}color:{{ . }}{{ end }};">
+        <div class="blog-header__title {{ if $header.titleShadow }}blog-header__title--shadow{{ end }}" style="font-size: {{ $header.titleFontSize }}px; {{ with $header.titleColor }}color:{{ . }}{{ end }};">
           {{ . }}
         </div>
         {{ end }}      
@@ -15,8 +15,8 @@
         <div style="height: {{ .spaceBetweenTitleSubtitle }}px"></div>
       {{ end }}
       {{ if .subtitle }}
-        {{ range .subtitle }} 
-        <div class="site-header__subtitle" style="font-size: {{ $header.subtitleFontSize }}px; {{ with $header.subtitleColor }}color:{{ . }}{{ end }}" data-cursive="{{ with $header.subtitleCursive }}{{ . }}{{ end }}">
+        {{ range .subtitle }}
+        <div class="blog-header__subtitle" style="font-size: {{ $header.subtitleFontSize }}px; {{ with $header.subtitleColor }}color:{{ . }}{{ end }}">
           {{ . }}
         </div>
         {{ end }}

+ 9 - 9
layouts/partials/header/blog-header-text.html

@@ -1,13 +1,13 @@
-{{ if .Params.header }}
-  {{ range .Params.header }}
+{{ if $.Params.header }}
+  {{ range $.Params.header }}
     {{ if eq .type "text" }}
-    <div class="blog-header blog-header__align-{{ .align }}" style="width: 100%; height: {{ $.Param "headerHeight" | default 112 }}px;">
+    <div class="blog-header blog-header__align-{{ .align }}" style="width: 100%; height: {{ .height | default 112 }}px;">
       {{ $header := . }}
       {{ if .title }}
         {{ range .title }}
-        <div class="blog-header__title" style="font-size: {{ $header.titleFontSize }}px; {{ with $header.titleColor }}color:{{ . }}{{ end }};">
-          {{ . }}
-        </div>
+          <div class="blog-header__title" style="font-size: {{ $header.titleFontSize }}px; {{ with $header.titleColor }}color:{{ . }}{{ end }};">
+            {{ . }}
+          </div>
         {{ end }}      
       {{ end }}
       {{ if .spaceBetweenTitleSubtitle }}
@@ -15,9 +15,9 @@
       {{ end }}
       {{ if .subtitle }}
         {{ range .subtitle }} 
-        <div class="blog-header__subtitle" style="font-size: {{ $header.subtitleFontSize }}px; {{ with $header.subtitleColor }}color:{{ . }}{{ end }}" data-cursive="{{ with $header.subtitleCursive }}{{ . }}{{ end }}">
-          {{ . }}
-        </div>
+          <div class="blog-header__subtitle" style="font-size: {{ $header.subtitleFontSize }}px; {{ with $header.subtitleColor }}color:{{ . }}{{ end }}" data-cursive="{{ with $header.subtitleCursive }}{{ . }}{{ end }}">
+            {{ . }}
+          </div>
         {{ end }}      
       {{ end }}
     </div>

+ 0 - 5
layouts/partials/header/taxo-header.html

@@ -1,5 +0,0 @@
-<div class="blog-header blog-header__align-center" style="width: 100%; height: {{ $.Param "headerHeight" | default 112 }}px;">
-  <div class="blog-header__title">
-    {{ .Title }}
-  </div>
-</div>

+ 3 - 0
layouts/partials/main/component/breadcrumb.html

@@ -1,4 +1,7 @@
 <nav class="breadcrumb" aria-label="breadcrumbs" data-is-blog="{{ if eq .Type "blog" }}true{{ else }}false{{ end }}">
+  {{ if eq .Type "blog" }}
+    {{ partial "main/component/toggle-menu.html" . }}
+  {{ end }}
   <ol>
     {{ template "breadcrumbnav" (dict "p1" . "p2" .) }}
   </ol>

+ 18 - 0
layouts/partials/main/component/tag-cloud.html

@@ -0,0 +1,18 @@
+{{ $totalTag := (len .Site.Taxonomies.tags) }}
+<h6 class="tag-cloud__label">
+  {{ .Title }}
+</h6>
+<div class="tag-cloud__tags">
+  {{ range $index, $element := .Site.Taxonomies.tags }}
+    {{ if $index }}
+      <a href="{{ $element.Page.RelPermalink }}" class="tag">
+        <span class="tag__text" style="font-size: {{ add 0.8 (div (len $element) (mul 1.0 $totalTag)) }}rem;">
+          {{ $element.Page.Title }}
+        </span>
+        <span class="tag__num" style="font-size: {{ add 0.8 (div (len $element) (mul 1.0 $totalTag)) }}rem;" dir="auto">
+          ({{ printf "%#v" (len $element) }})
+        </span>
+      </a>
+    {{ end }}
+  {{ end }}
+</div>

+ 25 - 0
layouts/partials/main/component/toggle-menu.html

@@ -0,0 +1,25 @@
+{{ if $.Param "enableWideBlogSwitch" }}
+  <label class="switch switch__abs">
+    {{ if ($.Param "wideViewAsDefault") }}
+      <input id="toggle-menu" aria-label="Toggle Menu Visibility" type="checkbox" />
+      <span class="slider">
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}{{ else }}hide{{ end }}">
+          {{ partial "svgs/arrow-forward.svg" (dict "width" 20 "height" 20) }}
+        </span>
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}hide{{ end }}">
+          {{ partial "svgs/arrow-back.svg" (dict "width" 20 "height" 20) }}
+        </span>
+      </span>
+    {{ else }}
+      <input id="toggle-menu" aria-label="Toggle Menu Visibility" type="checkbox" checked />
+      <span class="slider">
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}{{ else }}hide{{ end }}">
+          {{ partial "svgs/arrow-back.svg" (dict "width" 20 "height" 20) }}
+        </span>
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}hide{{ end }}">
+          {{ partial "svgs/arrow-forward.svg" (dict "width" 20 "height" 20) }}
+        </span>
+      </span>
+    {{ end }}
+  </label>
+{{ end }}

+ 21 - 8
layouts/partials/main/component/toggle-sidebar.html

@@ -1,13 +1,26 @@
 {{ if $.Param "enableTocSwitch" }}
-  <label class="switch">
-    <input id="toggle-sidebar" aria-label="Toggle Sidebar Visibility" type="checkbox" checked />
-    <span class="slider">
-      <span class="slider__icon">
-        {{ partial "svgs/arrow-forward.svg" (dict "width" 20 "height" 20) }}
+  <label class="switch switch__rel">
+    {{ if ($.Param "wideViewAsDefault") }}
+      <input id="toggle-sidebar" aria-label="Toggle Sidebar Visibility" type="checkbox" />
+      <span class="slider">
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}{{ else }}hide{{ end }}">
+          {{ partial "svgs/arrow-forward.svg" (dict "width" 20 "height" 20) }}
+        </span>
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}hide{{ end }}">
+          {{ partial "svgs/arrow-back.svg" (dict "width" 20 "height" 20) }}
+        </span>
       </span>
-      <span class="slider__icon hide">
-        {{ partial "svgs/arrow-back.svg" (dict "width" 20 "height" 20) }}
+    {{ else }}
+      <input id="toggle-sidebar" aria-label="Toggle Sidebar Visibility" type="checkbox" checked />
+      <span class="slider">
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}{{ else }}hide{{ end }}">
+          {{ partial "svgs/arrow-back.svg" (dict "width" 20 "height" 20) }}
+        </span>
+        <span class="slider__icon {{ if (ne ($.Param "languagedir") "rtl") }}hide{{ end }}">
+          {{ partial "svgs/arrow-forward.svg" (dict "width" 20 "height" 20) }}
+        </span>
       </span>
-    </span>
+    {{ end }}
+    
   </label>
 {{ end }}

+ 1 - 0
layouts/partials/main/header.html

@@ -14,6 +14,7 @@
           {{ partial "main/component/breadcrumb.html" . }}
         {{ end }}
         <div class="grow"></div>
+        {{ partial "main/component/toggle-sidebar.html" . }}
         {{ partial "main/component/edit-this-page.html" . }}
       </div>
     </div>

+ 2 - 2
layouts/partials/main/landing/section-card.html

@@ -24,7 +24,7 @@
         <div class="section__card--item">
           {{ with .image }}
             <div class="section__card--img-wrapper">
-            <img data-src="{{ . | relURL }}" width="{{ $card.imgWidth | default "100px" }}" alt="{{ $card.subtitle | default "section image" }}" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload section__card--img"/>
+              <img data-src="{{ . | relURL }}" alt="{{ $card.subtitle | default "section image" }}" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload section__card--img"/>
             </div>
           {{ end }}
           <div class="section__card--submain">
@@ -63,4 +63,4 @@
       {{ end }}
     </div>
   {{ end }}
-</div>
+</div>

+ 6 - 2
layouts/partials/main/list.html

@@ -8,7 +8,6 @@
     </nav>
 
     <article id="list-main" class="m" data-dir="{{ $.context.Param "languagedir" | default "ltr" }}">
-      {{ partial "main/component/toggle-sidebar.html" .context }}
       {{ partial "main/sections/list-main.html" .context }}
       {{ if .section_to_display }}
         {{ partial "main/sections/list-section.html" (dict "section_to_display" .section_to_display) }}
@@ -26,7 +25,8 @@
   var listSide = document.getElementById('list-side');
   var listMain = document.getElementById('list-main');
   var listMenu = document.getElementById('list-menu');
-  
+  var switchElem = document.querySelector('.switch');
+
   enquire.register("screen and (max-width:1280px)", {
     match: function () {
       listSide.className = 'r';
@@ -43,22 +43,26 @@
       listSide.className = 'hide';
       listMain.className = 'mr';
       listMenu.className = 'l';
+      switchElem.className = 'hide';
     },
     unmatch: function () {
       listSide.className = 'r';
       listMain.className = 'm';
       listMenu.className = 'l';
+      switchElem.className = 'switch';
     },
   }).register("screen and (max-width:600px)", {
     match: function () {
       listSide.className = 'hide';
       listMain.className = 'lmr';
       listMenu.className = 'hide';
+      switchElem.className = 'hide';
     },
     unmatch: function () {
       listSide.className = 'hide';
       listMain.className = 'mr';
       listMenu.className = 'l';
+      switchElem.className = 'hide';
     },
   });
 </script>

+ 49 - 16
layouts/partials/main/single.html

@@ -1,3 +1,5 @@
+{{ $wideViewAsDefault := ($.Param "wideViewAsDefault") }}
+
 <div class="mid">
   <div class="divider">
     
@@ -7,14 +9,13 @@
       {{ end }}
     </nav>
 
-    <article id="list-main" class="m" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
-      {{ partial "main/component/toggle-sidebar.html" . }}
+    <article id="list-main" class="{{ if $wideViewAsDefault }}mr{{ else }}m{{ end }}" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
       {{ partial "main/sections/list-main.html" . }}
       {{ partial "main/component/pagination-single.html" . }}
       {{ partial "comments/comments.html" . }}
     </article>
     
-    <section id="list-side" class="r" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
+    <section id="list-side" class="{{ if $wideViewAsDefault }}hide{{ else }}r{{ end }}" data-dir="{{ $.Param "languagedir" | default "ltr" }}">
       {{ if $.Param "enableToc" }}
         {{ partial "main/component/toc.html" . }}
       {{ end }}
@@ -28,40 +29,72 @@
   var listSide = document.getElementById('list-side');
   var listMain = document.getElementById('list-main');
   var singleMenu = document.getElementById('single-menu');
-
+  var switchElem = document.querySelector('.switch');
+  var wideViewAsDefault = JSON.parse({{ $wideViewAsDefault | jsonify }});
+  
   enquire.register("screen and (max-width:1280px)", {
     match: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        console.log(1);
+        singleMenu.className = 'l';  
+        listMain.className = 'mr';
+        listSide.className = 'hide';
+      } else {
+        console.log(2);
+        singleMenu.className = 'l';
+        listMain.className = 'm';
+        listSide.className = 'r';
+      }
     },
     unmatch: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        console.log(3);
+        singleMenu.className = 'l';
+        listMain.className = 'mr';
+        listSide.className = 'hide';
+      } else {
+        console.log(4);
+        listSide.className = 'r';
+        listMain.className = 'm';
+        singleMenu.className = 'l';
+      }
     },
   }).register("screen and (max-width:960px)", {
     match: function () {
-      listSide.className = 'hide';
-      listMain.className = 'mr';
+      console.log(5);
       singleMenu.className = 'l';
+      listMain.className = 'mr';
+      listSide.className = 'hide';
+      switchElem.className = 'hide';
     },
     unmatch: function () {
-      listSide.className = 'r';
-      listMain.className = 'm';
-      singleMenu.className = 'l';
+      if (wideViewAsDefault) {
+        console.log(6);
+        singleMenu.className = 'l';
+        listMain.className = 'mr';
+        listSide.className = 'hide';
+      } else {
+        console.log(7);
+        singleMenu.className = 'l';
+        listMain.className = 'm';
+        listSide.className = 'r';
+      }
+      switchElem.className = 'switch';
     },
   }).register("screen and (max-width:600px)", {
     match: function () {
+      console.log(8);
       listSide.className = 'hide';
       listMain.className = 'lmr';
       singleMenu.className = 'hide';
+      switchElem.className = 'hide';
     },
     unmatch: function () {
+      console.log(9);
       listSide.className = 'hide';
       listMain.className = 'mr';
       singleMenu.className = 'l';
-      
+      switchElem.className = 'hide';
     },
   });
 </script>

+ 1 - 0
layouts/shortcodes/boxmd.html

@@ -0,0 +1 @@
+<div class="box">{{ .Inner | markdownify }}</div>

+ 4 - 0
layouts/shortcodes/code.html

@@ -0,0 +1,4 @@
+{{ $id := substr (md5 .Inner) 0 16 }}
+<div id="{{ $id }}" class="codetab__content">
+  {{ .Inner | markdownify }}
+</div>

+ 47 - 0
layouts/shortcodes/codes.html

@@ -0,0 +1,47 @@
+{{ $id := substr (md5 .Inner) 0 16 }}
+<div id="{{ $id }}" class="codetab">
+  <div class="codetab__links">
+    {{ range .Params }}
+      <button class="codetab__link" aria-label="Tab link">{{ . }}</button>
+    {{ end }}
+  </div>
+  {{ .Inner }}
+</div>
+
+<script>
+  'use strict';
+
+  var containerId = JSON.parse({{ $id | jsonify }});
+  var containerElem = document.getElementById(containerId);
+  var codetabLinks = null;
+  var codetabContents = null;
+  var ids = [];
+
+  if (containerElem) {
+    codetabLinks = containerElem.querySelectorAll('.codetab__link');
+    codetabContents = containerElem.querySelectorAll('.codetab__content');
+  }
+
+  for (var i = 0; i < codetabContents.length; i++) {
+    ids = ids.concat(codetabContents[i].getAttribute('id'));
+    codetabContents[i].style.display = 'none';
+
+    if (0 === parseInt(i, 10) && !codetabContents[i].classList.contains('active')) {
+      codetabContents[i].classList.add('active');
+    }
+  }
+
+  for (var i = 0; i < codetabLinks.length; i++) {
+    codetabLinks[i].setAttribute('id', ids[i]);
+
+    if (0 === parseInt(i, 10) && !codetabLinks[i].classList.contains('active')) {
+      codetabLinks[i].classList.add('active');
+    } else {
+      codetabLinks[i].classList.remove('active');
+    }
+  }
+
+  if (codetabContents.length > 0) {
+    codetabContents[0].style.display = 'block';
+  }
+</script>

+ 4 - 0
layouts/shortcodes/tab.html

@@ -0,0 +1,4 @@
+{{ $id := substr (md5 .Inner) 0 16 }}
+<div id="{{ $id }}" class="tab__content">
+  {{ .Inner | markdownify }}
+</div>

+ 47 - 0
layouts/shortcodes/tabs.html

@@ -0,0 +1,47 @@
+{{ $id := substr (md5 .Inner) 0 16 }}
+<div id="{{ $id }}" class="tab">
+  <div class="tab__links">
+    {{ range .Params }}
+      <button class="tab__link" aria-label="Tab link">{{ . }}</button>
+    {{ end }}
+  </div>
+  {{ .Inner }}
+</div>
+
+<script>
+  'use strict';
+
+  var containerId = JSON.parse({{ $id | jsonify }});
+  var containerElem = document.getElementById(containerId);
+  var tabLinks = null;
+  var tabContents = null;
+  var ids = [];
+
+  if (containerElem) {
+    tabLinks = containerElem.querySelectorAll('.tab__link');
+    tabContents = containerElem.querySelectorAll('.tab__content');
+  }
+
+  for (var i = 0; i < tabContents.length; i++) {
+    ids = ids.concat(tabContents[i].getAttribute('id'));
+    tabContents[i].style.display = 'none';
+
+    if (0 === parseInt(i, 10) && !tabContents[i].classList.contains('active')) {
+      tabContents[i].classList.add('active');
+    }
+  }
+
+  for (var i = 0; i < tabLinks.length; i++) {
+    tabLinks[i].setAttribute('id', ids[i]);
+
+    if (0 === parseInt(i, 10) && !tabLinks[i].classList.contains('active')) {
+      tabLinks[i].classList.add('active');
+    } else {
+      tabLinks[i].classList.remove('active');
+    }
+  }
+
+  if (tabContents.length > 0) {
+    tabContents[0].style.display = 'block';
+  }
+</script>

Some files were not shown because too many files changed in this diff