@eggjs/vuepress-theme-egg
Version:
VuePress theme for Egg.js
294 lines (260 loc) • 7.19 kB
JavaScript
export const hashRE = /#.*$/;
export const extRE = /\.(md|html)$/;
export const endingSlashRE = /\/$/;
export const outboundRE = /^(https?:|mailto:|tel:)/;
/*
* find parent vm by ref
* @param {String} ref
* @param {Vue} vm
* @param {any} def default value
* @returns {Element}
*/
export function findContainerInVm(ref, vm, def) {
if (!ref) return def;
let container;
let parent = vm;
while ((parent = parent.$parent) && !container) {
container = parent.$refs[ref];
}
// Ensure it's html element (ref could be component)
if (container && container.$el) {
container = container.$el;
}
return container || def;
}
export function normalize(path) {
return decodeURI(path)
.replace(hashRE, '')
.replace(extRE, '');
}
export function getHash(path) {
const match = path.match(hashRE);
if (match) {
return match[0];
}
}
export function isExternal(path) {
return outboundRE.test(path);
}
export function isMailto(path) {
return /^mailto:/.test(path);
}
export function isTel(path) {
return /^tel:/.test(path);
}
export function getAbsoluteTop(dom) {
return dom && dom.getBoundingClientRect
? dom.getBoundingClientRect().top +
document.body.scrollTop +
document.documentElement.scrollTop
: 0;
}
export function ensureExt(path) {
if (isExternal(path)) {
return path;
}
const hashMatch = path.match(hashRE);
const hash = hashMatch ? hashMatch[0] : '';
const normalized = normalize(path);
if (endingSlashRE.test(normalized)) {
return path;
}
return normalized + '.html' + hash;
}
export function isActive(route, path) {
const routeHash = route.hash;
const linkHash = getHash(path);
if (linkHash && routeHash !== linkHash) {
return false;
}
const routePath = normalize(route.path);
const pagePath = normalize(path);
return routePath === pagePath;
}
export function resolvePage(pages, rawPath, base) {
if (base) {
rawPath = resolvePath(rawPath, base);
}
const path = normalize(rawPath);
for (let i = 0; i < pages.length; i++) {
if (normalize(pages[i].regularPath) === path) {
return Object.assign({}, pages[i], {
type: 'page',
path: ensureExt(pages[i].path),
});
}
}
console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`);
return {};
}
function resolvePath(relative, base, append) {
const firstChar = relative.charAt(0);
if (firstChar === '/') {
return relative;
}
if (firstChar === '?' || firstChar === '#') {
return base + relative;
}
const stack = base.split('/');
// remove trailing segment if:
// - not appending
// - appending to trailing slash (last segment is empty)
if (!append || !stack[stack.length - 1]) {
stack.pop();
}
// resolve relative path
const segments = relative.replace(/^\//, '').split('/');
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (segment === '..') {
stack.pop();
} else if (segment !== '.') {
stack.push(segment);
}
}
// ensure leading slash
if (stack[0] !== '') {
stack.unshift('');
}
return stack.join('/');
}
/**
* @param { Page } page
* @param { string } regularPath
* @param { SiteData } site
* @param { string } localePath
* @return { SidebarGroup }
*/
export function resolveSidebarItems(page, regularPath, site, localePath) {
const { pages, themeConfig } = site;
const localeConfig = localePath && themeConfig.locales
? themeConfig.locales[localePath] || themeConfig
: themeConfig;
const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar;
if (pageSidebarConfig === 'auto') {
return resolveHeaders(page);
}
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
if (!sidebarConfig) {
return [];
}
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig);
return config
? config.map(item => resolveItem(item, pages, base))
: [];
}
export function resolveNavItems(navItems, site, base = '/', depth = 1) {
const { pages } = site;
return navItems.map(item => {
if (typeof item === 'string') {
const result = resolvePage(
pages,
item,
item.startsWith('/') ? '/' : base
);
return {
text: result.title,
link: result.regularPath,
items: [],
type: 'link',
};
}
let newBase = base;
if (item.link) {
item.link = ensureExt(resolvePath(item.link, base));
const link = item.link.split(/#|\?/)[0];
// base transform : aaa => aaa/, bbb.html => /, aaa/bbb.html => aaa/
newBase = link.endsWith('/')
? link
: (link.endsWith('.html') ? (link.substring(0, link.lastIndexOf('/') + 1) || '/') : `${link}/`);
newBase = newBase.startsWith('/') ? newBase : `${base}${newBase}`;
}
if (item.items && depth < 3) {
item.items = resolveNavItems(item.items, site, newBase, depth + 1);
}
item.text = item.text || (getHash(item.link) || '').substring(1);
item.type = (item.items && item.items.length) ? 'links' : 'link';
return item;
});
}
/**
* @param { Page } page
* @return { SidebarGroup }
*/
function resolveHeaders(page) {
const headers = groupHeaders(page.headers || []);
return [{
type: 'group',
collapsable: false,
title: page.title,
children: headers.map(h => ({
type: 'auto',
title: h.title,
basePath: page.path,
path: page.path + '#' + h.slug,
children: h.children || [],
})),
}];
}
export function groupHeaders(headers) {
// group h3s under h2
headers = headers.map(h => Object.assign({}, h));
let lastH2;
headers.forEach(h => {
if (h.level === 2) {
lastH2 = h;
} else if (lastH2) {
(lastH2.children || (lastH2.children = [])).push(h);
}
});
return headers.filter(h => h.level === 2);
}
/**
* @param { Route } route
* @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
* @return { base: string, config: SidebarConfig }
*/
export function resolveMatchingConfig(regularPath, config) {
if (Array.isArray(config)) {
return {
base: '/',
config,
};
}
for (const base in config) {
if (ensureEndingSlash(regularPath).indexOf(base) === 0) {
return {
base,
config: config[base],
};
}
}
return {};
}
function ensureEndingSlash(path) {
return /(\.html|\/)$/.test(path)
? path
: path + '/';
}
function resolveItem(item, pages, base, isNested) {
if (typeof item === 'string') {
return resolvePage(pages, item, base);
} else if (Array.isArray(item)) {
return Object.assign(resolvePage(pages, item[0], base), {
title: item[1],
});
}
if (isNested) {
console.error(
'[vuepress] Nested sidebar groups are not supported. ' +
'Consider using navbar + categories instead.'
);
}
const children = item.children || [];
return {
type: 'group',
title: item.title,
children: children.map(child => resolveItem(child, pages, base, true)),
collapsable: item.collapsable !== false,
};
}