Compare commits

...

26 Commits

Author SHA1 Message Date
Xin
1c13e24535 Merge branch 'main' into image-zoom 2025-09-15 21:59:39 +01:00
Kowyo
708358de80 fix: enhance table readability (#826)
* feat: enhance table readability

* use v0.9.0 table style

* Update table styles

---------

Co-authored-by: Xin <xin@imfing.com>
2025-09-15 21:59:05 +01:00
ghac101
8699deb1dd fix(analytics): change default umami analytics file to script.js (#835)
Co-authored-by: Xin <5097752+imfing@users.noreply.github.com>
2025-09-15 21:28:27 +01:00
Ludovic Fernandez
a03dbf463f fix(docs): menu main duplicate field (#837) 2025-09-15 21:23:27 +01:00
Xin
6fc6391d06 fix(image-zoom): disable dragging during single touch interactions
- Updated logic to prevent dragging when a single touch is detected, improving tap detection accuracy.
- Adjusted event handling to ensure significant movement cancels tap only when necessary, enhancing user experience.
2025-09-11 23:30:38 +01:00
Xin
be49fe6f57 feat(image-zoom): improve zoom interactions and tap detection
- Added `will-change: transform` to CSS for better performance during zoom.
- Enhanced JavaScript to support tap detection for closing the zoom overlay with minimal movement.
- Updated zoom behavior to ensure scaling occurs from the center of the overlay.
- Refined event handling to prevent unintended interactions and improve user experience.
2025-09-11 23:20:06 +01:00
Xin
9e50415b94 feat(image-zoom): enhance zoom functionality with loading states and improved interactions
- Updated CSS to include loading indicators and refined transition effects for zoomed images.
- Enhanced JavaScript to manage image loading states, ensuring a smoother user experience during zoom interactions.
- Improved gesture handling for touch devices, including better management of pinch and drag events.
2025-09-11 23:00:03 +01:00
Xin
6e33f17cba feat(image-zoom): implement multi-touch pinch detection for zoom functionality
- Added support for pinch gestures to enhance the zoom experience on touch devices.
- Implemented event listeners for pointer events to manage pinch start and end.
- Updated closing behavior to account for active pinch gestures, improving user interaction.
2025-09-11 20:52:59 +01:00
Ludovic Fernandez
1c06ae5580 chore(scripts): generic way to build scripts (#829)
* chore: move core scripts

* chore: extract head scripts
2025-09-11 20:29:18 +01:00
Xin
ba0934b2e1 chore(image-zoom): enhance zoom functionality with improved transitions and closing behavior
- Updated CSS for smoother transitions and added closing effects for the zoom overlay.
- Enhanced JavaScript to support dedicated closing transitions and improved event handling for dismissing the overlay.
- Removed unnecessary scroll lock and overflow styles for better user experience.
2025-09-11 09:54:35 +01:00
Xin
a528d9adc0 chore(image-zoom): enhance mobile experience with scroll lock and touch actions 2025-09-11 08:54:52 +01:00
Xin
c2c4cafa13 Merge branch 'main' into image-zoom 2025-09-11 00:00:25 +01:00
Xin
09728a4aa9 feat(image-zoom): add minimal image zoom functionality
- Introduced CSS for image zoom overlay and image styling.
- Implemented JavaScript for handling image zoom interactions, including overlay creation and close functionality.
- Updated configuration to enable image zoom feature in site parameters.
- Added partial for including image zoom assets in the layout.
2025-09-10 23:59:51 +01:00
Ludovic Fernandez
ccb63d60f1 feat(tabs): revamp tabs (#815) 2025-09-10 23:54:27 +01:00
Keith Stockdale
3bc454bbf6 feat: support hiding the main sidebar in desktop site (#778)
* feat: Remove the main sidebar entirely to free up more space for the main content of the page

* fix: ensure that the footer switches are still visible when the main sidebar has been disabled

* refactor: Repurpose Params.sidebar.hide to disable the main sidebar and disable the placeholder rather than adding a new front matter parameter

* fix: change wording from "disable" to "hide" in the documentation for hiding the sidebar

* fix: using incorrect hidden class in sidebar.html broke mobile navigation. Fixed this

---------

Co-authored-by: Xin <5097752+imfing@users.noreply.github.com>
2025-09-10 22:45:12 +01:00
Xin
1b536e27a5 chore(build): update MAIN_VERSION to v0.11.1 and change source directory to docs 2025-09-06 14:03:29 +01:00
Xin
0e919e77f8 fix(build): update version source directory for v0.10.2 to exampleSite 2025-09-06 11:17:38 +00:00
Xin
83f3b5052e chore(docs): rename exampleSite to docs and create examples (#813)
* chore(docs): rename `exampleSite` to `docs` and create `examples`

* chore(build): update build script to support new version format and source directories; add v0.10 to documentation menu
2025-09-06 12:06:26 +01:00
Kowyo
f8eae96c11 docs: update file paths for hugo new template system (#821) 2025-09-04 19:17:28 +01:00
Ludovic Fernandez
ec97808b69 feat(opengraph): update the partial (#819)
* chore: update opengraph partial

* docs: improve Open Grapth section
2025-09-03 21:46:30 +01:00
Ludovic Fernandez
334158af7a chore: replace .Scratch with .Store (#818) 2025-09-03 15:37:18 +01:00
Ludovic Fernandez
184ee25011 fix(analytics): Matomo analytics (#817)
* fix: Matomo analytics

* chore: use with

* Revert "chore: use with"

This reverts commit 1d86e6fa0f.

---------

Co-authored-by: Xin <xin@imfing.com>
2025-09-03 10:53:27 +01:00
Xin
cc5884dd2a fix(banner): update link formats (#816)
* fix(banner): update link formats

* fix(banner): correct link format in localized messages for v0.11 announcement
2025-09-02 23:54:40 +01:00
Xin
493cfba523 fix(tags): update tag link to use the correct tag title instead of page title (#812) 2025-08-31 12:47:52 +01:00
Xin
5846274db7 chore: update thumbnail image tn.jpg 2025-08-30 14:05:31 +01:00
Xin
4635bdc846 chore: update main version to v0.11.0 in build script 2025-08-30 13:53:52 +01:00
193 changed files with 1031 additions and 319 deletions

View File

@@ -31,7 +31,7 @@ Use [Conventional Commits][conventional commits] message to make it easier to un
Similar to contributing code, you can also contribute to the documentation by submitting a pull request. Similar to contributing code, you can also contribute to the documentation by submitting a pull request.
The documentation site is located in the [`exampleSite`](../exampleSite/) folder. The documentation site is located in the [`docs`](../docs/) folder.
You can make changes to the documentation and create a pull request. A preview of the new documentation will be automatically generated and displayed in the pull request comment via [Netlify][netlify deploy preview]. You can make changes to the documentation and create a pull request. A preview of the new documentation will be automatically generated and displayed in the pull request comment via [Netlify][netlify deploy preview].
### 💬 GitHub Discussions ### 💬 GitHub Discussions
@@ -71,7 +71,7 @@ npm i
- [`assets`](../assets/): CSS styles and JavaScript files. - [`assets`](../assets/): CSS styles and JavaScript files.
- [`data`](../data/): The theme data files. Now only contains the `icons.yaml` file. - [`data`](../data/): The theme data files. Now only contains the `icons.yaml` file.
- [`exampleSite`](../exampleSite/): The documentation site for the theme. - [`docs`](../docs/): The documentation site for the theme.
- [`i18n`](../i18n/): The theme translation files. - [`i18n`](../i18n/): The theme translation files.
- [`layouts`](../layouts/): The theme layouts. - [`layouts`](../layouts/): The theme layouts.
- [`static`](../static/): The static files for the theme. For example, the favicon and the site logo. - [`static`](../static/): The static files for the theme. For example, the favicon and the site logo.
@@ -84,7 +84,7 @@ Please refer to the [Hugo documentation][hugo] for more information.
npm run dev:theme npm run dev:theme
``` ```
It will start the Hugo server on `http://localhost:1313/` for the `exampleSite` content. It starts the Hugo server on `http://localhost:1313/` for the `docs` content.
### Compile the styles ### Compile the styles

View File

@@ -84,12 +84,12 @@ assets/
### Example Site Development ### Example Site Development
The `exampleSite/` directory serves as both documentation and testing ground: The `docs/` directory serves as both documentation and testing ground:
- Test new features here before releasing - Test new features here before releasing
- Configuration examples in `exampleSite/hugo.yaml` showing multi-language setup - Configuration examples in `docs/hugo.yaml` showing multi-language setup
- Content examples demonstrate all theme capabilities - Content examples demonstrate all theme capabilities
- Run from exampleSite with: `hugo server --themesDir=../..` - Run from docs with: `hugo server --themesDir=../..`
### CSS Development Workflow ### CSS Development Workflow
@@ -115,7 +115,7 @@ The `exampleSite/` directory serves as both documentation and testing ground:
### Key Configuration Files ### Key Configuration Files
- `exampleSite/hugo.yaml` - Example Hugo configuration with multi-language setup - `docs/hugo.yaml` - Example Hugo configuration with multi-language setup
- `postcss.config.mjs` - PostCSS configuration for CSS processing - `postcss.config.mjs` - PostCSS configuration for CSS processing
- `package.json` - Node.js dependencies and build scripts - `package.json` - Node.js dependencies and build scripts
- `taskfile.yaml` - Task runner configuration - `taskfile.yaml` - Task runner configuration
@@ -155,7 +155,7 @@ The `exampleSite/` directory serves as both documentation and testing ground:
### Testing & Quality Assurance ### Testing & Quality Assurance
- Test all changes in `exampleSite/` before releasing - Test all changes in `docs/` before releasing
- Use `npm run dev:theme` for theme development with hot reloading - Use `npm run dev:theme` for theme development with hot reloading
- Format code with `npx prettier --write .` before committing - Format code with `npx prettier --write .` before committing
- Verify multi-language functionality across supported languages - Verify multi-language functionality across supported languages

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,84 @@
.hextra-zoom-image-overlay {
position: fixed;
inset: 0;
background: var(--hextra-image-zoom-backdrop, rgba(0, 0, 0, 0.9));
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
opacity: 0;
transition: opacity 260ms cubic-bezier(0.2, 0, 0, 1);
cursor: zoom-out;
overscroll-behavior: contain;
touch-action: none;
backdrop-filter: blur(var(--hextra-image-zoom-blur, 4px));
-webkit-backdrop-filter: blur(var(--hextra-image-zoom-blur, 4px));
/* Prevent iOS bounce */
position: fixed;
overflow: hidden;
will-change: transform;
}
.hextra-zoom-image-overlay.show {
opacity: 1;
}
.hextra-zoom-image-overlay.closing {
opacity: 0;
transition: opacity 360ms cubic-bezier(0.2, 0, 0, 1);
}
.hextra-zoom-image {
border-radius: 8px;
box-shadow: 0 18px 80px rgba(0, 0, 0, 0.5);
image-rendering: -webkit-optimize-contrast;
image-rendering: high-quality;
will-change: transform;
/* Prevent image selection on mobile */
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
/* Hardware acceleration */
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
.hextra-zoom-image-overlay.show .hextra-zoom-image {
transition:
transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
left 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
top 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.hextra-zoom-image-overlay.closing .hextra-zoom-image {
transition:
transform 340ms cubic-bezier(0.3, 0, 0.2, 1),
left 340ms cubic-bezier(0.3, 0, 0.2, 1),
top 340ms cubic-bezier(0.3, 0, 0.2, 1);
}
/* Disable transitions during interaction */
.hextra-zoom-image-overlay.interacting .hextra-zoom-image {
transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
.hextra-zoom-image-overlay,
.hextra-zoom-image {
transition: none !important;
}
}
.content img:not([data-no-zoom]):not(.not-prose img) {
cursor: zoom-in;
}
/* Loading indicator */
.hextra-zoom-image.loading {
opacity: 0;
}
.hextra-zoom-image.loaded {
opacity: 1;
transition: opacity 200ms ease-in-out;
}

View File

@@ -33,19 +33,19 @@
@apply hx:border-black/4 hx:bg-black/3 hx:break-words hx:rounded-md hx:border hx:py-0.5 hx:px-[.25em] hx:text-[.9em] hx:dark:border-white/10 hx:dark:bg-white/10; @apply hx:border-black/4 hx:bg-black/3 hx:break-words hx:rounded-md hx:border hx:py-0.5 hx:px-[.25em] hx:text-[.9em] hx:dark:border-white/10 hx:dark:bg-white/10;
} }
:where(table):not(:where(.hextra-code-block table, [class~=not-prose],[class~=not-prose] *)) { :where(table):not(:where(.hextra-code-block table, [class~=not-prose],[class~=not-prose] *)) {
@apply hx:block hx:overflow-x-auto hx:my-6 hx:p-0 hx:first:mt-0 hx:w-full hx:text-sm hx:leading-5; @apply hx:block hx:overflow-x-auto hx:my-6 hx:p-0 hx:first:mt-0 hx:w-full hx:text-sm hx:leading-5 hx:border-collapse;
thead { thead {
@apply hx:border-b hx:border-gray-200 hx:dark:border-neutral-800; @apply hx:bg-gray-50 hx:dark:bg-gray-600/20;
} }
tbody tr { tr {
@apply hx:m-0 hx:border-b hx:border-gray-100 hx:dark:border-neutral-800/50; @apply hx:m-0 hx:border-t hx:border-gray-300 hx:p-0 hx:dark:border-gray-600;
} }
th { th {
@apply hx:m-0 hx:p-2 hx:font-semibold hx:first:pl-0 hx:last:pr-0; @apply hx:m-0 hx:border hx:border-gray-300 hx:p-2 hx:font-semibold hx:dark:border-gray-600;
} }
td { td {
@apply hx:m-0 hx:p-2 hx:first:pl-0 hx:last:pr-0; @apply hx:m-0 hx:border hx:border-gray-300 hx:p-2 hx:dark:border-gray-600;
} }
} }
:where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) { :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) {

6
assets/js/head/banner.js Normal file
View File

@@ -0,0 +1,6 @@
// The section must not be in the banner.js (body) file because it can create a quick flash.
if (localStorage.getItem('{{ site.Params.banner.key | default `banner-closed` }}')) {
document.documentElement.style.setProperty("--hextra-banner-height", "0px");
document.documentElement.classList.add("hextra-banner-hidden");
}

14
assets/js/head/theme.js Normal file
View File

@@ -0,0 +1,14 @@
// The section must not be in the theme.js (body) file because it can create a quick flash (switch between light and dark).
function setTheme(theme) {
document.documentElement.classList.remove("light", "dark");
if (theme !== "light" && theme !== "dark") {
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
document.documentElement.classList.add(theme);
document.documentElement.style.colorScheme = theme;
}
setTheme("color-theme" in localStorage ? localStorage.getItem("color-theme") : '{{ site.Params.theme.default | default `system`}}')

408
assets/js/image-zoom.js Normal file
View File

@@ -0,0 +1,408 @@
(function () {
'use strict';
function createOverlayFromTarget(targetImg) {
const src = targetImg.currentSrc || targetImg.src;
const alt = targetImg.alt || "";
if (!src) return;
const rect = targetImg.getBoundingClientRect();
const overlay = document.createElement("div");
overlay.className = "hextra-zoom-image-overlay";
overlay.setAttribute("role", "dialog");
overlay.setAttribute("aria-modal", "true");
overlay.setAttribute("aria-label", alt || "Zoomed image");
const img = document.createElement("img");
img.className = "hextra-zoom-image loading";
img.src = src;
if (alt) img.alt = alt;
// Center-origin positioning for cleaner transforms
const startCX = rect.left + rect.width / 2;
const startCY = rect.top + rect.height / 2;
img.style.position = "fixed";
img.style.left = startCX + "px";
img.style.top = startCY + "px";
img.style.width = rect.width + "px";
img.style.height = rect.height + "px";
img.style.transformOrigin = "center center";
img.style.transform = "translate3d(-50%, -50%, 0) scale(1)";
// Ensure overlay scales from center when zooming the whole overlay
overlay.style.transformOrigin = "center center";
overlay.appendChild(img);
// Image loaded handler
img.addEventListener('load', function () {
img.classList.remove('loading');
img.classList.add('loaded');
});
// Gesture state management
let isPinching = false;
let isDragging = false;
let isInteracting = false;
let pinchEndTimer = null;
const pointers = new Map(); // pointerId -> {x, y, startX, startY}
let gestureState = {
scale: 1,
panX: 0,
panY: 0,
lastScale: 1,
lastPanX: 0,
lastPanY: 0,
initialDistance: 0,
midpointX: 0,
midpointY: 0
};
// Utility functions
function getDistance(p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function getMidpoint(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
};
}
function setInteracting(value) {
isInteracting = value;
if (value) {
overlay.classList.add('interacting');
} else {
overlay.classList.remove('interacting');
}
}
// Pointer event handlers
let tapCandidate = false;
let tapStartX = 0;
let tapStartY = 0;
let tapStartTime = 0;
function onPointerDown(e) {
e.preventDefault();
pointers.set(e.pointerId, {
x: e.clientX,
y: e.clientY,
startX: e.clientX,
startY: e.clientY
});
if (pointers.size === 1) {
isDragging = false;
setInteracting(true);
gestureState.lastPanX = gestureState.panX;
gestureState.lastPanY = gestureState.panY;
// Tap detection setup
tapCandidate = true;
tapStartX = e.clientX;
tapStartY = e.clientY;
tapStartTime = (typeof performance !== 'undefined' ? performance.now() : Date.now());
} else if (pointers.size === 2) {
// Two touches - start pinch
isDragging = false;
isPinching = true;
setInteracting(true);
const pts = Array.from(pointers.values());
gestureState.initialDistance = getDistance(pts[0], pts[1]);
gestureState.lastScale = gestureState.scale;
const midpoint = getMidpoint(pts[0], pts[1]);
gestureState.midpointX = midpoint.x;
gestureState.midpointY = midpoint.y;
if (pinchEndTimer) {
clearTimeout(pinchEndTimer);
pinchEndTimer = null;
}
}
}
function onPointerMove(e) {
if (!pointers.has(e.pointerId)) return;
e.preventDefault();
const pointer = pointers.get(e.pointerId);
pointer.x = e.clientX;
pointer.y = e.clientY;
if (isPinching && pointers.size === 2) {
// Handle pinch zoom
const pts = Array.from(pointers.values());
const currentDistance = getDistance(pts[0], pts[1]);
const scaleDelta = currentDistance / gestureState.initialDistance;
// Calculate new scale with limits - minimum is 1 (original zoom level)
const newScale = Math.max(1, Math.min(5, gestureState.lastScale * scaleDelta));
// Only update pan if scale is actually changing
// This prevents drift when pinching at minimum scale
if (Math.abs(newScale - gestureState.scale) > 0.001) {
gestureState.scale = newScale;
// Calculate pan based on pinch center movement
const currentMidpoint = getMidpoint(pts[0], pts[1]);
const panDeltaX = currentMidpoint.x - gestureState.midpointX;
const panDeltaY = currentMidpoint.y - gestureState.midpointY;
gestureState.panX = gestureState.lastPanX + panDeltaX;
gestureState.panY = gestureState.lastPanY + panDeltaY;
}
// Any multi-touch movement cancels tap
tapCandidate = false;
applyTransform();
} else if (pointers.size === 1) {
// Single pointer: no drag; only cancel tap if large move
const moveThreshold = 10;
if (Math.abs(pointer.x - tapStartX) > moveThreshold || Math.abs(pointer.y - tapStartY) > moveThreshold) {
tapCandidate = false;
}
}
}
function onPointerUp(e) {
pointers.delete(e.pointerId);
if (pointers.size === 0) {
// All pointers released
isDragging = false;
setInteracting(false);
// Tap-to-close when there was minimal movement and short duration
const now = (typeof performance !== 'undefined' ? performance.now() : Date.now());
const duration = now - tapStartTime;
if (tapCandidate && !isPinching && duration < 300) {
close();
}
tapCandidate = false;
if (isPinching) {
pinchEndTimer = setTimeout(() => {
isPinching = false;
}, 300);
}
} else if (pointers.size === 1) {
// Going from pinch to single touch — keep dragging disabled
isPinching = false;
isDragging = false;
const remaining = Array.from(pointers.values())[0];
gestureState.lastPanX = gestureState.panX;
gestureState.lastPanY = gestureState.panY;
remaining.startX = remaining.x;
remaining.startY = remaining.y;
}
}
function onPointerCancel(e) {
onPointerUp(e);
}
// Mouse wheel zoom and scroll handling
function onWheel(e) {
e.preventDefault();
// If it's a regular scroll (not pinch), dismiss the overlay
if (!e.ctrlKey && !e.metaKey) {
// Regular scroll - dismiss overlay gracefully
close();
return;
}
// Handle trackpad pinch (ctrl+wheel or cmd+wheel on Mac)
const delta = e.deltaY;
const scaleFactor = 0.01;
const zoomSpeed = Math.exp(-delta * scaleFactor);
const prevScale = gestureState.scale;
const unclamped = prevScale * zoomSpeed;
const nextScale = Math.max(1, Math.min(5, unclamped));
// Effective scale ratio (applied), use it to anchor zoom around cursor
const f = prevScale === 0 ? 1 : (nextScale / prevScale);
if (f !== 1) {
// Zoom towards mouse position
const rect = img.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const offsetX = e.clientX - centerX;
const offsetY = e.clientY - centerY;
// Adjust pan to keep cursor's point stable
gestureState.panX -= offsetX * (f - 1);
gestureState.panY -= offsetY * (f - 1);
}
gestureState.scale = nextScale;
setInteracting(true);
applyTransform();
setTimeout(() => setInteracting(false), 150);
}
// Keyboard navigation
function onKeyDown(e) {
if (e.key === "Escape") {
close();
} else if (e.key === "+" || e.key === "=") {
gestureState.scale = Math.min(5, gestureState.scale * 1.2);
applyTransform();
} else if (e.key === "-") {
gestureState.scale = Math.max(1, gestureState.scale / 1.2);
applyTransform();
} else if (e.key === "0") {
gestureState.scale = 1;
gestureState.panX = 0;
gestureState.panY = 0;
applyTransform();
}
}
// Apply transforms
let final = computeFinal();
let baseScale = final.scale;
function computeFinal() {
const vv = window.visualViewport;
const vw = vv ? vv.width : window.innerWidth;
const vh = vv ? vv.height : window.innerHeight;
const vx = vv ? vv.offsetLeft : 0;
const vy = vv ? vv.offsetTop : 0;
const margin = 20;
const maxW = Math.min(vw - margin * 2, 1200);
const maxH = vh - margin * 2;
const scale = Math.min(maxW / rect.width, maxH / rect.height, 2);
const centerX = vx + vw / 2;
const centerY = vy + vh / 2;
return { cx: centerX, cy: centerY, scale };
}
function applyTransform() {
img.style.left = final.cx + "px";
img.style.top = final.cy + "px";
const overlayScale = Math.max(1, gestureState.scale);
overlay.style.transform = overlayScale > 1 ? `scale(${overlayScale})` : "none";
const totalScale = baseScale;
const transform = `translate3d(-50%, -50%, 0) translate3d(${gestureState.panX}px, ${gestureState.panY}px, 0) scale(${totalScale})`;
img.style.transform = transform;
}
// Close function
function close(immediate = false) {
overlay.classList.add("closing");
// Animate back to original position
img.style.left = startCX + "px";
img.style.top = startCY + "px";
img.style.transform = `translate3d(-50%, -50%, 0) scale(1)`;
// Cleanup event listeners
cleanup();
if (immediate) {
overlay.remove();
return;
}
// Remove after animation
const done = () => overlay.remove();
overlay.addEventListener("transitionend", done, { once: true });
img.addEventListener("transitionend", done, { once: true });
// Fallback removal
setTimeout(() => {
if (overlay.parentNode) {
overlay.remove();
}
}, 400);
}
function cleanup() {
window.removeEventListener("keydown", onKeyDown, true);
overlay.removeEventListener("wheel", onWheel);
overlay.removeEventListener("pointermove", onPointerMove);
overlay.removeEventListener("pointerdown", onPointerDown);
overlay.removeEventListener("pointerup", onPointerUp);
overlay.removeEventListener("pointercancel", onPointerCancel);
window.removeEventListener("resize", onResize);
if (window.visualViewport) {
window.visualViewport.removeEventListener("resize", onResize);
window.visualViewport.removeEventListener("scroll", onResize);
}
if (pinchEndTimer) {
clearTimeout(pinchEndTimer);
}
}
// Handle viewport changes
function onResize() {
final = computeFinal();
baseScale = final.scale;
applyTransform();
}
// Setup event listeners
overlay.addEventListener("wheel", onWheel, { passive: false });
overlay.addEventListener("pointermove", onPointerMove);
overlay.addEventListener("pointerdown", onPointerDown);
overlay.addEventListener("pointerup", onPointerUp);
overlay.addEventListener("pointercancel", onPointerCancel);
window.addEventListener("keydown", onKeyDown, true);
window.addEventListener("resize", onResize, { passive: true });
if (window.visualViewport) {
window.visualViewport.addEventListener("resize", onResize, { passive: true });
window.visualViewport.addEventListener("scroll", onResize, { passive: true });
}
// Add to DOM
document.body.appendChild(overlay);
// Trigger opening animation
requestAnimationFrame(() => {
overlay.classList.add("show");
applyTransform();
});
}
// Initialize after DOM ready
function init() {
const container = document.querySelector(".content");
if (!container) return;
container.addEventListener("click", function (e) {
const target = e.target;
if (!(target instanceof HTMLImageElement)) return;
// Only allow images inside `.content` that are NOT within a `.not-prose` block
if (target.closest('.not-prose')) return;
if (target.dataset.noZoom === "" || target.dataset.noZoom === "true") return;
e.preventDefault();
e.stopPropagation();
createOverlayFromTarget(target);
}, true);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
})();

View File

@@ -7,16 +7,17 @@ BASE_URL=${1:-"http://localhost:1313"}
echo "Using base URL: $BASE_URL" echo "Using base URL: $BASE_URL"
# Version configuration - modify these arrays to specify versions to build # Version configuration - modify these arrays to specify versions to build
# Format: "ref:display_name" (ref can be tag, branch, or commit hash, display name is what will appear in URL) # MAIN_VERSION format: "ref:display_name:source_dir"
MAIN_VERSION="v0.10.1:latest" # VERSIONS format: "ref:display_name:source_dir" where source_dir is either "docs" or "exampleSite"
MAIN_VERSION="v0.11.1:latest:docs"
VERSIONS=( VERSIONS=(
"main:latest" # latest version always builds from main "main:latest:docs" # latest version always builds from main
"v0.9.6:v0.9" "v0.10.2:v0.10:exampleSite"
"v0.8.6:v0.8" "v0.9.6:v0.9:exampleSite"
) )
# Parse main version # Parse main version
IFS=':' read -r MAIN_REF MAIN_NAME <<< "$MAIN_VERSION" IFS=':' read -r MAIN_REF MAIN_NAME MAIN_DIR <<< "$MAIN_VERSION"
# Ensure clean public directory # Ensure clean public directory
rm -rf public rm -rf public
@@ -29,13 +30,13 @@ GIT_HASH=$(git rev-parse --short HEAD)
echo "Building main site from $MAIN_REF (commit: $GIT_HASH)" echo "Building main site from $MAIN_REF (commit: $GIT_HASH)"
hugo \ hugo \
--minify \ --minify \
--themesDir=../.. --source=exampleSite \ --themesDir=../.. --source=$MAIN_DIR \
--baseURL "$BASE_URL/" \ --baseURL "$BASE_URL/" \
--destination=../public --destination=../public
# Build all versions # Build all versions
for VERSION in "${VERSIONS[@]}"; do for VERSION in "${VERSIONS[@]}"; do
IFS=':' read -r REF NAME <<< "$VERSION" IFS=':' read -r REF NAME DIR <<< "$VERSION"
git checkout $REF git checkout $REF
GIT_HASH=$(git rev-parse --short HEAD) GIT_HASH=$(git rev-parse --short HEAD)
@@ -44,7 +45,7 @@ for VERSION in "${VERSIONS[@]}"; do
mkdir -p "public/versions/$NAME" mkdir -p "public/versions/$NAME"
hugo \ hugo \
--minify \ --minify \
--themesDir=../.. --source=exampleSite \ --themesDir=../.. --source=$DIR \
--baseURL "$BASE_URL/versions/$NAME/" \ --baseURL "$BASE_URL/versions/$NAME/" \
--destination="../public/versions/$NAME" --destination="../public/versions/$NAME"
done done

View File

@@ -1,4 +1,4 @@
# Theme development config for exampleSite # Theme development config for documentation site
# https://gohugo.io/configuration/build/#cache-busters # https://gohugo.io/configuration/build/#cache-busters
[build] [build]
[build.buildStats] [build.buildStats]

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -221,14 +221,14 @@ hugo gen chromastyles --style=github
می‌توانید اسکریپت‌های سفارشی را به انتهای head برای هر صفحه با افزودن فایل زیر اضافه کنید: می‌توانید اسکریپت‌های سفارشی را به انتهای head برای هر صفحه با افزودن فایل زیر اضافه کنید:
``` ```
layouts/partials/custom/head-end.html layouts/_partials/custom/head-end.html
``` ```
## بخش اضافی سفارشی در پاورقی ## بخش اضافی سفارشی در پاورقی
می‌توانید بخش اضافی در پاورقی با ایجاد یک فایل `layouts/partials/custom/footer.html` در سایت خود اضافه کنید. می‌توانید بخش اضافی در پاورقی با ایجاد یک فایل `layouts/_partials/custom/footer.html` در سایت خود اضافه کنید.
```html {filename="layouts/partials/custom/footer.html"} ```html {filename="layouts/_partials/custom/footer.html"}
<!-- عنصر پاورقی شما اینجا --> <!-- عنصر پاورقی شما اینجا -->
``` ```

View File

@@ -221,14 +221,14 @@ hugo gen chromastyles --style=github
すべてのページのheadの終わりにカスタムスクリプトを追加するには、以下のファイルを作成します: すべてのページのheadの終わりにカスタムスクリプトを追加するには、以下のファイルを作成します:
``` ```
layouts/partials/custom/head-end.html layouts/_partials/custom/head-end.html
``` ```
## フッターへのカスタムセクション追加 ## フッターへのカスタムセクション追加
フッターに追加セクションを追加するには、サイト内に`layouts/partials/custom/footer.html`ファイルを作成します。 フッターに追加セクションを追加するには、サイト内に`layouts/_partials/custom/footer.html`ファイルを作成します。
```html {filename="layouts/partials/custom/footer.html"} ```html {filename="layouts/_partials/custom/footer.html"}
<!-- ここにフッター要素を追加 --> <!-- ここにフッター要素を追加 -->
``` ```

View File

@@ -221,14 +221,14 @@ To override the default syntax highlighting theme, we can add the generated styl
You may add custom scripts to the end of the head for every page by adding the following file: You may add custom scripts to the end of the head for every page by adding the following file:
``` ```
layouts/partials/custom/head-end.html layouts/_partials/custom/head-end.html
``` ```
## Custom Extra Section in Footer ## Custom Extra Section in Footer
You can add extra section in the footer by creating a file `layouts/partials/custom/footer.html` in your site. You can add extra section in the footer by creating a file `layouts/_partials/custom/footer.html` in your site.
```html {filename="layouts/partials/custom/footer.html"} ```html {filename="layouts/_partials/custom/footer.html"}
<!-- Your footer element here --> <!-- Your footer element here -->
``` ```

View File

@@ -221,14 +221,14 @@ hugo gen chromastyles --style=github
您可以通过添加以下文件在每个页面的 head 末尾添加自定义脚本: 您可以通过添加以下文件在每个页面的 head 末尾添加自定义脚本:
``` ```
layouts/partials/custom/head-end.html layouts/_partials/custom/head-end.html
``` ```
## 自定义页脚额外部分 ## 自定义页脚额外部分
您可以通过在站点中创建 `layouts/partials/custom/footer.html` 文件来添加页脚的额外部分。 您可以通过在站点中创建 `layouts/_partials/custom/footer.html` 文件来添加页脚的额外部分。
```html {filename="layouts/partials/custom/footer.html"} ```html {filename="layouts/_partials/custom/footer.html"}
<!-- 您的页脚元素放在这里 --> <!-- 您的页脚元素放在这里 -->
``` ```

View File

@@ -7,7 +7,7 @@ tags:
Hugo تنظیمات خود را از فایل `hugo.yaml` در ریشه سایت شما می‌خواند. Hugo تنظیمات خود را از فایل `hugo.yaml` در ریشه سایت شما می‌خواند.
فایل پیکربندی جایی است که می‌توانید تمام جنبه‌های سایت خود را تنظیم کنید. فایل پیکربندی جایی است که می‌توانید تمام جنبه‌های سایت خود را تنظیم کنید.
برای آشنایی جامع با تنظیمات موجود و بهترین روش‌ها، فایل پیکربندی این سایت [`exampleSite/hugo.yaml`](https://github.com/imfing/hextra/blob/main/exampleSite/hugo.yaml) را در GitHub بررسی کنید. برای آشنایی جامع با تنظیمات موجود و بهترین روش‌ها، فایل پیکربندی این سایت [`docs/hugo.yaml`](https://github.com/imfing/hextra/blob/main/docs/hugo.yaml) را در GitHub بررسی کنید.
<!--more--> <!--more-->
@@ -416,4 +416,4 @@ outputs:
سایر ویژگی‌های Open Graph می‌توانند فقط یک مقدار داشته باشند. سایر ویژگی‌های Open Graph می‌توانند فقط یک مقدار داشته باشند.
به عنوان مثال، این صفحه یک تگ `og:image` (که تصویری برای پیش‌نمایش در اشتراک‌گذاری‌های اجتماعی پیکربندی می‌کند) و یک تگ `og:audio` دارد. به عنوان مثال، این صفحه یک تگ `og:image` (که تصویری برای پیش‌نمایش در اشتراک‌گذاری‌های اجتماعی پیکربندی می‌کند) و یک تگ `og:audio` دارد.
```yaml {filename ```yaml {filename

View File

@@ -7,7 +7,7 @@ tags:
Hugo はサイトのルートにある `hugo.yaml` から設定を読み込みます。 Hugo はサイトのルートにある `hugo.yaml` から設定を読み込みます。
この設定ファイルであなたのサイトのあらゆる側面を設定できます。 この設定ファイルであなたのサイトのあらゆる側面を設定できます。
利用可能な設定項目とベストプラクティスを網羅的に理解するには、GitHub 上のこのサイトの設定ファイル [`exampleSite/hugo.yaml`](https://github.com/imfing/hextra/blob/main/exampleSite/hugo.yaml) を参照してください。 利用可能な設定項目とベストプラクティスを網羅的に理解するには、GitHub 上のこのサイトの設定ファイル [`docs/hugo.yaml`](https://github.com/imfing/hextra/blob/main/docs/hugo.yaml) を参照してください。
<!--more--> <!--more-->
@@ -422,4 +422,4 @@ params:
images: images:
- "/img/config-image.jpg" - "/img/config-image.jpg"
audio: "config-talk.mp3" audio: "config-talk.mp3"
``` ```

View File

@@ -7,7 +7,7 @@ tags:
Hugo reads its configuration from `hugo.yaml` in the root of your Hugo site. Hugo reads its configuration from `hugo.yaml` in the root of your Hugo site.
The config file is where you can configure all aspects of your site. The config file is where you can configure all aspects of your site.
Check out the config file for this site [`exampleSite/hugo.yaml`](https://github.com/imfing/hextra/blob/main/exampleSite/hugo.yaml) on GitHub to get a comprehensive idea of available settings and best practices. Check out the config file for this site [`docs/hugo.yaml`](https://github.com/imfing/hextra/blob/main/docs/hugo.yaml) on GitHub to get a comprehensive idea of available settings and best practices.
<!--more--> <!--more-->
@@ -164,6 +164,21 @@ menu:
weight: 3 weight: 3
``` ```
### Hiding
Hiding the sidebar can be done using front matter:
```yaml {filename="content/docs/guide/configuration.md"}
---
title: Configuration
sidebar:
hide: true
---
```
This will hide the main sidebar from the page, freeing up space for the main content of the page.
## Right Sidebar ## Right Sidebar
### Table of Contents ### Table of Contents
@@ -404,7 +419,7 @@ params:
umami: umami:
serverURL: "https://example.com" serverURL: "https://example.com"
websiteID: "94db1cb1-74f4-4a40-ad6c-962362670409" websiteID: "94db1cb1-74f4-4a40-ad6c-962362670409"
# scriptName: "umami.js" # optional (default: umami.js) # scriptName: "script.js" # optional (default: script.js)
# https://umami.is/docs/tracker-configuration#data-host-url # https://umami.is/docs/tracker-configuration#data-host-url
# hostURL: "http://stats.example.org" # optional # hostURL: "http://stats.example.org" # optional
# https://umami.is/docs/tracker-configuration#data-auto-track # https://umami.is/docs/tracker-configuration#data-auto-track
@@ -456,19 +471,41 @@ The llms.txt file is automatically generated from your content structure and mak
### Open Graph ### Open Graph
To add [Open Graph](https://ogp.me/) metadata to a page, add values in the frontmatter params. To add [Open Graph](https://ogp.me/) metadata, you can:
- add values in the front-matter params of a page
- or add values in the Hugo configuration file
As a page can have multiple `image` and `video` tags, place their values in an array. As a page can have multiple `image` and `video` tags, place their values in an array.
Other Open Graph properties can have only one value. Other Open Graph properties can have only one value.
For example, this page has an `og:image` tag (which configures an image to preview on social shares) and an `og:audio` tag.
```yaml {filename="content/docs/guide/configuration.md"} {{< tabs items="Page Level, Global Level" >}}
title: "Configuration" {{< tab >}}
```md {filename="mypage.md"}
---
title: "My Page"
params: params:
images: images:
- "/img/config-image.jpg" - "/images/image01.jpg"
audio: "config-talk.mp3" audio: "podcast02.mp3"
videos:
- "video01.mp4"
---
Page content.
``` ```
{{< /tab >}}
{{< tab >}}
```yaml {filename="hugo.yaml"}
params:
images:
- "/images/image01.jpg"
audio: "podcast02.mp3"
videos:
- "video01.mp4"
```
{{< /tab >}}
{{< /tabs >}}
### Banner ### Banner

View File

@@ -7,7 +7,7 @@ tags:
Hugo 从站点根目录的 `hugo.yaml` 读取配置。 Hugo 从站点根目录的 `hugo.yaml` 读取配置。
配置文件可用来调整站点的所有方面。 配置文件可用来调整站点的所有方面。
查看本网站的示例配置文件 [`exampleSite/hugo.yaml`](https://github.com/imfing/hextra/blob/main/exampleSite/hugo.yaml) 以全面了解可用设置和最佳实践。 查看本网站的示例配置文件 [`docs/hugo.yaml`](https://github.com/imfing/hextra/blob/main/docs/hugo.yaml) 以全面了解可用设置和最佳实践。
<!--more--> <!--more-->
@@ -422,4 +422,4 @@ params:
images: images:
- "/img/config-image.jpg" - "/img/config-image.jpg"
audio: "config-talk.mp3" audio: "config-talk.mp3"
``` ```

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