first-commit
Some checks failed
CI Pipeline / build (push) Failing after 3m23s

This commit is contained in:
2025-08-27 14:05:33 +08:00
commit 9e1b8bdc9d
5159 changed files with 1081326 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
import { inBrowser, onContentUpdated } from 'vitepress';
export function useCodeGroups() {
if (import.meta.env.DEV) {
onContentUpdated(() => {
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
Array.from(el.children).forEach((child) => {
child.classList.remove('active');
});
el.children[0].classList.add('active');
});
});
}
if (inBrowser) {
window.addEventListener('click', (e) => {
const el = e.target;
if (el.matches('.vp-code-group input')) {
// input <- .tabs <- .vp-code-group
const group = el.parentElement?.parentElement;
if (!group)
return;
const i = Array.from(group.querySelectorAll('input')).indexOf(el);
if (i < 0)
return;
const blocks = group.querySelector('.blocks');
if (!blocks)
return;
const current = Array.from(blocks.children).find((child) => child.classList.contains('active'));
if (!current)
return;
const next = blocks.children[i];
if (!next || current === next)
return;
current.classList.remove('active');
next.classList.add('active');
const label = group?.querySelector(`label[for="${el.id}"]`);
label?.scrollIntoView({ block: 'nearest' });
}
});
}
}

View File

@@ -0,0 +1,73 @@
import { inBrowser } from 'vitepress';
export function useCopyCode() {
if (inBrowser) {
const timeoutIdMap = new WeakMap();
window.addEventListener('click', (e) => {
const el = e.target;
if (el.matches('div[class*="language-"] > button.copy')) {
const parent = el.parentElement;
const sibling = el.nextElementSibling?.nextElementSibling;
if (!parent || !sibling) {
return;
}
const isShell = /language-(shellscript|shell|bash|sh|zsh)/.test(parent.className);
const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'];
// Clone the node and remove the ignored nodes
const clone = sibling.cloneNode(true);
clone
.querySelectorAll(ignoredNodes.join(','))
.forEach((node) => node.remove());
let text = clone.textContent || '';
if (isShell) {
text = text.replace(/^ *(\$|>) /gm, '').trim();
}
copyToClipboard(text).then(() => {
el.classList.add('copied');
clearTimeout(timeoutIdMap.get(el));
const timeoutId = setTimeout(() => {
el.classList.remove('copied');
el.blur();
timeoutIdMap.delete(el);
}, 2000);
timeoutIdMap.set(el, timeoutId);
});
}
});
}
}
async function copyToClipboard(text) {
try {
return navigator.clipboard.writeText(text);
}
catch {
const element = document.createElement('textarea');
const previouslyFocusedElement = document.activeElement;
element.value = text;
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '');
element.style.contain = 'strict';
element.style.position = 'absolute';
element.style.left = '-9999px';
element.style.fontSize = '12pt'; // Prevent zooming on iOS
const selection = document.getSelection();
const originalRange = selection
? selection.rangeCount > 0 && selection.getRangeAt(0)
: null;
document.body.appendChild(element);
element.select();
// Explicit selection workaround for iOS
element.selectionStart = 0;
element.selectionEnd = text.length;
document.execCommand('copy');
document.body.removeChild(element);
if (originalRange) {
selection.removeAllRanges(); // originalRange can't be truthy when selection is falsy
selection.addRange(originalRange);
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
;
previouslyFocusedElement.focus();
}
}
}

View File

@@ -0,0 +1,81 @@
import { watchEffect } from 'vue';
import { createTitle, mergeHead } from '../../shared';
export function useUpdateHead(route, siteDataByRouteRef) {
let isFirstUpdate = true;
let managedHeadElements = [];
const updateHeadTags = (newTags) => {
if (import.meta.env.PROD && isFirstUpdate) {
// in production, the initial meta tags are already pre-rendered so we
// skip the first update.
isFirstUpdate = false;
newTags.forEach((tag) => {
const headEl = createHeadElement(tag);
for (const el of document.head.children) {
if (el.isEqualNode(headEl)) {
managedHeadElements.push(el);
return;
}
}
});
return;
}
const newElements = newTags.map(createHeadElement);
managedHeadElements.forEach((oldEl, oldIndex) => {
const matchedIndex = newElements.findIndex((newEl) => newEl?.isEqualNode(oldEl ?? null));
if (matchedIndex !== -1) {
delete newElements[matchedIndex];
}
else {
oldEl?.remove();
delete managedHeadElements[oldIndex];
}
});
newElements.forEach((el) => el && document.head.appendChild(el));
managedHeadElements = [...managedHeadElements, ...newElements].filter(Boolean);
};
watchEffect(() => {
const pageData = route.data;
const siteData = siteDataByRouteRef.value;
const pageDescription = pageData && pageData.description;
const frontmatterHead = (pageData && pageData.frontmatter.head) || [];
// update title and description
const title = createTitle(siteData, pageData);
if (title !== document.title) {
document.title = title;
}
const description = pageDescription || siteData.description;
let metaDescriptionElement = document.querySelector(`meta[name=description]`);
if (metaDescriptionElement) {
if (metaDescriptionElement.getAttribute('content') !== description) {
metaDescriptionElement.setAttribute('content', description);
}
}
else {
createHeadElement(['meta', { name: 'description', content: description }]);
}
updateHeadTags(mergeHead(siteData.head, filterOutHeadDescription(frontmatterHead)));
});
}
function createHeadElement([tag, attrs, innerHTML]) {
const el = document.createElement(tag);
for (const key in attrs) {
el.setAttribute(key, attrs[key]);
}
if (innerHTML) {
el.innerHTML = innerHTML;
}
if (tag === 'script' && attrs.async == null) {
// async is true by default for dynamically created scripts
;
el.async = false;
}
return el;
}
function isMetaDescription(headConfig) {
return (headConfig[0] === 'meta' &&
headConfig[1] &&
headConfig[1].name === 'description');
}
function filterOutHeadDescription(head) {
return head.filter((h) => !isMetaDescription(h));
}

View File

@@ -0,0 +1,99 @@
// Customized pre-fetch for page chunks based on
// https://github.com/GoogleChromeLabs/quicklink
import { onMounted, onUnmounted, watch } from 'vue';
import { useRoute } from '../router';
import { inBrowser, pathToFile } from '../utils';
const hasFetched = new Set();
const createLink = () => document.createElement('link');
const viaDOM = (url) => {
const link = createLink();
link.rel = `prefetch`;
link.href = url;
document.head.appendChild(link);
};
const viaXHR = (url) => {
const req = new XMLHttpRequest();
req.open('GET', url, (req.withCredentials = true));
req.send();
};
let link;
const doFetch = inBrowser &&
(link = createLink()) &&
link.relList &&
link.relList.supports &&
link.relList.supports('prefetch')
? viaDOM
: viaXHR;
export function usePrefetch() {
if (!inBrowser) {
return;
}
if (!window.IntersectionObserver) {
return;
}
let conn;
if ((conn = navigator.connection) &&
(conn.saveData || /2g/.test(conn.effectiveType))) {
// Don't prefetch if using 2G or if Save-Data is enabled.
return;
}
const rIC = window.requestIdleCallback || setTimeout;
let observer = null;
const observeLinks = () => {
if (observer) {
observer.disconnect();
}
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const link = entry.target;
observer.unobserve(link);
const { pathname } = link;
if (!hasFetched.has(pathname)) {
hasFetched.add(pathname);
const pageChunkPath = pathToFile(pathname);
if (pageChunkPath)
doFetch(pageChunkPath);
}
}
});
});
rIC(() => {
document
.querySelectorAll('#app a')
.forEach((link) => {
const { hostname, pathname } = new URL(link.href instanceof SVGAnimatedString
? link.href.animVal
: link.href, link.baseURI);
const extMatch = pathname.match(/\.\w+$/);
if (extMatch && extMatch[0] !== '.html') {
return;
}
if (
// only prefetch same tab navigation, since a new tab will load
// the lean js chunk instead.
link.target !== '_blank' &&
// only prefetch inbound links
hostname === location.hostname) {
if (pathname !== location.pathname) {
observer.observe(link);
}
else {
// No need to prefetch chunk for the current page, but also mark
// it as already fetched. This is because the initial page uses its
// lean chunk, and if we don't mark it, navigation to another page
// with a link back to the first page will fetch its full chunk
// which isn't needed.
hasFetched.add(pathname);
}
}
});
});
};
onMounted(observeLinks);
const route = useRoute();
watch(() => route.path, observeLinks);
onUnmounted(() => {
observer && observer.disconnect();
});
}