commit 5c567f6ae06a37689283efc0ba07aa1278c0e6a7
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon Dec 16 19:52:14 2024 +0000
Sync from a44cfb874a6f066214e851c98a410d89c6866992
diff --git a/.codesandbox/Dockerfile b/.codesandbox/Dockerfile
new file mode 100644
index 0000000000..c3b5c81a12
--- /dev/null
+++ b/.codesandbox/Dockerfile
@@ -0,0 +1 @@
+FROM node:18-bullseye
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..16d54bb13c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# build output
+dist/
+# generated types
+.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
+
+# jetbrains setting folder
+.idea/
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..22a15055d6
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,4 @@
+{
+ "recommendations": ["astro-build.astro-vscode"],
+ "unwantedRecommendations": []
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000..d642209762
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "command": "./node_modules/.bin/astro dev",
+ "name": "Development server",
+ "request": "launch",
+ "type": "node-terminal"
+ }
+ ]
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..55c657dc91
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+# Astro Starter Kit: Hackernews
+
+```sh
+npm create astro@latest -- --template hackernews
+```
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/hackernews)
+[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/hackernews)
+[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/hackernews/devcontainer.json)
+
+> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
+
+## 🚀 Project Structure
+
+Inside of your Astro project, you'll see the following folders and files:
+
+```text
+/
+├── public/
+│ └── favicon.svg
+├── src/
+│ ├── components/
+│ ├── layouts/
+│ │ └── Layout.astro
+│ └── pages/
+ └── stories/
+ └── [id].astro
+ └── users/
+ └── [id].astro
+│ └── [...stories].astro
+└── package.json
+```
+
+Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. Because the list of stories and users is always changing, dynamic routes like `[id].astro` are used to build pages when a specific page is requested.
+
+There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
+
+Any static assets, like images, can be placed in the `public/` directory.
+
+## Server-side rendering (SSR)
+
+This project uses the [`@astrojs/node`](https://docs.astro.build/en/guides/integrations-guide/node/) adapter to deploy the SSR site to Node targets. Check out Astro's [deployment docs](https://docs.astro.build/en/guides/deploy/) for details on other adapters and hosting environments.
+
+## 🧞 Commands
+
+All commands are run from the root of the project, from a terminal:
+
+| Command | Action |
+| :------------------------ | :----------------------------------------------- |
+| `npm install` | Installs dependencies |
+| `npm run dev` | Starts local dev server at `localhost:4321` |
+| `npm run build` | Build your production site to `./dist/` |
+| `npm run preview` | Preview your build locally, before deploying |
+| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
+| `npm run astro -- --help` | Get help using the Astro CLI |
+
+## 👀 Want to learn more?
+
+Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
diff --git a/astro.config.mjs b/astro.config.mjs
new file mode 100644
index 0000000000..bf6f1a0228
--- /dev/null
+++ b/astro.config.mjs
@@ -0,0 +1,11 @@
+// @ts-check
+import { defineConfig } from 'astro/config';
+import node from '@astrojs/node';
+
+// https://astro.build/config
+export default defineConfig({
+ output: 'server',
+ adapter: node({
+ mode: 'standalone',
+ }),
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..394aa303d4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@example/hackernews",
+ "type": "module",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/node": "^9.0.0",
+ "astro": "^5.0.8"
+ }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000000..f157bd1c5e
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/components/Comment.astro b/src/components/Comment.astro
new file mode 100644
index 0000000000..07e55d19b9
--- /dev/null
+++ b/src/components/Comment.astro
@@ -0,0 +1,59 @@
+---
+import type { IComment } from '../types.js';
+import For from './For.astro';
+import Show from './Show.astro';
+import Toggle from './Toggle.astro';
+
+interface Props {
+ comment: IComment;
+}
+
+const { comment } = Astro.props;
+---
+
+
+
+
+
+
+ {(comment: IComment) => }
+
+
+
+
+
diff --git a/src/components/For.astro b/src/components/For.astro
new file mode 100644
index 0000000000..6eae88e277
--- /dev/null
+++ b/src/components/For.astro
@@ -0,0 +1,23 @@
+---
+import Show from './Show.astro';
+
+interface Props {
+ each: Iterable;
+}
+
+const { each } = Astro.props;
+---
+
+{
+ (async function* () {
+ for await (const value of each) {
+ let html = await Astro.slots.render('default', [value]);
+ yield ;
+ yield '\n';
+ }
+ })()
+}
+
+
+
+
diff --git a/src/components/Nav.astro b/src/components/Nav.astro
new file mode 100644
index 0000000000..7eeba28656
--- /dev/null
+++ b/src/components/Nav.astro
@@ -0,0 +1,99 @@
+---
+interface Link {
+ href: string;
+ text: string;
+}
+
+const links: Link[] = [
+ { href: '/', text: 'HN' },
+ { href: '/new', text: 'New' },
+ { href: '/show', text: 'Show' },
+ { href: '/ask', text: 'Ask' },
+ { href: '/job', text: 'Jobs' },
+];
+---
+
+
+
+
diff --git a/src/components/Show.astro b/src/components/Show.astro
new file mode 100644
index 0000000000..ccb642fd70
--- /dev/null
+++ b/src/components/Show.astro
@@ -0,0 +1,9 @@
+---
+interface Props {
+ when: T | number | boolean | undefined | null;
+}
+
+const { when } = Astro.props;
+---
+
+{!!when ? : }
diff --git a/src/components/Story.astro b/src/components/Story.astro
new file mode 100644
index 0000000000..e91748a30a
--- /dev/null
+++ b/src/components/Story.astro
@@ -0,0 +1,77 @@
+---
+import type { IStory } from '../types.js';
+import Show from './Show.astro';
+
+interface Props {
+ story: IStory;
+}
+
+const { story } = Astro.props;
+---
+
+
+ {story.points}
+
+
+
+ {story.title}
+
+ ({story.domain})
+ {story.title}
+
+
+
+
+
+ by {story.user}{' '}
+ {story.time_ago}{' '}|{' '}
+
+ {story.comments_count ? `${story.comments_count} comments` : 'discuss'}
+
+ {story.time_ago}
+
+
+
+
+ {story.type}
+
+
+
+
diff --git a/src/components/Toggle.astro b/src/components/Toggle.astro
new file mode 100644
index 0000000000..799fca08cf
--- /dev/null
+++ b/src/components/Toggle.astro
@@ -0,0 +1,78 @@
+---
+interface Props {
+ open?: boolean;
+}
+
+const { open = false } = Astro.props;
+---
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
new file mode 100644
index 0000000000..b1630da8aa
--- /dev/null
+++ b/src/layouts/Layout.astro
@@ -0,0 +1,35 @@
+---
+import Nav from '../components/Nav.astro';
+---
+
+
+
+
+
+
+
+ Astro - Hacker News
+
+
+
+
+
+
+
+
diff --git a/src/lib/api.ts b/src/lib/api.ts
new file mode 100644
index 0000000000..49fd0c333a
--- /dev/null
+++ b/src/lib/api.ts
@@ -0,0 +1,24 @@
+const story = (path: string) => `https://node-hnapi.herokuapp.com/${path}`;
+const user = (path: string) => `https://hacker-news.firebaseio.com/v0/${path}.json`;
+
+export default async function fetchAPI(path: string) {
+ const url = path.startsWith('user') ? user(path) : story(path);
+ const headers = { 'User-Agent': 'chrome' };
+
+ try {
+ let response = await fetch(url, { headers });
+ let text = await response.text();
+ try {
+ if (text === null) {
+ return { error: 'Not found' };
+ }
+ return JSON.parse(text);
+ } catch (e) {
+ console.error(`Received from API: ${text}`);
+ console.error(e);
+ return { error: e };
+ }
+ } catch (error) {
+ return { error };
+ }
+}
diff --git a/src/pages/[...stories].astro b/src/pages/[...stories].astro
new file mode 100644
index 0000000000..fa227e0c12
--- /dev/null
+++ b/src/pages/[...stories].astro
@@ -0,0 +1,105 @@
+---
+import For from '../components/For.astro';
+import Show from '../components/Show.astro';
+import Story from '../components/Story.astro';
+import Layout from '../layouts/Layout.astro';
+import fetchAPI from '../lib/api';
+import type { IStory } from '../types.js';
+
+const mapStories = {
+ top: 'news',
+ new: 'newest',
+ show: 'show',
+ ask: 'ask',
+ job: 'jobs',
+};
+
+function safeParseInt(value: any, fallback: number) {
+ try {
+ return parseInt(value) || fallback;
+ } catch {
+ return fallback;
+ }
+}
+
+const page = safeParseInt(Astro.url.searchParams.get('page'), 1);
+const type =
+ Astro.params.stories && Astro.params.stories in mapStories
+ ? (Astro.params.stories.toString() as keyof typeof mapStories)
+ : 'top';
+
+const stories = (await fetchAPI(`${mapStories[type]}?page=${page}`)) as IStory[];
+---
+
+
+
+
+
+
+
+ {(story: IStory) => }
+
+
+
+
+
+
+
diff --git a/src/pages/stories/[id].astro b/src/pages/stories/[id].astro
new file mode 100644
index 0000000000..84383aa9e4
--- /dev/null
+++ b/src/pages/stories/[id].astro
@@ -0,0 +1,96 @@
+---
+import Comment from '../../components/Comment.astro';
+import For from '../../components/For.astro';
+import Show from '../../components/Show.astro';
+import Layout from '../../layouts/Layout.astro';
+import fetchAPI from '../../lib/api';
+import type { IComment, IStory } from '../../types.js';
+
+const { id } = Astro.params as { id: string };
+
+const story = (await fetchAPI(`item/${id}`)) as IStory;
+---
+
+
+
+
+
+
+ {story.comments_count ? story.comments_count + ' comments' : 'No comments yet.'}
+
+
+
+
+
+
+
diff --git a/src/pages/users/[id].astro b/src/pages/users/[id].astro
new file mode 100644
index 0000000000..e560859923
--- /dev/null
+++ b/src/pages/users/[id].astro
@@ -0,0 +1,69 @@
+---
+import Show from '../../components/Show.astro';
+import Layout from '../../layouts/Layout.astro';
+import fetchAPI from '../../lib/api';
+import type { IUser } from '../../types.js';
+
+const { id } = Astro.params as { id: string };
+
+const user = (await fetchAPI(`user/${id}`)) as IUser;
+---
+
+
+
+
+
+ User not found.
+ User : {user.id}
+
+
+ submissions |{' '}
+ comments
+
+
+
+
+
+
+
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000000..e27ee85e46
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,27 @@
+export interface IComment {
+ user: string;
+ time_ago: string;
+ content: string;
+ comments: IComment[];
+}
+
+export interface IStory {
+ id: string;
+ points: string;
+ url: string;
+ title: string;
+ domain: string;
+ type: string;
+ time_ago: string;
+ user: string;
+ comments_count: number;
+ comments: IComment[];
+}
+
+export interface IUser {
+ error: string;
+ id: string;
+ created: string;
+ karma: number;
+ about: string;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000..8bf91d3bb9
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"]
+}