diff --git a/.github/workflows/cf-pages.yml b/.github/workflows/cf-pages.yml index 03ec0b7..2e2661c 100644 --- a/.github/workflows/cf-pages.yml +++ b/.github/workflows/cf-pages.yml @@ -21,10 +21,13 @@ jobs: uses: actions/setup-node@v4 - name: Install dependencies - run: npm i + run: npm install - - name: Build - run: npm run build + - name: Fetch content from ATProto + run: npm run fetch + + - name: Generate static site + run: npm run generate - name: Deploy to Cloudflare Pages uses: cloudflare/pages-action@v1 diff --git a/.gitignore b/.gitignore index 0062c02..e87a534 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist node_modules package-lock.json repos +content/ diff --git a/package.json b/package.json index f2af4a8..c7fda56 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,18 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "fetch": "tsx scripts/generate.ts fetch", + "generate": "npm run build && tsx scripts/generate.ts" }, "dependencies": { "@atproto/api": "^0.15.8", - "@atproto/oauth-client-browser": "^0.3.39" + "@atproto/oauth-client-browser": "^0.3.39", + "highlight.js": "^11.11.1", + "marked": "^17.0.1" }, "devDependencies": { + "tsx": "^4.21.0", "typescript": "^5.7.0", "vite": "^6.0.0" } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..27c62e8 --- /dev/null +++ b/readme.md @@ -0,0 +1,35 @@ +# ailog + +## config + +```sh +$ ls public/ +config.json +networks.json +client-metadata.json +``` + +## preview + +```sh +$ npm run dev +``` + +## content generate + +speed up by using local cache + +```sh +$ npm run fetch # API download -> content/${did}/... +$ npm run generate # content html -> dist/ +$ npm run preview # dist/ preview server +``` + +```sh +content/ +└── did:plc:uqzpqmrjnptsxezjx4xuh2mn/ + ├── profile.json + └── ai.syui.log.post/ + └── 3mch5zca4nj2h.json +``` + diff --git a/scripts/generate.ts b/scripts/generate.ts new file mode 100644 index 0000000..dd297eb --- /dev/null +++ b/scripts/generate.ts @@ -0,0 +1,699 @@ +import * as fs from 'fs' +import * as path from 'path' +import { marked, Renderer } from 'marked' + +// Types +interface AppConfig { + title: string + handle: string + collection: string + network: string + color?: string +} + +interface Networks { + [key: string]: { + plc: string + bsky: string + } +} + +interface Profile { + did: string + handle: string + displayName?: string + description?: string + avatar?: string +} + +interface BlogPost { + uri: string + cid: string + title: string + content: string + createdAt: string +} + +// Highlight.js for syntax highlighting (core + common languages only) +let hljs: typeof import('highlight.js/lib/core').default + +async function loadHighlightJs() { + const core = await import('highlight.js/lib/core') + hljs = core.default + + const [js, ts, bash, json, yaml, md, css, xml, py, rust, go] = await Promise.all([ + import('highlight.js/lib/languages/javascript'), + import('highlight.js/lib/languages/typescript'), + import('highlight.js/lib/languages/bash'), + import('highlight.js/lib/languages/json'), + import('highlight.js/lib/languages/yaml'), + import('highlight.js/lib/languages/markdown'), + import('highlight.js/lib/languages/css'), + import('highlight.js/lib/languages/xml'), + import('highlight.js/lib/languages/python'), + import('highlight.js/lib/languages/rust'), + import('highlight.js/lib/languages/go'), + ]) + + hljs.registerLanguage('javascript', js.default) + hljs.registerLanguage('js', js.default) + hljs.registerLanguage('typescript', ts.default) + hljs.registerLanguage('ts', ts.default) + hljs.registerLanguage('bash', bash.default) + hljs.registerLanguage('sh', bash.default) + hljs.registerLanguage('json', json.default) + hljs.registerLanguage('yaml', yaml.default) + hljs.registerLanguage('yml', yaml.default) + hljs.registerLanguage('markdown', md.default) + hljs.registerLanguage('md', md.default) + hljs.registerLanguage('css', css.default) + hljs.registerLanguage('html', xml.default) + hljs.registerLanguage('xml', xml.default) + hljs.registerLanguage('python', py.default) + hljs.registerLanguage('py', py.default) + hljs.registerLanguage('rust', rust.default) + hljs.registerLanguage('go', go.default) +} + +// Markdown renderer +function setupMarked() { + const renderer = new Renderer() + + renderer.code = function({ text, lang }: { text: string; lang?: string }) { + let highlighted: string + if (lang && hljs.getLanguage(lang)) { + try { + highlighted = hljs.highlight(text, { language: lang }).value + } catch { + highlighted = escapeHtml(text) + } + } else { + highlighted = escapeHtml(text) + } + return `
${highlighted}`
+ }
+
+ marked.setOptions({
+ breaks: true,
+ gfm: true,
+ renderer,
+ })
+}
+
+function escapeHtml(str: string): string {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+}
+
+function formatDate(dateStr: string): string {
+ const date = new Date(dateStr)
+ return date.toLocaleDateString('ja-JP', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
+}
+
+// API functions
+async function resolveHandle(handle: string, bskyUrl: string): PromiseNo posts yet
' + } + const items = posts.map(post => { + const rkey = post.uri.split('/').pop() + return ` +