Bring project over into monorepo
This commit is contained in:
parent
60684fad72
commit
07ccbe03a6
40 changed files with 1289 additions and 0 deletions
21
examples/social-feed/.gitignore
vendored
Normal file
21
examples/social-feed/.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# 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
|
4
examples/social-feed/.vscode/extensions.json
vendored
Normal file
4
examples/social-feed/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
examples/social-feed/.vscode/launch.json
vendored
Normal file
11
examples/social-feed/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
46
examples/social-feed/README.md
Normal file
46
examples/social-feed/README.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Astro Starter Kit: Social Feed
|
||||
|
||||
```
|
||||
npm create astro@latest -- --template social-feed
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/social-feed)
|
||||
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/social-feed)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.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.
|
||||
|
||||
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.
|
||||
|
||||
## 🧞 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:3000` |
|
||||
| `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).
|
6
examples/social-feed/astro.config.mjs
Normal file
6
examples/social-feed/astro.config.mjs
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://www.example.com',
|
||||
});
|
17
examples/social-feed/package.json
Normal file
17
examples/social-feed/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@example/social-feed",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/rss": "^2.4.3",
|
||||
"astro": "^2.6.4"
|
||||
}
|
||||
}
|
BIN
examples/social-feed/public/avatar.webp
Normal file
BIN
examples/social-feed/public/avatar.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
13
examples/social-feed/public/favicon.svg
Normal file
13
examples/social-feed/public/favicon.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 36 36">
|
||||
<path fill="#000" d="M22.25 4h-8.5a1 1 0 0 0-.96.73l-5.54 19.4a.5.5 0 0 0 .62.62l5.05-1.44a2 2 0 0 0 1.38-1.4l3.22-11.66a.5.5 0 0 1 .96 0l3.22 11.67a2 2 0 0 0 1.38 1.39l5.05 1.44a.5.5 0 0 0 .62-.62l-5.54-19.4a1 1 0 0 0-.96-.73Z"/>
|
||||
<path fill="url(#gradient)" d="M18 28a7.63 7.63 0 0 1-5-2c-1.4 2.1-.35 4.35.6 5.55.14.17.41.07.47-.15.44-1.8 2.93-1.22 2.93.6 0 2.28.87 3.4 1.72 3.81.34.16.59-.2.49-.56-.31-1.05-.29-2.46 1.29-3.25 3-1.5 3.17-4.83 2.5-6-.67.67-2.6 2-5 2Z"/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="16" x2="16" y1="32" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#000"/>
|
||||
<stop offset="1" stop-color="#000" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<style>
|
||||
@media (prefers-color-scheme:dark){:root{filter:invert(100%)}}
|
||||
</style>
|
||||
</svg>
|
After Width: | Height: | Size: 873 B |
86
examples/social-feed/public/rss/styles.xsl
Normal file
86
examples/social-feed/public/rss/styles.xsl
Normal file
File diff suppressed because one or more lines are too long
BIN
examples/social-feed/src/assets/stock-1.jpg
Normal file
BIN
examples/social-feed/src/assets/stock-1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
examples/social-feed/src/assets/stock-2.jpg
Normal file
BIN
examples/social-feed/src/assets/stock-2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
53
examples/social-feed/src/components/Footer.astro
Normal file
53
examples/social-feed/src/components/Footer.astro
Normal file
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import settings from '../settings';
|
||||
import Icon from './Icon.astro';
|
||||
---
|
||||
|
||||
<footer>
|
||||
<div class="row">
|
||||
<p>
|
||||
Copyright © {new Date().getFullYear()}
|
||||
{settings.name}
|
||||
</p>
|
||||
<p>
|
||||
Designed & Developed with<Icon icon="heart" size="1.75em" />
|
||||
<span class="sr-only">love</span>
|
||||
</p>
|
||||
<p><a href="/rss.xml"><Icon icon="rss-alt" size="1.75em" />RSS</a></p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href={settings.social.twitter}>Twitter</a>
|
||||
<a href={settings.social.twitch}>Twitch</a>
|
||||
<a href={settings.social.github}>GitHub</a>
|
||||
<a href={settings.social.devto}>DEV</a>
|
||||
<a href={settings.social.codepen}>Codepen</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
border-top: 3px solid var(--theme-shade-subtle);
|
||||
border-radius: var(--theme-radius-xl);
|
||||
padding: 1.5rem;
|
||||
gap: 1rem;
|
||||
color: var(--theme-gray-200);
|
||||
font-size: var(--theme-text-sm);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
31
examples/social-feed/src/components/Header.astro
Normal file
31
examples/social-feed/src/components/Header.astro
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import settings from '../settings';
|
||||
import ThemeToggle from './ThemeToggle.astro';
|
||||
---
|
||||
|
||||
<header>
|
||||
<a class="site-name" href="/">{settings.username}</a>
|
||||
<ThemeToggle />
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: var(--theme-text-lg);
|
||||
font-weight: 700;
|
||||
font-family: var(--theme-font-brand);
|
||||
text-decoration: none;
|
||||
color: transparent;
|
||||
background: var(--theme-gradient-main);
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.site-name:hover {
|
||||
text-decoration: 1px solid underline var(--theme-accent-dark);
|
||||
}
|
||||
</style>
|
32
examples/social-feed/src/components/Icon.astro
Normal file
32
examples/social-feed/src/components/Icon.astro
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import { iconPaths } from './IconPaths';
|
||||
|
||||
export interface Props {
|
||||
icon: keyof typeof iconPaths;
|
||||
color?: string;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
const { icon, color, size } = Astro.props;
|
||||
const iconPath = iconPaths[icon];
|
||||
---
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 256 256"
|
||||
aria-hidden="true"
|
||||
stroke="currentcolor"
|
||||
fill="currentcolor"
|
||||
set:html={iconPath}
|
||||
/>
|
||||
|
||||
<style define:vars={{ color, size }}>
|
||||
svg {
|
||||
color: var(--color, inherit);
|
||||
vertical-align: middle;
|
||||
width: var(--size, 1em);
|
||||
height: var(--size, 1em);
|
||||
}
|
||||
</style>
|
33
examples/social-feed/src/components/IconPaths.ts
Normal file
33
examples/social-feed/src/components/IconPaths.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Want to add more icons?
|
||||
* 1. Find the icon you want as an SVG.
|
||||
* 2. Scale it to 256×256px.
|
||||
* 3. Paste the SVG code in your editor.
|
||||
* 4. Remove the `<svg>` wrapper so you only have elements like `<path>`, `<circle>`, `<rect>` etc.
|
||||
* 5. Remove any `stroke="{color}"` attributes
|
||||
* 6. Replace any `fill="{color}"` attributes with `stroke="none"`
|
||||
* (or add `stroke="none"` on shapes with no `fill` or `stroke` specified).
|
||||
*/
|
||||
export const iconPaths = {
|
||||
'arrow-right': `<path d="m117.5 69.6 7-7c2.9-3 7.6-3 10.6 0l60.7 60.7c3 3 3 7.7 0 10.6L135 194.6c-3 3-7.7 3-10.6 0l-7-6.9c-3-3-2.9-7.8.2-10.7l37.6-35.9H65.5a7.5 7.5 0 0 1-7.5-7.5v-10c0-4.1 3.4-7.5 7.5-7.5h89.8l-37.6-35.8a7.4 7.4 0 0 1-.2-10.7Z"/>`,
|
||||
'arrow-left': `<path d="m138.5 187-7 7c-2.9 3-7.6 3-10.6 0l-60.7-60.7c-3-3-3-7.7 0-10.6L121 62c3-3 7.7-3 10.6 0l7 7c3 2.9 2.9 7.7-.2 10.6l-37.6 35.9h89.8c4.1 0 7.5 3.3 7.5 7.5v10c0 4.2-3.4 7.5-7.5 7.5h-89.8l37.6 35.9a7.4 7.4 0 0 1 .2 10.7Z"/>`,
|
||||
'rss-alt': `<path d="M71.8 153.01a28.65 28.65 0 1 0 40.49 40.5 28.65 28.65 0 0 0 0-40.5 29.41 29.41 0 0 0-40.49 0Zm27.02 27.03a9.55 9.55 0 0 1-13.56 0 9.55 9.55 0 0 1 0-13.56 9.55 9.55 0 0 1 13.56 0 9.54 9.54 0 0 1 0 13.56Zm-6.78-73.62a9.55 9.55 0 1 0 0 19.1 47.74 47.74 0 0 1 47.75 47.74 9.55 9.55 0 1 0 19.1 0 66.84 66.84 0 0 0-66.85-66.84Zm0-38.2a9.55 9.55 0 1 0 0 19.1 85.94 85.94 0 0 1 85.94 85.94 9.55 9.55 0 1 0 19.1 0A105.04 105.04 0 0 0 92.04 68.22Z"/>`,
|
||||
'link-h': `<path d="M96.9 128.25a7.78 7.78 0 0 0 7.77 7.77h46.66a7.78 7.78 0 1 0 0-15.55h-46.66a7.77 7.77 0 0 0-7.78 7.78Zm15.55 23.32H89.12a23.33 23.33 0 0 1 0-46.65h23.33a7.78 7.78 0 1 0 0-15.55H89.12a38.88 38.88 0 0 0 0 77.76h23.33a7.78 7.78 0 1 0 0-15.56Zm54.43-62.2h-23.33a7.78 7.78 0 1 0 0 15.55h23.33a23.33 23.33 0 0 1 0 46.65h-23.33a7.77 7.77 0 1 0 0 15.56h23.33a38.88 38.88 0 1 0 0-77.76Z"/>`,
|
||||
'location-point': `<path d="M193.8 110.2a66.1 66.1 0 1 0-112.5 53.1l41.3 41.3a7.8 7.8 0 0 0 11 0l41-41.3a65.8 65.8 0 0 0 19.2-53.1Zm-30 42.1L128 188.1l-35.8-35.8a50.5 50.5 0 0 1-7.5-61.9A51 51 0 0 1 128 65.8a50.3 50.3 0 0 1 50.3 45.8 50.5 50.5 0 0 1-14.5 40.7ZM128 82a35 35 0 1 0 0 70 35 35 0 0 0 0-70Zm0 54.4a19.4 19.4 0 1 1 0-38.9 19.4 19.4 0 0 1 0 38.9Z"/>`,
|
||||
user: `<path d="M156.9 133.5a46.7 46.7 0 1 0-57.7 0 77.8 77.8 0 0 0-48.4 63.6 7.8 7.8 0 0 0 15.5 1.7 62.2 62.2 0 0 1 123.7 0 7.8 7.8 0 0 0 7.8 7h.8a7.8 7.8 0 0 0 6.9-8.6 77.8 77.8 0 0 0-48.6-63.7ZM128 128a31.1 31.1 0 1 1 0-62.2 31.1 31.1 0 0 1 0 62.2Z"/>`,
|
||||
heart: `<path d="M187.84 74.61A46.17 46.17 0 0 0 128 70.16a46.17 46.17 0 0 0-62.29 6.86 45.56 45.56 0 0 0 2.45 62.27l54.63 54.32a7.32 7.32 0 0 0 8.03 1.6c.9-.37 1.7-.92 2.39-1.6l54.63-54.32a45.68 45.68 0 0 0 13.48-32.34 45.48 45.48 0 0 0-13.48-32.34Zm-10.34 54.4L128 178.15l-49.5-49.14a31.15 31.15 0 0 1-6.8-33.89 31.24 31.24 0 0 1 11.47-14 31.51 31.51 0 0 1 17.33-5.34c8.26.02 16.17 3.3 22 9.11a7.34 7.34 0 0 0 10.41 0 31.42 31.42 0 0 1 42.74 1.6 31.05 31.05 0 0 1 1.26 42.52h.59Z"/>`,
|
||||
'moon-stars': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="20" d="M216 112V64m24 24h-48m-24-64v32m16-16h-32m65 113A92 92 0 0 1 103 39h0a92 92 0 1 0 114 114Z"/>`,
|
||||
sun: `<circle cx="128" cy="128" r="60" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="20"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="20" d="M128 36V16M63 63 49 49m-13 79H16m47 65-14 14m79 13v20m65-47 14 14m13-79h20m-47-65 14-14"/>`,
|
||||
'twitter-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M128 88c0-22 18.5-40.3 40.5-40a40 40 0 0 1 36.2 24H240l-32.3 32.3A127.9 127.9 0 0 1 80 224c-32 0-40-12-40-12s32-12 48-36c0 0-64-32-48-120 0 0 40 40 88 48Z"/>`,
|
||||
'codepen-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m232 101-104 59-104-59 100.1-56.8a8.3 8.3 0 0 1 7.8 0Z"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m232 165-100.1 56.8a8.3 8.3 0 0 1-7.8 0L24 165l104-59Zm0-64v64M24 101v64m104-5v62.8m0-179.6V106"/>`,
|
||||
'github-logo': `<g stroke-linecap="round" stroke-linejoin="round"><path fill="none" stroke-width="14.7" d="M55.7 167.2c13.9 1 21.3 13.1 22.2 14.6 4.2 7.2 10.4 9.6 18.3 7.1l1.1-3.4a60.3 60.3 0 0 1-25.8-11.9c-12-10.1-18-25.6-18-46.3"/><path fill="none" stroke-width="16" d="M61.4 205.1a24.5 24.5 0 0 1-3-6.1c-3.2-7.9-7.1-10.6-7.8-11.1l-1-.6c-2.4-1.6-9.5-6.5-7.2-13.9 1.4-4.5 6-7.2 12.3-7.2h.8c4 .3 7.6 1.5 10.7 3.2-9.1-10.1-13.6-24.3-13.6-42.3 0-11.3 3.5-21.7 10.1-30.4A46.7 46.7 0 0 1 65 67.3a8.3 8.3 0 0 1 5-4.7c2.8-.9 13.3-2.7 33.2 9.9a105 105 0 0 1 50.5 0c19.9-12.6 30.4-10.8 33.2-9.9 2.3.7 4.1 2.4 5 4.7 5 12.7 4 23.2 2.6 29.4 6.7 8.7 10 18.9 10 30.4 0 42.6-25.8 54.7-43.6 58.7 1.4 4.1 2.2 8.8 2.2 13.7l-.1 23.4v2.3"/><path fill="none" stroke-width="16" d="M160.9 185.7c1.4 4.1 2.2 8.8 2.2 13.7l-.1 23.4v2.3A98.6 98.6 0 1 0 61.4 205c-1.4-2.1-11.3-17.5-11.8-17.8-2.4-1.6-9.5-6.5-7.2-13.9 1.4-4.5 6-7.2 12.3-7.2h.8c4 .3 7.6 1.5 10.7 3.2-9.1-10.1-13.6-24.3-13.6-42.3 0-11.3 3.5-21.7 10.1-30.4A46.4 46.4 0 0 1 65 67.3a8.3 8.3 0 0 1 5-4.7c2.8-.9 13.3-2.7 33.2 9.9a105 105 0 0 1 50.5 0c19.9-12.6 30.4-10.8 33.2-9.9 2.3.7 4.1 2.4 5 4.7 5 12.7 4 23.2 2.6 29.4 6.7 8.7 10 18.9 10 30.4.1 42.6-25.8 54.7-43.6 58.6z"/><path fill="none" stroke-width="18.7" d="m170.1 203.3 17.3-12 17.2-18.7 9.5-26.6v-27.9l-9.5-27.5" /><path fill="none" stroke-width="22.7" d="m92.1 57.3 23.3-4.6 18.7-1.4 29.3 5.4m-110 32.6-8 16-4 21.4.6 20.3 3.4 13" /><path fill="none" stroke-width="13.3" d="M28.8 133a100 100 0 0 0 66.9 94.4v-8.7c-22.4 1.8-33-11.5-35.6-19.8-3.4-8.6-7.8-11.4-8.5-11.8"/></g>`,
|
||||
'twitch-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M165 200h-42a8 8 0 0 0-5 2l-46 38v-40H48a8 8 0 0 1-8-8V48a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8v108a8 8 0 0 1-3 6l-43 36a8 8 0 0 1-5 2Zm3-112v48m-48-48v48"/>`,
|
||||
'youtube-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m160 128-48-32v64l48-32z"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M24 128c0 30 3 47 5 56a16 16 0 0 0 10 11c34 13 89 13 89 13s56 0 89-13a16 16 0 0 0 10-11c2-9 5-26 5-56s-3-47-5-56a16 16 0 0 0-10-11c-33-13-89-13-89-13s-55 0-89 13a16 16 0 0 0-10 11c-2 9-5 26-5 56Z"/>`,
|
||||
'dribbble-logo': `<circle cx="128" cy="128" r="96" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M71 205a160 160 0 0 1 137-77l16 1m-36-76a160 160 0 0 1-124 59 165 165 0 0 1-30-3"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M86 42a161 161 0 0 1 74 177"/>`,
|
||||
'discord-logo': `<circle stroke="none" cx="96" cy="144" r="12"/><circle stroke="none" cx="160" cy="144" r="12"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M74 80a175 175 0 0 1 54-8 175 175 0 0 1 54 8m0 96a175 175 0 0 1-54 8 175 175 0 0 1-54-8"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m155 182 12 24a8 8 0 0 0 9 4c25-6 46-16 61-30a8 8 0 0 0 3-8L206 59a8 8 0 0 0-5-5 176 176 0 0 0-30-9 8 8 0 0 0-9 5l-8 24m-53 108-12 24a8 8 0 0 1-9 4c-25-6-46-16-61-30a8 8 0 0 1-3-8L50 59a8 8 0 0 1 5-5 176 176 0 0 1 30-9 8 8 0 0 1 9 5l8 24"/>`,
|
||||
'linkedin-logo': `<rect width="184" height="184" x="36" y="36" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" rx="8"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M120 112v64m-32-64v64m32-36a28 28 0 0 1 56 0v36"/><circle stroke="none" cx="88" cy="80" r="12"/>`,
|
||||
'instagram-logo': `<circle cx="128" cy="128" r="40" fill="none" stroke-miterlimit="10" stroke-width="16"/><rect width="184" height="184" x="36" y="36" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" rx="48"/><circle cx="180" cy="76" r="12" stroke="none" />`,
|
||||
'tiktok-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M168 106a96 96 0 0 0 56 18V84a56 56 0 0 1-56-56h-40v128a28 28 0 1 1-40-25V89a68 68 0 1 0 80 67Z"/>`,
|
||||
'devto-logo': `<path fill-rule="evenodd" d="M37 33c-2.2.5-4 2-4.7 4.2l-.3.8v181.8l.3.8c.7 2 2 3.3 4 4l.8.3H219l.7-.3c2-.7 3.3-2 4-4l.3-.8V38l-.3-.8c-.7-2-2-3.3-4-4l-.8-.3H37Zm43.6 58.3A20.7 20.7 0 0 1 98 109.6c.3 1.9.3 36.7 0 38.6a20.5 20.5 0 0 1-16 17.9c-2.6.5-1.8.5-14.9.6h-12V91h12.2c11 0 12.4 0 13.2.2Zm63.6 6.5v6.8H120v17.6h14.8v13.4H120v17.5h24.2v13.6h-30l-.8-.3a9.3 9.3 0 0 1-6.7-6.7l-.2-.9v-60l.2-.8c1-3.4 3.6-6 7-6.7.8-.2 1.8-.2 15.7-.2h14.8v6.7Zm27 8.6a268210 268210 0 0 1 9.5 36.4 5274.6 5274.6 0 0 0 13.5-51.6H209l-8.8 33a527.2 527.2 0 0 1-9.3 34.2 15 15 0 0 1-6.5 7.5c-1.3.6-2 .7-3.7.7-1.2 0-1.6 0-2.2-.2-3.2-.8-5.8-3.3-8-7.7l-.7-1.5-8.7-32.9-8.7-33c0-.2.3-.2 7.4-.2h7.4l4 15.3ZM68.7 128.8v24.3h9.5l.8-.3a9 9 0 0 0 4.7-3.4 7 7 0 0 0 1-2.4v-35.7l-.1-.8c-.6-2.3-1.9-3.8-4.3-5-1.8-.9-1.9-.9-7-1h-4.6v24.3Z" clip-rule="evenodd"/>`,
|
||||
'mastodon-logo': `<path d="M219.3 74.8a49.7 49.7 0 0 0-43-40.4c-3.7-.6-17.6-2.5-49.8-2.5h-.3c-32.2 0-39.1 2-42.8 2.5-21.1 3-40.5 17.8-45.2 39a132.2 132.2 0 0 0-2 32.4c.6 15.1.7 30.3 2 45.3 1 10 2.7 20 5 29.8 4.5 18 22.6 33.1 40.3 39.2a108.8 108.8 0 0 0 65.1 1.4c4.7-1.5 10.3-3.2 14.4-6a.5.5 0 0 0 .2-.4v-14.7a.4.4 0 0 0-.4-.4h-.2a158 158 0 0 1-38.1 4.4c-22.1 0-28-10.3-29.8-14.7a45.1 45.1 0 0 1-2.4-12 .4.4 0 0 1 .4 0c12.3 3 24.9 4.4 37.5 4.4h9.1c12.7-.4 26.1-1 38.6-3.5.3 0 .6 0 .9-.2 19.7-3.7 38.5-15.5 40.4-45.3l.3-13.5c0-4 1.3-29.3-.2-44.8Zm-30.4 74.3h-20.7V99c0-10.6-4.5-16-13.6-16-10 0-15 6.4-15 19v27.5h-20.5V102c0-12.6-5-19-15-19-9 0-13.5 5.4-13.5 16v50H69.9V97.4A37 37 0 0 1 78 72c5.6-6.2 13-9.4 22.1-9.4 10.7 0 18.7 4 24 12.1l5.2 8.6 5.2-8.6c5.3-8 13.3-12 24-12 9 0 16.5 3.1 22.1 9.3a36.9 36.9 0 0 1 8.2 25.3V149Z"/>`,
|
||||
};
|
130
examples/social-feed/src/components/Pagination.astro
Normal file
130
examples/social-feed/src/components/Pagination.astro
Normal file
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
import type { Page as Props } from 'astro';
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
const { currentPage, lastPage, url } = Astro.props;
|
||||
const firstPage = 1;
|
||||
|
||||
interface Item {
|
||||
page: number;
|
||||
url: string;
|
||||
current: boolean;
|
||||
}
|
||||
|
||||
const makeItem = (page: number): Item => ({
|
||||
page,
|
||||
url: page === 1 ? '/' : `/${page}`,
|
||||
current: page === currentPage,
|
||||
});
|
||||
|
||||
const items: (Item | null)[] = [];
|
||||
|
||||
/** The number of pages to show before/after the current page. */
|
||||
const beforeAfter = 1;
|
||||
// Get range of pages around current page.
|
||||
const min = Math.max(
|
||||
Math.min(currentPage - beforeAfter, lastPage - 2 * beforeAfter - 1),
|
||||
firstPage
|
||||
);
|
||||
const max = Math.min(
|
||||
Math.max(min + 2 * beforeAfter, firstPage + 2 * beforeAfter + 1),
|
||||
lastPage
|
||||
);
|
||||
// Always include first page.
|
||||
if (min > firstPage) items.push(makeItem(firstPage));
|
||||
// Show “…” if the range starts at page 4 or higher.
|
||||
if (min > firstPage + 2) items.push(null);
|
||||
// Show page 2 if the range starts at page 3.
|
||||
if (min === firstPage + 2) items.push(makeItem(firstPage + 1));
|
||||
// Show an item for pages in range around current page.
|
||||
for (let i = min; i <= max; i++) items.push(makeItem(i));
|
||||
// Show “…” if the range ends more than 2 pages before the last page.
|
||||
if (max < lastPage - 2) items.push(null);
|
||||
// Show the penultimate page if the range ends 2 pages before the last page.
|
||||
if (max === lastPage - 2) items.push(makeItem(lastPage - 1));
|
||||
// Always include last page.
|
||||
if (max < lastPage) items.push(makeItem(lastPage));
|
||||
---
|
||||
|
||||
{
|
||||
lastPage > 1 && (
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="pagination-arrow">
|
||||
{url.prev === undefined ? (
|
||||
<span>
|
||||
<Icon icon="arrow-left" />
|
||||
<span class="sr-only">Previous</span>
|
||||
</span>
|
||||
) : (
|
||||
<a href={url.prev} rel="prev">
|
||||
<Icon icon="arrow-left" />
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
{items.map((item) => (
|
||||
<li class="pagination-item">
|
||||
{item ? (
|
||||
<a href={item.url} aria-current={item.current}>
|
||||
{item.page}
|
||||
</a>
|
||||
) : (
|
||||
<span>…</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
<li class="pagination-arrow">
|
||||
{url.next === undefined ? (
|
||||
<span>
|
||||
<span class="sr-only">Next</span>
|
||||
<Icon icon="arrow-right" />
|
||||
</span>
|
||||
) : (
|
||||
<a href={url.next} rel="next">
|
||||
<span class="sr-only">Next</span>
|
||||
<Icon icon="arrow-right" />
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
list-style: none;
|
||||
gap: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.pagination-item > * {
|
||||
border: 1.5px solid var(--theme-accent-dark);
|
||||
border-radius: var(--theme-radius-base);
|
||||
padding: 0.375rem 0.75rem;
|
||||
background-color: var(--theme-bg-accent);
|
||||
font-size: var(--theme-text-sm);
|
||||
font-weight: 700;
|
||||
box-shadow: var(--theme-shadow-sm);
|
||||
}
|
||||
[aria-current='true'] {
|
||||
background-color: var(--theme-accent-dark);
|
||||
color: var(--theme-text-invert);
|
||||
}
|
||||
|
||||
.pagination-arrow > * {
|
||||
color: var(--theme-accent-dark);
|
||||
opacity: 0.35;
|
||||
font-size: 1.75rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pagination-arrow > a {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
107
examples/social-feed/src/components/ThemeToggle.astro
Normal file
107
examples/social-feed/src/components/ThemeToggle.astro
Normal file
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
import Icon from './Icon.astro';
|
||||
---
|
||||
|
||||
<theme-toggle>
|
||||
<button>
|
||||
<span class="sr-only">Dark theme</span>
|
||||
<span class="icon light"><Icon icon="sun" /></span>
|
||||
<span class="icon dark"><Icon icon="moon-stars" /></span>
|
||||
</button>
|
||||
</theme-toggle>
|
||||
|
||||
<style>
|
||||
button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
border: 0;
|
||||
border-radius: var(--theme-radius-full);
|
||||
padding: 0.1875rem;
|
||||
background-color: var(--theme-accent-light);
|
||||
box-shadow: var(--theme-shadow-lg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::before {
|
||||
position: absolute;
|
||||
inset: -0.125rem;
|
||||
content: '';
|
||||
border-radius: var(--theme-radius-full);
|
||||
background: var(--theme-gradient-main);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 0.3125rem;
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-accent-dark);
|
||||
}
|
||||
|
||||
.icon.light::before {
|
||||
content: '';
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--theme-accent-dark);
|
||||
border-radius: 999rem;
|
||||
}
|
||||
|
||||
:global(.theme-dark) .icon.light::before {
|
||||
transform: translateX(calc(100% + 0.25rem));
|
||||
}
|
||||
|
||||
:global(.theme-dark) .icon.dark,
|
||||
:global(html:not(.theme-dark)) .icon.light,
|
||||
button[aria-pressed='false'] .icon.light {
|
||||
color: var(--theme-text-invert);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.icon,
|
||||
.icon.light::before {
|
||||
transition: transform var(--theme-transition),
|
||||
color var(--theme-transition);
|
||||
}
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.icon.light::before {
|
||||
background-color: SelectedItem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
class ThemeToggle extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const button = this.querySelector('button')!;
|
||||
|
||||
/** Set the theme to dark/light mode. */
|
||||
const setTheme = (dark: boolean) => {
|
||||
document.documentElement.classList[dark ? 'add' : 'remove'](
|
||||
'theme-dark'
|
||||
);
|
||||
button.setAttribute('aria-pressed', String(dark));
|
||||
};
|
||||
|
||||
// Toggle the theme when a user clicks the button.
|
||||
button.addEventListener('click', () => setTheme(!this.isDark()));
|
||||
|
||||
// Initialize button state to reflect current theme.
|
||||
setTheme(this.isDark());
|
||||
}
|
||||
|
||||
isDark() {
|
||||
return document.documentElement.classList.contains('theme-dark');
|
||||
}
|
||||
}
|
||||
customElements.define('theme-toggle', ThemeToggle);
|
||||
</script>
|
162
examples/social-feed/src/components/UserProfile.astro
Normal file
162
examples/social-feed/src/components/UserProfile.astro
Normal file
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
import settings from '../settings';
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
type SocialEntry = [keyof (typeof settings)['social'], string];
|
||||
|
||||
const socialLinks = Object.entries(settings.social) as SocialEntry[];
|
||||
---
|
||||
|
||||
<div class="profile">
|
||||
<div>
|
||||
<div class="avatar">
|
||||
<img width="110" height="110" {...settings.avatar} />
|
||||
</div>
|
||||
<h1>
|
||||
{settings.name}
|
||||
<small>{settings.username}</small>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bio-sections">
|
||||
<div class="bio">
|
||||
<p>🚀 <a href="https://astro.build/">Astro</a> Mascot</p>
|
||||
<p>😊 The cutest</p>
|
||||
<p>🎨 Whimsical Speedy Web</p>
|
||||
</div>
|
||||
<div class="bio">
|
||||
{
|
||||
settings.pronouns && (
|
||||
<p>
|
||||
<Icon icon="user" color="var(--theme-accent-dark)" size="1.75rem" />
|
||||
<span class="sr-only">Pronouns</span>
|
||||
{settings.pronouns}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
settings.location && (
|
||||
<p>
|
||||
<Icon
|
||||
icon="location-point"
|
||||
color="var(--theme-accent-dark)"
|
||||
size="1.75rem"
|
||||
/>
|
||||
<span class="sr-only">Location</span>
|
||||
{settings.location}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
settings.homepage && (
|
||||
<p>
|
||||
<Icon
|
||||
icon="link-h"
|
||||
color="var(--theme-accent-dark)"
|
||||
size="1.75rem"
|
||||
/>
|
||||
<span class="sr-only">Homepage</span>
|
||||
<a href={settings.homepage}>
|
||||
{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<p>
|
||||
<Icon icon="rss-alt" color="var(--theme-accent-dark)" size="1.75rem" />
|
||||
<a href="/rss.xml">RSS Feed</a>
|
||||
</p>
|
||||
</div>
|
||||
<ul class="social">
|
||||
{
|
||||
socialLinks.map(([key, url]) => (
|
||||
<li>
|
||||
<a href={url}>
|
||||
<Icon icon={`${key}-logo`} size="1.75rem" />
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar::after {
|
||||
border-radius: var(--theme-radius-full);
|
||||
position: absolute;
|
||||
content: '';
|
||||
inset: 0;
|
||||
border: 0.1875rem solid var(--theme-text);
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: var(--theme-text-xl);
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: var(--theme-text-base);
|
||||
font-family: var(--theme-font-body);
|
||||
color: var(--theme-gray-200);
|
||||
}
|
||||
|
||||
.bio-sections {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: flex-start;
|
||||
gap: 1rem 1.75rem;
|
||||
}
|
||||
|
||||
.bio-sections > :nth-child(2) {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.bio {
|
||||
font-size: var(--theme-text-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bio > * + * {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.bio :global(svg) {
|
||||
/* Slightly hacky way to avoid the icon height being included in the box calculation. */
|
||||
margin: -50% 0;
|
||||
}
|
||||
|
||||
.social {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.social a {
|
||||
color: var(--theme-accent-dark);
|
||||
}
|
||||
|
||||
@media (min-width: 50em) {
|
||||
.bio-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
26
examples/social-feed/src/content/config.ts
Normal file
26
examples/social-feed/src/content/config.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// import { rssSchema } from '@astrojs/rss';
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const posts = defineCollection({
|
||||
// TODO: Extend rssSchema here — was doing that in my standalone project but seems to be broken in the monorepo.
|
||||
schema: z
|
||||
.object({ title: z.string(), description: z.string().optional(), pubDate: z.date() })
|
||||
.extend({
|
||||
tags: z.array(z.string()).default([]),
|
||||
cover: z
|
||||
.object({
|
||||
src: z.string(),
|
||||
// TODO: Support experimental assets instead of plain string schema:
|
||||
// image().refine(
|
||||
// (img) => img.width >= 885,
|
||||
// 'Cover image must be at least 885px wide.'
|
||||
// )
|
||||
alt: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
type: z.enum(['article', 'tweet']).default('tweet'),
|
||||
})
|
||||
.strict(),
|
||||
});
|
||||
|
||||
export const collections = { posts };
|
10
examples/social-feed/src/content/posts/first-post.md
Normal file
10
examples/social-feed/src/content/posts/first-post.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: First post on my new site!
|
||||
pubDate: 2023-01-01
|
||||
tags: [keyboards, thoughts]
|
||||
cover:
|
||||
src: stock-1.jpg
|
||||
alt: A laptop with a code editor open
|
||||
---
|
||||
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
4
examples/social-feed/src/content/posts/post-10.md
Normal file
4
examples/social-feed/src/content/posts/post-10.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Ten for Ten
|
||||
pubDate: 2023-01-10
|
||||
---
|
4
examples/social-feed/src/content/posts/post-4.md
Normal file
4
examples/social-feed/src/content/posts/post-4.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Four is more
|
||||
pubDate: 2023-01-04
|
||||
---
|
4
examples/social-feed/src/content/posts/post-5.md
Normal file
4
examples/social-feed/src/content/posts/post-5.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Take Five!
|
||||
pubDate: 2023-01-05
|
||||
---
|
4
examples/social-feed/src/content/posts/post-6.md
Normal file
4
examples/social-feed/src/content/posts/post-6.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Then We Were Six
|
||||
pubDate: 2023-01-06
|
||||
---
|
4
examples/social-feed/src/content/posts/post-7.md
Normal file
4
examples/social-feed/src/content/posts/post-7.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 7th Heaven
|
||||
pubDate: 2023-01-07
|
||||
---
|
4
examples/social-feed/src/content/posts/post-8.md
Normal file
4
examples/social-feed/src/content/posts/post-8.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Eight is Great!
|
||||
pubDate: 2023-01-08
|
||||
---
|
4
examples/social-feed/src/content/posts/post-9.md
Normal file
4
examples/social-feed/src/content/posts/post-9.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Nine Lives
|
||||
pubDate: 2023-01-09
|
||||
---
|
8
examples/social-feed/src/content/posts/second-post.md
Normal file
8
examples/social-feed/src/content/posts/second-post.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: Second time lucky
|
||||
pubDate: 2023-01-02
|
||||
---
|
||||
|
||||
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||
|
||||
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
9
examples/social-feed/src/content/posts/third-post.md
Normal file
9
examples/social-feed/src/content/posts/third-post.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Three is a magic number
|
||||
pubDate: 2023-01-03
|
||||
cover:
|
||||
src: stock-2.jpg
|
||||
alt: A backlit multicolored mechanical keyboard
|
||||
---
|
||||
|
||||
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
9
examples/social-feed/src/helpers/getSortedPosts.ts
Normal file
9
examples/social-feed/src/helpers/getSortedPosts.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { getCollection } from 'astro:content';
|
||||
|
||||
/** Get everything in your posts collection, sorted by date. */
|
||||
export async function getSortedPosts(order: 'asc' | 'desc' = 'desc') {
|
||||
const posts = await getCollection('posts');
|
||||
posts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
||||
if (order === 'asc') posts.reverse();
|
||||
return posts;
|
||||
}
|
112
examples/social-feed/src/layouts/Base.astro
Normal file
112
examples/social-feed/src/layouts/Base.astro
Normal file
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
import settings from '../settings';
|
||||
import '../style/theme.css';
|
||||
import '../style/global.css';
|
||||
import '../style/utilities.css';
|
||||
import Header from '../components/Header.astro';
|
||||
import UserProfile from '../components/UserProfile.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title = settings.name } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- RSS Feed Discovery -->
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="RSS"
|
||||
href="/rss.xml"
|
||||
/>
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<script is:inline>
|
||||
// This code is inlined in the head to make dark mode instant & blocking.
|
||||
const getThemePreference = () => {
|
||||
if (
|
||||
typeof localStorage !== 'undefined' &&
|
||||
localStorage.getItem('theme')
|
||||
) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
};
|
||||
const isDark = getThemePreference() === 'dark';
|
||||
document.documentElement.classList[isDark ? 'add' : 'remove'](
|
||||
'theme-dark'
|
||||
);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
// Watch the document element and persist user preference when it changes.
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark =
|
||||
document.documentElement.classList.contains('theme-dark');
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="flex-col">
|
||||
<div class="flex-col pad">
|
||||
<Header />
|
||||
<div class="flex-col wrapper">
|
||||
<UserProfile />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</body>
|
||||
|
||||
<style>
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pad {
|
||||
gap: 2.5rem;
|
||||
padding: 1.875rem 1.25rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
gap: 2.5rem;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 55rem;
|
||||
}
|
||||
|
||||
@media (min-width: 50em) {
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 12rem 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</html>
|
114
examples/social-feed/src/pages/[...page].astro
Normal file
114
examples/social-feed/src/pages/[...page].astro
Normal file
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
import type { GetStaticPathsOptions, Page } from 'astro';
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
import { getSortedPosts } from '../helpers/getSortedPosts';
|
||||
import Base from '../layouts/Base.astro';
|
||||
import settings from '../settings';
|
||||
|
||||
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
|
||||
return paginate(await getSortedPosts(), { pageSize: 3 });
|
||||
}
|
||||
|
||||
interface Props {
|
||||
page: Page<CollectionEntry<'posts'>>;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
---
|
||||
|
||||
<Base>
|
||||
<div class="stack">
|
||||
<!-- <h1>Page {page.currentPage}</h1> -->
|
||||
<ol>
|
||||
{
|
||||
page.data.map((post) => (
|
||||
<li>
|
||||
<article class="card">
|
||||
<header>
|
||||
<div style="display: flex;gap:0.875rem;align-items:center;">
|
||||
<img
|
||||
style="border-radius: var(--theme-radius-full);background-color:var(--theme-shade-subtle);"
|
||||
width="60"
|
||||
height="60"
|
||||
{...settings.avatar}
|
||||
/>
|
||||
<div>
|
||||
<p style="font-family:var(--theme-font-brand);font-weight:700;font-size:var(--theme-text-lg)">
|
||||
{settings.name}
|
||||
</p>
|
||||
<p style="color:var(--theme-gray-200);font-size:var(--theme-text-sm);">
|
||||
{settings.username} •{' '}
|
||||
<time datetime={post.data.pubDate.toISOString()}>
|
||||
{post.data.pubDate.toLocaleDateString('en', {
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</time>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{post.data.tags.length > 0 && (
|
||||
<ul class="tags">
|
||||
{post.data.tags.map((tag) => (
|
||||
<li class="tag">{tag}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<h2>
|
||||
<a href={'/post/' + post.slug}>{post.data.title}</a>
|
||||
</h2>
|
||||
</header>
|
||||
{post.render().then(({ Content }) => (
|
||||
<Content />
|
||||
))}
|
||||
</article>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
<Pagination {...page} />
|
||||
</div>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5rem;
|
||||
}
|
||||
ol {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
.card {
|
||||
padding: 1.25rem 1.25rem 1.5rem;
|
||||
background-color: var(--theme-bg-accent);
|
||||
border-radius: var(--theme-radius-xl);
|
||||
box-shadow: var(--theme-shadow-md);
|
||||
}
|
||||
header > * + * {
|
||||
margin-top: 0.625rem;
|
||||
}
|
||||
.tags {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 0.3125rem;
|
||||
}
|
||||
.tag {
|
||||
padding: 0.25rem 0.5625rem;
|
||||
border-radius: var(--theme-radius-full);
|
||||
background-color: var(--theme-accent-dark);
|
||||
color: var(--theme-text-invert);
|
||||
font-size: var(--theme-text-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
.tag::before {
|
||||
content: '#';
|
||||
}
|
||||
</style>
|
29
examples/social-feed/src/pages/post/[slug].astro
Normal file
29
examples/social-feed/src/pages/post/[slug].astro
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import { getSortedPosts } from '../../helpers/getSortedPosts';
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSortedPosts();
|
||||
return posts.map((post, idx) => ({
|
||||
params: { slug: post.slug },
|
||||
props: { post, prev: posts[idx - 1], next: posts[idx + 1] },
|
||||
}));
|
||||
}
|
||||
|
||||
interface Props {
|
||||
post: CollectionEntry<'posts'>;
|
||||
prev?: CollectionEntry<'posts'>;
|
||||
next?: CollectionEntry<'posts'>;
|
||||
}
|
||||
|
||||
const { post, prev, next } = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
---
|
||||
|
||||
<Base title={post.data.title}>
|
||||
<h1>{post.data.title}</h1>
|
||||
<Content />
|
||||
{prev && <a href={'/post/' + prev.slug}>Previous: {prev.data.title}</a>}
|
||||
{next && <a href={'/post/' + next.slug}>Next: {next.data.title}</a>}
|
||||
</Base>
|
23
examples/social-feed/src/pages/rss.xml.ts
Normal file
23
examples/social-feed/src/pages/rss.xml.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import type { APIContext } from 'astro';
|
||||
import { getSortedPosts } from '../helpers/getSortedPosts';
|
||||
import settings from '../settings';
|
||||
|
||||
const { title, description } = settings.rss;
|
||||
|
||||
export async function get(context: APIContext) {
|
||||
const posts = await getSortedPosts();
|
||||
return rss({
|
||||
// `<title>` field in output xml
|
||||
title,
|
||||
// `<description>` field in output xml
|
||||
description,
|
||||
// Pull in your project "site" from the endpoint context
|
||||
// https://docs.astro.build/en/reference/api-reference/#contextsite
|
||||
site: context.site!.href,
|
||||
// Array of `<item>`s in output xml
|
||||
// See "Generating items" section for examples using content collections and glob imports
|
||||
items: posts.map(({ data, slug }) => ({ ...data, link: `/post/${slug}` })),
|
||||
stylesheet: '/rss/styles.xsl',
|
||||
});
|
||||
}
|
24
examples/social-feed/src/settings.ts
Normal file
24
examples/social-feed/src/settings.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
export default {
|
||||
name: 'Houston Astro',
|
||||
username: '@houston',
|
||||
avatar: {
|
||||
src: '/avatar.webp',
|
||||
alt: 'Astro mascot Houston smiling',
|
||||
},
|
||||
rss: {
|
||||
title: 'Houston Astro’s Feed',
|
||||
description: 'Stay up-to-date with the latest posts from Houston Astro!',
|
||||
},
|
||||
pronouns: 'They/Them',
|
||||
location: 'Space',
|
||||
homepage: 'https://astro.build',
|
||||
social: {
|
||||
twitter: 'https://twitter.com/astrodotbuild',
|
||||
twitch: 'https://www.twitch.tv/bholmesdev',
|
||||
github: 'https://github.com/withastro',
|
||||
devto: 'https://dev.to/search?q=astro',
|
||||
codepen: 'https://codepen.io/delucis',
|
||||
mastodon: 'https://m.webtoo.ls/@astro',
|
||||
youtube: 'https://www.youtube.com/@astrodotbuild',
|
||||
},
|
||||
} as const;
|
40
examples/social-feed/src/style/global.css
Normal file
40
examples/social-feed/src/style/global.css
Normal file
|
@ -0,0 +1,40 @@
|
|||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--theme-bg);
|
||||
color: var(--theme-text);
|
||||
font-family: var(--theme-font-body);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
line-height: 1.2;
|
||||
font-family: var(--theme-font-brand);
|
||||
}
|
82
examples/social-feed/src/style/theme.css
Normal file
82
examples/social-feed/src/style/theme.css
Normal file
|
@ -0,0 +1,82 @@
|
|||
:root {
|
||||
/* Colors */
|
||||
--theme-bg: #eceaf5;
|
||||
--theme-bg-accent: #f6f5fb;
|
||||
--theme-text: #171b26;
|
||||
--theme-text-invert: #fdfdfd;
|
||||
--theme-gray-200: #505d84;
|
||||
--theme-gray-400: #505d84;
|
||||
--theme-gray-700: #505d84;
|
||||
--theme-accent-light: #ddd6fc;
|
||||
--theme-accent-medium: #8577eb;
|
||||
--theme-accent-dark: #5a48d9;
|
||||
--theme-shade-subtle: var(--theme-accent-light);
|
||||
|
||||
/* Gradients */
|
||||
--theme-gradient-main: linear-gradient(83.21deg, var(--theme-accent-dark) 6.77%, #c561f6 93.75%);
|
||||
--theme-gradient-text: linear-gradient(
|
||||
83.21deg,
|
||||
var(--theme-accent-dark) 10.42%,
|
||||
#7a4fe2 76.04%,
|
||||
#c561f6 100%
|
||||
);
|
||||
|
||||
/* Shadows */
|
||||
--theme-shadow-sm: 1px 1px 5px rgba(0, 0, 0, 0.1);
|
||||
--theme-shadow-md: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
||||
--theme-shadow-lg: 2px 2px 20px rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* Type Scale */
|
||||
--__type-scale-ratio: 1.2;
|
||||
--theme-text-base: 1rem;
|
||||
--theme-text-sm: calc(var(--theme-text-base) / var(--__type-scale-ratio)); /*13.3px*/
|
||||
--theme-text-lg: calc(var(--theme-text-base) * var(--__type-scale-ratio)); /*19.2px*/
|
||||
--theme-text-xl: calc(var(--theme-text-lg) * var(--__type-scale-ratio)); /*23px*/
|
||||
--theme-text-2xl: calc(var(--theme-text-xl) * var(--__type-scale-ratio)); /*13.3px*/
|
||||
--theme-text-3xl: calc(var(--theme-text-2xl) * var(--__type-scale-ratio)); /*27.6px*/
|
||||
--theme-text-4xl: calc(var(--theme-text-3xl) * var(--__type-scale-ratio)); /*33.2px*/
|
||||
|
||||
/* Fonts */
|
||||
--__font-system: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--theme-font-body: var(--__font-system);
|
||||
--theme-font-brand: 'Montserrat', var(--__font-system);
|
||||
|
||||
/* Corners */
|
||||
--theme-radius-base: 0.3125rem;
|
||||
--theme-radius-lg: 0.625rem;
|
||||
--theme-radius-xl: 1.25rem;
|
||||
--theme-radius-full: 999rem;
|
||||
|
||||
/* Transitions */
|
||||
--theme-transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
:root.theme-dark {
|
||||
/* Colors (dark) */
|
||||
--theme-bg: #141925;
|
||||
--theme-bg-accent: #202c49;
|
||||
--theme-text: #fdfdfd;
|
||||
--theme-text-invert: #171b26;
|
||||
--theme-gray-200: #c3cadb;
|
||||
--theme-gray-400: #8490b5;
|
||||
--theme-gray-700: #3d4663;
|
||||
--theme-accent-light: #ebd2f8;
|
||||
--theme-accent-medium: #c779ed;
|
||||
--theme-accent-dark: #c561f6;
|
||||
--theme-shade-subtle: var(--theme-bg-accent);
|
||||
|
||||
/* Gradients (dark) */
|
||||
--theme-gradient-main: linear-gradient(83.21deg, var(--theme-accent-dark) 6.25%, #5a48d9 100%);
|
||||
--theme-gradient-text: linear-gradient(
|
||||
83.21deg,
|
||||
var(--theme-accent-dark) 6.25%,
|
||||
#b45df1 75.52%,
|
||||
#5a48d9 100%
|
||||
);
|
||||
|
||||
/* Shadows (dark) */
|
||||
--theme-shadow-sm: 1px 1px 5px rgba(0, 0, 0, 0.1);
|
||||
--theme-shadow-md: 4px 4px 10px rgba(9, 11, 17, 0.2);
|
||||
--theme-shadow-lg: 4px 4px 30px rgba(0, 0, 0, 0.6);
|
||||
}
|
11
examples/social-feed/src/style/utilities.css
Normal file
11
examples/social-feed/src/style/utilities.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
3
examples/social-feed/tsconfig.json
Normal file
3
examples/social-feed/tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strictest"
|
||||
}
|
|
@ -345,6 +345,15 @@ importers:
|
|||
specifier: ^3.1.4
|
||||
version: link:../../packages/astro
|
||||
|
||||
examples/social-feed:
|
||||
dependencies:
|
||||
'@astrojs/rss':
|
||||
specifier: ^2.4.3
|
||||
version: link:../../packages/astro-rss
|
||||
astro:
|
||||
specifier: ^2.6.4
|
||||
version: link:../../packages/astro
|
||||
|
||||
examples/ssr:
|
||||
dependencies:
|
||||
'@astrojs/node':
|
||||
|
|
Loading…
Reference in a new issue