feat: add multi-language select and fix minor issues

fix: navbar icon should use home relative link

fix: copy code for raw <code> element

fix: missing breadcrumb hover style

fix: tabs typo preventing loading the script
This commit is contained in:
Xin 2023-08-13 22:18:20 +01:00
parent 3d7a4b7c99
commit 7a2cca9181
12 changed files with 120 additions and 32 deletions

View File

@ -1,10 +1,16 @@
document.querySelectorAll('.code-copy-btn').forEach(function (button) {
button.addEventListener('click', function (e) {
const targetId = e.target.getAttribute('data-clipboard-target');
e.preventDefault();
const targetId = button.getAttribute('data-clipboard-target');
const target = document.querySelector(targetId);
const codeElements = target.querySelectorAll('code');
// Select the last code element in case line numbers are present
const codeElement = codeElements[codeElements.length - 1];
let codeElement;
if (target.tagName === 'CODE') {
codeElement = target;
} else {
// Select the last code element in case line numbers are present
const codeElements = target.querySelectorAll('code');
codeElement = codeElements[codeElements.length - 1];
}
if (codeElement) {
// Replace double newlines with single newlines in the innerText
// as each line inside <span> has trailing newline '\n'

28
assets/js/lang.js Normal file
View File

@ -0,0 +1,28 @@
(function () {
const languageSwitchers = document.querySelectorAll('.language-switcher');
languageSwitchers.forEach((switcher) => {
switcher.addEventListener('click', (e) => {
e.preventDefault();
switcher.dataset.state = switcher.dataset.state === 'open' ? 'closed' : 'open';
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.toggle('hidden');
// Calculate position of language options element
const switcherRect = switcher.getBoundingClientRect();
const translateY = switcherRect.top - window.innerHeight - 15;
optionsElement.style.transform = `translate3d(${switcherRect.left}px, ${translateY}px, 0)`;
optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`;
});
});
// Dismiss language switcher when clicking outside
document.addEventListener('click', (e) => {
if (e.target.closest('.language-switcher') === null) {
languageSwitchers.forEach((switcher) => {
switcher.dataset.state = 'closed';
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.add('hidden');
});
}
});
})();

View File

@ -37,5 +37,6 @@ one: <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="-1 0 1
cards: <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 6.878V6a2.25 2.25 0 0 1 2.25-2.25h7.5A2.25 2.25 0 0 1 18 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 0 0 4.5 9v.878m13.5-3A2.25 2.25 0 0 1 19.5 9v.878m0 0a2.246 2.246 0 0 0-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0 1 21 12v6a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18v-6c0-.98.626-1.813 1.5-2.122"></path></svg>
copy: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor"><rect x="9" y="9" width="13" height="13" rx="2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></rect><path d="M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
check: <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 0 1-.944.12l-2.75-2.5a.625.625 0 0 1 .841-.925l2.208 2.007l3.849-5.886a.625.625 0 0 1 .865-.181Z" clip-rule="evenodd"/></svg>
check: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 0 1-.944.12l-2.75-2.5a.625.625 0 0 1 .841-.925l2.208 2.007l3.849-5.886a.625.625 0 0 1 .865-.181Z" clip-rule="evenodd"/></svg>
menu: <svg fill="none" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor"><g><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16"></path></g><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12h16"></path><g><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 18h16"></path></g></svg>
globe: <svg viewBox="2 2 16 16" fill="currentColor"><path fill-rule="evenodd" d="M4.083 9h1.946c.089-1.546.383-2.97.837-4.118A6.004 6.004 0 004.083 9zM10 2a8 8 0 100 16 8 8 0 000-16zm0 2c-.076 0-.232.032-.465.262-.238.234-.497.623-.737 1.182-.389.907-.673 2.142-.766 3.556h3.936c-.093-1.414-.377-2.649-.766-3.556-.24-.56-.5-.948-.737-1.182C10.232 4.032 10.076 4 10 4zm3.971 5c-.089-1.546-.383-2.97-.837-4.118A6.004 6.004 0 0115.917 9h-1.946zm-2.003 2H8.032c.093 1.414.377 2.649.766 3.556.24.56.5.948.737 1.182.233.23.389.262.465.262.076 0 .232-.032.465-.262.238-.234.498-.623.737-1.182.389-.907.673-2.142.766-3.556zm1.166 4.118c.454-1.147.748-2.572.837-4.118h1.946a6.004 6.004 0 01-2.783 4.118zm-6.268 0C6.412 13.97 6.118 12.546 6.03 11H4.083a6.004 6.004 0 002.783 4.118z" clip-rule="evenodd"></path></svg>

View File

@ -1,6 +1,6 @@
{{ $class := .Attributes.class | default "" }}
{{ $filename := .Attributes.filename | default "" }}
{{ $lang := .Attributes.lang | default .Type }}
{{- $class := .Attributes.class | default "" -}}
{{- $filename := .Attributes.filename | default "" -}}
{{- $lang := .Attributes.lang | default .Type -}}
<div class="code-block relative mt-6 first:mt-0 group/code">
@ -12,7 +12,7 @@
{{- highlight .Inner $lang .Options -}}
</div>
{{- else -}}
<pre><code id="code-block-{{ .Ordinal }}">{{- .Inner -}}</code></pre>
<pre><code id="code-block-{{ .Ordinal }}">{{ .Inner }}</code></pre>
{{- end -}}
<div class="opacity-0 transition group-hover/code:opacity-100 flex gap-1 absolute m-[11px] right-0 {{ if $filename }}top-8{{ else }}top-0{{ end }}">
<button class="code-copy-btn group/copybtn transition-all active:opacity-50 bg-primary-700/5 border border-black/5 text-gray-600 hover:text-gray-900 rounded-md p-1.5 dark:bg-primary-300/10 dark:border-white/10 dark:text-gray-400 dark:hover:text-gray-50" title="Copy code" data-clipboard-target="#code-block-{{ .Ordinal }}">

View File

@ -1,7 +1,7 @@
<div class="mt-1.5 flex items-center gap-1 overflow-hidden text-sm text-gray-500 dark:text-gray-400 contrast-more:text-current">
{{ range .Ancestors.Reverse }}
{{ if not .IsHome }}
<div class="whitespace-nowrap transition-colors min-w-[24px] overflow-hidden text-ellipsis">
<div class="whitespace-nowrap transition-colors min-w-[24px] overflow-hidden text-ellipsis hover:text-gray-900 dark:hover:text-gray-100">
<a href="{{ .Permalink }}">{{ .LinkTitle }}</a>
</div>
{{ partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"w-3.5 shrink-0\"") }}

View File

@ -2,7 +2,12 @@
<footer class="hextra-footer bg-gray-100 pb-[env(safe-area-inset-bottom)] dark:bg-neutral-900 print:bg-transparent">
{{- if $enableFooterSwitches }}{{ template "footer-switches" }}{{ end -}}
{{- if $enableFooterSwitches }}
<div class="mx-auto flex max-w-[90rem] gap-2 py-2 px-4">
{{ partial "language-switch.html" (dict "context" .) }}
{{ partial "theme-toggle.html" }}
</div>
{{ end -}}
<hr class="dark:border-neutral-800" />
<div class="mx-auto flex max-w-[90rem] justify-center py-12 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)] text-gray-600 dark:text-gray-400 md:justify-start">
<div class="flex w-full flex-col items-center sm:items-start">
@ -16,9 +21,3 @@
</div>
</div>
</footer>
{{- define "footer-switches" -}}
<div class="mx-auto flex max-w-[90rem] gap-2 py-2 px-4">
{{ partial "theme-toggle.html" }}
</div>
{{- end -}}

View File

@ -0,0 +1,30 @@
{{- $page := .context -}}
{{- $grow := .grow -}}
{{- $hideLabel := .hideLabel | default false -}}
{{- if site.IsMultiLingual -}}
<div class="flex justify-items-start {{ if $grow }}grow{{ end }}">
<button title="Change language" data-state="closed" class="language-switcher h-7 rounded-md px-2 text-left text-xs font-medium text-gray-600 transition-colors dark:text-gray-400 hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 grow" type="button" aria-label="Switch Language">
<div class="flex items-center gap-2 capitalize">
{{- partial "utils/icon" (dict "name" "globe" "attributes" "height=12") -}}
{{- if not $hideLabel }}<span>{{ site.Language.LanguageName }}</span>{{ end -}}
</div>
</button>
<ul class="language-options hidden z-20 max-h-64 overflow-auto rounded-md ring-1 ring-black/5 bg-white py-1 text-sm shadow-lg dark:ring-white/20 dark:bg-neutral-800" style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;">
{{ range site.Languages }}
{{ $link := partial "utils/lang-link" (dict "lang" .Lang "context" $page) }}
<li class="flex flex-col">
<a href="{{ $link }}" class="text-gray-800 dark:text-gray-100 hover:bg-primary-50 hover:text-primary-600 relative cursor-pointer whitespace-nowrap py-1.5 transition-colors ltr:pl-3 ltr:pr-9 rtl:pr-3 rtl:pl-9">
{{- .LanguageName -}}
{{- if eq .LanguageName site.Language.LanguageName -}}
<span class="absolute inset-y-0 flex items-center ltr:right-3 rtl:left-3">
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
</span>
{{- end -}}
</a>
</li>
{{ end -}}
</ul>
</div>
{{- end -}}

View File

@ -2,7 +2,7 @@
<div class="pointer-events-none absolute z-[-1] h-full w-full bg-white shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] contrast-more:shadow-[0_0_0_1px_#000] dark:bg-dark dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] contrast-more:dark:shadow-[0_0_0_1px_#fff]"></div>
<nav class="mx-auto flex items-center justify-end gap-2 h-16 px-6 max-w-[90rem]">
<a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="{{ .Site.BaseURL }}">
<a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="{{ .Site.Home.RelPermalink }}">
{{ partial "utils/icon.html" (dict "name" "hextra" "attributes" "height=20") }}
<span class="mx-2 font-extrabold hidden md:inline select-none" title="{{ .Site.Title }}">
{{ .Site.Title }}

View File

@ -1,9 +1,10 @@
{{ $jsTheme := resources.Get "js/theme.js" }}
{{ $jsMenu := resources.Get "js/menu.js" }}
{{ $jsCodeCopy := resources.Get "js/code-copy.js" }}
{{ $jsTabs := resources.Get "js/code-copy.js" }}
{{ $jsTabs := resources.Get "js/tabs.js" }}
{{ $jsLang := resources.Get "js/lang.js" }}
{{ $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs | resources.Concat "js/main.js" }}
{{ $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang | resources.Concat "js/main.js" }}
{{ if hugo.IsProduction }}
{{ $scripts = $scripts | minify | fingerprint }}
{{ end }}

View File

@ -43,7 +43,11 @@
{{ end }}
</div>
{{/* Hide theme switch when sidebar is disabled */}}
{{ template "theme-switch" (dict "class" (cond $disableSidebar "md:hidden" "")) }}
{{ $switchesClass := cond $disableSidebar "md:hidden" "" }}
<div class="{{ $switchesClass }} sticky bottom-0 bg-white dark:bg-dark mx-4 py-4 shadow-[0_-12px_16px_#fff] flex justify-end items-center gap-2 dark:border-neutral-800 dark:shadow-[0_-12px_16px_#111] contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:shadow-none border-t" data-toggle-animation="show">
{{ partial "language-switch" (dict "context" $context "grow" true) }}
{{ partial "theme-toggle" (dict "hideLabel" true) }}
</div>
</aside>
{{- define "sidebar-main" -}}
@ -156,12 +160,3 @@
{{- define "sidebar-separator" -}}
<div class="mt-4 border-t py-4 dark:border-neutral-800 contrast-more:border-neutral-400 dark:contrast-more:border-neutral-400" />
{{- end -}}
{{- define "theme-switch" -}}
{{- $class := .class -}}
<div class="{{ $class }} sticky bottom-0 bg-white dark:bg-dark mx-4 py-4 shadow-[0_-12px_16px_#fff] flex items-center gap-2 dark:border-neutral-800 dark:shadow-[0_-12px_16px_#111] contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:shadow-none border-t" data-toggle-animation="show">
<div class="grow flex flex-col">
{{ partial "theme-toggle" }}
</div>
</div>
{{- end -}}

View File

@ -1,8 +1,11 @@
{{- $hideLabel := .hideLabel | default false -}}
<button title="Change theme" data-theme="light" class="theme-toggle group h-7 rounded-md px-2 text-left text-xs font-medium text-gray-600 transition-colors dark:text-gray-400 hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-primary-100/5 dark:hover:text-gray-50" type="button" aria-label="Toggle Dark Mode">
<div class="flex items-center gap-2 capitalize">
{{- partial "utils/icon.html" (dict "name" "sun" "attributes" "height=12 class=\"group-data-[theme=light]:hidden\"") -}}
<span class="group-data-[theme=light]:hidden">Light</span>
{{- if not $hideLabel }}<span class="group-data-[theme=light]:hidden">Light</span>{{ end -}}
{{- partial "utils/icon.html" (dict "name" "moon" "attributes" "height=12 class=\"group-data-[theme=dark]:hidden\"") -}}
<span class="group-data-[theme=dark]:hidden">Dark</span>
{{- if not $hideLabel }}<span class="group-data-[theme=dark]:hidden">Dark</span>{{ end -}}
</div>
</button>

View File

@ -0,0 +1,25 @@
{{/* Get relative link of a page for given language */}}
{{/* If not found, return the homepage of the language page */}}
{{ $page := .context }}
{{ $lang := .lang }}
{{ $link := false }}
{{ range $page.AllTranslations }}
{{ if eq .Language.Lang $lang }}
{{ $link = .RelPermalink }}
{{ end }}
{{ end }}
{{ if not $link }}
{{ range where $page.Sites ".Language.Lang" $lang }}
{{ $link = .Home.RelPermalink }}
{{ end }}
{{ end }}
{{ if not $link }}
{{ $link = site.Home.RelPermalink }}
{{ end }}
{{ return $link }}