Commit 0a37ca8

HPCesia <me@hpcesia.com>
2025-03-09 09:21:02
feat: RepoCard
1 parent 10d65c1
Changed files (5)
src/components/user/index.ts
@@ -2,3 +2,4 @@ export { default as Collapse } from './Collapse.astro';
 export { default as Repl } from './Repl.astro';
 export { default as Tabs } from './Tabs.astro';
 export { default as TabItem } from './TabItem.astro';
+export { default as RepoCard } from './RepoCard.astro';
src/components/user/RepoCard.astro
@@ -0,0 +1,130 @@
+---
+import { Icon } from 'astro-icon/components';
+
+interface Props {
+  repo:
+    | {
+        owner: string;
+        name: string;
+      }
+    | `${string}/${string}`;
+  platform: 'github';
+}
+
+const { repo, platform } = Astro.props as Props;
+const repoName = typeof repo === 'string' ? repo : `${repo.owner}/${repo.name}`;
+
+let url: string;
+let platformIconName: string;
+
+switch (platform) {
+  case 'github': {
+    url = `https://github.com/${repoName}/`;
+    platformIconName = 'mdi:github';
+  }
+}
+---
+
+<a
+  href={url}
+  class="card border-base-content/25 my-4 overflow-hidden border"
+  data-repo={repoName}
+  data-platform={platform}
+>
+  <div class="card-body p-4">
+    <div class="card-title mb-4 justify-between">
+      <span class="text-xl">{repoName}</span>
+      <Icon name={platformIconName} class="text-5xl" />
+    </div>
+    <div class="repo-card-desc flex flex-col gap-2">
+      <div class="skeleton h-4 w-full"></div>
+      <div class="skeleton h-4 w-2/3"></div>
+    </div>
+    <div class="card-actions">
+      <div class="repo-card-star flex items-center justify-center gap-1.5">
+        <Icon name="material-symbols:star-outline-rounded" class="text-xl" />
+        <span class="skeleton h-4 w-6"></span>
+      </div>
+      <div class="repo-card-fork flex items-center justify-center gap-1.5">
+        <Icon name="material-symbols:fork-right-rounded" class="text-xl" />
+        <span class="skeleton h-4 w-6"></span>
+      </div>
+      <div class="repo-card-license hidden items-center justify-center gap-1.5">
+        <Icon name="material-symbols:balance-rounded" class="text-xl" />
+        <span></span>
+      </div>
+      <div class="repo-card-lang hidden items-center justify-center gap-1.5">
+        <Icon name="mingcute:code-line" class="text-xl" />
+        <span></span>
+      </div>
+    </div>
+  </div>
+</a>
+
+<script>
+  import { request as githubApiRequest } from '@octokit/request';
+
+  type Platform = 'github';
+
+  function init() {
+    const repoCards = document.querySelectorAll('.card[data-repo]');
+    repoCards.forEach(async (card) => {
+      const repoName = card.getAttribute('data-repo')!;
+      const platform = card.getAttribute('data-platform')! as Platform;
+
+      let description: string | null;
+      let language: string | null;
+      let license: string | null;
+      let stars: number;
+      let forks: number;
+
+      switch (platform) {
+        case 'github': {
+          const [owner, repo] = repoName.split('/');
+          const meta = await githubApiRequest('GET /repos/{owner}/{repo}', {
+            owner,
+            repo,
+            headers: {
+              'X-GitHub-Api-Version': '2022-11-28',
+            },
+          });
+          description = meta.data.description;
+          language = meta.data.language;
+          license = meta.data.license?.spdx_id ?? null;
+          forks = meta.data.forks_count;
+          stars = meta.data.stargazers_count;
+        }
+      }
+
+      const descriptionNode = card.querySelector('.repo-card-desc')!;
+      console.log(descriptionNode);
+      const languageNode = card.querySelector('.repo-card-lang')!;
+      const licenseNode = card.querySelector('.repo-card-license')!;
+      const forksNode = card.querySelector('.repo-card-fork')!.querySelector('span')!;
+      const starsNode = card.querySelector('.repo-card-star')!.querySelector('span')!;
+
+      descriptionNode.innerHTML = description || '';
+
+      if (language) {
+        languageNode.classList.remove('hidden');
+        languageNode.classList.add('flex');
+        languageNode.querySelector('span')!.innerText = language;
+      }
+
+      if (license) {
+        licenseNode.classList.remove('hidden');
+        licenseNode.classList.add('flex');
+        licenseNode.querySelector('span')!.innerText = license;
+      }
+
+      forksNode.classList.remove('skeleton', 'h-4', 'w-6');
+      forksNode.innerText = forks.toString();
+
+      starsNode.classList.remove('skeleton', 'h-4', 'w-6');
+      starsNode.innerText = stars.toString();
+    });
+  }
+
+  document.addEventListener('astro:after-swap', init);
+  init();
+</script>
src/content/posts/components.mdx
@@ -9,7 +9,7 @@ tags:
 published: 2025-02-10T21:23:23+08:00
 ---
 
-import { Collapse, Repl, TabItem, Tabs } from '@components/user';
+import { Collapse, Repl, RepoCard, TabItem, Tabs } from '@components/user';
 
 Components let you easily reuse a piece of UI or styling consistently. You can use them not just in `.astro` files, but also in `.mdx` files.
 
@@ -141,3 +141,18 @@ Components let you easily reuse a piece of UI or styling consistently. You can u
     ````
   </Fragment>
 </Repl>
+
+## Web Contents
+
+### RepoCard
+
+<Repl>
+  <RepoCard repo="Astral-Halo/astral-halo" platform='github' />
+  <RepoCard repo="withastro/astro" platform='github' />
+  <Fragment slot="desc">
+    ```jsx
+    <RepoCard repo="Astral-Halo/astral-halo" platform='github' />
+    <RepoCard repo="withastro/astro" platform='github' />
+    ```
+  </Fragment>
+</Repl>
\ No newline at end of file
src/styles/markdown.css
@@ -57,7 +57,7 @@ article {
     margin: 0.75rem 0;
   }
 
-  a {
+  a:not(.card) {
     @apply text-primary underline decoration-dashed;
 
     &[data-footnote-ref],
package.json
@@ -21,7 +21,9 @@
     "@iconify-json/ic": "^1.2.2",
     "@iconify-json/material-symbols": "^1.2.15",
     "@iconify-json/mdi": "^1.2.3",
+    "@iconify-json/mingcute": "^1.2.3",
     "@iconify/utils": "^2.3.0",
+    "@octokit/request": "^9.2.2",
     "@shikijs/transformers": "^3.1.0",
     "@swup/head-plugin": "^2.3.1",
     "@swup/parallel-plugin": "^0.4.0",