~repos /website

#astro#js#html#css

GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone https://git.pyrossh.dev/website/.git website
Discussions: https://groups.google.com/g/rust-embed-devs

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


bun.lock CHANGED
@@ -13,10 +13,16 @@
13
13
  "astro-color-scheme": "^1.1.6",
14
14
  "astro-expressive-code": "^0.42.0",
15
15
  "astro-icon": "^1.1.5",
16
+ "gray-matter": "^4.0.3",
16
17
  "pretty-bytes": "^7.1.0",
18
+ "rehype-expressive-code": "^0.42.0",
19
+ "rehype-stringify": "^10.0.1",
20
+ "remark-parse": "^11.0.0",
21
+ "remark-rehype": "^11.1.2",
17
22
  "simple-git": "^3.27.0",
18
23
  "spamscanner": "^6.1.5",
19
24
  "timeago.js": "^4.0.2",
25
+ "unified": "^11.0.5",
20
26
  },
21
27
  "devDependencies": {
22
28
  "@astrojs/rss": "^4.0.18",
@@ -1227,6 +1233,8 @@
1227
1233
 
1228
1234
  "esniff": ["[email protected]", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
1229
1235
 
1236
+ "esprima": ["[email protected]", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
1237
+
1230
1238
  "estree-walker": ["[email protected]", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
1231
1239
 
1232
1240
  "etag": ["[email protected]", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
@@ -1247,6 +1255,8 @@
1247
1255
 
1248
1256
  "extend": ["[email protected]", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
1249
1257
 
1258
+ "extend-shallow": ["[email protected]", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
1259
+
1250
1260
  "extract-zip": ["[email protected]", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
1251
1261
 
1252
1262
  "fast-content-type-parse": ["[email protected]", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="],
@@ -1335,6 +1345,8 @@
1335
1345
 
1336
1346
  "graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
1337
1347
 
1348
+ "gray-matter": ["[email protected]", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
1349
+
1338
1350
  "h3": ["[email protected]", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
1339
1351
 
1340
1352
  "has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -1433,6 +1445,8 @@
1433
1445
 
1434
1446
  "is-docker": ["[email protected]", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
1435
1447
 
1448
+ "is-extendable": ["[email protected]", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
1449
+
1436
1450
  "is-extglob": ["[email protected]", "", {}, "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww=="],
1437
1451
 
1438
1452
  "is-fullwidth-code-point": ["[email protected]", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
@@ -1483,6 +1497,8 @@
1483
1497
 
1484
1498
  "kareem": ["[email protected]", "", {}, "sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw=="],
1485
1499
 
1500
+ "kind-of": ["[email protected]", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
1501
+
1486
1502
  "kleur": ["[email protected]", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
1487
1503
 
1488
1504
  "kolorist": ["[email protected]", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
@@ -1933,6 +1949,8 @@
1933
1949
 
1934
1950
  "sax": ["[email protected]", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
1935
1951
 
1952
+ "section-matter": ["[email protected]", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
1953
+
1936
1954
  "seedrandom": ["[email protected]", "", {}, "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="],
1937
1955
 
1938
1956
  "selderee": ["[email protected]", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
@@ -2013,6 +2031,8 @@
2013
2031
 
2014
2032
  "strip-ansi-cjs": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
2015
2033
 
2034
+ "strip-bom-string": ["[email protected]", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
2035
+
2016
2036
  "strip-final-newline": ["[email protected]", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
2017
2037
 
2018
2038
  "striptags": ["[email protected]", "", {}, "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="],
@@ -2591,6 +2611,8 @@
2591
2611
 
2592
2612
  "gauge/signal-exit": ["[email protected]", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
2593
2613
 
2614
+ "gray-matter/js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
2615
+
2594
2616
  "hasha/is-stream": ["[email protected]", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
2595
2617
 
2596
2618
  "hasha/type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -2973,6 +2995,8 @@
2973
2995
 
2974
2996
  "form-data/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
2975
2997
 
2998
+ "gray-matter/js-yaml/argparse": ["[email protected]", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
2999
+
2976
3000
  "hast-util-raw/unist-util-visit/unist-util-visit-parents": ["[email protected]", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
2977
3001
 
2978
3002
  "hast-util-select/unist-util-visit/unist-util-visit-parents": ["[email protected]", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
package.json CHANGED
@@ -22,10 +22,16 @@
22
22
  "astro-color-scheme": "^1.1.6",
23
23
  "astro-expressive-code": "^0.42.0",
24
24
  "astro-icon": "^1.1.5",
25
+ "gray-matter": "^4.0.3",
25
26
  "pretty-bytes": "^7.1.0",
27
+ "rehype-expressive-code": "^0.42.0",
28
+ "rehype-stringify": "^10.0.1",
29
+ "remark-parse": "^11.0.0",
30
+ "remark-rehype": "^11.1.2",
26
31
  "simple-git": "^3.27.0",
27
32
  "spamscanner": "^6.1.5",
28
- "timeago.js": "^4.0.2"
33
+ "timeago.js": "^4.0.2",
34
+ "unified": "^11.0.5"
29
35
  },
30
36
  "devDependencies": {
31
37
  "@astrojs/rss": "^4.0.18",
src/content.config.ts DELETED
@@ -1,51 +0,0 @@
1
- import fs from "fs/promises";
2
- import { glob } from 'astro/loaders';
3
- import { defineCollection, z } from 'astro:content';
4
- import { REPOS } from './consts';
5
- import { checkFileExists } from "./utils/files";
6
-
7
- export const collections = {
8
- repos: defineCollection({
9
- loader: {
10
- name: "loader",
11
- load: async ({ renderMarkdown, store }) => {
12
- for (const r of REPOS) {
13
- const repoPath = `/Users/pyrossh/Code/pyrossh/${r.title}`;
14
- const readmePath = repoPath + "/README.md";
15
- const readmeContent = await checkFileExists(readmePath) ? await renderMarkdown(await fs.readFile(readmePath, 'utf-8')) : {
16
- html: '',
17
- metadata: {},
18
- }
19
- store.set({
20
- id: r.title,
21
- data: {
22
- title: r.title,
23
- description: r.description,
24
- tags: r.tags,
25
- absolutePath: repoPath,
26
- },
27
- rendered: readmeContent,
28
- });
29
- }
30
- },
31
- },
32
- schema: z.object({
33
- title: z.string(),
34
- description: z.string().max(80),
35
- tags: z.array(z.string()),
36
- absolutePath: z.string(),
37
- }),
38
- }),
39
- content: defineCollection({
40
- loader: glob({ base: './src/content', pattern: '**/*.{md,mdx}' }),
41
- // Type-check frontmatter using a schema
42
- schema: z.object({
43
- title: z.string(),
44
- description: z.string(),
45
- // Transform string to Date object
46
- pubDate: z.coerce.date(),
47
- updatedDate: z.coerce.date().optional(),
48
- heroImage: z.string().optional(),
49
- }),
50
- }),
51
- };
src/layouts/FileLayout.astro CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  import { Icon } from "astro-icon/components";
3
- import { type CollectionEntry } from "astro:content";
4
3
  import allIcons from "@iconify-json/vscode-icons/icons.json" assert { type: "json" };
5
4
  import RepoLayout from "@/layouts/RepoLayout.astro";
5
+ import type { RuntimeRepo } from "@/utils/content";
6
6
  import { resolveFileIcon, type FileNode } from "@/utils/files";
7
7
 
8
8
  type Props = {
9
- repo: CollectionEntry<"repos">;
9
+ repo: RuntimeRepo;
10
10
  file: FileNode;
11
11
  };
12
12
 
src/layouts/RepoLayout.astro CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
- import { type CollectionEntry } from "astro:content";
3
2
  import { Icon } from "astro-icon/components";
4
3
  import Layout from "@/layouts/BaseLayout.astro";
4
+ import type { RuntimeRepo } from "@/utils/content";
5
5
 
6
6
  type Props = {
7
- repo: CollectionEntry<"repos">;
7
+ repo: RuntimeRepo;
8
8
  };
9
9
 
10
10
  const { repo } = Astro.props;
src/pages/posts/[...postId]/index.astro CHANGED
@@ -1,11 +1,9 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
- import { render } from "astro:content";
4
2
  import Layout from "@/layouts/BaseLayout.astro";
5
3
  import FormattedDate from "@/components/FormattedDate.astro";
4
+ import { getPost } from "@/utils/content";
6
5
 
7
- const posts = await getCollection("content");
6
+ const post = await getPost(Astro.params.postId);
8
- const post = posts.find((item) => item.id === Astro.params.postId);
9
7
  if (!post) {
10
8
  return Astro.redirect("/404");
11
9
  }
@@ -13,7 +11,6 @@ if (!post) {
13
11
  const {
14
12
  data: { title, description, pubDate },
15
13
  } = post;
16
- const { Content } = await render(post);
17
14
  ---
18
15
 
19
16
  <Layout title={title} description={description}>
@@ -22,7 +19,7 @@ const { Content } = await render(post);
22
19
  <h2>{description}</h2>
23
20
  <FormattedDate date={pubDate} />
24
21
  <hr />
25
- <Content />
22
+ <div set:html={post.html} />
26
23
  </article>
27
24
  </Layout>
28
25
  <style>
src/pages/posts/index.astro CHANGED
@@ -1,11 +1,9 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import FormattedDate from "../../components/FormattedDate.astro";
4
3
  import Layout from "@/layouts/BaseLayout.astro";
4
+ import { getPosts } from "@/utils/content";
5
5
 
6
- const posts = (await getCollection("content")).sort(
6
+ const posts = await getPosts();
7
- (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
8
- );
9
7
  ---
10
8
 
11
9
  <Layout title="Posts" description="Posts">
src/pages/repos/[...repoId]/commits/[...hash]/index.astro CHANGED
@@ -1,12 +1,11 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import RepoLayout from "@/layouts/RepoLayout.astro";
4
3
  import Commit from "@/components/Commit.astro";
5
4
  import { generateHTMLDiff, getCommits } from "@/utils/commit";
5
+ import { getRepo } from "@/utils/content";
6
6
  import "diff2html/bundles/css/diff2html.min.css";
7
7
 
8
- const repos = await getCollection("repos");
9
- const repo = repos.find((item) => item.id === Astro.params.repoId);
8
+ const repo = getRepo(Astro.params.repoId);
10
9
  if (!repo) {
11
10
  return Astro.redirect("/404");
12
11
  }
src/pages/repos/[...repoId]/commits/index.astro CHANGED
@@ -1,11 +1,10 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import RepoLayout from "@/layouts/RepoLayout.astro";
4
3
  import Commit from "@/components/Commit.astro";
5
4
  import { getCommits } from "@/utils/commit";
5
+ import { getRepo } from "@/utils/content";
6
6
 
7
- const repos = await getCollection("repos");
8
- const repo = repos.find((item) => item.id === Astro.params.repoId);
7
+ const repo = getRepo(Astro.params.repoId);
9
8
  if (!repo) {
10
9
  return Astro.redirect("/404");
11
10
  }
src/pages/repos/[...repoId]/files/[...file]/blame/index.astro CHANGED
@@ -1,10 +1,9 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import FileLayout from "@/layouts/FileLayout.astro";
4
3
  import { getFileHistory, getFiles } from "@/utils/commit";
4
+ import { getRepo } from "@/utils/content";
5
5
 
6
- const repos = await getCollection("repos");
7
- const repo = repos.find((item) => item.id === Astro.params.repoId);
6
+ const repo = getRepo(Astro.params.repoId);
8
7
  if (!repo) {
9
8
  return Astro.redirect("/404");
10
9
  }
src/pages/repos/[...repoId]/files/[...file]/history/index.astro CHANGED
@@ -1,11 +1,10 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import FileLayout from "@/layouts/FileLayout.astro";
4
3
  import Commit from "@/components/Commit.astro";
5
4
  import { getFileHistory, getFiles } from "@/utils/commit";
5
+ import { getRepo } from "@/utils/content";
6
6
 
7
- const repos = await getCollection("repos");
8
- const repo = repos.find((item) => item.id === Astro.params.repoId);
7
+ const repo = getRepo(Astro.params.repoId);
9
8
  if (!repo) {
10
9
  return Astro.redirect("/404");
11
10
  }
src/pages/repos/[...repoId]/files/[...file]/index.astro CHANGED
@@ -1,12 +1,11 @@
1
1
  ---
2
2
  import fs from "fs/promises";
3
- import { getCollection } from "astro:content";
4
3
  import { Code } from "astro-expressive-code/components";
5
4
  import FileLayout from "@/layouts/FileLayout.astro";
6
5
  import { getFiles } from "@/utils/commit";
6
+ import { getRepo } from "@/utils/content";
7
7
 
8
- const repos = await getCollection("repos");
9
- const repo = repos.find((item) => item.id === Astro.params.repoId);
8
+ const repo = getRepo(Astro.params.repoId);
10
9
  if (!repo) {
11
10
  return Astro.redirect("/404");
12
11
  }
src/pages/repos/[...repoId]/files/index.astro CHANGED
@@ -1,12 +1,11 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
2
  import RepoLayout from "@/layouts/RepoLayout.astro";
4
3
  import RecursiveList from "@/components/RecursiveList.astro";
5
4
  import { getFiles } from "@/utils/commit";
5
+ import { getRepo } from "@/utils/content";
6
6
  import { buildFileTree, sortChildren } from "@/utils/files";
7
7
 
8
- const repos = await getCollection("repos");
9
- const repo = repos.find((item) => item.id === Astro.params.repoId);
8
+ const repo = getRepo(Astro.params.repoId);
10
9
  if (!repo) {
11
10
  return Astro.redirect("/404");
12
11
  }
src/pages/repos/[...repoId]/index.astro CHANGED
@@ -1,20 +1,18 @@
1
1
  ---
2
- import { getCollection } from "astro:content";
3
- import { render } from "astro:content";
4
2
  import RepoLayout from "@/layouts/RepoLayout.astro";
3
+ import { getRepo, getRepoReadmeHtml } from "@/utils/content";
5
4
 
6
- const repos = await getCollection("repos");
7
- const repo = repos.find((item) => item.id === Astro.params.repoId);
5
+ const repo = getRepo(Astro.params.repoId);
8
6
  if (!repo) {
9
7
  return Astro.redirect("/404");
10
8
  }
11
9
 
12
- const { Content } = await render(repo);
10
+ const readmeHtml = await getRepoReadmeHtml(repo);
13
11
  ---
14
12
 
15
13
  <RepoLayout repo={repo}>
16
14
  <article>
17
- <Content />
15
+ <div set:html={readmeHtml} />
18
16
  </article>
19
17
  </RepoLayout>
20
18
  <style>
src/pages/repos/[...repoId]/issues/[...issueId]/index.astro CHANGED
@@ -1,22 +1,15 @@
1
1
  ---
2
- import { type CollectionEntry, getCollection } from "astro:content";
3
2
  import { actions } from "astro:actions";
4
3
  import RepoLayout from "@/layouts/RepoLayout.astro";
5
4
  import Issue from "@/components/Issue.astro";
6
5
  import TimeAgo from "@/components/TimeAgo.astro";
6
+ import { getRepo } from "@/utils/content";
7
7
  import {
8
8
  getGitBugIssue,
9
9
  getGitBugIssues,
10
- type GitBugIssue,
11
10
  } from "@/utils/gitBug";
12
11
 
13
- type Props = {
14
- repo: CollectionEntry<"repos">;
15
- issue: GitBugIssue;
16
- };
17
-
18
- const repos = await getCollection("repos");
19
- const repo = repos.find((item) => item.id === Astro.params.repoId);
12
+ const repo = getRepo(Astro.params.repoId);
20
13
  if (!repo) {
21
14
  return Astro.redirect("/404");
22
15
  }
src/pages/repos/[...repoId]/issues/index.astro CHANGED
@@ -1,16 +1,11 @@
1
1
  ---
2
- import { type CollectionEntry, getCollection } from "astro:content";
3
2
  import { actions } from "astro:actions";
4
3
  import RepoLayout from "@/layouts/RepoLayout.astro";
5
4
  import Issue from "@/components/Issue.astro";
5
+ import { getRepo } from "@/utils/content";
6
6
  import { getGitBugIssues } from "@/utils/gitBug";
7
7
 
8
- type Props = {
9
- repo: CollectionEntry<"repos">;
10
- };
11
-
12
- const repos = await getCollection("repos");
13
- const repo = repos.find((item) => item.id === Astro.params.repoId);
8
+ const repo = getRepo(Astro.params.repoId);
14
9
  if (!repo) {
15
10
  return Astro.redirect("/404");
16
11
  }
src/pages/rss.xml.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { APIRoute } from 'astro';
2
- import { getCollection } from 'astro:content';
3
2
  import rss from '@astrojs/rss';
4
3
  import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
4
+ import { getPosts } from '../utils/content';
5
5
 
6
6
  export const GET: APIRoute = async ({ site }) => {
7
- const posts = await getCollection('content');
7
+ const posts = await getPosts();
8
8
  return rss({
9
9
  title: SITE_TITLE,
10
10
  description: SITE_DESCRIPTION,
src/utils/content.ts ADDED
@@ -0,0 +1,127 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import matter from "gray-matter";
4
+ import rehypeExpressiveCode from "rehype-expressive-code";
5
+ import rehypeStringify from "rehype-stringify";
6
+ import remarkParse from "remark-parse";
7
+ import remarkRehype from "remark-rehype";
8
+ import { unified } from "unified";
9
+ import { REPOS } from "@/consts";
10
+ import ecConfig from "../../ec.config.mjs";
11
+
12
+ export interface RuntimeRepo {
13
+ id: string;
14
+ data: {
15
+ title: string;
16
+ description: string;
17
+ tags: string[];
18
+ absolutePath: string;
19
+ };
20
+ }
21
+
22
+ export interface RuntimePost {
23
+ id: string;
24
+ data: {
25
+ title: string;
26
+ description: string;
27
+ pubDate: Date;
28
+ updatedDate?: Date;
29
+ heroImage?: string;
30
+ };
31
+ body: string;
32
+ html: string;
33
+ }
34
+
35
+ const contentDir = path.resolve("src/content");
36
+
37
+ const renderMarkdown = async (markdown: string) => {
38
+ const file = await unified()
39
+ .use(remarkParse)
40
+ .use(remarkRehype)
41
+ .use(rehypeExpressiveCode, ecConfig)
42
+ .use(rehypeStringify)
43
+ .process(markdown);
44
+
45
+ return String(file);
46
+ };
47
+
48
+ const toDate = (value: unknown) => {
49
+ if (value instanceof Date) {
50
+ return value;
51
+ }
52
+ return new Date(String(value));
53
+ };
54
+
55
+ const readMarkdownFiles = async (dir: string): Promise<string[]> => {
56
+ const entries = await fs.readdir(dir, { withFileTypes: true });
57
+ const files = await Promise.all(
58
+ entries.map(async (entry) => {
59
+ const itemPath = path.join(dir, entry.name);
60
+ if (entry.isDirectory()) {
61
+ return await readMarkdownFiles(itemPath);
62
+ }
63
+ return /\.(md|mdx)$/.test(entry.name) ? [itemPath] : [];
64
+ }),
65
+ );
66
+ return files.flat();
67
+ };
68
+
69
+ export const getRepos = (): RuntimeRepo[] =>
70
+ REPOS.map((repo) => ({
71
+ id: repo.title,
72
+ data: {
73
+ ...repo,
74
+ absolutePath: `/Users/pyrossh/Code/pyrossh/${repo.title}`,
75
+ },
76
+ }));
77
+
78
+ export const getRepo = (repoId?: string) =>
79
+ getRepos().find((repo) => repo.id === repoId);
80
+
81
+ export const getRepoReadmeHtml = async (repo: RuntimeRepo) => {
82
+ try {
83
+ const readme = await fs.readFile(
84
+ path.join(repo.data.absolutePath, "README.md"),
85
+ "utf-8",
86
+ );
87
+ return await renderMarkdown(readme);
88
+ } catch {
89
+ return "";
90
+ }
91
+ };
92
+
93
+ export const getPosts = async (): Promise<RuntimePost[]> => {
94
+ const files = await readMarkdownFiles(contentDir);
95
+ const posts = await Promise.all(
96
+ files.map(async (filePath) => {
97
+ const source = await fs.readFile(filePath, "utf-8");
98
+ const parsed = matter(source);
99
+ const relativePath = path.relative(contentDir, filePath);
100
+ const id = relativePath.replace(/\.(md|mdx)$/, "");
101
+
102
+ return {
103
+ id,
104
+ data: {
105
+ title: String(parsed.data.title ?? ""),
106
+ description: String(parsed.data.description ?? ""),
107
+ pubDate: toDate(parsed.data.pubDate),
108
+ updatedDate: parsed.data.updatedDate
109
+ ? toDate(parsed.data.updatedDate)
110
+ : undefined,
111
+ heroImage: parsed.data.heroImage
112
+ ? String(parsed.data.heroImage)
113
+ : undefined,
114
+ },
115
+ body: parsed.content,
116
+ html: await renderMarkdown(parsed.content),
117
+ };
118
+ }),
119
+ );
120
+
121
+ return posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
122
+ };
123
+
124
+ export const getPost = async (postId?: string) => {
125
+ const posts = await getPosts();
126
+ return posts.find((post) => post.id === postId);
127
+ };