136 lines
4.3 KiB
TypeScript
136 lines
4.3 KiB
TypeScript
|
|
import fs from 'node:fs'
|
|||
|
|
import path from 'node:path'
|
|||
|
|
|
|||
|
|
import { docs, type DocAction, type DocActionCollection, type DocActionGroup } from '../src/config/docs.config'
|
|||
|
|
|
|||
|
|
const rootDir = path.resolve(import.meta.dirname, '..')
|
|||
|
|
const indexPath = path.join(rootDir, 'index.html')
|
|||
|
|
const startMarker = '<!-- STATIC_DOCS_START -->'
|
|||
|
|
const endMarker = '<!-- STATIC_DOCS_END -->'
|
|||
|
|
|
|||
|
|
function escapeHtml(value: string) {
|
|||
|
|
return value
|
|||
|
|
.replaceAll('&', '&')
|
|||
|
|
.replaceAll('<', '<')
|
|||
|
|
.replaceAll('>', '>')
|
|||
|
|
.replaceAll('"', '"')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isActionGroup(action: DocAction | DocActionGroup): action is DocActionGroup {
|
|||
|
|
return 'actions' in action
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function splitActionCollection(collection: DocActionCollection | undefined) {
|
|||
|
|
if (!collection?.length) return { actions: [], groups: [] }
|
|||
|
|
|
|||
|
|
if (isActionGroup(collection[0])) {
|
|||
|
|
const groups = collection as DocActionGroup[]
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
actions: [],
|
|||
|
|
groups: groups.filter((group) => group.actions.length > 0),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
actions: collection as DocAction[],
|
|||
|
|
groups: [],
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderLink(action: DocAction, type: 'download' | 'open') {
|
|||
|
|
const attrs = [
|
|||
|
|
`href="${escapeHtml(action.href)}"`,
|
|||
|
|
type === 'open' ? 'target="_blank"' : '',
|
|||
|
|
type === 'open' ? 'rel="noopener noreferrer"' : '',
|
|||
|
|
type === 'download' ? 'download' : '',
|
|||
|
|
].filter(Boolean).join(' ')
|
|||
|
|
|
|||
|
|
return `<li><a ${attrs}>${escapeHtml(action.label)}</a></li>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderActionList(actions: DocAction[], type: 'download' | 'open') {
|
|||
|
|
return `<ul class="static-action-list">${actions.map((action) => renderLink(action, type)).join('')}</ul>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderActionCollection(collection: DocActionCollection | undefined, title: string, type: 'download' | 'open') {
|
|||
|
|
const { actions, groups } = splitActionCollection(collection)
|
|||
|
|
|
|||
|
|
if (actions.length === 0 && groups.length === 0) return ''
|
|||
|
|
|
|||
|
|
const content = groups.length > 0
|
|||
|
|
? groups.map((group) => `
|
|||
|
|
<li>
|
|||
|
|
<span class="static-action-group-title">${escapeHtml(group.title)}</span>
|
|||
|
|
${renderActionList(group.actions, type)}
|
|||
|
|
</li>`).join('')
|
|||
|
|
: actions.map((action) => renderLink(action, type)).join('')
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<li>
|
|||
|
|
<span class="static-action-title">${title}</span>
|
|||
|
|
<ul class="static-action-list static-action-list-nested">${content}
|
|||
|
|
</ul>
|
|||
|
|
</li>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDocLinks(title: string, links: DocAction[]) {
|
|||
|
|
if (links.length === 0) return ''
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<li>
|
|||
|
|
<span class="static-action-title">AI</span>
|
|||
|
|
${renderActionList(links, 'open')}
|
|||
|
|
</li>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDoc(doc: typeof docs[number]) {
|
|||
|
|
const title = escapeHtml(doc.title)
|
|||
|
|
const heading = doc.href ? `<a href="${escapeHtml(doc.href)}">${title}</a>` : title
|
|||
|
|
const actionGroups = doc.actionGroups
|
|||
|
|
const actions = [
|
|||
|
|
renderActionCollection(actionGroups?.open, 'Открыть', 'open'),
|
|||
|
|
renderActionCollection(actionGroups?.download, 'Скачать', 'download'),
|
|||
|
|
doc.actionGroups ? '' : renderDocLinks(doc.title, doc.links),
|
|||
|
|
].filter(Boolean).join('')
|
|||
|
|
const actionBlock = actions ? `
|
|||
|
|
<ul class="static-actions-list">${actions}
|
|||
|
|
</ul>` : ''
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<li class="static-card">
|
|||
|
|
<article>
|
|||
|
|
<div class="static-meta">${escapeHtml(doc.label)} · ${escapeHtml(doc.status)}</div>
|
|||
|
|
<h2>${heading}</h2>
|
|||
|
|
<p>${escapeHtml(doc.description)}</p>${actionBlock}
|
|||
|
|
</article>
|
|||
|
|
</li>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderStaticDocs() {
|
|||
|
|
return `<section aria-labelledby="static-docs-title">
|
|||
|
|
<h2 id="static-docs-title">Список документаций</h2>
|
|||
|
|
<ul class="static-docs">${docs.map(renderDoc).join('')}
|
|||
|
|
</ul>
|
|||
|
|
</section>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const indexHtml = fs.readFileSync(indexPath, 'utf8')
|
|||
|
|
const startIndex = indexHtml.indexOf(startMarker)
|
|||
|
|
const endIndex = indexHtml.indexOf(endMarker)
|
|||
|
|
|
|||
|
|
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|||
|
|
throw new Error(`Не найдены маркеры ${startMarker} / ${endMarker} в index.html`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const nextIndexHtml = [
|
|||
|
|
indexHtml.slice(0, startIndex + startMarker.length),
|
|||
|
|
'\n ',
|
|||
|
|
renderStaticDocs().replaceAll('\n', '\n '),
|
|||
|
|
'\n ',
|
|||
|
|
indexHtml.slice(endIndex),
|
|||
|
|
].join('')
|
|||
|
|
|
|||
|
|
fs.writeFileSync(indexPath, nextIndexHtml, 'utf8')
|
|||
|
|
console.log('Подготовлен static fallback в index.html')
|