Fix dependencies: Add flag-icons, tailwind directives, and postcss config

This commit is contained in:
MivoDev
2026-01-18 11:16:21 +07:00
commit 266a4b1296
10758 changed files with 1547435 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import { useMediaQuery } from '@vueuse/core';
import { computed } from 'vue';
import { useSidebar } from './sidebar';
export function useAside() {
const { hasSidebar } = useSidebar();
const is960 = useMediaQuery('(min-width: 960px)');
const is1280 = useMediaQuery('(min-width: 1280px)');
const isAsideEnabled = computed(() => {
if (!is1280.value && !is960.value) {
return false;
}
return hasSidebar.value ? is1280.value : is960.value;
});
return {
isAsideEnabled
};
}

View File

@@ -0,0 +1,2 @@
import { useData as useData$ } from 'vitepress';
export const useData = useData$;

View File

@@ -0,0 +1,16 @@
import { computed } from 'vue';
import { useData } from './data';
export function useEditLink() {
const { theme, page } = useData();
return computed(() => {
const { text = 'Edit this page', pattern = '' } = theme.value.editLink || {};
let url;
if (typeof pattern === 'function') {
url = pattern(page.value);
}
else {
url = pattern.replace(/:path/g, page.value.filePath);
}
return { url, text };
});
}

View File

@@ -0,0 +1,41 @@
import { onUnmounted, readonly, ref, watch } from 'vue';
import { inBrowser } from '../../shared';
export const focusedElement = ref();
let active = false;
let listeners = 0;
export function useFlyout(options) {
const focus = ref(false);
if (inBrowser) {
!active && activateFocusTracking();
listeners++;
const unwatch = watch(focusedElement, (el) => {
if (el === options.el.value || options.el.value?.contains(el)) {
focus.value = true;
options.onFocus?.();
}
else {
focus.value = false;
options.onBlur?.();
}
});
onUnmounted(() => {
unwatch();
listeners--;
if (!listeners) {
deactivateFocusTracking();
}
});
}
return readonly(focus);
}
function activateFocusTracking() {
document.addEventListener('focusin', handleFocusIn);
active = true;
focusedElement.value = document.activeElement;
}
function deactivateFocusTracking() {
document.removeEventListener('focusin', handleFocusIn);
}
function handleFocusIn() {
focusedElement.value = document.activeElement;
}

View File

@@ -0,0 +1,26 @@
import { computed } from 'vue';
import { ensureStartingSlash } from '../support/utils';
import { useData } from './data';
export function useLangs({ correspondingLink = false } = {}) {
const { site, localeIndex, page, theme, hash } = useData();
const currentLang = computed(() => ({
label: site.value.locales[localeIndex.value]?.label,
link: site.value.locales[localeIndex.value]?.link ||
(localeIndex.value === 'root' ? '/' : `/${localeIndex.value}/`)
}));
const localeLinks = computed(() => Object.entries(site.value.locales).flatMap(([key, value]) => currentLang.value.label === value.label
? []
: {
text: value.label,
link: normalizeLink(value.link || (key === 'root' ? '/' : `/${key}/`), theme.value.i18nRouting !== false && correspondingLink, page.value.relativePath.slice(currentLang.value.link.length - 1), !site.value.cleanUrls) + hash.value
}));
return { localeLinks, currentLang };
}
function normalizeLink(link, addPath, path, addExt) {
return addPath
? link.replace(/\/$/, '') +
ensureStartingSlash(path
.replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, addExt ? '.html' : ''))
: link;
}

View File

@@ -0,0 +1,18 @@
import { onContentUpdated } from 'vitepress';
import { computed, shallowRef } from 'vue';
import { getHeaders } from '../composables/outline';
import { useData } from './data';
export function useLocalNav() {
const { theme, frontmatter } = useData();
const headers = shallowRef([]);
const hasLocalNav = computed(() => {
return headers.value.length > 0;
});
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline);
});
return {
headers,
hasLocalNav
};
}

View File

@@ -0,0 +1,30 @@
import { useRoute } from 'vitepress';
import { ref, watch } from 'vue';
export function useNav() {
const isScreenOpen = ref(false);
function openScreen() {
isScreenOpen.value = true;
window.addEventListener('resize', closeScreenOnTabletWindow);
}
function closeScreen() {
isScreenOpen.value = false;
window.removeEventListener('resize', closeScreenOnTabletWindow);
}
function toggleScreen() {
isScreenOpen.value ? closeScreen() : openScreen();
}
/**
* Close screen when the user resizes the window wider than tablet size.
*/
function closeScreenOnTabletWindow() {
window.outerWidth >= 768 && closeScreen();
}
const route = useRoute();
watch(() => route.path, closeScreen);
return {
isScreenOpen,
openScreen,
closeScreen,
toggleScreen
};
}

View File

@@ -0,0 +1,178 @@
import { getScrollOffset } from 'vitepress';
import { onMounted, onUnmounted, onUpdated } from 'vue';
import { throttleAndDebounce } from '../support/utils';
import { useAside } from './aside';
const ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
// cached list of anchor elements from resolveHeaders
const resolvedHeaders = [];
export function resolveTitle(theme) {
return ((typeof theme.outline === 'object' &&
!Array.isArray(theme.outline) &&
theme.outline.label) ||
theme.outlineTitle ||
'On this page');
}
export function getHeaders(range) {
const headers = [
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
]
.filter((el) => el.id && el.hasChildNodes())
.map((el) => {
const level = Number(el.tagName[1]);
return {
element: el,
title: serializeHeader(el),
link: '#' + el.id,
level
};
});
return resolveHeaders(headers, range);
}
function serializeHeader(h) {
let ret = '';
for (const node of h.childNodes) {
if (node.nodeType === 1) {
if (ignoreRE.test(node.className))
continue;
ret += node.textContent;
}
else if (node.nodeType === 3) {
ret += node.textContent;
}
}
return ret.trim();
}
export function resolveHeaders(headers, range) {
if (range === false) {
return [];
}
const levelsRange = (typeof range === 'object' && !Array.isArray(range)
? range.level
: range) || 2;
const [high, low] = typeof levelsRange === 'number'
? [levelsRange, levelsRange]
: levelsRange === 'deep'
? [2, 6]
: levelsRange;
return buildTree(headers, high, low);
}
export function useActiveAnchor(container, marker) {
const { isAsideEnabled } = useAside();
const onScroll = throttleAndDebounce(setActiveLink, 100);
let prevActiveLink = null;
onMounted(() => {
requestAnimationFrame(setActiveLink);
window.addEventListener('scroll', onScroll);
});
onUpdated(() => {
// sidebar update means a route change
activateLink(location.hash);
});
onUnmounted(() => {
window.removeEventListener('scroll', onScroll);
});
function setActiveLink() {
if (!isAsideEnabled.value) {
return;
}
const scrollY = window.scrollY;
const innerHeight = window.innerHeight;
const offsetHeight = document.body.offsetHeight;
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
// resolvedHeaders may be repositioned, hidden or fix positioned
const headers = resolvedHeaders
.map(({ element, link }) => ({
link,
top: getAbsoluteTop(element)
}))
.filter(({ top }) => !Number.isNaN(top))
.sort((a, b) => a.top - b.top);
// no headers available for active link
if (!headers.length) {
activateLink(null);
return;
}
// page top
if (scrollY < 1) {
activateLink(null);
return;
}
// page bottom - highlight last link
if (isBottom) {
activateLink(headers[headers.length - 1].link);
return;
}
// find the last header above the top of viewport
let activeLink = null;
for (const { link, top } of headers) {
if (top > scrollY + getScrollOffset() + 4) {
break;
}
activeLink = link;
}
activateLink(activeLink);
}
function activateLink(hash) {
if (prevActiveLink) {
prevActiveLink.classList.remove('active');
}
if (hash == null) {
prevActiveLink = null;
}
else {
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
}
const activeLink = prevActiveLink;
if (activeLink) {
activeLink.classList.add('active');
marker.value.style.top = activeLink.offsetTop + 39 + 'px';
marker.value.style.opacity = '1';
}
else {
marker.value.style.top = '33px';
marker.value.style.opacity = '0';
}
}
}
function getAbsoluteTop(element) {
let offsetTop = 0;
while (element !== document.body) {
if (element === null) {
// child element is:
// - not attached to the DOM (display: none)
// - set to fixed position (not scrollable)
// - body or html element (null offsetParent)
return NaN;
}
offsetTop += element.offsetTop;
element = element.offsetParent;
}
return offsetTop;
}
function buildTree(data, min, max) {
resolvedHeaders.length = 0;
const result = [];
const stack = [];
data.forEach((item) => {
const node = { ...item, children: [] };
let parent = stack[stack.length - 1];
while (parent && parent.level >= node.level) {
stack.pop();
parent = stack[stack.length - 1];
}
if (node.element.classList.contains('ignore-header') ||
(parent && 'shouldIgnore' in parent)) {
stack.push({ level: node.level, shouldIgnore: true });
return;
}
if (node.level > max || node.level < min)
return;
resolvedHeaders.push({ element: node.element, link: node.link });
if (parent)
parent.children.push(node);
else
result.push(node);
stack.push(node);
});
return result;
}

View File

@@ -0,0 +1,57 @@
import { computed } from 'vue';
import { isActive } from '../../shared';
import { getFlatSideBarLinks, getSidebar } from '../support/sidebar';
import { useData } from './data';
export function usePrevNext() {
const { page, theme, frontmatter } = useData();
return computed(() => {
const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath);
const links = getFlatSideBarLinks(sidebar);
// ignore inner-page links with hashes
const candidates = uniqBy(links, (link) => link.link.replace(/[?#].*$/, ''));
const index = candidates.findIndex((link) => {
return isActive(page.value.relativePath, link.link);
});
const hidePrev = (theme.value.docFooter?.prev === false && !frontmatter.value.prev) ||
frontmatter.value.prev === false;
const hideNext = (theme.value.docFooter?.next === false && !frontmatter.value.next) ||
frontmatter.value.next === false;
return {
prev: hidePrev
? undefined
: {
text: (typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
: typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.text
: undefined) ??
candidates[index - 1]?.docFooterText ??
candidates[index - 1]?.text,
link: (typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link
},
next: hideNext
? undefined
: {
text: (typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined) ??
candidates[index + 1]?.docFooterText ??
candidates[index + 1]?.text,
link: (typeof frontmatter.value.next === 'object'
? frontmatter.value.next.link
: undefined) ?? candidates[index + 1]?.link
}
};
});
}
function uniqBy(array, keyFn) {
const seen = new Set();
return array.filter((item) => {
const k = keyFn(item);
return seen.has(k) ? false : seen.add(k);
});
}

View File

@@ -0,0 +1,136 @@
import { useMediaQuery } from '@vueuse/core';
import { computed, onMounted, onUnmounted, ref, watch, watchEffect, watchPostEffect } from 'vue';
import { isActive } from '../../shared';
import { hasActiveLink as containsActiveLink, getSidebar, getSidebarGroups } from '../support/sidebar';
import { useData } from './data';
export function useSidebar() {
const { frontmatter, page, theme } = useData();
const is960 = useMediaQuery('(min-width: 960px)');
const isOpen = ref(false);
const _sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar;
const relativePath = page.value.relativePath;
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
});
const sidebar = ref(_sidebar.value);
watch(_sidebar, (next, prev) => {
if (JSON.stringify(next) !== JSON.stringify(prev))
sidebar.value = _sidebar.value;
});
const hasSidebar = computed(() => {
return (frontmatter.value.sidebar !== false &&
sidebar.value.length > 0 &&
frontmatter.value.layout !== 'home');
});
const leftAside = computed(() => {
if (hasAside)
return frontmatter.value.aside == null
? theme.value.aside === 'left'
: frontmatter.value.aside === 'left';
return false;
});
const hasAside = computed(() => {
if (frontmatter.value.layout === 'home')
return false;
if (frontmatter.value.aside != null)
return !!frontmatter.value.aside;
return theme.value.aside !== false;
});
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
});
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
}
function toggle() {
isOpen.value ? close() : open();
}
return {
isOpen,
sidebar,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
};
}
/**
* a11y: cache the element that opened the Sidebar (the menu button) then
* focus that button again when Menu is closed with Escape key.
*/
export function useCloseSidebarOnEscape(isOpen, close) {
let triggerElement;
watchEffect(() => {
triggerElement = isOpen.value
? document.activeElement
: undefined;
});
onMounted(() => {
window.addEventListener('keyup', onEscape);
});
onUnmounted(() => {
window.removeEventListener('keyup', onEscape);
});
function onEscape(e) {
if (e.key === 'Escape' && isOpen.value) {
close();
triggerElement?.focus();
}
}
}
export function useSidebarControl(item) {
const { page, hash } = useData();
const collapsed = ref(false);
const collapsible = computed(() => {
return item.value.collapsed != null;
});
const isLink = computed(() => {
return !!item.value.link;
});
const isActiveLink = ref(false);
const updateIsActiveLink = () => {
isActiveLink.value = isActive(page.value.relativePath, item.value.link);
};
watch([page, item, hash], updateIsActiveLink);
onMounted(updateIsActiveLink);
const hasActiveLink = computed(() => {
if (isActiveLink.value) {
return true;
}
return item.value.items
? containsActiveLink(page.value.relativePath, item.value.items)
: false;
});
const hasChildren = computed(() => {
return !!(item.value.items && item.value.items.length);
});
watchEffect(() => {
collapsed.value = !!(collapsible.value && item.value.collapsed);
});
watchPostEffect(() => {
;
(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false);
});
function toggle() {
if (collapsible.value) {
collapsed.value = !collapsed.value;
}
}
return {
collapsed,
collapsible,
isLink,
isActiveLink,
hasActiveLink,
hasChildren,
toggle
};
}

View File

@@ -0,0 +1,94 @@
import { onMounted, onUnmounted } from 'vue';
import { throttleAndDebounce } from '../support/utils';
/**
* Defines grid configuration for each sponsor size in tuple.
*
* [Screen width, Column size]
*
* It sets grid size on matching screen size. For example, `[768, 5]` will
* set 5 columns when screen size is bigger or equal to 768px.
*
* Column will set only when item size is bigger than the column size. For
* example, even we define 5 columns, if we only have 1 sponsor yet, we would
* like to show it in 1 column to make it stand out.
*/
const GridSettings = {
xmini: [[0, 2]],
mini: [],
small: [
[920, 6],
[768, 5],
[640, 4],
[480, 3],
[0, 2]
],
medium: [
[960, 5],
[832, 4],
[640, 3],
[480, 2]
],
big: [
[832, 3],
[640, 2]
]
};
export function useSponsorsGrid({ el, size = 'medium' }) {
const onResize = throttleAndDebounce(manage, 100);
onMounted(() => {
manage();
window.addEventListener('resize', onResize);
});
onUnmounted(() => {
window.removeEventListener('resize', onResize);
});
function manage() {
adjustSlots(el.value, size);
}
}
function adjustSlots(el, size) {
const tsize = el.children.length;
const asize = el.querySelectorAll('.vp-sponsor-grid-item:not(.empty)').length;
const grid = setGrid(el, size, asize);
manageSlots(el, grid, tsize, asize);
}
function setGrid(el, size, items) {
const settings = GridSettings[size];
const screen = window.innerWidth;
let grid = 1;
settings.some(([breakpoint, value]) => {
if (screen >= breakpoint) {
grid = items < value ? items : value;
return true;
}
});
setGridData(el, grid);
return grid;
}
function setGridData(el, value) {
el.dataset.vpGrid = String(value);
}
function manageSlots(el, grid, tsize, asize) {
const diff = tsize - asize;
const rem = asize % grid;
const drem = rem === 0 ? rem : grid - rem;
neutralizeSlots(el, drem - diff);
}
function neutralizeSlots(el, count) {
if (count === 0) {
return;
}
count > 0 ? addSlots(el, count) : removeSlots(el, count * -1);
}
function addSlots(el, count) {
for (let i = 0; i < count; i++) {
const slot = document.createElement('div');
slot.classList.add('vp-sponsor-grid-item', 'empty');
el.append(slot);
}
}
function removeSlots(el, count) {
for (let i = 0; i < count; i++) {
el.removeChild(el.lastElementChild);
}
}