first commit

This commit is contained in:
Missdrop
2025-07-16 16:30:56 +00:00
commit 7ee33927cb
11326 changed files with 1230901 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
const initCopyCode = () => {
HTMLElement.prototype.wrap = function (wrapper) {
this.parentNode.insertBefore(wrapper, this);
this.parentNode.removeChild(this);
wrapper.appendChild(this);
};
document.querySelectorAll("figure.highlight").forEach((element) => {
const container = document.createElement("div");
element.wrap(container);
container.classList.add("highlight-container");
container.insertAdjacentHTML(
"beforeend",
'<div class="copy-button"><i class="fa-regular fa-copy"></i></div>',
);
container.insertAdjacentHTML(
"beforeend",
'<div class="fold-button"><i class="fa-solid fa-chevron-down"></i></div>',
);
const copyButton = container.querySelector(".copy-button");
const foldButton = container.querySelector(".fold-button");
copyButton.addEventListener("click", () => {
const codeLines = [...container.querySelectorAll(".code .line")];
const code = codeLines.map((line) => line.innerText).join("\n");
// Copy code to clipboard
navigator.clipboard.writeText(code);
// Display 'copied' icon
copyButton.querySelector("i").className = "fa-regular fa-check";
// Reset icon after a while
setTimeout(() => {
copyButton.querySelector("i").className = "fa-regular fa-copy";
}, 1000);
});
foldButton.addEventListener("click", () => {
container.classList.toggle("folded");
foldButton.querySelector("i").className = container.classList.contains(
"folded",
)
? "fa-solid fa-chevron-up"
: "fa-solid fa-chevron-down";
});
});
};
export default initCopyCode;

View File

@@ -0,0 +1,170 @@
export default function imageViewer() {
let isBigImage = false;
let scale = 1;
let isMouseDown = false;
let dragged = false;
let currentImgIndex = 0;
let lastMouseX = 0;
let lastMouseY = 0;
let translateX = 0;
let translateY = 0;
const maskDom = document.querySelector(".image-viewer-container");
if (!maskDom) {
console.warn(
"Image viewer container not found. Exiting imageViewer function.",
);
return;
}
const targetImg = maskDom.querySelector("img");
if (!targetImg) {
console.warn(
"Target image not found in image viewer container. Exiting imageViewer function.",
);
return;
}
const showHandle = (isShow) => {
document.body.style.overflow = isShow ? "hidden" : "auto";
isShow
? maskDom.classList.add("active")
: maskDom.classList.remove("active");
};
const zoomHandle = (event) => {
event.preventDefault();
const rect = targetImg.getBoundingClientRect();
const offsetX = event.clientX - rect.left;
const offsetY = event.clientY - rect.top;
const dx = offsetX - rect.width / 2;
const dy = offsetY - rect.height / 2;
const oldScale = scale;
scale += event.deltaY * -0.001;
scale = Math.min(Math.max(0.8, scale), 4);
if (oldScale < scale) {
// Zooming in
translateX -= dx * (scale - oldScale);
translateY -= dy * (scale - oldScale);
} else {
// Zooming out
translateX = 0;
translateY = 0;
}
targetImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
};
const dragStartHandle = (event) => {
event.preventDefault();
isMouseDown = true;
lastMouseX = event.clientX;
lastMouseY = event.clientY;
targetImg.style.cursor = "grabbing";
};
let lastTime = 0;
const throttle = 100;
const dragHandle = (event) => {
if (isMouseDown) {
const currentTime = new Date().getTime();
if (currentTime - lastTime < throttle) {
return;
}
lastTime = currentTime;
const deltaX = event.clientX - lastMouseX;
const deltaY = event.clientY - lastMouseY;
translateX += deltaX;
translateY += deltaY;
lastMouseX = event.clientX;
lastMouseY = event.clientY;
targetImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
dragged = true;
}
};
const dragEndHandle = (event) => {
if (isMouseDown) {
event.stopPropagation();
}
isMouseDown = false;
targetImg.style.cursor = "grab";
};
targetImg.addEventListener("wheel", zoomHandle, { passive: false });
targetImg.addEventListener("mousedown", dragStartHandle, { passive: false });
targetImg.addEventListener("mousemove", dragHandle, { passive: false });
targetImg.addEventListener("mouseup", dragEndHandle, { passive: false });
targetImg.addEventListener("mouseleave", dragEndHandle, { passive: false });
maskDom.addEventListener("click", (event) => {
if (!dragged) {
isBigImage = false;
showHandle(isBigImage);
scale = 1;
translateX = 0;
translateY = 0;
targetImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
}
dragged = false;
});
const imgDoms = document.querySelectorAll(
".markdown-body img, .masonry-item img, #shuoshuo-content img",
);
const escapeKeyListener = (event) => {
if (event.key === "Escape" && isBigImage) {
isBigImage = false;
showHandle(isBigImage);
scale = 1;
translateX = 0;
translateY = 0;
targetImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
// Remove the event listener when the image viewer is closed
document.removeEventListener("keydown", escapeKeyListener);
}
};
if (imgDoms.length > 0) {
imgDoms.forEach((img, index) => {
img.addEventListener("click", () => {
currentImgIndex = index;
isBigImage = true;
showHandle(isBigImage);
targetImg.src = img.src;
document.addEventListener("keydown", escapeKeyListener);
});
});
const handleArrowKeys = (event) => {
if (!isBigImage) return;
if (event.key === "ArrowUp" || event.key === "ArrowLeft") {
currentImgIndex =
(currentImgIndex - 1 + imgDoms.length) % imgDoms.length;
} else if (event.key === "ArrowDown" || event.key === "ArrowRight") {
currentImgIndex = (currentImgIndex + 1) % imgDoms.length;
} else {
return;
}
const currentImg = imgDoms[currentImgIndex];
let newSrc = currentImg.src;
if (currentImg.hasAttribute("lazyload")) {
newSrc = currentImg.getAttribute("data-src");
currentImg.src = newSrc;
currentImg.removeAttribute("lazyload");
}
targetImg.src = newSrc;
};
document.addEventListener("keydown", handleArrowKeys);
} else {
// console.warn("No images found to attach image viewer functionality.");
}
}

View File

@@ -0,0 +1,165 @@
import { main } from "../main.js";
const elementCode = ".mermaid";
const saveOriginalData = function () {
return new Promise((resolve, reject) => {
try {
var els = document.querySelectorAll(elementCode),
count = els.length;
els.forEach((element) => {
element.setAttribute("data-original-code", element.innerHTML);
count--;
if (count == 0) {
resolve();
}
});
} catch (error) {
reject(error);
}
});
};
const resetProcessed = function () {
return new Promise((resolve, reject) => {
try {
var els = document.querySelectorAll(elementCode),
count = els.length;
els.forEach((element) => {
if (element.getAttribute("data-original-code") != null) {
element.removeAttribute("data-processed");
element.innerHTML = element.getAttribute("data-original-code");
}
count--;
if (count == 0) {
resolve();
}
});
} catch (error) {
reject(error);
}
});
};
export const ModeToggle = {
modeToggleButton_dom: null,
iconDom: null,
mermaidLightTheme: null,
mermaidDarkTheme: null,
async mermaidInit(theme) {
if (window.mermaid) {
await resetProcessed();
mermaid.initialize({ theme });
mermaid.init({ theme }, document.querySelectorAll(elementCode));
}
},
enableLightMode() {
document.body.classList.remove("dark-mode");
document.documentElement.classList.remove("dark");
document.body.classList.add("light-mode");
document.documentElement.classList.add("light");
this.iconDom.className = "fa-regular fa-moon";
main.styleStatus.isDark = false;
main.setStyleStatus();
this.mermaidInit(this.mermaidLightTheme);
this.setGiscusTheme();
},
enableDarkMode() {
document.body.classList.remove("light-mode");
document.documentElement.classList.remove("light");
document.body.classList.add("dark-mode");
document.documentElement.classList.add("dark");
this.iconDom.className = "fa-regular fa-brightness";
main.styleStatus.isDark = true;
main.setStyleStatus();
this.mermaidInit(this.mermaidDarkTheme);
this.setGiscusTheme();
},
async setGiscusTheme(theme) {
if (document.querySelector("#giscus-container")) {
let giscusFrame = document.querySelector("iframe.giscus-frame");
while (!giscusFrame) {
await new Promise((r) => setTimeout(r, 1000));
giscusFrame = document.querySelector("iframe.giscus-frame");
}
while (giscusFrame.classList.contains("giscus-frame--loading"))
await new Promise((r) => setTimeout(r, 1000));
theme ??= main.styleStatus.isDark ? "dark" : "light";
giscusFrame.contentWindow.postMessage(
{
giscus: {
setConfig: {
theme: theme,
},
},
},
"https://giscus.app",
);
}
},
isDarkPrefersColorScheme() {
return (
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)")
);
},
initModeStatus() {
const styleStatus = main.getStyleStatus();
if (styleStatus) {
styleStatus.isDark ? this.enableDarkMode() : this.enableLightMode();
} else {
this.isDarkPrefersColorScheme().matches
? this.enableDarkMode()
: this.enableLightMode();
}
},
initModeToggleButton() {
this.modeToggleButton_dom.addEventListener("click", () => {
const isDark = document.body.classList.contains("dark-mode");
isDark ? this.enableLightMode() : this.enableDarkMode();
});
},
initModeAutoTrigger() {
const isDarkMode = this.isDarkPrefersColorScheme();
isDarkMode.addEventListener("change", (e) => {
e.matches ? this.enableDarkMode() : this.enableLightMode();
});
},
async init() {
this.modeToggleButton_dom = document.querySelector(
".tool-dark-light-toggle",
);
this.iconDom = document.querySelector(".tool-dark-light-toggle i");
this.mermaidLightTheme =
typeof theme.mermaid !== "undefined" &&
typeof theme.mermaid.style !== "undefined" &&
typeof theme.mermaid.style.light !== "undefined"
? theme.mermaid.style.light
: "default";
this.mermaidDarkTheme =
typeof theme.mermaid !== "undefined" &&
typeof theme.mermaid.style !== "undefined" &&
typeof theme.mermaid.style.dark !== "undefined"
? theme.mermaid.style.dark
: "dark";
this.initModeStatus();
this.initModeToggleButton();
this.initModeAutoTrigger();
try {
await saveOriginalData().catch(console.error);
} catch (error) {}
},
};
// Exported function
export default function initModeToggle() {
ModeToggle.init();
}

View File

@@ -0,0 +1,327 @@
export default function initLocalSearch() {
// Search DB path
let searchPath = config.path;
if (!searchPath) {
// Search DB path
console.warn("`hexo-generator-searchdb` plugin is not installed!");
return;
}
// Popup Window
let isfetched = false;
let datas;
let isXml = true;
if (searchPath.length === 0) {
searchPath = "search.xml";
} else if (searchPath.endsWith("json")) {
isXml = false;
}
const searchInputDom = document.querySelector(".search-input");
const resultContent = document.getElementById("search-result");
const getIndexByWord = (word, text, caseSensitive) => {
let wordLen = word.length;
if (wordLen === 0) return [];
let startPosition = 0;
let position = [];
let index = [];
if (!caseSensitive) {
text = text.toLowerCase();
word = word.toLowerCase();
}
while ((position = text.indexOf(word, startPosition)) > -1) {
index.push({ position, word });
startPosition = position + wordLen;
}
return index;
};
// Merge hits into slices
const mergeIntoSlice = (start, end, index, searchText) => {
let currentItem = index[index.length - 1];
let { position, word } = currentItem;
let hits = [];
let searchTextCountInSlice = 0;
// Merge hits into the slice
while (position + word.length <= end && index.length !== 0) {
if (word === searchText) {
searchTextCountInSlice++;
}
hits.push({
position,
length: word.length,
});
const wordEnd = position + word.length;
// Move to the next position of the hit
index.pop();
for (let i = index.length - 1; i >= 0; i--) {
currentItem = index[i];
position = currentItem.position;
word = currentItem.word;
if (wordEnd <= position) {
break;
} else {
index.pop();
}
}
}
return {
hits,
start,
end,
searchTextCount: searchTextCountInSlice,
};
};
// Highlight title and content
const highlightKeyword = (text, slice) => {
let result = "";
let prevEnd = slice.start;
slice.hits.forEach((hit) => {
result += text.substring(prevEnd, hit.position);
let end = hit.position + hit.length;
result += `<b class="search-keyword">${text.substring(
hit.position,
end,
)}</b>`;
prevEnd = end;
});
result += text.substring(prevEnd, slice.end);
return result;
};
const inputEventFunction = () => {
if (!isfetched) return;
let searchText = searchInputDom.value.trim().toLowerCase();
let keywords = searchText.split(/[-\s]+/);
if (keywords.length > 1) {
keywords.push(searchText);
}
let resultItems = [];
if (searchText.length > 0) {
// Perform local searching
datas.forEach(({ title, content, url }) => {
let titleInLowerCase = title.toLowerCase();
let contentInLowerCase = content.toLowerCase();
let indexOfTitle = [];
let indexOfContent = [];
let searchTextCount = 0;
keywords.forEach((keyword) => {
indexOfTitle = indexOfTitle.concat(
getIndexByWord(keyword, titleInLowerCase, false),
);
indexOfContent = indexOfContent.concat(
getIndexByWord(keyword, contentInLowerCase, false),
);
});
// Show search results
if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
let hitCount = indexOfTitle.length + indexOfContent.length;
// Sort index by position of keyword
[indexOfTitle, indexOfContent].forEach((index) => {
index.sort((itemLeft, itemRight) => {
if (itemRight.position !== itemLeft.position) {
return itemRight.position - itemLeft.position;
}
return itemLeft.word.length - itemRight.word.length;
});
});
let slicesOfTitle = [];
if (indexOfTitle.length !== 0) {
let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
searchTextCount += tmp.searchTextCountInSlice;
slicesOfTitle.push(tmp);
}
let slicesOfContent = [];
while (indexOfContent.length !== 0) {
let item = indexOfContent[indexOfContent.length - 1];
let { position, word } = item;
// Cut out 100 characters
let start = position - 20;
let end = position + 80;
if (start < 0) {
start = 0;
}
if (end < position + word.length) {
end = position + word.length;
}
if (end > content.length) {
end = content.length;
}
let tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
searchTextCount += tmp.searchTextCountInSlice;
slicesOfContent.push(tmp);
}
// Sort slices in content by search text's count and hits' count
slicesOfContent.sort((sliceLeft, sliceRight) => {
if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
return sliceRight.searchTextCount - sliceLeft.searchTextCount;
} else if (sliceLeft.hits.length !== sliceRight.hits.length) {
return sliceRight.hits.length - sliceLeft.hits.length;
}
return sliceLeft.start - sliceRight.start;
});
// Select top N slices in content
let upperBound = parseInt(
theme.navbar.search.top_n_per_article
? theme.navbar.search.top_n_per_article
: 1,
10,
);
if (upperBound >= 0) {
slicesOfContent = slicesOfContent.slice(0, upperBound);
}
let resultItem = "";
if (slicesOfTitle.length !== 0) {
resultItem += `<li><a href="${url}" class="search-result-title">${highlightKeyword(
title,
slicesOfTitle[0],
)}</a>`;
} else {
resultItem += `<li><a href="${url}" class="search-result-title">${title}</a>`;
}
slicesOfContent.forEach((slice) => {
resultItem += `<a href="${url}"><p class="search-result">${highlightKeyword(
content,
slice,
)}...</p></a>`;
});
resultItem += "</li>";
resultItems.push({
item: resultItem,
id: resultItems.length,
hitCount,
searchTextCount,
});
}
});
}
if (keywords.length === 1 && keywords[0] === "") {
resultContent.innerHTML =
'<div id="no-result"><i class="fa-solid fa-magnifying-glass fa-5x"></i></div>';
} else if (resultItems.length === 0) {
resultContent.innerHTML =
'<div id="no-result"><i class="fa-solid fa-box-open fa-5x"></i></div>';
} else {
resultItems.sort((resultLeft, resultRight) => {
if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
return resultRight.searchTextCount - resultLeft.searchTextCount;
} else if (resultLeft.hitCount !== resultRight.hitCount) {
return resultRight.hitCount - resultLeft.hitCount;
}
return resultRight.id - resultLeft.id;
});
let searchResultList = '<ul class="search-result-list">';
resultItems.forEach((result) => {
searchResultList += result.item;
});
searchResultList += "</ul>";
resultContent.innerHTML = searchResultList;
window.pjax && window.pjax.refresh(resultContent);
}
};
const fetchData = () => {
fetch(config.root + searchPath)
.then((response) => response.text())
.then((res) => {
// Get the contents from search data
isfetched = true;
datas = isXml
? [
...new DOMParser()
.parseFromString(res, "text/xml")
.querySelectorAll("entry"),
].map((element) => {
return {
title: element.querySelector("title").textContent,
content: element.querySelector("content").textContent,
url: element.querySelector("url").textContent,
};
})
: JSON.parse(res);
// Only match articles with not empty titles
datas = datas
.filter((data) => data.title)
.map((data) => {
data.title = data.title.trim();
data.content = data.content
? data.content.trim().replace(/<[^>]+>/g, "")
: "";
data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, "/");
return data;
});
// Remove loading animation
const noResultDom = document.querySelector("#no-result");
noResultDom &&
(noResultDom.innerHTML =
'<i class="fa-solid fa-magnifying-glass fa-5x"></i>');
});
};
if (theme.navbar.search.preload) {
fetchData();
}
if (searchInputDom) {
searchInputDom.addEventListener("input", inputEventFunction);
}
// Handle and trigger popup window
document.querySelectorAll(".search-popup-trigger").forEach((element) => {
element.addEventListener("click", () => {
document.body.style.overflow = "hidden";
document.querySelector(".search-pop-overlay").classList.add("active");
setTimeout(() => searchInputDom.focus(), 500);
if (!isfetched) fetchData();
});
});
// Monitor main search box
const onPopupClose = () => {
document.body.style.overflow = "";
document.querySelector(".search-pop-overlay").classList.remove("active");
};
document
.querySelector(".search-pop-overlay")
.addEventListener("click", (event) => {
if (event.target === document.querySelector(".search-pop-overlay")) {
onPopupClose();
}
});
document
.querySelector(".search-input-field-pre")
.addEventListener("click", () => {
searchInputDom.value = "";
searchInputDom.focus();
inputEventFunction();
});
document
.querySelector(".popup-btn-close")
.addEventListener("click", onPopupClose);
try {
swup.hooks.on("page:view", (visit) => {
onPopupClose();
});
} catch (e) {}
window.addEventListener("keyup", (event) => {
if (event.key === "Escape") {
onPopupClose();
}
});
}

View File

@@ -0,0 +1,28 @@
const footerRuntime = () => {
const startTime = theme.footerStart;
window.setTimeout(footerRuntime, 1000); // passing function reference instead of string
const X = new Date(startTime);
const Y = new Date();
const T = Y.getTime() - X.getTime();
const M = 24 * 60 * 60 * 1000;
const a = T / M;
const A = Math.floor(a);
const b = (a - A) * 24;
const B = Math.floor(b);
const c = (b - B) * 60;
const C = Math.floor((b - B) * 60);
const D = Math.floor((c - C) * 60);
const runtime_days = document.getElementById("runtime_days");
const runtime_hours = document.getElementById("runtime_hours");
const runtime_minutes = document.getElementById("runtime_minutes");
const runtime_seconds = document.getElementById("runtime_seconds");
if (runtime_days) runtime_days.innerHTML = A;
if (runtime_hours) runtime_hours.innerHTML = B;
if (runtime_minutes) runtime_minutes.innerHTML = C;
if (runtime_seconds) runtime_seconds.innerHTML = D;
};
window.addEventListener("DOMContentLoaded", footerRuntime);

View File

@@ -0,0 +1,34 @@
const initScrollTopBottom = () => {
const backToTopButton_dom = document.querySelector(".tool-scroll-to-top");
const backToBottomButton_dom = document.querySelector(
".tool-scroll-to-bottom",
);
const backToTop = () => {
window.scrollTo({
top: 0, // scrolls to the top of the page
behavior: "smooth",
});
};
const backToBottom = () => {
const docHeight = document.body.scrollHeight;
window.scrollTo({
top: docHeight, // scrolls to the bottom of the page
behavior: "smooth",
});
};
const initBackToTop = () => {
backToTopButton_dom.addEventListener("click", backToTop);
};
const initBackToBottom = () => {
backToBottomButton_dom.addEventListener("click", backToBottom);
};
initBackToTop();
initBackToBottom();
};
export default initScrollTopBottom;

View File

@@ -0,0 +1,58 @@
/* main function */
import { main } from "../main.js";
export function initTocToggle() {
const TocToggle = {
toggleBar: document.querySelector(".page-aside-toggle"),
postPageContainerDom: document.querySelector(".post-page-container"),
toggleBarIcon: document.querySelector(".page-aside-toggle i"),
articleContentContainerDom: document.querySelector(
".article-content-container",
),
mainContentDom: document.querySelector(".main-content"),
isOpenPageAside: false,
initToggleBarButton() {
this.toggleBar &&
this.toggleBar.addEventListener("click", () => {
this.isOpenPageAside = !this.isOpenPageAside;
main.styleStatus.isOpenPageAside = this.isOpenPageAside;
main.setStyleStatus();
this.changePageLayoutWhenOpenToggle(this.isOpenPageAside);
});
},
toggleClassName(element, className, condition) {
if (element) {
element.classList.toggle(className, condition);
}
},
changePageLayoutWhenOpenToggle(isOpen) {
this.toggleClassName(this.toggleBarIcon, "fas", isOpen);
this.toggleClassName(this.toggleBarIcon, "fa-indent", isOpen);
this.toggleClassName(this.toggleBarIcon, "fa-outdent", !isOpen);
this.toggleClassName(this.postPageContainerDom, "show-toc", isOpen);
this.toggleClassName(this.mainContentDom, "has-toc", isOpen);
},
pageAsideHandleOfTOC(isOpen) {
this.toggleBar.style.display = "flex";
this.isOpenPageAside = isOpen;
this.changePageLayoutWhenOpenToggle(isOpen);
},
};
TocToggle.initToggleBarButton();
return TocToggle;
}
// Event listeners
try {
swup.hooks.on("page:view", () => {
initTocToggle();
});
} catch (e) {}
document.addEventListener("DOMContentLoaded", initTocToggle);