first commit
This commit is contained in:
48
public/js/tools/codeBlock.js
Normal file
48
public/js/tools/codeBlock.js
Normal 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;
|
||||
170
public/js/tools/imageViewer.js
Normal file
170
public/js/tools/imageViewer.js
Normal 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.");
|
||||
}
|
||||
}
|
||||
165
public/js/tools/lightDarkSwitch.js
Normal file
165
public/js/tools/lightDarkSwitch.js
Normal 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();
|
||||
}
|
||||
327
public/js/tools/localSearch.js
Normal file
327
public/js/tools/localSearch.js
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
28
public/js/tools/runtime.js
Normal file
28
public/js/tools/runtime.js
Normal 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);
|
||||
34
public/js/tools/scrollTopBottom.js
Normal file
34
public/js/tools/scrollTopBottom.js
Normal 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;
|
||||
58
public/js/tools/tocToggle.js
Normal file
58
public/js/tools/tocToggle.js
Normal 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);
|
||||
Reference in New Issue
Block a user