Compare commits
19
Commits
v4.0.0-beta.13
...
v4.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03bf8b0c3f | ||
|
|
e294e4ed70 | ||
|
|
d3eacc9daf | ||
|
|
c0f57bab7a | ||
|
|
752fd5805f | ||
|
|
0fc8264927 | ||
|
|
55340540b5 | ||
|
|
df9253a8e9 | ||
|
|
96202fea2b | ||
|
|
e55168d873 | ||
|
|
e0418efe2f | ||
|
|
08a8b5d767 | ||
|
|
9ddd11797b | ||
|
|
6f3ab760f9 | ||
|
|
4f2764e0a5 | ||
|
|
37f6a77a1a | ||
|
|
8e3687431f | ||
|
|
cfcdb20d17 | ||
|
|
0c941ebb04 |
+1
-1
@@ -10,7 +10,7 @@
|
||||
main() {
|
||||
DART_SASS_VERSION=1.97.1
|
||||
GO_VERSION=1.25.5
|
||||
HUGO_VERSION=0.157.0
|
||||
HUGO_VERSION=0.160.1
|
||||
NODE_VERSION=24.12.0
|
||||
|
||||
export TZ=Europe/Oslo
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
overflow-y: scroll;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -17,25 +18,4 @@ body {
|
||||
-ms-text-autospace: ideograph-alpha;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* scrollbar styles for Firefox */
|
||||
* {
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: var(--scrollbar-thumb) transparent;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* scrollbar styles for Chromium */
|
||||
::-webkit-scrollbar {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-thumb);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
/**/
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
@import "../../breakpoints";
|
||||
@import "../../mixins";
|
||||
|
||||
.artalk-container {
|
||||
@include card-style;
|
||||
padding: 5px;
|
||||
|
||||
@include respond(md) {
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
}
|
||||
|
||||
.artalk {
|
||||
--at-color-bg: var(--card-background);
|
||||
--at-color-font: var(--card-text-color-main);
|
||||
--at-color-sub: var(--card-text-color-secondary);
|
||||
--at-color-main: var(--accent-color);
|
||||
--at-color-border: var(--card-separator-color);
|
||||
--at-border-radius: var(--card-border-radius);
|
||||
--at-color-link: var(--accent-color);
|
||||
}
|
||||
|
||||
.artalk a {
|
||||
color: var(--at-color-link);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-color-darker);
|
||||
}
|
||||
}
|
||||
|
||||
.artalk .atk-content a,
|
||||
.artalk .atk-editor-plug-preview a {
|
||||
color: var(--accent-color);
|
||||
box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-color-darker);
|
||||
box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset;
|
||||
}
|
||||
}
|
||||
|
||||
[data-scheme="dark"] .artalk {
|
||||
--at-color-main: var(--accent-color);
|
||||
--at-color-link: var(--accent-color);
|
||||
--at-color-bg: var(--card-background);
|
||||
--at-color-bg-transl: rgba(66, 66, 66, 0.95);
|
||||
--at-color-bg-grey: rgba(255, 255, 255, 0.08);
|
||||
--at-color-bg-grey-transl: rgba(255, 255, 255, 0.06);
|
||||
--at-color-bg-light: rgba(255, 255, 255, 0.08);
|
||||
--at-color-border: var(--card-separator-color);
|
||||
--at-color-font: var(--card-text-color-main);
|
||||
--at-color-deep: var(--card-text-color-main);
|
||||
--at-color-sub: var(--card-text-color-secondary);
|
||||
--at-color-meta: var(--card-text-color-secondary);
|
||||
--at-color-grey: var(--card-text-color-secondary);
|
||||
--at-color-gradient: linear-gradient(
|
||||
180deg,
|
||||
transparent,
|
||||
var(--card-background)
|
||||
);
|
||||
}
|
||||
|
||||
.artalk code {
|
||||
font-family: var(--code-font-family);
|
||||
}
|
||||
|
||||
.artalk .atk-avatar img {
|
||||
border-radius: var(--card-border-radius);
|
||||
}
|
||||
|
||||
[data-scheme="dark"] .artalk .atk-avatar img {
|
||||
filter: brightness(0.75);
|
||||
}
|
||||
|
||||
[data-scheme="dark"] .artalk .atk-send-btn {
|
||||
color: var(--accent-color-text);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
padding: var(--container-padding);
|
||||
pointer-events: none;
|
||||
|
||||
&[aria-hidden="true"] {
|
||||
display: none;
|
||||
@@ -21,6 +22,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
pointer-events: auto;
|
||||
|
||||
@include respond(md) {
|
||||
flex-direction: row;
|
||||
|
||||
@@ -52,12 +52,18 @@
|
||||
|
||||
/* LineNumbersTable */
|
||||
.chroma .lnt {
|
||||
margin-right: 0.4em;
|
||||
margin-right: 0;
|
||||
user-select: none;
|
||||
padding: 0 0.4em 0 0.4em;
|
||||
color: #7f7f7f;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Optimise code block selection experience */
|
||||
span.line {
|
||||
margin-left: 0.4em;
|
||||
}
|
||||
|
||||
/* LineNumbers */
|
||||
.chroma .ln {
|
||||
margin-right: 0.4em;
|
||||
|
||||
@@ -133,10 +133,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--card-text-color-main);
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--card-separator-color);
|
||||
}
|
||||
}
|
||||
|
||||
.widget--toc {
|
||||
|
||||
@@ -44,24 +44,72 @@
|
||||
}
|
||||
}
|
||||
|
||||
@property --left-edge-fade-size {
|
||||
syntax: "<length>";
|
||||
inherits: false;
|
||||
initial-value: 0px;
|
||||
}
|
||||
|
||||
@property --right-edge-fade-size {
|
||||
syntax: "<length>";
|
||||
inherits: false;
|
||||
initial-value: 64px;
|
||||
}
|
||||
|
||||
.subsection-list {
|
||||
--shadow-overlap-x: calc(var(--shadow-l2-blur) + 4px);
|
||||
--shadow-overlap-y: calc(var(--shadow-l2-blur) + var(--shadow-l2-y) + 4px);
|
||||
--edge-fade-size: 64px;
|
||||
--left-edge-fade-size: 0px;
|
||||
--right-edge-fade-size: var(--edge-fade-size);
|
||||
|
||||
overflow-x: auto;
|
||||
|
||||
/// Limit the horizontal overlap to the container padding to prevent page overflow on mobile.
|
||||
/// On desktop, it still allows the full shadow to be visible.
|
||||
--overlap-x: min(var(--shadow-overlap-x), var(--container-padding));
|
||||
|
||||
/// These values are computed from the shadow of the article-list--tile (which uses shadow-l1 and shadow-l2)
|
||||
/// so that the shadow is not cut off
|
||||
margin-left: calc(var(--shadow-overlap-x) * -1);
|
||||
margin-right: calc(var(--shadow-overlap-x) * -1);
|
||||
/// so that the shadow is not cut off, while staying within the viewport on small screens.
|
||||
/// Clamp both sides to prevent page-level horizontal overflow.
|
||||
margin-left: calc(var(--overlap-x) * -1);
|
||||
margin-right: calc(var(--overlap-x) * -1);
|
||||
margin-bottom: calc(var(--shadow-overlap-y) * -1);
|
||||
|
||||
|
||||
/// Smooth fade on both horizontal edges to avoid hard cuts where shadows are clamped.
|
||||
--subsection-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
rgb(0 0 0 / 0.06) calc(var(--left-edge-fade-size) * 0.12),
|
||||
rgb(0 0 0 / 0.2) calc(var(--left-edge-fade-size) * 0.3),
|
||||
rgb(0 0 0 / 0.45) calc(var(--left-edge-fade-size) * 0.52),
|
||||
rgb(0 0 0 / 0.72) calc(var(--left-edge-fade-size) * 0.76),
|
||||
black var(--left-edge-fade-size),
|
||||
black calc(100% - var(--right-edge-fade-size)),
|
||||
rgb(0 0 0 / 0.72) calc(100% - (var(--right-edge-fade-size) * 0.76)),
|
||||
rgb(0 0 0 / 0.45) calc(100% - (var(--right-edge-fade-size) * 0.52)),
|
||||
rgb(0 0 0 / 0.2) calc(100% - (var(--right-edge-fade-size) * 0.3)),
|
||||
rgb(0 0 0 / 0.06) calc(100% - (var(--right-edge-fade-size) * 0.12)),
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: var(--subsection-mask-image);
|
||||
mask-image: var(--subsection-mask-image);
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin-left: calc(var(--overlap-x) * -1);
|
||||
margin-right: calc(var(--overlap-x) * -1);
|
||||
}
|
||||
|
||||
.article-list--tile {
|
||||
display: flex;
|
||||
padding: var(--spacing-xs) var(--shadow-overlap-x) var(--shadow-overlap-y) var(--shadow-overlap-x);
|
||||
padding: var(--spacing-xs) var(--overlap-x) var(--shadow-overlap-y) var(--overlap-x);
|
||||
width: max-content;
|
||||
gap: var(--spacing-lg);
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding: var(--spacing-xs) var(--overlap-x) var(--shadow-overlap-y) var(--overlap-x);
|
||||
}
|
||||
|
||||
article {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -75,4 +123,35 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only apply scroll-driven edge fades in browsers that support scroll timelines.
|
||||
// Browsers without support (e.g. Firefox) fall back to the static right-edge fade
|
||||
// defined by the explicit CSS variable declarations above.
|
||||
@supports (animation-timeline: scroll(self inline)) {
|
||||
.subsection-list {
|
||||
animation: subsection-left-fade linear both, subsection-right-fade linear both;
|
||||
animation-timeline: scroll(self inline);
|
||||
animation-range: 1px 24px, 0% 100%;
|
||||
animation-timing-function: ease-out, linear;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes subsection-left-fade {
|
||||
from {
|
||||
--left-edge-fade-size: 0px;
|
||||
}
|
||||
to {
|
||||
--left-edge-fade-size: var(--edge-fade-size);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes subsection-right-fade {
|
||||
0%,
|
||||
92% {
|
||||
--right-edge-fade-size: var(--edge-fade-size);
|
||||
}
|
||||
100% {
|
||||
--right-edge-fade-size: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,44 @@
|
||||
@include card-style;
|
||||
overflow: hidden;
|
||||
|
||||
.hide-on-mobile,
|
||||
.pagination-link {
|
||||
display: none;
|
||||
|
||||
@include respond(md) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.page-link {
|
||||
padding: 16px 0;
|
||||
padding: var(--spacing-sm) 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font: inherit;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
min-width: 48px;
|
||||
min-width: 36px;
|
||||
color: var(--card-text-color-secondary);
|
||||
text-decoration: none;
|
||||
|
||||
@include respond(md) {
|
||||
min-width: 48px;
|
||||
padding: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
flex: 0 0 48px;
|
||||
padding: 16px;
|
||||
flex: 0 0 var(--spacing-3xl);
|
||||
padding: var(--spacing-sm);
|
||||
|
||||
@include respond(md) {
|
||||
flex: 0 0 48px;
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.current):not(.disabled) {
|
||||
@@ -44,5 +66,194 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&.pagination-jump-trigger {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pagination-jump-dialog {
|
||||
border: none;
|
||||
@include card-style;
|
||||
box-shadow: var(--shadow-l2);
|
||||
color: var(--body-text-color);
|
||||
margin: auto; // Center dialog
|
||||
width: calc(100% - (var(--container-padding) * 2));
|
||||
max-width: 320px;
|
||||
padding: var(--card-padding);
|
||||
|
||||
&::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0;
|
||||
animation: backdrop-fade-in 0.3s ease forwards;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
animation: scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
||||
}
|
||||
|
||||
&.closing {
|
||||
animation: scale-down 0.2s ease forwards;
|
||||
|
||||
&::backdrop {
|
||||
animation: backdrop-fade-out 0.2s ease forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-jump-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
color: var(--card-text-color-main);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.3rem;
|
||||
color: var(--card-text-color-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-jump-input-group {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: stretch;
|
||||
|
||||
input[type="number"] {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 0 var(--spacing-md);
|
||||
height: 44px; // Explicit height for perfect alignment
|
||||
border: 1.5px solid var(--card-separator-color);
|
||||
border-radius: 6px;
|
||||
background-color: var(--body-background);
|
||||
color: var(--card-text-color-main);
|
||||
font-size: 1.6rem;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
/* Hide spinner */
|
||||
-moz-appearance: textfield;
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 20%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 var(--spacing-xl);
|
||||
height: 44px; // Match input height
|
||||
background-color: var(--accent-color);
|
||||
color: var(--accent-color-text);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--accent-color-darker);
|
||||
box-shadow: var(--shadow-l1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-jump-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-sm);
|
||||
font-size: 1.3rem;
|
||||
color: var(--card-text-color-tertiary);
|
||||
margin-top: calc(var(--spacing-xs) * -1);
|
||||
|
||||
kbd {
|
||||
background-color: var(--body-background);
|
||||
border: 1px solid var(--card-separator-color);
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
font-family: var(--base-font-family);
|
||||
font-size: 1.1rem;
|
||||
box-shadow: 0 2px 0 var(--card-separator-color);
|
||||
color: var(--card-text-color-main);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-down {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backdrop-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backdrop-fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,12 @@
|
||||
|
||||
--section-separation: 40px;
|
||||
|
||||
--scrollbar-thumb: hsl(0, 0%, 85%);
|
||||
--scrollbar-track: var(--body-background);
|
||||
|
||||
&[data-scheme="dark"] {
|
||||
--body-background: #303030;
|
||||
--accent-color: #ecf0f1;
|
||||
--accent-color-darker: #bdc3c7;
|
||||
--accent-color-text: #000;
|
||||
--body-text-color: rgba(255, 255, 255, 0.7);
|
||||
--scrollbar-thumb: hsl(0, 0%, 40%);
|
||||
--scrollbar-track: var(--body-background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as params from '@params';
|
||||
|
||||
export function setupCodeCopy() {
|
||||
/**
|
||||
* Add copy button to code block
|
||||
*/
|
||||
const highlights = document.querySelectorAll('.article-content div.highlight');
|
||||
const copyText = params.codeblock.copy,
|
||||
copiedText = params.codeblock.copied;
|
||||
|
||||
if (!navigator.clipboard) {
|
||||
/// Clipboard API is only supported in secure contexts (HTTPS)
|
||||
console.warn('Clipboard API not supported, copy button will not work.');
|
||||
return;
|
||||
}
|
||||
|
||||
highlights.forEach(highlight => {
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.innerHTML = copyText;
|
||||
copyButton.classList.add('copyCodeButton');
|
||||
highlight.appendChild(copyButton);
|
||||
|
||||
const codeBlock = highlight.querySelector('code[data-lang]');
|
||||
if (!codeBlock) return;
|
||||
|
||||
copyButton.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(codeBlock.textContent)
|
||||
.then(() => {
|
||||
copyButton.textContent = copiedText;
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = copyText;
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err)
|
||||
console.log('Something went wrong', err);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Consent-gated comments bootstrap.
|
||||
*
|
||||
* This module controls whether third-party comments are rendered based on
|
||||
* functional cookie consent, and injects template scripts in sequence to
|
||||
* avoid eager or duplicate script execution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Consent event payload used by the comments gate.
|
||||
* It mirrors cookie consent state shape but keeps optional fields for safety.
|
||||
*/
|
||||
interface CommentsConsentState {
|
||||
necessary?: boolean;
|
||||
analytics?: boolean;
|
||||
functional?: boolean;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve functional consent with layered fallback:
|
||||
* 1) explicit event detail, 2) CookieConsent public API, 3) dataset mirror.
|
||||
*/
|
||||
const hasFunctionalConsent = (consentDetail?: CommentsConsentState | null): boolean => {
|
||||
const cookieConsent = (window as Window & {
|
||||
cookieConsent?: {
|
||||
hasConsent?: (category: 'necessary' | 'analytics' | 'functional') => boolean;
|
||||
};
|
||||
}).cookieConsent;
|
||||
|
||||
return (
|
||||
consentDetail?.functional ??
|
||||
cookieConsent?.hasConsent?.('functional') ??
|
||||
document.documentElement.dataset.consentFunctional === 'true'
|
||||
);
|
||||
};
|
||||
|
||||
interface DeferredScript {
|
||||
placeholder: Comment;
|
||||
script: HTMLScriptElement;
|
||||
}
|
||||
|
||||
const prepareDeferredScripts = (fragment: DocumentFragment): DeferredScript[] => {
|
||||
return Array.from(fragment.querySelectorAll('script')).map(script => {
|
||||
const placeholder = document.createComment('comments-script-placeholder');
|
||||
script.replaceWith(placeholder);
|
||||
return { placeholder, script };
|
||||
});
|
||||
};
|
||||
|
||||
const activateDeferredScripts = async (scripts: DeferredScript[]): Promise<void> => {
|
||||
// Inject scripts sequentially so third-party embeds can rely on execution order.
|
||||
for (const { placeholder, script } of scripts) {
|
||||
if (!placeholder.parentNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newScript = document.createElement('script');
|
||||
Array.from(script.attributes).forEach(attr => {
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
|
||||
if (script.textContent) {
|
||||
newScript.text = script.textContent;
|
||||
}
|
||||
|
||||
let done: Promise<void> = Promise.resolve();
|
||||
if (newScript.src) {
|
||||
done = new Promise<void>(resolve => {
|
||||
newScript.onload = newScript.onerror = () => resolve();
|
||||
});
|
||||
}
|
||||
|
||||
placeholder.replaceWith(newScript);
|
||||
await done;
|
||||
}
|
||||
};
|
||||
|
||||
const initCommentsConsent = (): void => {
|
||||
// Main entry: gate comments rendering by functional cookie consent.
|
||||
const placeholder = document.getElementById('comments-consent-placeholder') as HTMLElement | null;
|
||||
const container = document.getElementById('comments-container') as HTMLElement | null;
|
||||
const template = document.getElementById('comments-template') as HTMLTemplateElement | null;
|
||||
|
||||
if (!placeholder || !container || !template) {
|
||||
return;
|
||||
}
|
||||
|
||||
let commentsLoaded = false;
|
||||
let commentsLoading = false;
|
||||
|
||||
const showComments = async (): Promise<void> => {
|
||||
placeholder.style.display = 'none';
|
||||
container.style.display = 'block';
|
||||
|
||||
if (commentsLoaded || commentsLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent duplicate template hydration while scripts are still loading.
|
||||
commentsLoading = true;
|
||||
|
||||
try {
|
||||
const clone = template.content.cloneNode(true) as DocumentFragment;
|
||||
const scripts = prepareDeferredScripts(clone);
|
||||
|
||||
container.appendChild(clone);
|
||||
await activateDeferredScripts(scripts);
|
||||
commentsLoaded = true;
|
||||
} finally {
|
||||
commentsLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const hideComments = (): void => {
|
||||
placeholder.style.display = 'block';
|
||||
container.style.display = 'none';
|
||||
};
|
||||
|
||||
window.addEventListener('onCookieConsentChange', (event: Event) => {
|
||||
// Cross-module contract: this event is dispatched by cookies.ts.
|
||||
const customEvent = event as CustomEvent<CommentsConsentState | null>;
|
||||
if (hasFunctionalConsent(customEvent.detail)) {
|
||||
showComments();
|
||||
} else {
|
||||
hideComments();
|
||||
}
|
||||
});
|
||||
|
||||
if (hasFunctionalConsent()) {
|
||||
showComments();
|
||||
} else {
|
||||
hideComments();
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initCommentsConsent, { once: true });
|
||||
} else {
|
||||
initCommentsConsent();
|
||||
}
|
||||
@@ -175,6 +175,7 @@ class CookieConsent {
|
||||
}
|
||||
|
||||
private dispatchConsentEvent(): void {
|
||||
// Cross-module event consumed by consent-gated features (for example commentsConsent.ts).
|
||||
const event = new CustomEvent('onCookieConsentChange', {
|
||||
detail: this.state
|
||||
});
|
||||
|
||||
+4
-31
@@ -10,6 +10,8 @@ import createElement from './createElement';
|
||||
import StackColorScheme from './colorScheme';
|
||||
import { setupScrollspy } from './scrollspy';
|
||||
import { setupSmoothAnchors } from './smoothAnchors';
|
||||
import { setupPaginationJump } from './pagination';
|
||||
import { setupCodeCopy } from './code-copy';
|
||||
|
||||
let Stack = {
|
||||
init: () => {
|
||||
@@ -22,39 +24,10 @@ let Stack = {
|
||||
if (articleContent) {
|
||||
setupSmoothAnchors();
|
||||
setupScrollspy();
|
||||
setupCodeCopy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add copy button to code block
|
||||
*/
|
||||
const highlights = document.querySelectorAll('.article-content div.highlight');
|
||||
const copyText = `Copy`,
|
||||
copiedText = `Copied!`;
|
||||
|
||||
highlights.forEach(highlight => {
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.innerHTML = copyText;
|
||||
copyButton.classList.add('copyCodeButton');
|
||||
highlight.appendChild(copyButton);
|
||||
|
||||
const codeBlock = highlight.querySelector('code[data-lang]');
|
||||
if (!codeBlock) return;
|
||||
|
||||
copyButton.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(codeBlock.textContent)
|
||||
.then(() => {
|
||||
copyButton.textContent = copiedText;
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = copyText;
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err)
|
||||
console.log('Something went wrong', err);
|
||||
});
|
||||
});
|
||||
});
|
||||
setupPaginationJump();
|
||||
|
||||
new StackColorScheme(document.getElementById('dark-mode-toggle')!);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
export function setupPaginationJump() {
|
||||
const triggers = document.querySelectorAll<HTMLButtonElement>('.pagination-jump-trigger');
|
||||
const dialog = document.getElementById('pagination-jump-dialog') as HTMLDialogElement;
|
||||
|
||||
if (!dialog || triggers.length === 0) return;
|
||||
|
||||
const nav = document.querySelector('.pagination') as HTMLElement;
|
||||
const input = document.getElementById('pagination-jump-input') as HTMLInputElement;
|
||||
const form = dialog.querySelector('.pagination-jump-form') as HTMLFormElement;
|
||||
const supportsDialog = typeof dialog.showModal === 'function' && typeof dialog.close === 'function';
|
||||
let lastFocusedElement: HTMLElement | null = null;
|
||||
|
||||
if (!supportsDialog || !nav || !input || !form) return;
|
||||
|
||||
const closeDialog = (): void => {
|
||||
if (dialog.classList.contains('closing')) return;
|
||||
dialog.classList.add('closing');
|
||||
dialog.addEventListener(
|
||||
'animationend',
|
||||
() => {
|
||||
dialog.classList.remove('closing');
|
||||
dialog.close();
|
||||
if (lastFocusedElement?.isConnected) {
|
||||
lastFocusedElement.focus();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
};
|
||||
|
||||
// Open dialog when triggers are clicked
|
||||
triggers.forEach((trigger) => {
|
||||
trigger.addEventListener('click', () => {
|
||||
const activeElement = document.activeElement;
|
||||
lastFocusedElement = activeElement instanceof HTMLElement ? activeElement : trigger;
|
||||
dialog.showModal();
|
||||
input.value = '';
|
||||
input.focus();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle ESC key closing the dialog
|
||||
dialog.addEventListener('cancel', (e) => {
|
||||
e.preventDefault();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
// Close dialog when clicking backdrop
|
||||
dialog.addEventListener('click', (e) => {
|
||||
const rect = dialog.getBoundingClientRect();
|
||||
const isInDialog =
|
||||
rect.top <= e.clientY &&
|
||||
e.clientY <= rect.top + rect.height &&
|
||||
rect.left <= e.clientX &&
|
||||
e.clientX <= rect.left + rect.width;
|
||||
if (!isInDialog) {
|
||||
closeDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// Allow Enter key to trigger validation and submit
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (form.reportValidity()) {
|
||||
form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPage = parseInt(input.value);
|
||||
if (isNaN(targetPage) || targetPage < 1) return;
|
||||
|
||||
const totalPages = parseInt(nav.dataset.total || '0');
|
||||
if (targetPage > totalPages) return;
|
||||
|
||||
const firstUrl = nav.dataset.firstUrl || '';
|
||||
const formatUrl = nav.dataset.formatUrl || '';
|
||||
|
||||
let targetUrl = '';
|
||||
if (targetPage === 1) {
|
||||
targetUrl = firstUrl;
|
||||
} else {
|
||||
// formatUrl is the URL for page 2. E.g., /tags/page/2/ or /page/2/
|
||||
// Replace the '2' before the trailing slash or .html with the target page number
|
||||
targetUrl = formatUrl.replace(/2([^\d]*)$/, `${targetPage}$1`);
|
||||
}
|
||||
|
||||
if (targetUrl) {
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
});
|
||||
}
|
||||
@@ -77,6 +77,9 @@ SortBy = "default"
|
||||
[imageProcessing]
|
||||
autoOrient = false
|
||||
|
||||
[imageProcessing.external]
|
||||
timeout = "5s"
|
||||
|
||||
[imageProcessing.content]
|
||||
widths = [800, 1600, 2400]
|
||||
enabled = true
|
||||
@@ -98,6 +101,10 @@ SortBy = "default"
|
||||
enabled = true
|
||||
provider = "disqus"
|
||||
|
||||
[comments.artalk]
|
||||
serverUrl = ""
|
||||
site = ""
|
||||
|
||||
[comments.disqusjs]
|
||||
shortname = ""
|
||||
apiUrl = ""
|
||||
|
||||
@@ -13,3 +13,7 @@ Cactus = [
|
||||
Style = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.css"
|
||||
Core = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.esm.min.js"
|
||||
Lightbox = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe-lightbox.esm.min.js"
|
||||
|
||||
[Artalk]
|
||||
Style = "https://cdn.jsdelivr.net/npm/artalk@2.9.1/dist/Artalk.css"
|
||||
Script = "https://cdn.jsdelivr.net/npm/artalk@2.9.1/+esm"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
baseURL = "https://demo.stack.cai.im/"
|
||||
languageCode = "en-us"
|
||||
locale = "en-us"
|
||||
title = "Hugo Theme Stack"
|
||||
# Theme i18n support
|
||||
# See i18n folder for available languages
|
||||
@@ -12,7 +12,7 @@ hasCJKLanguage = false
|
||||
path = "github.com/CaiJimmy/hugo-theme-stack/v3"
|
||||
|
||||
[pagination]
|
||||
pagerSize = 5
|
||||
pagerSize = 3
|
||||
|
||||
[permalinks]
|
||||
post = "/p/:slug/"
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
[en]
|
||||
languageName = "English"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 1
|
||||
label = "English"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 1
|
||||
|
||||
[en.params.sidebar]
|
||||
subtitle = "Card-style Hugo theme designed for bloggers"
|
||||
|
||||
[zh]
|
||||
languageName = "简体中文"
|
||||
title = "Hugo 主题 Stack"
|
||||
weight = 2
|
||||
label = "简体中文"
|
||||
title = "Hugo 主题 Stack"
|
||||
weight = 2
|
||||
|
||||
[zh.params.sidebar]
|
||||
subtitle = "为博客设计的卡片式 Hugo 主题"
|
||||
|
||||
[zh-hant-tw]
|
||||
languageName = "正體中文"
|
||||
title = "Hugo 主題 Stack"
|
||||
weight = 3
|
||||
label = "正體中文"
|
||||
title = "Hugo 主題 Stack"
|
||||
weight = 3
|
||||
|
||||
[zh-hant-tw.params.sidebar]
|
||||
subtitle = "為博客設計的卡片式 Hugo 主題"
|
||||
|
||||
[ja]
|
||||
languageName = "日本語"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 4
|
||||
label = "日本語"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 4
|
||||
|
||||
[ja.params.sidebar]
|
||||
subtitle = "ブログ用のカードスタイルの Hugo テーマ"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 01
|
||||
date: 2000-01-01
|
||||
description: Test post 01 for pagination behavior.
|
||||
slug: pagination-test-01
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **01**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 02
|
||||
date: 2000-01-02
|
||||
description: Test post 02 for pagination behavior.
|
||||
slug: pagination-test-02
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **02**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 03
|
||||
date: 2000-01-03
|
||||
description: Test post 03 for pagination behavior.
|
||||
slug: pagination-test-03
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **03**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 04
|
||||
date: 2000-01-04
|
||||
description: Test post 04 for pagination behavior.
|
||||
slug: pagination-test-04
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **04**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 05
|
||||
date: 2000-01-05
|
||||
description: Test post 05 for pagination behavior.
|
||||
slug: pagination-test-05
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **05**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 06
|
||||
date: 2000-01-06
|
||||
description: Test post 06 for pagination behavior.
|
||||
slug: pagination-test-06
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **06**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 07
|
||||
date: 2000-01-07
|
||||
description: Test post 07 for pagination behavior.
|
||||
slug: pagination-test-07
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **07**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 08
|
||||
date: 2000-01-08
|
||||
description: Test post 08 for pagination behavior.
|
||||
slug: pagination-test-08
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **08**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 09
|
||||
date: 2000-01-09
|
||||
description: Test post 09 for pagination behavior.
|
||||
slug: pagination-test-09
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **09**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 10
|
||||
date: 2000-01-10
|
||||
description: Test post 10 for pagination behavior.
|
||||
slug: pagination-test-10
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **10**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 11
|
||||
date: 2000-01-11
|
||||
description: Test post 11 for pagination behavior.
|
||||
slug: pagination-test-11
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **11**.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Pagination Test 12
|
||||
date: 2000-01-12
|
||||
description: Test post 12 for pagination behavior.
|
||||
slug: pagination-test-12
|
||||
tags:
|
||||
- pagination
|
||||
- test
|
||||
categories:
|
||||
- Testing
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is pagination test post **12**.
|
||||
@@ -29,6 +29,10 @@ darkMode = "الوضع الداكن"
|
||||
warning = "تحذير"
|
||||
caution = "تنبيه"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "نسخ"
|
||||
copied = "تم النسخ!"
|
||||
|
||||
[notFound]
|
||||
title = "غير موجود"
|
||||
subtitle = "تعذر العثور على الصفحة المطلوبة."
|
||||
|
||||
@@ -30,6 +30,10 @@ darkMode = "Цёмны рэжым"
|
||||
warning = "Папярэджанне"
|
||||
caution = "Асцярога"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Капіяваць"
|
||||
copied = "Скапіявана!"
|
||||
|
||||
[notFound]
|
||||
title = "Не знойдзена"
|
||||
subtitle = "Запытваемая старонка не існуе"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Тъмен Режим"
|
||||
warning = "Предупреждение"
|
||||
caution = "Внимание"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Копирай"
|
||||
copied = "Копирано!"
|
||||
|
||||
[notFound]
|
||||
title = "Не е намерено"
|
||||
subtitle = "Страницата която търсите не е открита"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "ডার্ক মোড"
|
||||
warning = "সতর্কতা"
|
||||
caution = "সাবধানতা"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "কপি"
|
||||
copied = "কপি হয়েছে!"
|
||||
|
||||
[notFound]
|
||||
title = "পাওয়া যায়নি"
|
||||
subtitle = "এই পাতাটি বিদ্যমান নেই"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Mode fosc"
|
||||
warning = "Avís"
|
||||
caution = "Precaució"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copia"
|
||||
copied = "Copiat!"
|
||||
|
||||
[notFound]
|
||||
title = "No Trobat"
|
||||
subtitle = "Aquesta pàgina no existeix"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Tmavý režim"
|
||||
warning = "Varování"
|
||||
caution = "Upozornění"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopírovat"
|
||||
copied = "Zkopírováno!"
|
||||
|
||||
[notFound]
|
||||
title = "Nenalezeno"
|
||||
subtitle = "Tato stránka neexistuje"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Dunkler Modus"
|
||||
warning = "Warnung"
|
||||
caution = "Vorsicht"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopieren"
|
||||
copied = "Kopiert!"
|
||||
|
||||
[notFound]
|
||||
title = "Seite nicht gefunden"
|
||||
subtitle = "Diese Seite existiert nicht"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Σκοτεινό θέμα"
|
||||
warning = "Προειδοποίηση"
|
||||
caution = "Προσοχή"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Αντιγραφή"
|
||||
copied = "Αντιγράφηκε!"
|
||||
|
||||
[notFound]
|
||||
title = "Δε βρέθηκε"
|
||||
subtitle = "Η σελίδα δε βρέθηκε."
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Dark Mode"
|
||||
warning = "Warning"
|
||||
caution = "Caution"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copy"
|
||||
copied = "Copied!"
|
||||
|
||||
[notFound]
|
||||
title = "Not Found"
|
||||
subtitle = "This page does not exist"
|
||||
@@ -76,3 +80,8 @@ darkMode = "Dark Mode"
|
||||
[cookies.functional]
|
||||
title = "Functional"
|
||||
text = "These cookies enable features like comments and embedded content."
|
||||
|
||||
[pagination]
|
||||
jumpToPage = "Jump to page"
|
||||
jump = "Go"
|
||||
pressEnter = "Press Enter to jump"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Modo oscuro"
|
||||
warning = "Advertencia"
|
||||
caution = "Precaución"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copiar"
|
||||
copied = "¡Copiado!"
|
||||
|
||||
[notFound]
|
||||
title = "No Encontrado"
|
||||
subtitle = "Esta página no existe"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "حالت شب"
|
||||
warning = "هشدار"
|
||||
caution = "احتیاط"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "کپی"
|
||||
copied = "کپی شد!"
|
||||
|
||||
[notFound]
|
||||
title = "یافت نشد"
|
||||
subtitle = "این صحه وجود ندارد"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Mode sombre"
|
||||
warning = "Avertissement"
|
||||
caution = "Attention"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copier"
|
||||
copied = "Copié !"
|
||||
|
||||
[notFound]
|
||||
title = "Page non trouvée"
|
||||
subtitle = "Cette page n'existe pas."
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "डार्क मोड"
|
||||
warning = "चेतावनी"
|
||||
caution = "सावधानी"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "कॉपी"
|
||||
copied = "कॉपी हो गया!"
|
||||
|
||||
[notFound]
|
||||
title = "404 नहीं मिला।"
|
||||
subtitle = "यह पृष्ठ मौजूद नहीं है।"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Sötét Mód"
|
||||
warning = "Figyelmeztetés"
|
||||
caution = "Óvatosság"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Másolás"
|
||||
copied = "Másolva!"
|
||||
|
||||
[notFound]
|
||||
title = "Nem található"
|
||||
subtitle = "Ez az oldal nem létezik"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Mode Gelap"
|
||||
warning = "Peringatan"
|
||||
caution = "Perhatian"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Salin"
|
||||
copied = "Disalin!"
|
||||
|
||||
[notFound]
|
||||
title = "Tidak ditemukan"
|
||||
subtitle = "Halaman yang Anda akses tidak ditemukan."
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Modalità scura"
|
||||
warning = "Avvertenza"
|
||||
caution = "Attenzione"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copia"
|
||||
copied = "Copiato!"
|
||||
|
||||
[notFound]
|
||||
title = "Non trovato"
|
||||
subtitle = "Questa pagina non esiste."
|
||||
|
||||
@@ -20,6 +20,10 @@ darkMode = "ダークモード"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "コピー"
|
||||
copied = "コピーしました!"
|
||||
|
||||
[notFound]
|
||||
title = "404 Not Found"
|
||||
subtitle = "指定されたページは存在しません。"
|
||||
@@ -67,3 +71,8 @@ darkMode = "ダークモード"
|
||||
[cookies.functional]
|
||||
title = "機能的"
|
||||
text = "これらのCookieは、コメントや埋め込みコンテンツなどの機能を有効にします."
|
||||
|
||||
[pagination]
|
||||
jumpToPage = "ページにジャンプ"
|
||||
jump = "ジャンプ"
|
||||
pressEnter = "Enter キーでジャンプ"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "다크 모드"
|
||||
warning = "경고"
|
||||
caution = "주의"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "복사"
|
||||
copied = "복사됨!"
|
||||
|
||||
[notFound]
|
||||
title = "찾을 수 없음"
|
||||
subtitle = "페이지를 찾을 수 없습니다."
|
||||
|
||||
@@ -24,6 +24,10 @@ darkMode = "Donkere modus"
|
||||
warning = "Waarschuwing"
|
||||
caution = "Let op"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopiëren"
|
||||
copied = "Gekopieerd!"
|
||||
|
||||
[notFound]
|
||||
title = "Niet gevonden"
|
||||
subtitle = "Deze pagina bestaat niet."
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Mòde fosc"
|
||||
warning = "Avertiment"
|
||||
caution = "Atencion"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copiar"
|
||||
copied = "Copiat!"
|
||||
|
||||
[notFound]
|
||||
title = "Non trobat"
|
||||
subtitle = "Aquesta pagina existís pas"
|
||||
|
||||
@@ -35,6 +35,10 @@ darkMode = "Tryb ciemny"
|
||||
warning = "Ostrzeżenie"
|
||||
caution = "Uwaga"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopiuj"
|
||||
copied = "Skopiowano!"
|
||||
|
||||
[notFound]
|
||||
title = "Nie znaleziono"
|
||||
subtitle = "Ta strona nie istnieje"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Modo Escuro"
|
||||
warning = "Aviso"
|
||||
caution = "Cuidado"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copiar"
|
||||
copied = "Copiado!"
|
||||
|
||||
[notFound]
|
||||
title = "Não Encontrado"
|
||||
subtitle = "Esta página não existe."
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Modo Escuro"
|
||||
warning = "Aviso"
|
||||
caution = "Cuidado"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Copiar"
|
||||
copied = "Copiado!"
|
||||
|
||||
[notFound]
|
||||
title = "Não Encontrado"
|
||||
subtitle = "Esta página não existe."
|
||||
|
||||
@@ -30,6 +30,10 @@ darkMode = "Тёмный режим"
|
||||
warning = "Предупреждение"
|
||||
caution = "Осторожно"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Копировать"
|
||||
copied = "Скопировано!"
|
||||
|
||||
[notFound]
|
||||
title = "Не найдено"
|
||||
subtitle = "Запрашиваемая страница не существует"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Tmavý režim"
|
||||
warning = "Varovanie"
|
||||
caution = "Upozornenie"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopírovať"
|
||||
copied = "Skopírované!"
|
||||
|
||||
[notFound]
|
||||
title = "Nenájdené"
|
||||
subtitle = "Tato stránka neexistuje"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "ธีมมืด"
|
||||
warning = "คำเตือน"
|
||||
caution = "ระวัง"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "คัดลอก"
|
||||
copied = "คัดลอกแล้ว!"
|
||||
|
||||
[notFound]
|
||||
title = "ไม่พบหัวข้อ"
|
||||
subtitle = "ไม่พบหน้านี้ในระบบ"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Koyu Mod"
|
||||
warning = "Uyarı"
|
||||
caution = "Dikkat"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Kopyala"
|
||||
copied = "Kopyalandı!"
|
||||
|
||||
[notFound]
|
||||
title = "Bulunamadı"
|
||||
subtitle = "Aradığınız sayfa mevcut değil."
|
||||
|
||||
@@ -30,6 +30,10 @@ darkMode = "Темна тема"
|
||||
warning = "Попередження"
|
||||
caution = "Обережно"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Копіювати"
|
||||
copied = "Скопійовано!"
|
||||
|
||||
[notFound]
|
||||
title = "Не знайдено"
|
||||
subtitle = "Ця сторінка не існує"
|
||||
|
||||
@@ -29,6 +29,10 @@ darkMode = "Chế độ nền tối"
|
||||
warning = "Cảnh báo"
|
||||
caution = "Thận trọng"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "Sao chép"
|
||||
copied = "Đã sao chép!"
|
||||
|
||||
[notFound]
|
||||
title = "Không tìm thấy"
|
||||
subtitle = "Trang này không tồn tại"
|
||||
|
||||
@@ -20,6 +20,10 @@ darkMode = "深色模式"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "複製"
|
||||
copied = "已複製!"
|
||||
|
||||
[notFound]
|
||||
title = "找不到頁面"
|
||||
subtitle = "本頁面並不存在"
|
||||
@@ -67,3 +71,8 @@ darkMode = "深色模式"
|
||||
[cookies.functional]
|
||||
title = "功能性"
|
||||
text = "這些 Cookie 啟用評論和嵌入內容等功能."
|
||||
|
||||
[pagination]
|
||||
jumpToPage = "跳轉到頁碼"
|
||||
jump = "跳轉"
|
||||
pressEnter = "按 Enter 鍵跳轉"
|
||||
|
||||
@@ -20,6 +20,10 @@ darkMode = "夜晚模式"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "複製"
|
||||
copied = "已複製!"
|
||||
|
||||
[notFound]
|
||||
title = "404 錯誤"
|
||||
subtitle = "頁面不存在"
|
||||
@@ -67,3 +71,8 @@ darkMode = "夜晚模式"
|
||||
[cookies.functional]
|
||||
title = "功能性"
|
||||
text = "這些 Cookie 啟用評論和嵌入內容等功能."
|
||||
|
||||
[pagination]
|
||||
jumpToPage = "跳轉到頁碼"
|
||||
jump = "跳轉"
|
||||
pressEnter = "按 Enter 鍵跳轉"
|
||||
|
||||
@@ -20,6 +20,10 @@ darkMode = "暗色模式"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[article.codeblock]
|
||||
copy = "复制"
|
||||
copied = "已复制!"
|
||||
|
||||
[notFound]
|
||||
title = "404 错误"
|
||||
subtitle = "页面不存在"
|
||||
@@ -67,3 +71,8 @@ darkMode = "暗色模式"
|
||||
[cookies.functional]
|
||||
title = "功能性"
|
||||
text = "这些 Cookie 启用评论和嵌入内容等功能."
|
||||
|
||||
[pagination]
|
||||
jumpToPage = "跳转到页码"
|
||||
jump = "跳转"
|
||||
pressEnter = "按 Enter 键跳转"
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="inline-meta">
|
||||
{{ partial "helper/icon" "language" }}
|
||||
{{ range $Page.Translations }}
|
||||
<a href="{{ .RelPermalink }}" class="link">{{ .Language.LanguageName }}</a>
|
||||
<a href="{{ .RelPermalink }}" class="link">{{ .Language.Label }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{{- $opts := dict "minify" hugo.IsProduction "format" "esm" -}}
|
||||
{{- $galleryScript := resources.Get "ts/gallery.ts" | js.Build $opts -}}
|
||||
|
||||
{{ $style := .Site.Data.external.PhotoSwipe.Style }}
|
||||
{{ $core := .Site.Data.external.PhotoSwipe.Core }}
|
||||
{{ $lightbox := .Site.Data.external.PhotoSwipe.Lightbox }}
|
||||
{{ $style := hugo.Data.external.PhotoSwipe.Style }}
|
||||
{{ $core := hugo.Data.external.PhotoSwipe.Core }}
|
||||
{{ $lightbox := hugo.Data.external.PhotoSwipe.Lightbox }}
|
||||
|
||||
<script type="module">
|
||||
import gallery from '{{ $galleryScript.RelPermalink }}';
|
||||
@@ -29,4 +29,4 @@
|
||||
});
|
||||
lightbox.init();
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
{{/*
|
||||
Comments include entry.
|
||||
- If functional cookie consent is required, render a placeholder + template and load commentsConsent.ts.
|
||||
- Otherwise, render provider partial directly.
|
||||
*/}}
|
||||
{{ if .Site.Params.comments.enabled }}
|
||||
{{- $needsConsent := and .Site.Params.cookies.enabled .Site.Params.cookies.categories.functional -}}
|
||||
{{- if $needsConsent -}}
|
||||
@@ -8,40 +13,16 @@
|
||||
{{ T "cookies.managePreferences" }}
|
||||
</button>
|
||||
</div>
|
||||
<div id="comments-container" style="display: none;">
|
||||
<div id="comments-container" style="display: none;"></div>
|
||||
<template id="comments-template">
|
||||
{{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }}
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var placeholder = document.getElementById('comments-consent-placeholder');
|
||||
var container = document.getElementById('comments-container');
|
||||
|
||||
function showComments() {
|
||||
if (placeholder) placeholder.style.display = 'none';
|
||||
if (container) container.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideComments() {
|
||||
if (placeholder) placeholder.style.display = 'block';
|
||||
if (container) container.style.display = 'none';
|
||||
}
|
||||
|
||||
window.addEventListener('onCookieConsentChange', function(e) {
|
||||
if (e.detail && e.detail.functional) {
|
||||
showComments();
|
||||
} else {
|
||||
hideComments();
|
||||
}
|
||||
});
|
||||
|
||||
// Check if already consented
|
||||
if (window.cookieConsent && window.cookieConsent.hasConsent('functional')) {
|
||||
showComments();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</template>
|
||||
{{- $opts := dict "minify" hugo.IsProduction -}}
|
||||
{{/* commentsConsent.ts is intentionally built and loaded here as an independent entry */}}
|
||||
{{- $commentsScript := resources.Get "ts/commentsConsent.ts" | js.Build $opts | fingerprint -}}
|
||||
<script type="text/javascript" src="{{ $commentsScript.RelPermalink }}" integrity="{{ $commentsScript.Data.Integrity }}" defer></script>
|
||||
{{- else -}}
|
||||
{{/* No consent required - load comments normally */}}
|
||||
{{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }}
|
||||
{{- end -}}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- $style := resources.Get "scss/partials/comments/artalk.scss" | toCSS | minify -}}
|
||||
{{- with .Site.Params.comments.artalk -}}
|
||||
<link rel="stylesheet" href="{{ hugo.Data.external.Artalk.Style }}">
|
||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
||||
|
||||
<div id="Comments" class="artalk-container"></div>
|
||||
|
||||
<script type="module">
|
||||
import Artalk from '{{ hugo.Data.external.Artalk.Script }}';
|
||||
|
||||
function isDarkMode() {
|
||||
return document.documentElement.dataset.scheme === 'dark';
|
||||
}
|
||||
|
||||
const artalkInstance = Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '{{ $.RelPermalink }}',
|
||||
pageTitle: '{{ $.Title }}',
|
||||
server: '{{ .serverUrl }}',
|
||||
site: '{{ .site }}',
|
||||
darkMode: isDarkMode(),
|
||||
locale: 'auto',
|
||||
});
|
||||
|
||||
window.addEventListener('onColorSchemeChange', (e) => {
|
||||
if (!artalkInstance || typeof artalkInstance.setDarkMode !== 'function') return;
|
||||
artalkInstance.setDarkMode(e.detail === 'dark');
|
||||
});
|
||||
</script>
|
||||
{{- end -}}
|
||||
@@ -1,4 +1,12 @@
|
||||
{{- $opts := dict "minify" hugo.IsProduction -}}
|
||||
{{- $opts := dict
|
||||
"minify" hugo.IsProduction
|
||||
"params" (dict
|
||||
"codeblock" (dict
|
||||
"copy" (i18n "article.codeblock.copy")
|
||||
"copied" (i18n "article.codeblock.copied")
|
||||
)
|
||||
)
|
||||
-}}
|
||||
{{- $script := resources.Get "ts/main.ts" | js.Build $opts | fingerprint -}}
|
||||
|
||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||
@@ -7,4 +15,4 @@
|
||||
{{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}}
|
||||
{{- $customScript := . | js.Build $opts | fingerprint -}}
|
||||
<script type="text/javascript" src="{{ $customScript.RelPermalink }}" defer></script>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- $ThemeVersion := "4.0.0" -}}
|
||||
{{- $ThemeVersion := "4.0.2" -}}
|
||||
<footer class="site-footer">
|
||||
<section class="copyright">
|
||||
©
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
|
||||
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) }}
|
||||
{{- if $image -}}
|
||||
<meta property='og:image' content='{{ absURL $image.permalink }}' />
|
||||
<meta property='og:image' content='{{ absURL $image.Permalink }}' />
|
||||
{{- end -}}
|
||||
@@ -12,5 +12,5 @@
|
||||
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
|
||||
{{- if $image -}}
|
||||
<meta name="twitter:card" content="{{ .Site.Params.opengraph.twitter.card }}">
|
||||
<meta name="twitter:image" content='{{ absURL $image.permalink }}' />
|
||||
<meta name="twitter:image" content='{{ absURL $image.Permalink }}' />
|
||||
{{- end -}}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- $List := index .Context.Site.Data.external .Namespace -}}
|
||||
{{- $List := index hugo.Data.external .Namespace -}}
|
||||
{{- with $List -}}
|
||||
{{- range . -}}
|
||||
{{- if eq .type "script" -}}
|
||||
@@ -26,4 +26,4 @@
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Error: external resource '%s' is not found" .Namespace -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
{{ if .Image }}
|
||||
{{ $url := urls.Parse .Image }}
|
||||
{{ $permalink := .Image }}
|
||||
{{ $resource := "" }}
|
||||
|
||||
{{ $result = dict
|
||||
"Resource" nil
|
||||
"Permalink" $permalink
|
||||
"Height" nil
|
||||
"Width" nil
|
||||
}}
|
||||
{{ $resource := dict }}
|
||||
{{ $local := true }}
|
||||
|
||||
{{ if not (in (slice "https" "http") $url.Scheme) }}
|
||||
@@ -22,14 +29,14 @@
|
||||
{{ $resource = .Resources.Get (printf "%s" (.Image | safeURL)) }}
|
||||
{{ else }}
|
||||
{{/* Remote image */}}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with try (resources.GetRemote $url (dict "timeout" .Context.Site.Params.imageProcessing.external.timeout)) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ warnf "Failed to fetch remote resource %q: %s" $url . }}
|
||||
{{ else with .Value }}
|
||||
{{ $resource = . }}
|
||||
{{ $local = false }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ warnf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -1,70 +1,142 @@
|
||||
{{- $pag := $.Paginator -}}
|
||||
{{- if gt $pag.TotalPages 1 -}}
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{{- /* Previous page button */ -}}
|
||||
<a class="page-link {{ if not $pag.Prev }}disabled{{ end }}"
|
||||
{{- if $pag.Prev -}}href="{{ $pag.Prev.URL }}" {{- end -}}
|
||||
aria-label="Previous page"
|
||||
{{- if not $pag.Prev -}}aria-disabled="true"{{- end -}}>
|
||||
{{ partial "helper/icon" "chevron-left" }}
|
||||
</a>
|
||||
|
||||
{{- /* Page numbers logic */ -}}
|
||||
{{- $window := 4 -}}
|
||||
{{- $showFirst := true -}}
|
||||
{{- $showLast := true -}}
|
||||
|
||||
{{- /* First page */ -}}
|
||||
<a class="page-link {{ if eq $pag.PageNumber 1 }}current{{ end }}" aria-label="Page 1"
|
||||
{{- if eq $pag.PageNumber 1 -}}aria-current="page" {{- end -}}
|
||||
{{- if ne $pag.PageNumber 1 -}}href="{{ $pag.First.URL }}"{{- end -}}>
|
||||
1
|
||||
</a>
|
||||
|
||||
{{- /* Left ellipsis - show if current page is > 3 */ -}}
|
||||
{{- if gt $pag.PageNumber 3 -}}
|
||||
<span class="page-link pagination-link">
|
||||
{{ partial "helper/icon" "dots" }}
|
||||
</span>
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Middle pages - show current and neighbors */ -}}
|
||||
{{- /* Find page 2's URL to extract the pagination base path for JavaScript */ -}}
|
||||
{{- $page2Url := "" -}}
|
||||
{{- range $pag.Pagers -}}
|
||||
{{- if and (gt .PageNumber 1) (lt .PageNumber $pag.TotalPages) -}}
|
||||
{{- if and (ge .PageNumber (sub $pag.PageNumber $window)) (le .PageNumber (add $pag.PageNumber $window)) -}}
|
||||
<a class="page-link {{ if eq .PageNumber $pag.PageNumber }}current{{ end }}"
|
||||
{{- if eq .PageNumber $pag.PageNumber -}}aria-current="page" {{- end -}} href="{{ .URL }}"
|
||||
aria-label="Page {{ .PageNumber }}">
|
||||
{{ .PageNumber }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- if eq .PageNumber 2 -}}
|
||||
{{- $page2Url = .URL -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Right ellipsis - show if current page is < last-2 */ -}}
|
||||
{{- if lt $pag.PageNumber (sub $pag.TotalPages 2) -}}
|
||||
<span class="page-link pagination-link">
|
||||
{{ partial "helper/icon" "dots" }}
|
||||
</span>
|
||||
{{- end -}}
|
||||
<nav
|
||||
class="pagination"
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
data-total="{{ $pag.TotalPages }}"
|
||||
data-first-url="{{ $pag.First.URL }}"
|
||||
data-format-url="{{ $page2Url }}">
|
||||
{{- /* Previous page button */ -}}
|
||||
<a
|
||||
class="page-link {{ if not $pag.Prev }}disabled{{ end }}"
|
||||
{{- if $pag.Prev -}}
|
||||
href="{{ $pag.Prev.URL }}"
|
||||
aria-label="Previous page"
|
||||
{{- end -}}
|
||||
{{- if not $pag.Prev -}}aria-disabled="true"{{- end -}}>
|
||||
{{ partial "helper/icon" "chevron-left" }}
|
||||
</a>
|
||||
|
||||
{{- /* Last page (if more than 1 page total) */ -}}
|
||||
{{- /* Page numbers logic */ -}}
|
||||
{{- /* Compute visible middle window [start, end] (excluding first and last page). */ -}}
|
||||
{{- /* Keep 3 middle pages when possible; if total <= 5, show all middle pages. */ -}}
|
||||
{{- $curr := $pag.PageNumber -}}
|
||||
{{- $total := $pag.TotalPages -}}
|
||||
{{- $middleMinPage := 2 -}}
|
||||
{{- $middleMaxPage := sub $total 1 -}}
|
||||
{{- $start := $middleMinPage -}}
|
||||
{{- $end := $middleMaxPage -}}
|
||||
|
||||
{{- if gt $pag.TotalPages 1 -}}
|
||||
<a class="page-link {{ if eq $pag.PageNumber $pag.TotalPages }}current{{ end }}"
|
||||
{{- if ne $pag.PageNumber $pag.TotalPages -}}href="{{ $pag.Last.URL }}" {{- end -}}
|
||||
aria-label="Page {{ $pag.TotalPages }}"
|
||||
{{- if eq $pag.PageNumber $pag.TotalPages -}}aria-current="page"{{- end -}}>
|
||||
{{ $pag.TotalPages }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- if gt $total 5 -}}
|
||||
{{- $start = sub $curr 1 -}}
|
||||
{{- if lt $start $middleMinPage -}}
|
||||
{{- $start = $middleMinPage -}}
|
||||
{{- end -}}
|
||||
{{- $end = add $start 2 -}}
|
||||
{{- if gt $end $middleMaxPage -}}
|
||||
{{- $end = $middleMaxPage -}}
|
||||
{{- $start = sub $end 2 -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Next page button */ -}}
|
||||
<a class="page-link {{ if not $pag.Next }}disabled{{ end }}"
|
||||
{{- if $pag.Next -}}href="{{ $pag.Next.URL }}" {{- end -}}
|
||||
aria-label="Next page"
|
||||
{{- if not $pag.Next -}}aria-disabled="true"{{- end -}}>
|
||||
{{ partial "helper/icon" "chevron-right" }}
|
||||
</a>
|
||||
</nav>
|
||||
{{- end -}}
|
||||
{{- /* First page */ -}}
|
||||
<a
|
||||
class="page-link {{ if eq $curr 1 }}current{{ end }}"
|
||||
aria-label="Page 1"
|
||||
{{- if eq $curr 1 -}}aria-current="page"{{- end -}}
|
||||
{{- if ne $curr 1 -}}href="{{ $pag.First.URL }}"{{- end -}}>
|
||||
1
|
||||
</a>
|
||||
|
||||
{{- /* Left ellipsis - show if the window start is beyond page 2 */ -}}
|
||||
{{- if gt $start $middleMinPage -}}
|
||||
<button
|
||||
type="button"
|
||||
class="page-link pagination-link pagination-jump-trigger"
|
||||
aria-label="{{ i18n "pagination.jumpToPage" | default "Jump to page" }}">
|
||||
{{ partial "helper/icon" "dots" }}
|
||||
</button>
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Middle pages - show pages in [start, end] */ -}}
|
||||
{{- range $pag.Pagers -}}
|
||||
{{- if and (ge .PageNumber $start) (le .PageNumber $end) -}}
|
||||
{{- $hideOnMobile := and (ne .PageNumber $curr) (ne .PageNumber (sub $curr 1)) (ne .PageNumber (add $curr 1)) -}}
|
||||
<a
|
||||
class="page-link {{ if eq .PageNumber $curr }}current{{ end }} {{ if $hideOnMobile }}hide-on-mobile{{ end }}"
|
||||
{{- if eq .PageNumber $curr -}}aria-current="page"{{- end -}}
|
||||
href="{{ .URL }}"
|
||||
aria-label="Page {{ .PageNumber }}">
|
||||
{{ .PageNumber }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Right ellipsis - show if the window end is before the second-to-last page */ -}}
|
||||
{{- if lt $end $middleMaxPage -}}
|
||||
<button
|
||||
type="button"
|
||||
class="page-link pagination-link pagination-jump-trigger"
|
||||
aria-label="{{ i18n "pagination.jumpToPage" | default "Jump to page" }}">
|
||||
{{ partial "helper/icon" "dots" }}
|
||||
</button>
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Last page */ -}}
|
||||
<a
|
||||
class="page-link {{ if eq $curr $total }}current{{ end }}"
|
||||
{{- if ne $curr $total -}}href="{{ $pag.Last.URL }}"{{- end -}}
|
||||
aria-label="Page {{ $total }}"
|
||||
{{- if eq $curr $total -}}aria-current="page"{{- end -}}>
|
||||
{{ $total }}
|
||||
</a>
|
||||
|
||||
{{- /* Next page button */ -}}
|
||||
<a
|
||||
class="page-link {{ if not $pag.Next }}disabled{{ end }}"
|
||||
{{- if $pag.Next -}}
|
||||
href="{{ $pag.Next.URL }}"
|
||||
aria-label="Next page"
|
||||
{{- end -}}
|
||||
{{- if not $pag.Next -}}aria-disabled="true"{{- end -}}>
|
||||
{{ partial "helper/icon" "chevron-right" }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<dialog id="pagination-jump-dialog" aria-labelledby="pagination-jump-title">
|
||||
<form method="dialog" class="pagination-jump-form">
|
||||
<header>
|
||||
<label id="pagination-jump-title" class="title" for="pagination-jump-input">
|
||||
{{ i18n "pagination.jumpToPage" | default "Jump to page:" }}
|
||||
</label>
|
||||
<span class="subtitle">(1 - {{ $pag.TotalPages }})</span>
|
||||
</header>
|
||||
<div class="pagination-jump-input-group">
|
||||
<input
|
||||
type="number"
|
||||
id="pagination-jump-input"
|
||||
min="1"
|
||||
max="{{ $pag.TotalPages }}"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
required>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ i18n "pagination.jump" | default "Go" }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="pagination-jump-hint">
|
||||
<kbd>Enter</kbd>
|
||||
<span>{{ i18n "pagination.pressEnter" | default "Press Enter to jump" }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
{{- end -}}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
{{ end }}
|
||||
<li class="menu-bottom-section">
|
||||
<ol class="menu">
|
||||
{{- $currentLanguageCode := .Language.Lang -}}
|
||||
{{- $currentLanguageCode := .Language.Name -}}
|
||||
{{ if hugo.IsMultilingual }}
|
||||
<li id="i18n-switch">
|
||||
{{ partial "helper/icon" "language" }}
|
||||
@@ -72,11 +72,11 @@
|
||||
{{ range .Site.Home.AllTranslations }}
|
||||
{{ $target := . }}
|
||||
{{ range $.AllTranslations }}
|
||||
{{ if eq .Language.Lang $target.Language.Lang }}
|
||||
{{ if eq .Language.Name $target.Language.Name }}
|
||||
{{ $target = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<option value="{{ $target.RelPermalink }}" {{ if eq $target.Language.Lang $currentLanguageCode }}selected{{ end }}>{{ .Language.LanguageName }}</option>
|
||||
<option value="{{ $target.RelPermalink }}" {{ if eq $target.Language.Name $currentLanguageCode }}selected{{ end }}>{{ .Language.Label }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
</li>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{ partial "helper/icon" $icon }}
|
||||
</div>
|
||||
<h2 class="widget-title section-title">
|
||||
{{ if $showLink }}
|
||||
{{ if and $showLink $taxonomyPage }}
|
||||
<a href="{{ $taxonomyPage.RelPermalink }}">
|
||||
{{ $widgetTitle }}
|
||||
</a>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.LanguageCode }}" dir="{{ default `ltr` .Language.LanguageDirection }}">
|
||||
<html lang="{{ .Site.Language.Locale }}" dir="{{ default `ltr` .Language.Direction }}">
|
||||
<head>
|
||||
{{- partial "head/head.html" . -}}
|
||||
{{- block "head" . -}}{{ end }}
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
<link>{{ .Permalink }}</link>
|
||||
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
|
||||
<generator>Hugo -- gohugo.io</generator>
|
||||
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
|
||||
<language>{{ site.Language.Locale }}</language>{{ with $authorEmail }}
|
||||
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
|
||||
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
|
||||
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
|
||||
@@ -52,7 +52,7 @@
|
||||
{{- end -}}
|
||||
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
|
||||
{{- if $image -}}
|
||||
{{- $imgTag := printf "<img src=\"%s\" alt=\"Featured image of post %s\" />" ($image.permalink | absURL) .Title -}}
|
||||
{{- $imgTag := printf "<img src=\"%s\" alt=\"Featured image of post %s\" />" ($image.Permalink | absURL) .Title -}}
|
||||
{{- $content = printf "%s%s" $imgTag $content -}}
|
||||
{{- end -}}
|
||||
{{- $content | transform.XMLEscape | safeHTML -}}
|
||||
|
||||
Reference in New Issue
Block a user