initial vitepress site with basic nav

This commit is contained in:
mrflos 2023-05-20 19:37:42 +03:00
parent a7df2e049d
commit 2029f16583
1900 changed files with 1014692 additions and 0 deletions

View file

@ -0,0 +1,17 @@
import { computed } from 'vue';
import { useMediaQuery } from '@vueuse/core';
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 { ref, watch, readonly, onUnmounted } 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 { useData } from './data';
import { ensureStartingSlash } from '../support/utils';
export function useLangs({ removeCurrent = true, correspondingLink = false } = {}) {
const { site, localeIndex, page, theme } = 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]) => removeCurrent && 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)
}));
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,30 @@
import { ref, watch } from 'vue';
import { useRoute } from 'vitepress';
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,155 @@
import { onMounted, onUnmounted, onUpdated } from 'vue';
import { useAside } from '../composables/aside';
import { throttleAndDebounce } from '../support/utils';
// magic number to avoid repeated retrieval
const PAGE_OFFSET = 71;
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 h2,h3,h4,h5,h6')]
.filter((el) => el.id && el.hasChildNodes())
.map((el) => {
const level = Number(el.tagName[1]);
return {
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 (node.classList.contains('VPBadge') ||
node.classList.contains('header-anchor')) {
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;
headers = headers.filter((h) => h.level >= high && h.level <= low);
const ret = [];
outer: for (let i = 0; i < headers.length; i++) {
const cur = headers[i];
if (i === 0) {
ret.push(cur);
}
else {
for (let j = i - 1; j >= 0; j--) {
const prev = headers[j];
if (prev.level < cur.level) {
;
(prev.children || (prev.children = [])).push(cur);
continue outer;
}
}
ret.push(cur);
}
}
return ret;
}
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 links = [].slice.call(container.value.querySelectorAll('.outline-link'));
const anchors = [].slice
.call(document.querySelectorAll('.content .header-anchor'))
.filter((anchor) => {
return links.some((link) => {
return link.hash === anchor.hash && anchor.offsetParent !== null;
});
});
const scrollY = window.scrollY;
const innerHeight = window.innerHeight;
const offsetHeight = document.body.offsetHeight;
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
// page bottom - highlight last one
if (anchors.length && isBottom) {
activateLink(anchors[anchors.length - 1].hash);
return;
}
for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i];
const nextAnchor = anchors[i + 1];
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor);
if (isActive) {
activateLink(hash);
return;
}
}
}
function activateLink(hash) {
if (prevActiveLink) {
prevActiveLink.classList.remove('active');
}
if (hash !== null) {
prevActiveLink = container.value.querySelector(`a[href="${decodeURIComponent(hash)}"]`);
}
const activeLink = prevActiveLink;
if (activeLink) {
activeLink.classList.add('active');
marker.value.style.top = activeLink.offsetTop + 33 + 'px';
marker.value.style.opacity = '1';
}
else {
marker.value.style.top = '33px';
marker.value.style.opacity = '0';
}
}
}
function getAnchorTop(anchor) {
return anchor.parentElement.offsetTop - PAGE_OFFSET;
}
function isAnchorActive(index, anchor, nextAnchor) {
const scrollTop = window.scrollY;
if (index === 0 && scrollTop === 0) {
return [true, null];
}
if (scrollTop < getAnchorTop(anchor)) {
return [false, null];
}
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, anchor.hash];
}
return [false, null];
}

View file

@ -0,0 +1,40 @@
import { computed } from 'vue';
import { useData } from './data';
import { isActive } from '../../shared';
import { getSidebar, getFlatSideBarLinks } from '../support/sidebar';
export function usePrevNext() {
const { page, theme, frontmatter } = useData();
return computed(() => {
const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath);
const candidates = getFlatSideBarLinks(sidebar);
const index = candidates.findIndex((link) => {
return isActive(page.value.relativePath, link.link);
});
return {
prev: frontmatter.value.prev === false
? undefined
: {
text: (typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
: typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.text
: undefined) ?? candidates[index - 1]?.text,
link: (typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link
},
next: frontmatter.value.next === false
? undefined
: {
text: (typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined) ?? candidates[index + 1]?.text,
link: (typeof frontmatter.value.next === 'object'
? frontmatter.value.next.link
: undefined) ?? candidates[index + 1]?.link
}
};
});
}

View file

@ -0,0 +1,130 @@
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue';
import { useMediaQuery } from '@vueuse/core';
import { useRoute } from 'vitepress';
import { isActive } from '../../shared';
import { hasActiveLink as containsActiveLink, getSidebar, getSidebarGroups } from '../support/sidebar';
import { useData } from './data';
export function useSidebar() {
const route = useRoute();
const { theme, frontmatter } = useData();
const is960 = useMediaQuery('(min-width: 960px)');
const isOpen = ref(false);
const sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar;
const relativePath = route.data.relativePath;
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
});
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 } = useData();
const collapsed = ref(false);
const collapsible = computed(() => {
return item.value.collapsed != null;
});
const isLink = computed(() => {
return !!item.value.link;
});
const isActiveLink = computed(() => {
return isActive(page.value.relativePath, item.value.link);
});
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);
});
watchEffect(() => {
;
(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);
}
}