Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 5 additions & 102 deletions src/client/imageFigureNumber.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,8 @@
/**
* Client module to automatically add figure numbers and captions to images
* based on their alt text in blog posts and docs.
* Client module hook for route updates.
* Figure numbering and captions are now handled natively via React MDX component
* (@theme/MDXComponents/Img) and CSS counters, avoiding raw VDOM DOM mutations.
*/
export function onRouteDidUpdate({ location, previousLocation }) {
// Only run on blog and doc pages
if (!location.pathname.match(/\/blog\//) && !location.pathname.match(/\/docs\//)) {
return;
}

// Wait for DOM to be ready with multiple attempts
setTimeout(() => addFigureNumbers(), 100);
setTimeout(() => addFigureNumbers(), 500);
setTimeout(() => addFigureNumbers(), 1000);
}

function addFigureNumbers() {
// Find all images in markdown content
const articleContent = document.querySelector("article");
if (!articleContent) {
return;
}

// Find all images that are not in header or footer
const images = Array.from(articleContent.querySelectorAll("img")).filter((img) => {
// Filter out logos, avatars, icons
const parentClass = img.parentElement?.className || "";
const isLogo = parentClass.includes("logo") || img.alt.includes("logo");
const isIcon = img.width < 100 || img.height < 100;
return !isLogo && !isIcon;
});

let figureCount = 0;

images.forEach((img) => {
// Get alt text
const altText = img.getAttribute("alt") || "";
if (!altText.trim()) {
return;
}

// Check if already has figcaption with figure number
const existingFigure = img.closest("figure");
if (existingFigure) {
const existingFigcaption = existingFigure.querySelector("figcaption");
if (existingFigcaption && existingFigcaption.textContent.match(/^(图|Figure)\d+:/)) {
figureCount++;
return;
}
}

// Increment counter
figureCount++;

// Create figure wrapper if it doesn't exist
let figure = img.closest("figure");
if (!figure) {
figure = document.createElement("figure");
img.parentNode.insertBefore(figure, img);
figure.appendChild(img);
}

// Remove existing figcaption if any (but only if it doesn't have figure number)
const existingFigcaption = figure.querySelector("figcaption");
if (existingFigcaption && !existingFigcaption.textContent.match(/^(图|Figure)\d+:/)) {
existingFigcaption.remove();
} else if (existingFigcaption) {
// Update existing figcaption with figure number
const currentLang = document.documentElement.lang || "en";
const prefix = currentLang.startsWith("zh") ? "图" : "Figure";
existingFigcaption.textContent = `${prefix}${figureCount}: ${altText}`;
return;
}

// Create figcaption with number and alt text
const figcaption = document.createElement("figcaption");
const currentLang = document.documentElement.lang || "en";

// Use appropriate prefix based on language
const prefix = currentLang.startsWith("zh") ? "图" : "Figure";
figcaption.textContent = `${prefix}${figureCount}: ${altText}`;

// Append figcaption to figure
figure.appendChild(figcaption);

// Add styling class
figure.style.cssText = `
margin: 2em 0;
text-align: center;
`;

img.style.cssText = `
max-width: 100%;
height: auto;
border-radius: 8px;
`;

figcaption.style.cssText = `
margin-top: 0.8em;
font-size: 0.9em;
color: var(--ifm-color-emphasis-600);
font-style: italic;
`;
});
export function onRouteDidUpdate() {
// No imperative DOM mutation needed
}
33 changes: 33 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,48 @@ pre {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
}

.theme-doc-markdown article,
.theme-blog-markdown article,
article.markdown,
.markdown {
counter-reset: hami-figure-counter;
}

figure.hami-doc-figure {
counter-increment: hami-figure-counter;
margin: 2em 0;
text-align: center;
}

figure.hami-doc-figure img,
.theme-doc-markdown img,
.theme-blog-markdown img,
article.markdown img {
max-width: 100%;
height: auto;
background: #ffffff;
border-radius: 10px;
padding: 4px;
cursor: zoom-in;
}

figure.hami-doc-figure figcaption {
margin-top: 0.8em;
font-size: 0.9em;
color: var(--ifm-color-emphasis-600);
font-style: italic;
}

figure.hami-doc-figure figcaption::before {
content: "Figure " counter(hami-figure-counter) ": ";
font-weight: 600;
}

html[lang^="zh"] figure.hami-doc-figure figcaption::before {
content: "图 " counter(hami-figure-counter) ": ";
font-weight: 600;
}

body.hami-lightbox-open {
overflow: hidden;
}
Expand Down
2 changes: 0 additions & 2 deletions src/theme/BlogPostItem/Container/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import useImageLightbox from "../../utils/useImageLightbox";

export default function BlogPostItemContainer({ children, className }) {
useImageLightbox();
const { isBlogPostPage } = useBlogPost();

return (
Expand Down
2 changes: 0 additions & 2 deletions src/theme/DocItem/Layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import DocItemContent from "@theme/DocItem/Content";
import DocBreadcrumbs from "@theme/DocBreadcrumbs";
import ContentVisibility from "@theme/ContentVisibility";
import styles from "./styles.module.css";
import useImageLightbox from "../../utils/useImageLightbox";

function useDocTOC() {
const { frontMatter, toc } = useDoc();
Expand All @@ -33,7 +32,6 @@ function useDocTOC() {
}

export default function DocItemLayout({ children }) {
useImageLightbox();
const docTOC = useDocTOC();
const { metadata } = useDoc();
return (
Expand Down
10 changes: 7 additions & 3 deletions src/theme/Layout/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from "react";
import Layout from "@theme-original/Layout";
import useImageLightbox from "../utils/useImageLightbox";
import ImageLightbox from "../Lightbox";

export default function LayoutWrapper(props) {
useImageLightbox();
return <Layout {...props} />;
return (
<>
<Layout {...props} />
<ImageLightbox />
</>
);
}
Loading