Rework layout and refactors (#4)
Reviewed-on: https://git.juancman.dev/juancmandev/website/pulls/4 Co-authored-by: juancmandev <juancmandev@protonmail.com> Co-committed-by: juancmandev <juancmandev@protonmail.com>
This commit is contained in:
parent
9b4a54f702
commit
b4447f0e38
55
README.md
55
README.md
@ -1,54 +1 @@
|
|||||||
# Astro Starter Kit: Basics
|
TODO: Update README
|
||||||
|
|
||||||
```sh
|
|
||||||
npm create astro@latest -- --template basics
|
|
||||||
```
|
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
|
||||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/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/
|
|
||||||
│ │ └── Card.astro
|
|
||||||
│ ├── layouts/
|
|
||||||
│ │ └── Layout.astro
|
|
||||||
│ └── 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: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).
|
|
BIN
src/assets/random/bile-titan-typing.gif
Normal file
BIN
src/assets/random/bile-titan-typing.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
@ -2,44 +2,24 @@ import { Code, RssIcon } from 'lucide-react';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import formatDate from '@/utils/format-date';
|
import formatDate from '@/utils/format-date';
|
||||||
import type { lang } from '@/i18n/utils';
|
import type { lang } from '@/i18n/utils';
|
||||||
|
import { footerLocales } from '@/i18n/footerLocales';
|
||||||
const locales = {
|
import VerticalLinkButton from '@/components/vertical-link-button';
|
||||||
en: {
|
|
||||||
developed_by: 'Developed by ',
|
|
||||||
build_handcrafted: 'Built handcrafted with ',
|
|
||||||
last_build: 'Last build',
|
|
||||||
},
|
|
||||||
es: {
|
|
||||||
developed_by: 'Desarrollado por ',
|
|
||||||
build_handcrafted: 'Construido a mano con ',
|
|
||||||
last_build: 'Última build',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
lang: lang;
|
lang: lang;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Footer({ lang }: Props) {
|
export default function Footer({ lang }: Props) {
|
||||||
const rssUrl =
|
|
||||||
lang == 'en'
|
|
||||||
? 'https://juancman.dev/feed.xml'
|
|
||||||
: 'https://juancman.dev/es/feed.xml';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className='border-t border-secondary px-4 py-12 text-center text-sm md:px-16 prose prose-invert min-w-full'>
|
<footer className='px-2 pt-14 pb-28 text-center lg:px-0 prose prose-invert min-w-full'>
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
{locales[lang].developed_by}
|
{footerLocales[lang].built_with}
|
||||||
<strong className='font-bold text-primary'>juancmandev</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{locales[lang].build_handcrafted}
|
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size={null}
|
size={null}
|
||||||
variant='link'
|
variant='link'
|
||||||
className='m-0 p-0 text-base no-underline hover:underline'
|
className='m-0 p-0 text-base visited:text-purple-500'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href='https://astro.build/'
|
href='https://astro.build/'
|
||||||
@ -50,38 +30,20 @@ export default function Footer({ lang }: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{locales[lang].last_build}: {formatDate(new Date(), lang)}.
|
{footerLocales[lang].last_build}: {formatDate(new Date(), lang)}.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section className='w-max mx-auto flex items-center gap-12'>
|
<section className='w-max mx-auto flex items-center gap-12'>
|
||||||
<Button
|
<VerticalLinkButton
|
||||||
asChild
|
href={`https://juancman.dev/${lang === 'es' && lang}feed.xml`}
|
||||||
size={null}
|
|
||||||
variant='link'
|
|
||||||
className='flex flex-col justify-center'
|
|
||||||
>
|
>
|
||||||
<a
|
<RssIcon className='w-6' />
|
||||||
target='_blank'
|
RSS feed
|
||||||
href='https://git.juancman.dev/juancmandev/website'
|
</VerticalLinkButton>
|
||||||
>
|
<VerticalLinkButton href='https://git.juancman.dev/juancmandev/website'>
|
||||||
<Code className='w-6' />
|
<Code className='w-6' />
|
||||||
Source Code
|
Source Code
|
||||||
</a>
|
</VerticalLinkButton>
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
size={null}
|
|
||||||
variant='link'
|
|
||||||
className='flex flex-col justify-center'
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
target='_blank'
|
|
||||||
href={rssUrl}
|
|
||||||
>
|
|
||||||
<RssIcon className='w-6' />
|
|
||||||
RSS feed
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
</section>
|
</section>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
@ -28,11 +28,9 @@ const { lang } = Astro.props;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<header
|
<header
|
||||||
class='py-2 fixed top-0 z-50 flex w-full items-center justify-between border-b border-secondary backdrop-blur-lg'
|
class='py-2 px-2 lg:px-0 fixed top-0 z-50 flex w-full items-center justify-between border-b border-secondary backdrop-blur-lg'
|
||||||
>
|
>
|
||||||
<div
|
<div class='w-full max-w-[800px] mx-auto flex items-center justify-between'>
|
||||||
class='px-4 sm:px-0 flex w-full max-w-[65ch] items-center justify-between mx-auto'
|
|
||||||
>
|
|
||||||
<section class='flex max-w-max'>
|
<section class='flex max-w-max'>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
href={lang === 'en' ? '/' : '/es'}
|
href={lang === 'en' ? '/' : '/es'}
|
||||||
@ -52,7 +50,7 @@ const { lang } = Astro.props;
|
|||||||
/>
|
/>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</section>
|
</section>
|
||||||
<section class='flex items-center gap-2'>
|
<section class='flex items-center gap-4'>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant='link'
|
variant='link'
|
||||||
href={locales[lang].to}
|
href={locales[lang].to}
|
||||||
|
@ -11,7 +11,7 @@ export default function CustomAnchor(props: TAnchor) {
|
|||||||
) : (
|
) : (
|
||||||
<a
|
<a
|
||||||
{...props}
|
{...props}
|
||||||
className='inline-flex outline-ring visited:text-purple-600'
|
className='inline-flex outline-ring visited:text-purple-500'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import LinkButton from '@/components/link-button';
|
import LinkButton from '@/components/link-button';
|
||||||
import {
|
import {
|
||||||
NotebookText,
|
NotebookText,
|
||||||
BriefcaseBusiness,
|
|
||||||
MonitorPlay,
|
MonitorPlay,
|
||||||
Newspaper,
|
Newspaper,
|
||||||
PocketKnife,
|
PocketKnife,
|
||||||
@ -20,7 +19,6 @@ export const navItems: TNavItem[] = [
|
|||||||
type: 'blog',
|
type: 'blog',
|
||||||
icon: <NotebookText />,
|
icon: <NotebookText />,
|
||||||
},
|
},
|
||||||
{ type: 'portfolio', icon: <BriefcaseBusiness /> },
|
|
||||||
{
|
{
|
||||||
type: 'videos',
|
type: 'videos',
|
||||||
icon: <MonitorPlay />,
|
icon: <MonitorPlay />,
|
||||||
@ -51,7 +49,7 @@ export default function Navigation(props: Props) {
|
|||||||
const t = useTranslations(props.lang as any);
|
const t = useTranslations(props.lang as any);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='px-4 sm:px-0 max-w-[65ch] mx-auto prose prose-invert pt-5 pb-20'>
|
<nav className='prose prose-invert max-w-[800px] mx-auto px-2 lg:px-0 py-16'>
|
||||||
<h2 id='navigation'>{t('navigation')}</h2>
|
<h2 id='navigation'>{t('navigation')}</h2>
|
||||||
<ul className='list-none p-0 flex flex-wrap gap-4'>
|
<ul className='list-none p-0 flex flex-wrap gap-4'>
|
||||||
{navItems.map((navItem, index) => (
|
{navItems.map((navItem, index) => (
|
||||||
|
@ -1,31 +1,19 @@
|
|||||||
import formatDate from '@/utils/format-date';
|
import formatDate from '@/utils/format-date';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
type Props = {
|
export default function PostItem(props: TPostItem) {
|
||||||
id: string;
|
|
||||||
date: Date | string;
|
|
||||||
title: string;
|
|
||||||
type: 'blog' | 'portfolio' | 'videos';
|
|
||||||
lang: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function PostItem(props: Props) {
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size={null}
|
size={null}
|
||||||
variant='link'
|
variant='link'
|
||||||
className='group hover:no-underline focus:no-underline text-foreground visited:text-purple-600 px-4 whitespace-normal py-2 flex flex-col items-start italic border border-secondary hover:border-foreground focus:border-foreground transition-colors rounded-md'
|
className='group p-0 hover:no-underline focus:no-underline text-foreground visited:text-purple-500 whitespace-normal flex flex-col items-start italic transition-colors rounded-md'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
className='no-underline'
|
className='no-underline'
|
||||||
href={
|
href={`/${props.lang === 'es' && 'es'}/${props.type}/${props.id}`}
|
||||||
props.lang === 'en'
|
|
||||||
? `/${props.type}/${props.id}`
|
|
||||||
: `/es/${props.type}/${[props.id]}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span className='text-foreground text-sm font-light no-underline'>
|
<span className='text-foreground text-sm font-light no-underline'>
|
||||||
{formatDate(props.date, props.lang)}
|
{formatDate(props.date, props.lang)}
|
||||||
</span>
|
</span>
|
||||||
<span className='text-lg font-semibold group-hover:underline group-focus:underline'>
|
<span className='text-lg font-semibold group-hover:underline group-focus:underline'>
|
||||||
|
28
src/components/post-items-list.tsx
Normal file
28
src/components/post-items-list.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import PostItem from '@/components/post-item';
|
||||||
|
import type { CollectionEntry } from 'astro:content';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: any;
|
||||||
|
lang: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PostItemList(props: Props) {
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{props.items.map(
|
||||||
|
(item: CollectionEntry<'blog' | 'videos'>) =>
|
||||||
|
item && (
|
||||||
|
<li key={item.id}>
|
||||||
|
<PostItem
|
||||||
|
type={item.collection}
|
||||||
|
lang={props.lang}
|
||||||
|
id={item.id}
|
||||||
|
date={item.data.date!}
|
||||||
|
title={item.data.title!}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
21
src/components/random.astro
Normal file
21
src/components/random.astro
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import { Image } from 'astro:assets';
|
||||||
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import { readdirSync } from 'fs';
|
||||||
|
|
||||||
|
const images = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
|
'/src/assets/random/*.{jpeg,jpg,png,gif}'
|
||||||
|
);
|
||||||
|
const imagePaths = readdirSync('./src/assets/random/');
|
||||||
|
const randomIndex = Math.floor(Math.random() * (imagePaths.length - 0));
|
||||||
|
const randomImage = `/src/assets/random/${imagePaths[randomIndex]}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Image
|
||||||
|
decoding='sync'
|
||||||
|
loading='eager'
|
||||||
|
src={images[randomImage]()}
|
||||||
|
alt='image'
|
||||||
|
width={300}
|
||||||
|
class='w-auto h-auto rounded-md aspect-auto object-cover mx-auto'
|
||||||
|
/>
|
24
src/components/vertical-link-button.tsx
Normal file
24
src/components/vertical-link-button.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Button } from './ui/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function VerticalLinkButton(props: Props) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size={null}
|
||||||
|
variant='link'
|
||||||
|
className='flex flex-col justify-center visited:text-purple-500'
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
target={props.href?.startsWith('h') ? '_blank' : ''}
|
||||||
|
href={props.href}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -10,7 +10,8 @@ const contentSchema = z.object({
|
|||||||
tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
author: z.string(),
|
author: z.string(),
|
||||||
rss: z.boolean(),
|
rss: z.boolean(),
|
||||||
draft: z.boolean({}).optional(),
|
draft: z.boolean().optional(),
|
||||||
|
archived: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const blog = defineCollection({
|
const blog = defineCollection({
|
||||||
@ -18,14 +19,6 @@ const blog = defineCollection({
|
|||||||
schema: contentSchema,
|
schema: contentSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = defineCollection({
|
|
||||||
loader: glob({
|
|
||||||
pattern: '**/[^_]*.{md,mdx}',
|
|
||||||
base: './src/content/portfolio',
|
|
||||||
}),
|
|
||||||
schema: contentSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pages = defineCollection({
|
const pages = defineCollection({
|
||||||
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/pages' }),
|
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/pages' }),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
@ -41,7 +34,6 @@ const videos = defineCollection({
|
|||||||
|
|
||||||
export const collections = {
|
export const collections = {
|
||||||
blog,
|
blog,
|
||||||
portfolio,
|
|
||||||
pages,
|
pages,
|
||||||
videos,
|
videos,
|
||||||
};
|
};
|
||||||
|
@ -9,11 +9,6 @@ author: Juan Manzanero
|
|||||||
rss: true
|
rss: true
|
||||||
---
|
---
|
||||||
|
|
||||||
 _Photo by
|
|
||||||
[Ashni](https://unsplash.com/@ashni_ahlawat?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)
|
|
||||||
on
|
|
||||||
[Unsplash](https://unsplash.com/photos/text-ePWaAwUn80k?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)_
|
|
||||||
|
|
||||||
Get your news without visiting websites with algorithms that shows content that you don't want to see.
|
Get your news without visiting websites with algorithms that shows content that you don't want to see.
|
||||||
|
|
||||||
## Algorithms that Dictates What You See
|
## Algorithms that Dictates What You See
|
||||||
|
12
src/i18n/footerLocales.ts
Normal file
12
src/i18n/footerLocales.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const footerLocales = {
|
||||||
|
en: {
|
||||||
|
developed_by: 'Developed by ',
|
||||||
|
built_with: 'Built with ',
|
||||||
|
last_build: 'Last build',
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
developed_by: 'Desarrollado por ',
|
||||||
|
built_with: 'Construido con ',
|
||||||
|
last_build: 'Última build',
|
||||||
|
},
|
||||||
|
};
|
@ -63,7 +63,7 @@ const { title, description } = Astro.props;
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Header lang={lang} />
|
<Header lang={lang} />
|
||||||
<main class='px-4 sm:px-0 max-w-[65ch] pt-28 pb-5 mx-auto'>
|
<main class='width-global px-2 lg:px-0 pt-32'>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Navigation lang={lang} />
|
<Navigation lang={lang} />
|
||||||
|
@ -6,7 +6,7 @@ import Layout from '@/layouts/Layout.astro';
|
|||||||
title='Not found'
|
title='Not found'
|
||||||
description='Error 404: Not found.'
|
description='Error 404: Not found.'
|
||||||
>
|
>
|
||||||
<div class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1 class=''>Error 404: Not found</h1>
|
<h1 class=''>Error 404: Not found</h1>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -32,7 +32,7 @@ const { Content, remarkPluginFrontmatter: data } = await render(project);
|
|||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
>
|
>
|
||||||
<article class='prose prose-invert'>
|
<article class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -36,7 +36,7 @@ const { Content, remarkPluginFrontmatter: data } = await render(post);
|
|||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
>
|
>
|
||||||
<article class='prose prose-invert'>
|
<article class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import PostItem from '@/components/post-item';
|
import PostItemList from '@/components/post-items-list';
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import Layout from '@/layouts/Layout.astro';
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
import { sortContentByDate } from '@/utils/sorts';
|
||||||
@ -27,26 +27,12 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
<PostItemList
|
||||||
<ul class='mt-4 flex flex-col gap-4'>
|
items={filterEnPosts}
|
||||||
{
|
lang={lang}
|
||||||
filterEnPosts.map(
|
/>
|
||||||
(blogpost) =>
|
</div>
|
||||||
blogpost && (
|
|
||||||
<li>
|
|
||||||
<PostItem
|
|
||||||
type='blog'
|
|
||||||
lang={lang}
|
|
||||||
id={blogpost.id}
|
|
||||||
date={blogpost.data.date!}
|
|
||||||
title={blogpost.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -32,7 +32,7 @@ const { Content, remarkPluginFrontmatter: data } = await render(page);
|
|||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
>
|
>
|
||||||
<article class='prose prose-invert'>
|
<article class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -36,7 +36,7 @@ const { Content, remarkPluginFrontmatter: data } = await render(blog);
|
|||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
>
|
>
|
||||||
<article class='prose prose-invert'>
|
<article class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -27,7 +27,7 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class='prose prose-invert'>
|
<section class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
|
@ -12,9 +12,6 @@ const markdownParser = new MarkdownIt();
|
|||||||
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
|
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
'/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}'
|
'/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}'
|
||||||
);
|
);
|
||||||
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
|
|
||||||
'/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}'
|
|
||||||
);
|
|
||||||
|
|
||||||
export async function GET(context: any) {
|
export async function GET(context: any) {
|
||||||
const items: RSSFeedItem[] = [];
|
const items: RSSFeedItem[] = [];
|
||||||
@ -29,16 +26,6 @@ export async function GET(context: any) {
|
|||||||
return lang === 'es' && post;
|
return lang === 'es' && post;
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
|
||||||
);
|
|
||||||
const filterPortfolio = portfolio.filter((project) => {
|
|
||||||
const [lang] = project.id.split('/');
|
|
||||||
|
|
||||||
return lang === 'es' && project;
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const post of filterBlog) {
|
for await (const post of filterBlog) {
|
||||||
const body = markdownParser.render(post.body!);
|
const body = markdownParser.render(post.body!);
|
||||||
const html = htmlParser.parse(body);
|
const html = htmlParser.parse(body);
|
||||||
@ -79,47 +66,6 @@ export async function GET(context: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const project of filterPortfolio) {
|
|
||||||
const body = markdownParser.render(project.body!);
|
|
||||||
const html = htmlParser.parse(body);
|
|
||||||
const images = html.querySelectorAll('img');
|
|
||||||
|
|
||||||
for await (const img of images) {
|
|
||||||
const src = img.getAttribute('src')!;
|
|
||||||
|
|
||||||
if (src.startsWith('@/')) {
|
|
||||||
const prefixRemoved = src.replace('@/', '');
|
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
|
||||||
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
|
||||||
(res: any) => res.default
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imagePath) {
|
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
|
||||||
img.setAttribute(
|
|
||||||
'src',
|
|
||||||
context.site + optimizedImg.src.replace('/', '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (src.startsWith('/images')) {
|
|
||||||
// images starting with `/images/` is the public dir
|
|
||||||
img.setAttribute('src', context.site + src.replace('/', ''));
|
|
||||||
} else {
|
|
||||||
throw Error('src unknown');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
title: project.data.title,
|
|
||||||
pubDate: project.data.date,
|
|
||||||
description: project.data.description,
|
|
||||||
link: `/portfolio/${project.id.split('.')[0]}/`,
|
|
||||||
content: sanitizeHtml(html.toString(), {
|
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rss({
|
return rss({
|
||||||
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
|
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
|
||||||
title: 'juancmandev',
|
title: 'juancmandev',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import LinkButton from '@/components/link-button';
|
import LinkButton from '@/components/link-button';
|
||||||
import PostItem from '@/components/post-item';
|
import PostItemList from '@/components/post-items-list';
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import Layout from '@/layouts/Layout.astro';
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
import { sortContentByDate } from '@/utils/sorts';
|
||||||
@ -26,28 +26,11 @@ const allEsPosts = allPosts.map((post) => {
|
|||||||
sortContentByDate(allEsPosts);
|
sortContentByDate(allEsPosts);
|
||||||
const last3Blogs = allEsPosts.slice(0, 3);
|
const last3Blogs = allEsPosts.slice(0, 3);
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const allEnProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang !== 'en')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
sortContentByDate(allEnProjects);
|
|
||||||
const last3Projects = allEnProjects.slice(0, 3);
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<div class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1 class='text-primary'>Bienvenido a mi dominio, extraño.</h1>
|
<h1 class='text-primary'>Bienvenido a mi dominio, extraño.</h1>
|
||||||
<p>
|
<p>
|
||||||
Soy <strong class='text-primary'>juancmandev</strong>; <strong
|
Soy <strong class='text-primary'>juancmandev</strong>; <strong
|
||||||
@ -63,24 +46,10 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
</p>
|
</p>
|
||||||
<section>
|
<section>
|
||||||
<h2>Últimos posts</h2>
|
<h2>Últimos posts</h2>
|
||||||
<ul class='mt-0 p-0 list-none'>
|
<PostItemList
|
||||||
{
|
items={last3Blogs}
|
||||||
last3Blogs.map(
|
lang={lang}
|
||||||
(blogpost) =>
|
/>
|
||||||
blogpost && (
|
|
||||||
<li class='p-0'>
|
|
||||||
<PostItem
|
|
||||||
type='blog'
|
|
||||||
lang={lang}
|
|
||||||
id={blogpost.id}
|
|
||||||
date={blogpost.data.date}
|
|
||||||
title={blogpost.data.title}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
href='/es/blog'
|
href='/es/blog'
|
||||||
@ -88,32 +57,5 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
>Más posts</LinkButton
|
>Más posts</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<h2>Últimos proyectos</h2>
|
|
||||||
<ul class='mt-0 p-0 list-none'>
|
|
||||||
{
|
|
||||||
last3Projects.map(
|
|
||||||
(project) =>
|
|
||||||
project && (
|
|
||||||
<li class='p-0'>
|
|
||||||
<PostItem
|
|
||||||
lang={lang}
|
|
||||||
type='portfolio'
|
|
||||||
id={project.id}
|
|
||||||
date={project.data.date!}
|
|
||||||
title={project.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<LinkButton
|
|
||||||
variant='secondary'
|
|
||||||
href='/es/portfolio'
|
|
||||||
className='no-underline'
|
|
||||||
>Más proyectos</LinkButton
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from '@/layouts/Layout.astro';
|
|
||||||
import components from '@/components/mdx/wrapper';
|
|
||||||
import formatDate from '@/utils/format-date';
|
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
|
||||||
import { getCollection, getEntry, render } from 'astro:content';
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const filterEnProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang === 'es')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return filterEnProjects.map((project) => ({
|
|
||||||
params: { slug: project?.id },
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
|
||||||
const { slug } = Astro.params;
|
|
||||||
const project = await getEntry('portfolio', `${lang}/${slug}`)!;
|
|
||||||
const { Content, remarkPluginFrontmatter: data } = await render(project);
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout
|
|
||||||
title={data.title}
|
|
||||||
description={data.description}
|
|
||||||
>
|
|
||||||
<article class='prose prose-invert'>
|
|
||||||
<h1>{data.title}</h1>
|
|
||||||
<Content components={{ ...components }} />
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
<strong>Publicado: </strong>
|
|
||||||
{data.date && formatDate(new Date(data.date), lang)}
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
</Layout>
|
|
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
import PostItem from '@/components/post-item';
|
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
|
||||||
import Layout from '@/layouts/Layout.astro';
|
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
|
|
||||||
const pageData = {
|
|
||||||
title: 'Portfolio',
|
|
||||||
description: 'Revisa mis proyectos.',
|
|
||||||
};
|
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const allEsProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang === 'es')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
sortContentByDate(allEsProjects);
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout {...pageData}>
|
|
||||||
<section class='prose prose-invert'>
|
|
||||||
<h1>{pageData.title}</h1>
|
|
||||||
<p>{pageData.description}</p>
|
|
||||||
</section>
|
|
||||||
<ul class='mt-4 flex flex-col gap-4'>
|
|
||||||
{
|
|
||||||
allEsProjects.map(
|
|
||||||
(project) =>
|
|
||||||
project && (
|
|
||||||
<li>
|
|
||||||
<PostItem
|
|
||||||
lang={lang}
|
|
||||||
type='portfolio'
|
|
||||||
id={project.id}
|
|
||||||
date={project.data.date!}
|
|
||||||
title={project.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</Layout>
|
|
@ -34,7 +34,7 @@ const { Content, remarkPluginFrontmatter: data } = await render(video);
|
|||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
>
|
>
|
||||||
<article class='prose prose-invert'>
|
<article class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import PostItem from '@/components/post-item';
|
import PostItemList from '@/components/post-items-list';
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import Layout from '@/layouts/Layout.astro';
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
import { sortContentByDate } from '@/utils/sorts';
|
||||||
@ -20,23 +20,12 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
<PostItemList
|
||||||
<ul class='mt-4 flex flex-col gap-4'>
|
items={allVideos}
|
||||||
{
|
lang={lang}
|
||||||
allVideos.map((video: any) => (
|
/>
|
||||||
<li>
|
</div>
|
||||||
<PostItem
|
|
||||||
lang={lang}
|
|
||||||
type='videos'
|
|
||||||
id={video.id.split('.')[0]}
|
|
||||||
date={video.data.date!}
|
|
||||||
title={video.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -12,9 +12,6 @@ const markdownParser = new MarkdownIt();
|
|||||||
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
|
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
'/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}'
|
'/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}'
|
||||||
);
|
);
|
||||||
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
|
|
||||||
'/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}'
|
|
||||||
);
|
|
||||||
|
|
||||||
export async function GET(context: any) {
|
export async function GET(context: any) {
|
||||||
const items: RSSFeedItem[] = [];
|
const items: RSSFeedItem[] = [];
|
||||||
@ -29,16 +26,6 @@ export async function GET(context: any) {
|
|||||||
return lang !== 'es' && post;
|
return lang !== 'es' && post;
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
|
||||||
);
|
|
||||||
const filterPortfolio = portfolio.filter((project) => {
|
|
||||||
const [lang] = project.id.split('/');
|
|
||||||
|
|
||||||
return lang !== 'es' && project;
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const post of filterBlog) {
|
for await (const post of filterBlog) {
|
||||||
const body = markdownParser.render(post.body!);
|
const body = markdownParser.render(post.body!);
|
||||||
const html = htmlParser.parse(body);
|
const html = htmlParser.parse(body);
|
||||||
@ -79,47 +66,6 @@ export async function GET(context: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const project of filterPortfolio) {
|
|
||||||
const body = markdownParser.render(project.body!);
|
|
||||||
const html = htmlParser.parse(body);
|
|
||||||
const images = html.querySelectorAll('img');
|
|
||||||
|
|
||||||
for await (const img of images) {
|
|
||||||
const src = img.getAttribute('src')!;
|
|
||||||
|
|
||||||
if (src.startsWith('@/')) {
|
|
||||||
const prefixRemoved = src.replace('@/', '');
|
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
|
||||||
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
|
||||||
(res: any) => res.default
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imagePath) {
|
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
|
||||||
img.setAttribute(
|
|
||||||
'src',
|
|
||||||
context.site + optimizedImg.src.replace('/', '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (src.startsWith('/images')) {
|
|
||||||
// images starting with `/images/` is the public dir
|
|
||||||
img.setAttribute('src', context.site + src.replace('/', ''));
|
|
||||||
} else {
|
|
||||||
throw Error('src unknown');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
title: project.data.title,
|
|
||||||
pubDate: project.data.date,
|
|
||||||
description: project.data.description,
|
|
||||||
link: `/portfolio/${project.id.split('.')[0]}/`,
|
|
||||||
content: sanitizeHtml(html.toString(), {
|
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rss({
|
return rss({
|
||||||
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
|
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
|
||||||
title: 'juancmandev',
|
title: 'juancmandev',
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
import Layout from '@/layouts/Layout.astro';
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import LinkButton from '@/components/link-button';
|
import LinkButton from '@/components/link-button';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import PostItem from '@/components/post-item';
|
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
import { sortContentByDate } from '@/utils/sorts';
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
|
import PostItemList from '@/components/post-items-list';
|
||||||
|
import Random from '@/components/random.astro';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: 'juancmandev',
|
title: 'juancmandev',
|
||||||
@ -26,31 +27,15 @@ const allEnPosts = allPosts.map((post) => {
|
|||||||
sortContentByDate(allEnPosts);
|
sortContentByDate(allEnPosts);
|
||||||
const last3Blogs = allEnPosts.slice(0, 3);
|
const last3Blogs = allEnPosts.slice(0, 3);
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const allEnProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang !== 'es')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
sortContentByDate(allEnProjects);
|
|
||||||
const last3Projects = allEnProjects.slice(0, 3);
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<div class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1 class='text-primary'>Welcome to my domain, stranger.</h1>
|
<h1 class='text-primary'>Welcome to my domain, stranger.</h1>
|
||||||
|
<!-- <Random /> -->
|
||||||
<p>
|
<p>
|
||||||
I am <strong class='text-primary'>juancmandev</strong>; <strong
|
I am <strong class='text-primary'>Juan Manzanero</strong>; <strong
|
||||||
>Web Developer</strong
|
>Web Developer</strong
|
||||||
>, <strong>Linux</strong> enthusiast, and <strong>privacy</strong> defender.
|
>, <strong>Linux</strong> enthusiast, and <strong>privacy</strong> defender.
|
||||||
</p>
|
</p>
|
||||||
@ -61,21 +46,10 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
</p>
|
</p>
|
||||||
<section>
|
<section>
|
||||||
<h2>Latest posts</h2>
|
<h2>Latest posts</h2>
|
||||||
<ul class='mt-0 p-0 list-none'>
|
<PostItemList
|
||||||
{
|
items={last3Blogs}
|
||||||
last3Blogs.map((blogpost: any) => (
|
lang={lang}
|
||||||
<li class='p-0'>
|
/>
|
||||||
<PostItem
|
|
||||||
type='blog'
|
|
||||||
lang={lang}
|
|
||||||
id={blogpost.id}
|
|
||||||
date={blogpost.data.date!}
|
|
||||||
title={blogpost.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
href='/blog'
|
href='/blog'
|
||||||
@ -83,32 +57,5 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
>More posts</LinkButton
|
>More posts</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<h2>Latest projects</h2>
|
|
||||||
<ul class='mt-0 p-0 list-none'>
|
|
||||||
{
|
|
||||||
last3Projects.map(
|
|
||||||
(project) =>
|
|
||||||
project && (
|
|
||||||
<li class='p-0'>
|
|
||||||
<PostItem
|
|
||||||
lang={lang}
|
|
||||||
type='portfolio'
|
|
||||||
id={project.id}
|
|
||||||
date={project.data.date!}
|
|
||||||
title={project.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<LinkButton
|
|
||||||
variant='secondary'
|
|
||||||
href='/portfolio'
|
|
||||||
className='no-underline'
|
|
||||||
>More projects</LinkButton
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -14,7 +14,7 @@ const data = await pb.collection('microblogs').getFullList({
|
|||||||
title='Microblog'
|
title='Microblog'
|
||||||
description='Short-format writing. Instead of using shitty social media.'
|
description='Short-format writing. Instead of using shitty social media.'
|
||||||
>
|
>
|
||||||
<div class='prose prose-invert'>
|
<div class='prose prose-invert max-w-[800px] mx-auto'>
|
||||||
<h1>Microblog</h1>
|
<h1>Microblog</h1>
|
||||||
<p>Short-format writing.</p>
|
<p>Short-format writing.</p>
|
||||||
<p>Instead of using shitty social media.</p>
|
<p>Instead of using shitty social media.</p>
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from '@/layouts/Layout.astro';
|
|
||||||
import components from '@/components/mdx/wrapper';
|
|
||||||
import formatDate from '@/utils/format-date';
|
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
|
||||||
import { getCollection, getEntry, render } from 'astro:content';
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const filterEnProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang === 'en')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return filterEnProjects.map((project) => ({
|
|
||||||
params: { slug: project?.id },
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
|
||||||
const { slug } = Astro.params;
|
|
||||||
const project = await getEntry('portfolio', `${lang}/${slug}`)!;
|
|
||||||
const { Content, remarkPluginFrontmatter: data } = await render(project);
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout
|
|
||||||
title={data.title}
|
|
||||||
description={data.description}
|
|
||||||
>
|
|
||||||
<article class='prose prose-invert'>
|
|
||||||
<h1>{data.title}</h1>
|
|
||||||
<Content components={{ ...components }} />
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
<strong>Posted: </strong>
|
|
||||||
{data.date && formatDate(new Date(data.date), lang)}
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
</Layout>
|
|
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
import PostItem from '@/components/post-item';
|
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
|
||||||
import Layout from '@/layouts/Layout.astro';
|
|
||||||
import { sortContentByDate } from '@/utils/sorts';
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
|
|
||||||
const pageData = {
|
|
||||||
title: 'Portfolio',
|
|
||||||
description: 'Check my projects.',
|
|
||||||
};
|
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
|
||||||
'portfolio',
|
|
||||||
({ data }) => data.draft !== true
|
|
||||||
);
|
|
||||||
const allEnProjects = allProjects.map((project) => {
|
|
||||||
const [lang, id] = project.id.split('/');
|
|
||||||
|
|
||||||
if (lang === 'en')
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
id: id.split('.')[0],
|
|
||||||
};
|
|
||||||
else null;
|
|
||||||
});
|
|
||||||
sortContentByDate(allEnProjects);
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout {...pageData}>
|
|
||||||
<section class='prose prose-invert'>
|
|
||||||
<h1>{pageData.title}</h1>
|
|
||||||
<p>{pageData.description}</p>
|
|
||||||
</section>
|
|
||||||
<ul class='mt-4 flex flex-col gap-4'>
|
|
||||||
{
|
|
||||||
allEnProjects.map(
|
|
||||||
(project) =>
|
|
||||||
project && (
|
|
||||||
<li>
|
|
||||||
<PostItem
|
|
||||||
lang={lang}
|
|
||||||
type='portfolio'
|
|
||||||
id={project.id}
|
|
||||||
date={project.data.date!}
|
|
||||||
title={project.data.title!}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</Layout>
|
|
@ -4,7 +4,7 @@ const robotsTxt = `
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
|
|
||||||
Sitemap: ${new URL('sitemap-index.xml', import.meta.env.SITE).href}
|
Sitemap: ${new URL('sitemap-index.xml', 'https://juancman.dev').href}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
export const GET: APIRoute = () => {
|
export const GET: APIRoute = () => {
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
.prose {
|
||||||
|
@apply max-w-none;
|
||||||
|
}
|
||||||
[data-rehype-pretty-code-figure] {
|
[data-rehype-pretty-code-figure] {
|
||||||
@apply bg-[#1e1e2e] rounded-md pt-4;
|
@apply bg-[#1e1e2e] rounded-md pt-4;
|
||||||
}
|
}
|
||||||
|
7
src/types/post-item.ts
Normal file
7
src/types/post-item.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
type TPostItem = {
|
||||||
|
id: string;
|
||||||
|
date: Date | string;
|
||||||
|
title: string;
|
||||||
|
type: 'blog' | 'portfolio' | 'videos';
|
||||||
|
lang: string;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user