Files

93 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// wiki 知識庫的純邏輯:目錄解析與渲染、頁名、驗證。讀寫與 git 在 register.tsx。
export type Entry = { domain: string; topic: string }
export const CACHE_MS = 10 * 60 * 1000
// 指令名稱只能用英數與底線,領域與主題也沿用。
const NAME_RE = /^[A-Za-z0-9_]+$/
export const validName = (s: string): boolean => NAME_RE.test(s)
export const commandName = (e: Entry): string => `${e.domain}-${e.topic}`
export const pageName = (e: Entry): string => `${e.domain}-${e.topic}`
// Home 的格式:「## 領域」章、「- [主題](頁名)」節。
export const parseCatalog = (home: string): Entry[] => {
const out: Entry[] = []
let domain = ''
for (const line of home.split('\n')) {
const h = /^##\s+(.+?)\s*$/.exec(line)
if (h) {
domain = h[1]!
continue
}
const m = /^\s*[-*]\s+\[(.+?)\]\((.+?)\)\s*$/.exec(line)
if (m && domain) out.push({ domain, topic: m[1]! })
}
return out
}
const group = (entries: Entry[]): Map<string, string[]> => {
const g = new Map<string, string[]>()
for (const e of entries) g.set(e.domain, [...(g.get(e.domain) ?? []), e.topic])
return g
}
export const addEntry = (entries: Entry[], e: Entry): Entry[] =>
entries.some(x => x.domain === e.domain && x.topic === e.topic) ? entries : [...entries, e]
export const renderHome = (entries: Entry[]): string => {
const lines = ['# 知識庫', '']
for (const [domain, topics] of group(entries)) {
lines.push(`## ${domain}`, '')
for (const t of topics) lines.push(`- [${t}](${pageName({ domain, topic: t })})`)
lines.push('')
}
return lines.join('\n')
}
export const renderSidebar = (entries: Entry[]): string => {
const lines = ['**[知識庫](Home)**', '']
for (const [domain, topics] of group(entries)) {
lines.push(`**${domain}**`, '')
for (const t of topics) lines.push(`- [${t}](${pageName({ domain, topic: t })})`)
lines.push('')
}
return lines.join('\n')
}
export const renderPage = (e: Entry, body: string, source: string): string =>
`# ${e.topic}\n\n> 領域:${e.domain}\n\n${body.trim()}\n\n---\n\n出處:${source}\n`
export const commitMessage = (e: Entry, existed: boolean): string =>
`feat: ${existed ? '更新' : '新增'}知識「${e.domain}/${e.topic}」`
// tea 設定檔(YAML)中,找出網址主機相符的登入的 token。
export const teaToken = (yaml: string, host: string): string | undefined => {
for (const block of yaml.split(/^\s*-\s+name:/m).slice(1)) {
const url = /^\s*url:\s*(\S+)/m.exec(block)?.[1]
const token = /^\s*token:\s*(\S+)/m.exec(block)?.[1]
if (!url || !token) continue
try {
if (new URL(url.replace(/^['"]|['"]$/g, '')).host === host) return token.replace(/^['"]|['"]$/g, '')
} catch {}
}
return undefined
}
export const wikiUrlFromRemote = (remote: string): string | undefined => {
const base = remote.trim().replace(/\.git$/, '')
return base ? `${base}.wiki.git` : undefined
}
// 指令參數:第一行「領域 主題 [出處]」,其後為內容。
export const parseWriteArgs = (args: string): { entry: Entry; source: string; body: string } | undefined => {
const [first = '', ...rest] = args.trim().split('\n')
const [domain, topic, ...src] = first.trim().split(/\s+/)
const body = rest.join('\n').trim()
if (!domain || !topic || !body || !validName(domain) || !validName(topic)) return undefined
return { entry: { domain, topic }, source: src.join(' ') || '使用者提供', body }
}