feat: 零点原核频道v1+登录双门地基+五湖开场——zero_point裁决链/灯塔查号/code_repo_login/通知卡重设计/浅色主题联动(冰朔20260815夜谕)

This commit is contained in:
冰朔 2026-08-16 00:02:05 +08:00
commit 07fd4c85ef
16 changed files with 3082 additions and 88 deletions

View file

@ -0,0 +1,146 @@
//! 知识渲染件 · 渲染入口(常驻模块 · 插座口 knowledge-render
//!
//! 职责:把仓库里的 Markdown 文件变成可读的页面——frontmatter 解析、
//! Notion/Outline 导出兼容、标题锚点、大纲同源、跳转。
import { useMemo } from 'react'
import DOMPurify from 'dompurify'
import { marked, Parser, type Tokens } from 'marked'
export { knowledgeRenderManifest } from './manifest'
function escapeHtml(value: string) {
return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;')
}
export function splitFrontmatter(body: string) {
const normalized = body.replace(/\r\n/g, '\n')
if (!normalized.startsWith('---\n')) return { content: normalized, metadata: {} as Record<string, string>, tags: [] as string[] }
const closing = normalized.indexOf('\n---\n', 4)
if (closing < 0) return { content: normalized, metadata: {} as Record<string, string>, tags: [] as string[] }
const metadata: Record<string, string> = {}
const tags: string[] = []
let pendingListKey = ''
for (const line of normalized.slice(4, closing).split('\n')) {
const listItem = /^\s+-\s+(.+)$/.exec(line)
if (listItem && pendingListKey) {
if (pendingListKey === 'tags') tags.push(listItem[1].trim().replace(/^['"]|['"]$/g, ''))
continue
}
const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line)
if (!match) continue
pendingListKey = match[2] === '' ? match[1] : ''
const value = match[2].replace(/^['"]|['"]$/g, '')
metadata[match[1]] = value
if (match[1] === 'tags') {
const inline = /^\[(.*)\]$/.exec(match[2].trim())
const source = inline ? inline[1] : value
source.split(',').map((part) => part.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean).forEach((tag) => tags.push(tag))
}
}
return { content: normalized.slice(closing + 5), metadata, tags }
}
function stripNotionLinks(line: string) {
// Notion 页面链接 → 只留链接文字(跳转关系按谕旨失效)
return line.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (whole, text: string, href: string) =>
(/notion\.(?:so|site)/.test(href) ? text : whole))
}
export function notionCompat(content: string) {
// 存量旧库文件尚未洗净:渲染时按"进门即洗"同规矩兜底过滤一遍。
// 内容一字不改,只把外来知识库的外壳换成光湖原生 Markdown冰朔 2026-08-15 谕:不兼容,进门就转)。
const result: string[] = []
for (const line of content.replace(/\r\n/g, '\n').split('\n')) {
const trimmed = line.trim()
if (trimmed === '<aside>' || trimmed === '</aside>') continue
if (trimmed.startsWith(':::toggle')) {
const title = trimmed.slice(':::toggle'.length).trim()
result.push(title ? `**▸ ${title}**` : '**▸ 详情**')
continue
}
if (trimmed === ':::' || trimmed.startsWith(':::toc')) continue
result.push(stripNotionLinks(line.replace(/<br\s*\/?>/g, '\n')))
}
// Notion 粗体写法(**文字:**紧接正文)不合 CommonMark给收尾星号补一口气
return result.join('\n').replace(/(\*\*[^*\n]+\*\*)(?=[^\s.,;:!?,。;:!?、)\]"'])/g, '$1 ')
}
const CALLOUT_TINTS: [RegExp, string][] = [
[/📌|🌌|🧭|💜|🗂|🏷|📚/, 'callout-lavender'],
[/💡|⚡|🌟|☀|✨|🔑/, 'callout-amber'],
[/⚠|🔥|❗|🚨|❌/, 'callout-rose'],
[/✅|🌿|🍀|💚|✔/, 'callout-mint'],
[/🌊|💧|🔵|❄|🧊/, 'callout-sky'],
]
function normalizeTitle(value: string) {
return value.replace(/[\s·•・\-_|*`]/g, '').toLowerCase()
}
function calloutTint(text: string) {
for (const [pattern, tint] of CALLOUT_TINTS) if (pattern.test(text)) return tint
return 'callout-slate'
}
export function markdownHtml(body: string, knownTitles: readonly string[] = []) {
const compat = notionCompat(body)
const titleMap = new Map<string, string>()
for (const title of knownTitles) titleMap.set(normalizeTitle(title), title)
const withWikiLinks = compat.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_whole, target: string, label?: string) =>
`<button class="wiki-link" type="button" data-wiki="${escapeHtml(target.trim())}">${escapeHtml((label || target).trim())}</button>`)
let headingIndex = 0
const renderer = new marked.Renderer()
renderer.heading = (token) => {
const id = `heading-${headingIndex}`
headingIndex += 1
const inlineHtml = token.tokens.length ? Parser.parseInline(token.tokens) : escapeHtml(token.text)
return `<h${token.depth} id="${id}">${inlineHtml}</h${token.depth}>\n`
}
renderer.blockquote = (token) => {
const inner = Parser.parse(token.tokens) as string
return `<blockquote class="callout ${calloutTint(token.raw)}">${inner}</blockquote>\n`
}
renderer.link = (token) => {
const inner = token.tokens.length ? Parser.parseInline(token.tokens) : escapeHtml(token.text)
const href = token.href || ''
if (/notion\.(?:so|site)/i.test(href)) {
// Notion 残留链接:认得出是自家页面就转成内部跳转,认不出标"外来页面"。
const hit = titleMap.get(normalizeTitle(token.text))
if (hit) return `<button class="wiki-link" type="button" data-wiki="${escapeHtml(hit)}">${inner}</button>`
return `<a class="external-link" href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}<span class="external-mark">外来页面</span></a>`
}
if (/^https?:\/\//i.test(href)) return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}</a>`
return `<a href="${escapeHtml(href)}">${inner}</a>`
}
const html = marked.parse(withWikiLinks, { async: false, gfm: true, breaks: false, renderer }) as string
return DOMPurify.sanitize(html, { ADD_ATTR: ['data-wiki', 'id', 'target', 'rel'], ADD_TAGS: ['button'] })
}
export function documentOutline(body: string) {
const headings: { id: string; level: number; title: string }[] = []
const plain = (tokens: unknown): string => (tokens as { type: string; text?: string; tokens?: unknown[] }[])
.map((token) => (token.tokens && token.tokens.length ? plain(token.tokens) : token.text || '')).join('')
marked.lexer(splitFrontmatter(body).content, { gfm: true })
.filter((token): token is Tokens.Heading => token.type === 'heading')
.forEach((token, index) => {
if (index >= 24) return
headings.push({ id: `heading-${index}`, level: token.depth, title: plain(token.tokens).trim() })
})
return headings
}
export function jumpToHeading(id: string) {
const element = document.querySelector<HTMLElement>(`.document-scroll [id="${id}"]`)
if (!element) return
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
element.classList.remove('heading-flash')
void element.offsetWidth
element.classList.add('heading-flash')
}
export function MarkdownDocument({ body, knownTitles, onWiki }: { body: string; knownTitles?: readonly string[]; onWiki?: (target: string) => void }) {
const parsed = useMemo(() => splitFrontmatter(body), [body])
const html = useMemo(() => markdownHtml(parsed.content, knownTitles), [parsed.content, knownTitles])
return <article className="markdown-document" onClick={(event) => {
const element = (event.target as HTMLElement).closest<HTMLElement>('[data-wiki]')
if (element?.dataset.wiki && onWiki) onWiki(element.dataset.wiki)
}} dangerouslySetInnerHTML={{ __html: html }} />
}

View file

@ -0,0 +1,22 @@
//! 模块形状声明 · 插座制(冰朔 2026-08-15 架构谕)
//!
//! 软件只认这一个插座形状:每个模块 = manifest我是谁/插哪个口/从哪来)
//! + index渲染入口导出。常驻模块住在频道里将来 origin='repo' 的
//! 模块躺代码仓库,人格体按需拉取部署。
export const knowledgeRenderManifest = {
moduleId: 'hololake.knowledge-render',
name: '知识渲染件',
version: '0.1.0',
slot: 'knowledge-render',
origin: 'resident',
exports: ['splitFrontmatter', 'documentOutline', 'jumpToHeading', 'MarkdownDocument'],
} as const
export type ModuleManifest = {
moduleId: string
name: string
version: string
slot: string
origin: 'resident' | 'repo'
exports: readonly string[]
}