// 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 => { const g = new Map() 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 } }