ini project
This commit is contained in:
parent
6a29d775e5
commit
5541d9718e
31 changed files with 1643 additions and 21 deletions
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# ----------------------------
|
||||||
|
# STAGE 1: BUILD DO PROJETO
|
||||||
|
# ----------------------------
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
|
# Define o diretório de trabalho customizado
|
||||||
|
WORKDIR /var/app/usr/astro
|
||||||
|
|
||||||
|
# Copia apenas arquivos de dependência inicialmente (para cache)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Instala as dependências do projeto
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copia todo o restante do projeto para o diretório
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Executa o build do Astro
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# STAGE 2: PRODUÇÃO
|
||||||
|
# ----------------------------
|
||||||
|
FROM node:20-alpine AS production
|
||||||
|
|
||||||
|
# Define o mesmo caminho de trabalho
|
||||||
|
WORKDIR /var/app/usr/astro
|
||||||
|
|
||||||
|
# Instala servidor estático global
|
||||||
|
RUN npm install -g serve
|
||||||
|
|
||||||
|
# Copia os arquivos do build da imagem anterior
|
||||||
|
COPY --from=build /var/app/usr/astro/dist ./dist
|
||||||
|
|
||||||
|
# Expõe a porta 3000 (ou outra se quiser mudar depois)
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Comando para servir os arquivos da pasta dist
|
||||||
|
CMD ["serve", "dist", "-l", "3000"]
|
||||||
179
Jenkinsfile
vendored
Normal file
179
Jenkinsfile
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
DEPLOY_USER = "jenkins"
|
||||||
|
DEPLOY_SERVER = "192.168.1.81"
|
||||||
|
IMAGE_NAME = "astro-app"
|
||||||
|
CONTAINER_NAME = "astro-app"
|
||||||
|
REMOTE_PATH = "/home/jenkins/app"
|
||||||
|
SONAR_PROJECT_KEY = "astro-app"
|
||||||
|
DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/1328166779662372884/YtUDw2ADESmRw1V5xuA9fOF7ZZujXtHpLAzmaQn99TPKI3rfLmH8ApLQw9mn8d9G6dSb"
|
||||||
|
}
|
||||||
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Commit') {
|
||||||
|
steps {
|
||||||
|
echo 'Pipeline iniciado via push ou merge.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Checkout Código') {
|
||||||
|
steps {
|
||||||
|
git branch: 'main', url: 'https://forgeo-olymp.duckdns.org/rayankonecny/astro-app.git'
|
||||||
|
script {
|
||||||
|
env.GIT_COMMIT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
|
||||||
|
env.BUILD_TAG = "${env.BUILD_NUMBER}-${env.GIT_COMMIT}"
|
||||||
|
echo "BUILD_TAG definido como: ${env.BUILD_TAG}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'npm ci'
|
||||||
|
sh 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh 'npm test || echo "Testes não críticos"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy to Stage') {
|
||||||
|
steps {
|
||||||
|
sshagent(credentials: ['chave-ssh-id']) {
|
||||||
|
sh """
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} '
|
||||||
|
set -e
|
||||||
|
rm -rf ${REMOTE_PATH}
|
||||||
|
mkdir -p ${REMOTE_PATH}
|
||||||
|
cd ${REMOTE_PATH}
|
||||||
|
|
||||||
|
cat <<EOF > Dockerfile
|
||||||
|
# STAGE 1: Build (não será usado aqui no servidor)
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
|
# STAGE 2: Produção estática
|
||||||
|
FROM node:20-alpine
|
||||||
|
WORKDIR /var/app/usr/astro
|
||||||
|
RUN npm install -g serve
|
||||||
|
COPY dist ./dist
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["serve", "dist", "-l", "3000"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit
|
||||||
|
'
|
||||||
|
|
||||||
|
# Copia arquivos necessários via SCP
|
||||||
|
scp -r ./dist ${DEPLOY_USER}@${DEPLOY_SERVER}:${REMOTE_PATH}/dist
|
||||||
|
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} '
|
||||||
|
cd ${REMOTE_PATH}
|
||||||
|
podman build -t ${IMAGE_NAME}:${BUILD_TAG}-stage -t ${IMAGE_NAME}:stage .
|
||||||
|
podman stop ${CONTAINER_NAME}-stage || true
|
||||||
|
podman rm ${CONTAINER_NAME}-stage || true
|
||||||
|
podman run -d --network host --name ${CONTAINER_NAME}-stage ${IMAGE_NAME}:${BUILD_TAG}-stage
|
||||||
|
'
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Aprovação para Produção') {
|
||||||
|
steps {
|
||||||
|
input message: "Aprovar o deploy em produção?", ok: "Deployar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy to Production') {
|
||||||
|
steps {
|
||||||
|
sshagent(credentials: ['chave-ssh-id']) {
|
||||||
|
sh """
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} '
|
||||||
|
echo "${BUILD_TAG}" > ${REMOTE_PATH}/last_successful_build.txt
|
||||||
|
cd ${REMOTE_PATH}
|
||||||
|
podman build -t ${IMAGE_NAME}:${BUILD_TAG}-prod -t ${IMAGE_NAME}:prod .
|
||||||
|
podman stop ${CONTAINER_NAME} || true
|
||||||
|
podman rm ${CONTAINER_NAME} || true
|
||||||
|
podman run -d --network host --name ${CONTAINER_NAME} ${IMAGE_NAME}:${BUILD_TAG}-prod
|
||||||
|
'
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Verificar Produção') {
|
||||||
|
steps {
|
||||||
|
sshagent(credentials: ['chave-ssh-id']) {
|
||||||
|
sh """
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} '
|
||||||
|
podman ps | grep ${CONTAINER_NAME} || echo "Container não encontrado"
|
||||||
|
'
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Rollback Manual') {
|
||||||
|
when {
|
||||||
|
expression { return params.ROLLBACK == true }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sshagent(credentials: ['chave-ssh-id']) {
|
||||||
|
sh """
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} '
|
||||||
|
TAG=$(cat ${REMOTE_PATH}/last_successful_build.txt || echo "latest")
|
||||||
|
echo "[INFO] Restaurando versão: \$TAG"
|
||||||
|
podman stop ${CONTAINER_NAME} || true
|
||||||
|
podman rm ${CONTAINER_NAME} || true
|
||||||
|
podman run -d --network host --name ${CONTAINER_NAME} ${IMAGE_NAME}:\$TAG
|
||||||
|
'
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Limpeza') {
|
||||||
|
steps {
|
||||||
|
sshagent(credentials: ['chave-ssh-id']) {
|
||||||
|
sh """
|
||||||
|
ssh ${DEPLOY_USER}@${DEPLOY_SERVER} 'podman image prune -f'
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
script {
|
||||||
|
sh """
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d '{"content": "✅ *Deploy bem-sucedido!* Projeto: ${IMAGE_NAME}, Versão: ${BUILD_TAG}"}' \
|
||||||
|
${DISCORD_WEBHOOK_URL}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
script {
|
||||||
|
sh """
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d '{"content": "❌ *Deploy falhou!* Projeto: ${IMAGE_NAME}, Build: ${BUILD_TAG}"}' \
|
||||||
|
${DISCORD_WEBHOOK_URL}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
booleanParam(name: 'ROLLBACK', defaultValue: false, description: 'Executar rollback da produção?')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
```sh
|
```sh
|
||||||
npm create astro@latest -- --template minimal
|
npm create astro@latest -- --template minimal
|
||||||
```
|
```
|
||||||
|
curl -L -o tea https://gitea.com/gitea/tea/releases/download/v0.9.2/tea-0.9.2-linux-amd64
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
import preact from "@astrojs/preact";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({});
|
export default defineConfig({
|
||||||
|
site: "https://example.com",
|
||||||
|
integrations: [preact()]
|
||||||
|
});
|
||||||
891
package-lock.json
generated
891
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^5.5.2"
|
"@astrojs/preact": "^4.0.5",
|
||||||
|
"@astrojs/rss": "^4.0.11",
|
||||||
|
"astro": "^5.5.2",
|
||||||
|
"preact": "^10.26.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/components/BlogPost.astro
Normal file
5
src/components/BlogPost.astro
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
const { title, url } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<li><a href={url}>{title}</a></li>
|
||||||
17
src/components/Footer.astro
Normal file
17
src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
import Social from "./Social.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<Social platform="twitter" username="astrodotbuild" />
|
||||||
|
<Social platform="github" username="withastro" />
|
||||||
|
<Social platform="youtube" username="astrodotbuild" />
|
||||||
|
</footer>
|
||||||
17
src/components/Greeting.jsx
Normal file
17
src/components/Greeting.jsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
export default function Greeting({messages}) {
|
||||||
|
|
||||||
|
const randomMessage = () => messages[(Math.floor(Math.random() * messages.length))];
|
||||||
|
|
||||||
|
const [greeting, setGreeting] = useState(messages[0]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{greeting}! Thank you for visiting!</h3>
|
||||||
|
<button onClick={() => setGreeting(randomMessage())}>
|
||||||
|
New Greeting
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/components/Hamburger.astro
Normal file
9
src/components/Hamburger.astro
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="hamburger">
|
||||||
|
<span class="line"></span>
|
||||||
|
<span class="line"></span>
|
||||||
|
<span class="line"></span>
|
||||||
|
</div>
|
||||||
12
src/components/Header.astro
Normal file
12
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import Hamburger from './Hamburger.astro';
|
||||||
|
import Navigation from './Navigation.astro';
|
||||||
|
import ThemeIcon from './ThemeIcon.astro';
|
||||||
|
---
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<Hamburger />
|
||||||
|
<ThemeIcon />
|
||||||
|
<Navigation />
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
9
src/components/Navigation.astro
Normal file
9
src/components/Navigation.astro
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/blog">Blog</a>
|
||||||
|
</div>
|
||||||
14
src/components/Social.astro
Normal file
14
src/components/Social.astro
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
const { platform, username } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a href={`https://www.${platform}.com/${username}`}>{platform}</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: white;
|
||||||
|
background-color: #4c1d95;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
src/components/ThemeIcon.astro
Normal file
47
src/components/ThemeIcon.astro
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
---
|
||||||
|
<button id="themeToggle">
|
||||||
|
<svg width="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path class="sun" fill-rule="evenodd" d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm12-7a.8.8 0 0 1-.8.8h-2.4a.8.8 0 0 1 0-1.6h2.4a.8.8 0 0 1 .8.8zM4 12a.8.8 0 0 1-.8.8H.8a.8.8 0 0 1 0-1.6h2.5a.8.8 0 0 1 .8.8zm16.5-8.5a.8.8 0 0 1 0 1l-1.8 1.8a.8.8 0 0 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM6.3 17.7a.8.8 0 0 1 0 1l-1.7 1.8a.8.8 0 1 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM12 0a.8.8 0 0 1 .8.8v2.5a.8.8 0 0 1-1.6 0V.8A.8.8 0 0 1 12 0zm0 20a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-1.6 0v-2.4a.8.8 0 0 1 .8-.8zM3.5 3.5a.8.8 0 0 1 1 0l1.8 1.8a.8.8 0 1 1-1 1L3.5 4.6a.8.8 0 0 1 0-1zm14.2 14.2a.8.8 0 0 1 1 0l1.8 1.7a.8.8 0 0 1-1 1l-1.8-1.7a.8.8 0 0 1 0-1z"/>
|
||||||
|
<path class="moon" fill-rule="evenodd" d="M16.5 6A10.5 10.5 0 0 1 4.7 16.4 8.5 8.5 0 1 0 16.4 4.7l.1 1.3zm-1.7-2a9 9 0 0 1 .2 2 9 9 0 0 1-11 8.8 9.4 9.4 0 0 1-.8-.3c-.4 0-.8.3-.7.7a10 10 0 0 0 .3.8 10 10 0 0 0 9.2 6 10 10 0 0 0 4-19.2 9.7 9.7 0 0 0-.9-.3c-.3-.1-.7.3-.6.7a9 9 0 0 1 .3.8z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sun { fill: black; }
|
||||||
|
.moon { fill: transparent; }
|
||||||
|
|
||||||
|
:global(.dark) .sun { fill: transparent; }
|
||||||
|
:global(.dark) .moon { fill: white; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
const theme = (() => {
|
||||||
|
const localStorageTheme = localStorage?.getItem("theme") ?? '';
|
||||||
|
if (['dark', 'light'].includes(localStorageTheme)) {
|
||||||
|
return localStorageTheme;
|
||||||
|
}
|
||||||
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (theme === 'light') {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem('theme', theme);
|
||||||
|
|
||||||
|
const handleToggleClick = () => {
|
||||||
|
const element = document.documentElement;
|
||||||
|
element.classList.toggle("dark");
|
||||||
|
|
||||||
|
const isDark = element.classList.contains("dark");
|
||||||
|
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("themeToggle")?.addEventListener("click", handleToggleClick);
|
||||||
|
</script>
|
||||||
25
src/layouts/BaseLayout.astro
Normal file
25
src/layouts/BaseLayout.astro
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
import Header from '../components/Header.astro';
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
import '../styles/global.css';
|
||||||
|
|
||||||
|
const { pageTitle } = 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>{pageTitle}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Header />
|
||||||
|
<h1>{pageTitle}</h1>
|
||||||
|
<slot />
|
||||||
|
<Footer />
|
||||||
|
<script>
|
||||||
|
import "../scripts/menu.js";
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
src/layouts/MarkdownPostLayout.astro
Normal file
39
src/layouts/MarkdownPostLayout.astro
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from './BaseLayout.astro';
|
||||||
|
const { frontmatter } = Astro.props;
|
||||||
|
---
|
||||||
|
<BaseLayout pageTitle={frontmatter.title}>
|
||||||
|
<p><em>{frontmatter.description}</em></p>
|
||||||
|
<p>{frontmatter.pubDate.toString().slice(0,10)}</p>
|
||||||
|
|
||||||
|
<p>Written by: {frontmatter.author}</p>
|
||||||
|
|
||||||
|
<img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
|
||||||
|
|
||||||
|
<div class="tags">
|
||||||
|
{frontmatter.tags.map((tag: string) => (
|
||||||
|
<p class="tag"><a href={`/tags/${tag}`}>{tag}</a></p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</BaseLayout>
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: #00539F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
margin: 0.25em;
|
||||||
|
border: dotted 1px #a1a1a1;
|
||||||
|
border-radius: .5em;
|
||||||
|
padding: .5em 1em;
|
||||||
|
font-size: 1.15em;
|
||||||
|
background-color: #F8FCFD;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
72
src/pages/about.astro
Normal file
72
src/pages/about.astro
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
const pageTitle = "About Me";
|
||||||
|
const happy = true;
|
||||||
|
const finished = true;
|
||||||
|
const goal = 3;
|
||||||
|
const identity = {
|
||||||
|
firstName: "Sarah",
|
||||||
|
country: "Canada",
|
||||||
|
occupation: "Technical Writer",
|
||||||
|
hobbies: ["photography", "birdwatching", "baseball"],
|
||||||
|
};
|
||||||
|
const skills = ["HTML", "CSS", "JavaScript", "React", "Astro", "Writing Docs"];
|
||||||
|
const skillColor = "navy";
|
||||||
|
const fontWeight = "bold";
|
||||||
|
const textCase = "uppercase";
|
||||||
|
---
|
||||||
|
<style define:vars={{skillColor, fontWeight, textCase}}>
|
||||||
|
h1 {
|
||||||
|
color: purple;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
.skill {
|
||||||
|
color: var(--skillColor);
|
||||||
|
font-weight: var(--fontWeight);
|
||||||
|
text-transform: var(--textCase);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<BaseLayout pageTitle={pageTitle}>
|
||||||
|
<h2>My awesome blog subtitle</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
I am working through Astro's introductory tutorial. This is the second page
|
||||||
|
on my website, and it's the first one I built myself!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This site will update as I complete more of the tutorial, so keep checking
|
||||||
|
back and see how my journey is going!
|
||||||
|
</p>
|
||||||
|
<p>Here are a few facts about me:</p>
|
||||||
|
<ul>
|
||||||
|
<li>My name is {identity.firstName}.</li>
|
||||||
|
<li>
|
||||||
|
I live in {identity.country} and I work as a {identity.occupation}.
|
||||||
|
</li>
|
||||||
|
{
|
||||||
|
identity.hobbies.length >= 2 && (
|
||||||
|
<li>
|
||||||
|
Two of my hobbies are: {identity.hobbies[0]} and {identity.hobbies[1]}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<p>My skills are:</p>
|
||||||
|
<ul>
|
||||||
|
{skills.map((skill) => <li class="skill">{skill}</li>)}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{happy && <p>I am happy to be learning Astro!</p>}
|
||||||
|
|
||||||
|
{finished && <p>I finished this tutorial!</p>}
|
||||||
|
|
||||||
|
{
|
||||||
|
goal === 3 ? (
|
||||||
|
<p>My goal is to finish in 3 days.</p>
|
||||||
|
) : (
|
||||||
|
<p>My goal is not 3 days.</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</BaseLayout>
|
||||||
19
src/pages/blog.astro
Normal file
19
src/pages/blog.astro
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout pageTitle={"Blog Page"}>
|
||||||
|
|
||||||
|
<h1>My Astro Learning Blog</h1>
|
||||||
|
<p>This is where I will post about my journey learning Astro.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/posts/post-1/">Post 1</a></li>
|
||||||
|
<li><a href="/posts/post-2/">Post 2</a></li>
|
||||||
|
<li><a href="/posts/post-3/">Post 3</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
---
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import Greeting from '../components/Greeting';
|
||||||
|
---
|
||||||
|
|
||||||
---
|
<BaseLayout pageTitle={"Home Page"}>
|
||||||
|
<h2>My awesome blog subtitle</h2>
|
||||||
<html lang="en">
|
<Greeting client:load messages={["Hej", "Hallo", "Hola", "Habari"]} />
|
||||||
<head>
|
</BaseLayout>
|
||||||
<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>Astro</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Astro</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
||||||
33
src/pages/posts/blog.astro
Normal file
33
src/pages/posts/blog.astro
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro'
|
||||||
|
import BlogPost from '../../components/BlogPost.astro';
|
||||||
|
|
||||||
|
// Recupera os arquivos .md como objeto
|
||||||
|
const allPostsObject = import.meta.glob('./*.md', { eager: true });
|
||||||
|
|
||||||
|
// Transforma em array de objetos (ex: [{ frontmatter, url, ... }])
|
||||||
|
const allPosts = Object.entries(allPostsObject).map(([path, post]: [string, any]) => {
|
||||||
|
const url = path
|
||||||
|
.replace('./', '/posts/')
|
||||||
|
.replace('.md', '/');
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Extrai as tags únicas
|
||||||
|
const tags = [...new Set(allPosts.map((post) => post.frontmatter.tags).flat())];
|
||||||
|
|
||||||
|
const pageTitle = "My Astro Learning Blog";
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout pageTitle={pageTitle}>
|
||||||
|
<p>This is where I will post about my journey learning Astro.</p>
|
||||||
|
<ul>
|
||||||
|
{allPosts.map((post) => (
|
||||||
|
<BlogPost url={post.url} title={post.frontmatter.title} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</BaseLayout>
|
||||||
29
src/pages/posts/post-1.md
Normal file
29
src/pages/posts/post-1.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
layout: ../../layouts/MarkdownPostLayout.astro
|
||||||
|
title: 'My First Blog Post'
|
||||||
|
pubDate: 2022-07-01
|
||||||
|
description: 'This is the first post of my new Astro blog.'
|
||||||
|
author: 'Astro Learner'
|
||||||
|
image:
|
||||||
|
url: 'https://docs.astro.build/assets/rose.webp'
|
||||||
|
alt: 'The Astro logo on a dark background with a pink glow.'
|
||||||
|
tags: ["astro", "blogging", "learning in public"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# My First Blog Post
|
||||||
|
|
||||||
|
Published on: 2022-07-01
|
||||||
|
|
||||||
|
Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website.
|
||||||
|
|
||||||
|
## What I've accomplished
|
||||||
|
|
||||||
|
1. **Installing Astro**: First, I created a new Astro project and set up my online accounts.
|
||||||
|
|
||||||
|
2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder.
|
||||||
|
|
||||||
|
3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts!
|
||||||
|
|
||||||
|
## What's next
|
||||||
|
|
||||||
|
I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come.
|
||||||
11
src/pages/posts/post-2.md
Normal file
11
src/pages/posts/post-2.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: My Second Blog Post
|
||||||
|
author: Astro Learner
|
||||||
|
description: "After learning some Astro, I couldn't stop!"
|
||||||
|
image:
|
||||||
|
url: "https://docs.astro.build/assets/arc.webp"
|
||||||
|
alt: "The Astro logo on a dark background with a purple gradient arc."
|
||||||
|
pubDate: 2022-07-08
|
||||||
|
tags: ["astro", "blogging", "learning in public", "successes"]
|
||||||
|
---
|
||||||
|
After a successful first week learning Astro, I decided to try some more. I wrote and imported a small component from memory!
|
||||||
11
src/pages/posts/post-3.md
Normal file
11
src/pages/posts/post-3.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: My Third Blog Post
|
||||||
|
author: Astro Learner
|
||||||
|
description: "I had some challenges, but asking in the community really helped!"
|
||||||
|
image:
|
||||||
|
url: "https://docs.astro.build/assets/rays.webp"
|
||||||
|
alt: "The Astro logo on a dark background with rainbow rays."
|
||||||
|
pubDate: 2022-07-15
|
||||||
|
tags: ["astro", "learning in public", "setbacks", "community"]
|
||||||
|
---
|
||||||
|
It wasn't always smooth sailing, but I'm enjoying building with Astro. And, the [Discord community](https://astro.build/chat) is really friendly and helpful!
|
||||||
12
src/pages/posts/post-4.md
Normal file
12
src/pages/posts/post-4.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
layout: ../../layouts/MarkdownPostLayout.astro
|
||||||
|
title: My Fourth Blog Post
|
||||||
|
author: Astro Learner
|
||||||
|
description: "This post will show up on its own!"
|
||||||
|
image:
|
||||||
|
url: "https://docs.astro.build/default-og-image.png"
|
||||||
|
alt: "The word astro against an illustration of planets and stars."
|
||||||
|
pubDate: 2022-08-08
|
||||||
|
tags: ["astro", "successes"]
|
||||||
|
---
|
||||||
|
This post should show up with my other blog posts, because `import.meta.glob()` is returning a list of all my posts in order to create my list.
|
||||||
11
src/pages/rss.xml.js
Normal file
11
src/pages/rss.xml.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import rss, { pagesGlobToRssItems } from '@astrojs/rss';
|
||||||
|
|
||||||
|
export async function GET(context) {
|
||||||
|
return rss({
|
||||||
|
title: 'Astro Learner | Blog',
|
||||||
|
description: 'My journey learning Astro',
|
||||||
|
site: context.site,
|
||||||
|
items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')),
|
||||||
|
customData: `<language>en-us</language>`,
|
||||||
|
});
|
||||||
|
}
|
||||||
27
src/pages/tags/[tag].astro
Normal file
27
src/pages/tags/[tag].astro
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import BlogPost from '../../components/BlogPost.astro';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));
|
||||||
|
|
||||||
|
const uniqueTags = [...new Set(allPosts.map((post: any) => post.frontmatter.tags).flat())];
|
||||||
|
|
||||||
|
return uniqueTags.map((tag) => {
|
||||||
|
const filteredPosts = allPosts.filter((post: any) => post.frontmatter.tags.includes(tag));
|
||||||
|
return {
|
||||||
|
params: { tag },
|
||||||
|
props: { posts: filteredPosts },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag } = Astro.params;
|
||||||
|
const { posts } = Astro.props;
|
||||||
|
---
|
||||||
|
<BaseLayout pageTitle={tag}>
|
||||||
|
<p>Posts tagged with {tag}</p>
|
||||||
|
<ul>
|
||||||
|
{posts.map((post: any) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
|
||||||
|
</ul>
|
||||||
|
</BaseLayout>
|
||||||
3
src/scripts/menu.js
Normal file
3
src/scripts/menu.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
document.querySelector(".hamburger").addEventListener("click", () => {
|
||||||
|
document.querySelector(".nav-links").classList.toggle("expanded");
|
||||||
|
});
|
||||||
9
src/styles/about.css
Normal file
9
src/styles/about.css
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
h1 {
|
||||||
|
color: purple;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
.skill {
|
||||||
|
color: var(--skillColor);
|
||||||
|
font-weight: var(--fontWeight);
|
||||||
|
text-transform: var(--textCase);
|
||||||
|
}
|
||||||
70
src/styles/global.css
Normal file
70
src/styles/global.css
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* nav styles */
|
||||||
|
|
||||||
|
.hamburger {
|
||||||
|
padding-right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger .line {
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #ff9776;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
width: 100%;
|
||||||
|
top: 5rem;
|
||||||
|
left: 48px;
|
||||||
|
background-color: #ff9776;
|
||||||
|
display: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a:hover,
|
||||||
|
.nav-links a:focus {
|
||||||
|
background-color: #ff9776;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 636px) {
|
||||||
|
.nav-links {
|
||||||
|
margin-left: 5em;
|
||||||
|
display: block;
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
background-color: #0d0950;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .nav-links a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
"include": [
|
||||||
"exclude": ["dist"]
|
".astro/types.d.ts",
|
||||||
}
|
"**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue