Initial commit
This commit is contained in:
37
.vitepress/theme/plugins/analytics.ts
Normal file
37
.vitepress/theme/plugins/analytics.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Code based on vitepress-plugin-google-analytics.
|
||||
// Customized as the plugin did not consider the script loading time.
|
||||
// https://github.com/ZhongxuYang/vitepress-plugin-google-analytics
|
||||
|
||||
function mountGoogleAnalytics(id: string) {
|
||||
if (("dataLayer" in window && window.gtag) || window.location.hostname === "localhost") {
|
||||
return
|
||||
}
|
||||
|
||||
const analyticsScript = document.createElement("script")
|
||||
|
||||
analyticsScript.addEventListener("load", () => {
|
||||
// @ts-expect-error Missing types
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag(..._args: any[]) {
|
||||
// @ts-expect-error Missing types
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
|
||||
gtag("js", new Date())
|
||||
gtag("config", id)
|
||||
|
||||
window.gtag = gtag
|
||||
})
|
||||
|
||||
analyticsScript.src = `https://www.googletagmanager.com/gtag/js?id=${id}`
|
||||
|
||||
document.body.appendChild(analyticsScript)
|
||||
}
|
||||
|
||||
export default function ({ id }: { id: string }) {
|
||||
// eslint-disable-next-line n/prefer-global/process
|
||||
if (process.env.NODE_ENV === "production" && id && typeof window !== "undefined") {
|
||||
mountGoogleAnalytics(id)
|
||||
}
|
||||
}
|
||||
109
.vitepress/theme/plugins/component.ts
Normal file
109
.vitepress/theme/plugins/component.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { DefaultTheme, PageData, SiteConfig } from 'vitepress'
|
||||
import { normalize } from 'vitepress/dist/client/shared'
|
||||
import { ensureStartingSlash, getTranslator } from '../utils'
|
||||
|
||||
import { readFileSync } from 'fs'
|
||||
import { basename } from 'path'
|
||||
import fg from 'fast-glob'
|
||||
import matter from 'gray-matter'
|
||||
|
||||
import { generateSidebarItem, getTitleFromContent } from './sidebar'
|
||||
|
||||
import type { Translator } from '../../../website/translators'
|
||||
import { findPath } from '../utils'
|
||||
|
||||
export interface ComponentData {
|
||||
path: string
|
||||
link: string
|
||||
title: string
|
||||
titleLower: string
|
||||
description?: string
|
||||
|
||||
text?: string
|
||||
translator?: Translator
|
||||
logo?: string
|
||||
dependencies?: Array<string>
|
||||
categories?: Array<string>
|
||||
|
||||
repository?: string
|
||||
|
||||
items?: DefaultTheme.SidebarItem[]
|
||||
}
|
||||
|
||||
export interface DocsPageData extends PageData {
|
||||
component?: ComponentData
|
||||
breadcrumbs?: DefaultTheme.SidebarItem[]
|
||||
}
|
||||
|
||||
export const components: ComponentData[] = fg
|
||||
.sync([
|
||||
'website/manuals/faq/explore/*.md',
|
||||
'!website/manuals/faq/explore/index.md',
|
||||
])
|
||||
.map(file => {
|
||||
const content = readFileSync(file, 'utf-8')
|
||||
const { data } = matter(content)
|
||||
const {
|
||||
title = getTitleFromContent(content) || basename(file),
|
||||
translator,
|
||||
logo,
|
||||
categories = [],
|
||||
dependencies = [],
|
||||
items,
|
||||
repository,
|
||||
description,
|
||||
} = data
|
||||
|
||||
const filePath = file.substring(file.indexOf('/') + 1)
|
||||
const component: ComponentData = {
|
||||
path: filePath,
|
||||
link: ensureStartingSlash(normalize(filePath)),
|
||||
repository,
|
||||
title,
|
||||
titleLower: title.toLowerCase(),
|
||||
text: title,
|
||||
description,
|
||||
logo,
|
||||
dependencies: Array.isArray(dependencies) ? dependencies : Array(dependencies),
|
||||
categories: Array.isArray(categories) ? categories : Array(categories),
|
||||
}
|
||||
|
||||
component.translator = getTranslator(translator)
|
||||
|
||||
if (items) {
|
||||
component.items = generateSidebarItem(items, component.link)
|
||||
}
|
||||
|
||||
return component
|
||||
})
|
||||
.sort((a, b) => (a.text && b.text) ? a.text.localeCompare(b.text) : 0)
|
||||
|
||||
export default class DocsComponent {
|
||||
static prepareData(
|
||||
pageData: DocsPageData,
|
||||
siteConfig: SiteConfig,
|
||||
): DocsPageData {
|
||||
const component = components.find(component => pageData.relativePath.startsWith(component.path.replace(/index\.md$/, '')))
|
||||
|
||||
pageData.component = component
|
||||
pageData.breadcrumbs = findPath(pageData, siteConfig.userConfig)
|
||||
|
||||
pageData.title = !pageData.frontmatter.title && pageData.breadcrumbs.length
|
||||
? pageData.breadcrumbs.map(item => item.text).reverse().join(siteConfig.userConfig.themeConfig.titleSeparator)
|
||||
: pageData.title
|
||||
|
||||
if (
|
||||
component
|
||||
&& !pageData.description
|
||||
&& pageData.component.description
|
||||
) {
|
||||
pageData.description = pageData.component.description
|
||||
}
|
||||
|
||||
return pageData
|
||||
}
|
||||
}
|
||||
|
||||
export { DocsComponent }
|
||||
|
||||
export const { prepareData } = DocsComponent
|
||||
280
.vitepress/theme/plugins/markdown.ts
Normal file
280
.vitepress/theme/plugins/markdown.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import type { RenderRule } from 'markdown-it/lib/renderer'
|
||||
import type StateBlock from 'markdown-it/lib/rules_block/state_block'
|
||||
import { isSpace } from 'markdown-it/lib/common/utils'
|
||||
import container from 'markdown-it-container'
|
||||
import kbd from 'markdown-it-kbd'
|
||||
|
||||
export const addPlugins = (md: MarkdownIt) => {
|
||||
md.use(...createContainer('info', 'Информация', md))
|
||||
.use(...createContainer('tip', 'Подсказка', md))
|
||||
.use(...createContainer('warning', 'Внимание', md))
|
||||
.use(...createContainer('danger', 'Осторожно', md))
|
||||
.use(...createContainer('details', 'Подробнее', md))
|
||||
|
||||
.use(kbd)
|
||||
|
||||
md.block.ruler.at('table', table)
|
||||
}
|
||||
|
||||
type ContainerArgs = [typeof container, string, { render: RenderRule }]
|
||||
|
||||
function createContainer(
|
||||
klass: string,
|
||||
defaultTitle: string,
|
||||
md: MarkdownIt
|
||||
): ContainerArgs {
|
||||
return [
|
||||
container,
|
||||
klass,
|
||||
{
|
||||
render(tokens, idx, _options, env) {
|
||||
const token = tokens[idx]
|
||||
const info = token.info.trim().slice(klass.length).trim()
|
||||
const attrs = md.renderer.renderAttrs(token)
|
||||
if (token.nesting === 1) {
|
||||
const title = md.renderInline(info || defaultTitle, {
|
||||
references: env.references
|
||||
})
|
||||
if (klass === 'details')
|
||||
return `<details class="${klass} custom-block"${attrs}><summary>${title}</summary>\n`
|
||||
return `<div class="${klass} custom-block"${attrs}><p class="custom-block-title">${title}</p>\n`
|
||||
} else return klass === 'details' ? `</details>\n` : `</div>\n`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// from https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/rules_block/table.js
|
||||
// GFM table, non-standard
|
||||
|
||||
function table(
|
||||
state: StateBlock,
|
||||
startLine: number,
|
||||
endLine: number,
|
||||
silent: any
|
||||
) {
|
||||
var ch, lineText, pos, i, l, nextLine, headers, columns, columnCount, token,
|
||||
aligns, t, tableLines, tbodyLines, oldParentType, terminate,
|
||||
terminatorRules, firstCh, secondCh;
|
||||
|
||||
// should have at least two lines
|
||||
if (startLine + 2 > endLine) { return false; }
|
||||
|
||||
nextLine = startLine + 1;
|
||||
|
||||
if (state.sCount[nextLine] < state.blkIndent) { return false; }
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; }
|
||||
|
||||
// first character of the second line should be '|', '-', ':',
|
||||
// and no other characters are allowed but spaces;
|
||||
// basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
|
||||
|
||||
pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
||||
if (pos >= state.eMarks[nextLine]) { return false; }
|
||||
|
||||
firstCh = state.src.charCodeAt(pos++);
|
||||
if (firstCh !== 0x7C/* | */ && firstCh !== 0x2D/* - */ && firstCh !== 0x3A/* : */) { return false; }
|
||||
|
||||
if (pos >= state.eMarks[nextLine]) { return false; }
|
||||
|
||||
secondCh = state.src.charCodeAt(pos++);
|
||||
if (secondCh !== 0x7C/* | */ && secondCh !== 0x2D/* - */ && secondCh !== 0x3A/* : */ && !isSpace(secondCh)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if first character is '-', then second character must not be a space
|
||||
// (due to parsing ambiguity with list)
|
||||
if (firstCh === 0x2D/* - */ && isSpace(secondCh)) { return false; }
|
||||
|
||||
while (pos < state.eMarks[nextLine]) {
|
||||
ch = state.src.charCodeAt(pos);
|
||||
|
||||
if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; }
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
lineText = getLine(state, startLine + 1);
|
||||
|
||||
columns = lineText.split('|');
|
||||
aligns = [];
|
||||
for (i = 0; i < columns.length; i++) {
|
||||
t = columns[i].trim();
|
||||
if (!t) {
|
||||
// allow empty columns before and after table, but not in between columns;
|
||||
// e.g. allow ` |---| `, disallow ` ---||--- `
|
||||
if (i === 0 || i === columns.length - 1) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^:?-+:?$/.test(t)) { return false; }
|
||||
if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
|
||||
aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');
|
||||
} else if (t.charCodeAt(0) === 0x3A/* : */) {
|
||||
aligns.push('left');
|
||||
} else {
|
||||
aligns.push('');
|
||||
}
|
||||
}
|
||||
|
||||
lineText = getLine(state, startLine).trim();
|
||||
if (lineText.indexOf('|') === -1) { return false; }
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
|
||||
columns = escapedSplit(lineText);
|
||||
if (columns.length && columns[0] === '') columns.shift();
|
||||
if (columns.length && columns[columns.length - 1] === '') columns.pop();
|
||||
|
||||
// header row will define an amount of columns in the entire table,
|
||||
// and align row should be exactly the same (the rest of the rows can differ)
|
||||
columnCount = columns.length;
|
||||
headers = [...columns];
|
||||
if (columnCount === 0 || columnCount !== aligns.length) { return false; }
|
||||
|
||||
if (silent) { return true; }
|
||||
|
||||
oldParentType = state.parentType;
|
||||
// @ts-expect-error
|
||||
state.parentType = 'table';
|
||||
|
||||
// use 'blockquote' lists for termination because it's
|
||||
// the most similar to tables
|
||||
terminatorRules = state.md.block.ruler.getRules('blockquote');
|
||||
|
||||
token = state.push('table_open', 'table', 1);
|
||||
token.map = tableLines = [ startLine, 0 ];
|
||||
|
||||
token = state.push('thead_open', 'thead', 1);
|
||||
token.map = [ startLine, startLine + 1 ];
|
||||
|
||||
token = state.push('tr_open', 'tr', 1);
|
||||
token.map = [ startLine, startLine + 1 ];
|
||||
|
||||
for (i = 0; i < columns.length; i++) {
|
||||
token = state.push('th_open', 'th', 1);
|
||||
if (aligns[i]) {
|
||||
token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
|
||||
}
|
||||
|
||||
token = state.push('inline', '', 0);
|
||||
token.content = columns[i].trim();
|
||||
token.children = [];
|
||||
|
||||
token = state.push('th_close', 'th', -1);
|
||||
}
|
||||
|
||||
token = state.push('tr_close', 'tr', -1);
|
||||
token = state.push('thead_close', 'thead', -1);
|
||||
|
||||
for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
|
||||
if (state.sCount[nextLine] < state.blkIndent) { break; }
|
||||
|
||||
terminate = false;
|
||||
for (i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (terminate) { break; }
|
||||
lineText = getLine(state, nextLine).trim();
|
||||
if (!lineText) { break; }
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { break; }
|
||||
columns = escapedSplit(lineText);
|
||||
if (columns.length && columns[0] === '') columns.shift();
|
||||
if (columns.length && columns[columns.length - 1] === '') columns.pop();
|
||||
|
||||
if (nextLine === startLine + 2) {
|
||||
token = state.push('tbody_open', 'tbody', 1);
|
||||
token.map = tbodyLines = [ startLine + 2, 0 ];
|
||||
}
|
||||
|
||||
token = state.push('tr_open', 'tr', 1);
|
||||
token.map = [ nextLine, nextLine + 1 ];
|
||||
|
||||
for (i = 0; i < columnCount; i++) {
|
||||
token = state.push('td_open', 'td', 1);
|
||||
const attrs = [];
|
||||
if (aligns[i]) {
|
||||
token.attrs = attrs.push([ 'style', 'text-align:' + aligns[i] ]);
|
||||
}
|
||||
|
||||
attrs.push(['data-label', headers[i].trim()]);
|
||||
|
||||
if (attrs.length) {
|
||||
token.attrs = attrs;
|
||||
}
|
||||
|
||||
token = state.push('inline', '', 0);
|
||||
token.content = columns[i] ? columns[i].trim() : '';
|
||||
token.children = [];
|
||||
|
||||
token = state.push('td_close', 'td', -1);
|
||||
}
|
||||
token = state.push('tr_close', 'tr', -1);
|
||||
}
|
||||
|
||||
if (tbodyLines) {
|
||||
token = state.push('tbody_close', 'tbody', -1);
|
||||
tbodyLines[1] = nextLine;
|
||||
}
|
||||
|
||||
token = state.push('table_close', 'table', -1);
|
||||
tableLines[1] = nextLine;
|
||||
|
||||
state.parentType = oldParentType;
|
||||
state.line = nextLine;
|
||||
return true;
|
||||
}
|
||||
|
||||
function getLine(
|
||||
state: StateBlock,
|
||||
line: number
|
||||
): string {
|
||||
var pos = state.bMarks[line] + state.tShift[line],
|
||||
max = state.eMarks[line];
|
||||
|
||||
return state.src.slice(pos, max)
|
||||
}
|
||||
|
||||
function escapedSplit(str: string): string[] {
|
||||
var result = [],
|
||||
pos = 0,
|
||||
max = str.length,
|
||||
ch,
|
||||
isEscaped = false,
|
||||
lastPos = 0,
|
||||
current = '';
|
||||
|
||||
ch = str.charCodeAt(pos);
|
||||
|
||||
while (pos < max) {
|
||||
if (ch === 0x7c/* | */) {
|
||||
if (!isEscaped) {
|
||||
// pipe separating cells, '|'
|
||||
result.push(current + str.substring(lastPos, pos));
|
||||
current = '';
|
||||
lastPos = pos + 1;
|
||||
} else {
|
||||
// escaped pipe, '\|'
|
||||
current += str.substring(lastPos, pos - 1);
|
||||
lastPos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
isEscaped = (ch === 0x5c/* \ */);
|
||||
pos++;
|
||||
|
||||
ch = str.charCodeAt(pos);
|
||||
}
|
||||
|
||||
result.push(current + str.substring(lastPos));
|
||||
|
||||
return result;
|
||||
}
|
||||
97
.vitepress/theme/plugins/sidebar.ts
Normal file
97
.vitepress/theme/plugins/sidebar.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { DefaultTheme } from 'vitepress'
|
||||
import { normalize } from 'vitepress/dist/client/shared'
|
||||
import faqCategories from '../../../website/faq/categories.json'
|
||||
|
||||
import { readFileSync } from 'fs'
|
||||
import { join, basename } from 'path'
|
||||
import fg from 'fast-glob'
|
||||
import matter from 'gray-matter'
|
||||
|
||||
declare interface Options {
|
||||
root: string | Array<string>
|
||||
ignore?: Array<string>
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
export default class Sidebar {
|
||||
static generateSidebar(
|
||||
options: Options
|
||||
): DefaultTheme.SidebarItem[] {
|
||||
const entries = fg.sync(options.root, options)
|
||||
|
||||
return entries
|
||||
.map(path => Sidebar.getData(path, options))
|
||||
.sort((a, b) => (a.text && b.text) ? a.text.localeCompare(b.text) : 0)
|
||||
}
|
||||
|
||||
static generateSidebarItem(
|
||||
items: DefaultTheme.SidebarItem[],
|
||||
path: string,
|
||||
): DefaultTheme.SidebarItem[] {
|
||||
items = items.map(({ text, link, items }) => {
|
||||
const item: DefaultTheme.SidebarItem = { text }
|
||||
|
||||
if (link) {
|
||||
item.link = join(path, link).replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
if (items) {
|
||||
item.collapsed = true
|
||||
item.items = Sidebar.generateSidebarItem(items, path)
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
static getData(
|
||||
path: string,
|
||||
options?: Partial<Options>
|
||||
): DefaultTheme.SidebarItem {
|
||||
const src = readFileSync(path, 'utf-8')
|
||||
const { data } = matter(src)
|
||||
const {
|
||||
title = Sidebar.getTitleFromContent(src) || basename(path),
|
||||
items,
|
||||
} = data
|
||||
|
||||
const link = normalize(path.replace(/^website/, ''))
|
||||
|
||||
const output: DefaultTheme.SidebarItem = {
|
||||
text: title,
|
||||
link,
|
||||
}
|
||||
|
||||
if (items) {
|
||||
output.collapsed = options.collapsed === null || options.collapsed === undefined || options.collapsed
|
||||
output.items = Sidebar.generateSidebarItem(items, link)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
static getTitleFromContent(
|
||||
content: string,
|
||||
): string | undefined {
|
||||
const lines = content.split('\n')
|
||||
for (let i = 0, len = lines.length; i < len; i += 1) {
|
||||
let str = lines[i].toString().replace('\r', '')
|
||||
if (str.indexOf('# ') !== -1) {
|
||||
str = str.replace('# ', '')
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export { Sidebar }
|
||||
|
||||
export const {
|
||||
generateSidebar,
|
||||
generateSidebarItem,
|
||||
getTitleFromContent
|
||||
} = Sidebar
|
||||
Reference in New Issue
Block a user