Migrate to Astro V5 (#22)
* fix video url * update deploy script for using rsync * remove delete from script * translate resources page * fix images dimensions and favicon * add css optimization plugin and improve images * update project dependencies * refactor lang * post translated to spanish * fix metadata * update dependencies * update dependencies * update dependencies * update to Astro 5.x * format index.astro * Migrate content layer to Astro V5
This commit is contained in:
parent
9363bf7a20
commit
4f0e80b988
@ -1,28 +1,34 @@
|
|||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from 'astro/config';
|
||||||
import react from "@astrojs/react";
|
import react from '@astrojs/react';
|
||||||
import tailwind from "@astrojs/tailwind";
|
import tailwind from '@astrojs/tailwind';
|
||||||
import mdx from "@astrojs/mdx";
|
import mdx from '@astrojs/mdx';
|
||||||
import rehypePrettyCode from "rehype-pretty-code";
|
import rehypePrettyCode from 'rehype-pretty-code';
|
||||||
import rehypeSlug from "rehype-slug";
|
import rehypeSlug from 'rehype-slug';
|
||||||
import sitemap from "@astrojs/sitemap";
|
import sitemap from '@astrojs/sitemap';
|
||||||
|
|
||||||
import playformInline from "@playform/inline";
|
import playformInline from '@playform/inline';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://juancman.dev/",
|
site: 'https://juancman.dev/',
|
||||||
integrations: [sitemap(), react(), tailwind({
|
integrations: [
|
||||||
|
sitemap(),
|
||||||
|
react(),
|
||||||
|
tailwind({
|
||||||
applyBaseStyles: false,
|
applyBaseStyles: false,
|
||||||
}), mdx({
|
}),
|
||||||
|
mdx({
|
||||||
syntaxHighlight: false,
|
syntaxHighlight: false,
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
[
|
[
|
||||||
rehypePrettyCode,
|
rehypePrettyCode,
|
||||||
{
|
{
|
||||||
theme: "catppuccin-mocha",
|
theme: 'catppuccin-mocha',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
}), playformInline()],
|
}),
|
||||||
|
playformInline(),
|
||||||
|
],
|
||||||
});
|
});
|
34
package.json
34
package.json
@ -10,21 +10,21 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.7.0",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/mdx": "^3.1.9",
|
"@astrojs/mdx": "^4.0.8",
|
||||||
"@astrojs/react": "^3.6.2",
|
"@astrojs/react": "^4.2.0",
|
||||||
"@astrojs/rss": "^4.0.9",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
"@astrojs/tailwind": "^5.1.2",
|
"@astrojs/tailwind": "^5.1.5",
|
||||||
"@playform/inline": "^0.1.0",
|
"@playform/inline": "^0.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.5",
|
||||||
"astro": "^4.16.10",
|
"astro": "^5.2.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.3",
|
||||||
"lucide-react": "^0.396.0",
|
"lucide-react": "^0.396.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"marked": "^13.0.3",
|
"marked": "^13.0.3",
|
||||||
@ -33,12 +33,12 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"rehype-pretty-code": "^0.13.2",
|
"rehype-pretty-code": "^0.13.2",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"sanitize-html": "^2.13.1",
|
"sanitize-html": "^2.14.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
2503
pnpm-lock.yaml
generated
2503
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,18 @@
|
|||||||
import { Code, RssIcon } from "lucide-react";
|
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';
|
||||||
|
|
||||||
const locales = {
|
const locales = {
|
||||||
en: {
|
en: {
|
||||||
developed_by: "Developed by ",
|
developed_by: 'Developed by ',
|
||||||
build_handcrafted: "Built handcrafted with ",
|
build_handcrafted: 'Built handcrafted with ',
|
||||||
last_build: "Last build",
|
last_build: 'Last build',
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
developed_by: "Desarrollado por ",
|
developed_by: 'Desarrollado por ',
|
||||||
build_handcrafted: "Construido a mano con ",
|
build_handcrafted: 'Construido a mano con ',
|
||||||
last_build: "Última build",
|
last_build: 'Última build',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,26 +22,29 @@ type Props = {
|
|||||||
|
|
||||||
export default function Footer({ lang }: Props) {
|
export default function Footer({ lang }: Props) {
|
||||||
const rssUrl =
|
const rssUrl =
|
||||||
lang == "en"
|
lang == 'en'
|
||||||
? "https://juancman.dev/feed.xml"
|
? 'https://juancman.dev/feed.xml'
|
||||||
: "https://juancman.dev/es/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='border-t border-secondary px-4 py-12 text-center text-sm md:px-16 prose prose-invert min-w-full'>
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
{locales[lang].developed_by}
|
{locales[lang].developed_by}
|
||||||
<strong className="font-bold text-primary">juancmandev</strong>
|
<strong className='font-bold text-primary'>juancmandev</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{locales[lang].build_handcrafted}
|
{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 no-underline hover:underline'
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href='https://astro.build/'
|
||||||
|
target='_blank'
|
||||||
>
|
>
|
||||||
<a href="https://astro.build/" target="_blank">
|
|
||||||
Astro
|
Astro
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
@ -50,26 +53,32 @@ export default function Footer({ lang }: Props) {
|
|||||||
{locales[lang].last_build}: {formatDate(new Date(), lang)}.
|
{locales[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
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size={null}
|
size={null}
|
||||||
variant="link"
|
variant='link'
|
||||||
className="flex flex-col justify-center"
|
className='flex flex-col justify-center'
|
||||||
>
|
>
|
||||||
<a target="_blank" href="https://github.com/juancmandev/website">
|
<a
|
||||||
<Code className="w-6" />
|
target='_blank'
|
||||||
|
href='https://github.com/juancmandev/website'
|
||||||
|
>
|
||||||
|
<Code className='w-6' />
|
||||||
Source Code
|
Source Code
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size={null}
|
size={null}
|
||||||
variant="link"
|
variant='link'
|
||||||
className="flex flex-col justify-center"
|
className='flex flex-col justify-center'
|
||||||
>
|
>
|
||||||
<a target="_blank" href={rssUrl}>
|
<a
|
||||||
<RssIcon className="w-6" />
|
target='_blank'
|
||||||
|
href={rssUrl}
|
||||||
|
>
|
||||||
|
<RssIcon className='w-6' />
|
||||||
RSS feed
|
RSS feed
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import logo from "@/assets/logo.png";
|
import logo from '@/assets/logo.png';
|
||||||
import { Image } from "astro:assets";
|
import { Image } from 'astro:assets';
|
||||||
import LinkButton from "@/components/link-button";
|
import LinkButton from '@/components/link-button';
|
||||||
import { ChevronUp, Compass } from "lucide-react";
|
import { ChevronUp, Compass } from 'lucide-react';
|
||||||
import type { lang } from "@/i18n/utils";
|
import type { lang } from '@/i18n/utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
lang: lang;
|
lang: lang;
|
||||||
@ -11,16 +11,16 @@ type Props = {
|
|||||||
|
|
||||||
const locales = {
|
const locales = {
|
||||||
en: {
|
en: {
|
||||||
to: "/es",
|
to: '/es',
|
||||||
switch_language: "🇲🇽",
|
switch_language: '🇲🇽',
|
||||||
top: "Top",
|
top: 'Top',
|
||||||
navigation: "Navigation",
|
navigation: 'Navigation',
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
to: "/",
|
to: '/',
|
||||||
switch_language: "🇺🇸",
|
switch_language: '🇺🇸',
|
||||||
top: "Arriba",
|
top: 'Arriba',
|
||||||
navigation: "Navevación",
|
navigation: 'Navevación',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,44 +28,52 @@ 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 fixed top-0 z-50 flex w-full items-center justify-between border-b border-secondary backdrop-blur-lg'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="px-4 sm:px-0 flex w-full max-w-[65ch] items-center justify-between mx-auto"
|
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'}
|
||||||
size="icon"
|
size='icon'
|
||||||
variant="link"
|
variant='link'
|
||||||
className="rounded-full px-0"
|
className='rounded-full px-0'
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={logo}
|
src={logo}
|
||||||
width={80}
|
width={80}
|
||||||
height={80}
|
height={80}
|
||||||
loading="eager"
|
loading='eager'
|
||||||
decoding="sync"
|
decoding='sync'
|
||||||
fetchpriority="high"
|
fetchpriority='high'
|
||||||
class="w-auto h-auto aspect-square"
|
class='w-auto h-auto aspect-square'
|
||||||
alt="juancmandev logo"
|
alt='juancmandev logo'
|
||||||
/>
|
/>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</section>
|
</section>
|
||||||
<section class="flex items-center gap-2">
|
<section class='flex items-center gap-2'>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="link"
|
variant='link'
|
||||||
href={locales[lang].to}
|
href={locales[lang].to}
|
||||||
className="p-0 gap-1 text-base"
|
className='p-0 gap-1 text-base'
|
||||||
>
|
>
|
||||||
{locales[lang].switch_language}
|
{locales[lang].switch_language}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<LinkButton variant="link" className="p-0 gap-0.5" href="#">
|
<LinkButton
|
||||||
<ChevronUp className="w-5" />
|
variant='link'
|
||||||
|
className='p-0 gap-0.5'
|
||||||
|
href='#'
|
||||||
|
>
|
||||||
|
<ChevronUp className='w-5' />
|
||||||
{locales[lang].top}
|
{locales[lang].top}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<LinkButton variant="link" className="p-0 gap-0.5" href="#navigation">
|
<LinkButton
|
||||||
<Compass className="w-5" />
|
variant='link'
|
||||||
|
className='p-0 gap-0.5'
|
||||||
|
href='#navigation'
|
||||||
|
>
|
||||||
|
<Compass className='w-5' />
|
||||||
{locales[lang].navigation}
|
{locales[lang].navigation}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
href: string;
|
href: string;
|
||||||
variant?:
|
variant?:
|
||||||
| "default"
|
| 'default'
|
||||||
| "destructive"
|
| 'destructive'
|
||||||
| "outline"
|
| 'outline'
|
||||||
| "secondary"
|
| 'secondary'
|
||||||
| "ghost"
|
| 'ghost'
|
||||||
| "link"
|
| 'link'
|
||||||
| null
|
| null
|
||||||
| undefined;
|
| undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
size?: 'default' | 'sm' | 'lg' | 'icon' | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkButton(props: Props) {
|
export default function LinkButton(props: Props) {
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
---
|
---
|
||||||
import { Image } from "astro:assets";
|
import { Image } from 'astro:assets';
|
||||||
|
|
||||||
const props = Astro.props;
|
const props = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
id="img"
|
id='img'
|
||||||
decoding="async"
|
decoding='async'
|
||||||
src={props.src}
|
src={props.src}
|
||||||
alt={props.alt}
|
alt={props.alt}
|
||||||
width={props.width}
|
width={props.width}
|
||||||
height={props.height}
|
height={props.height}
|
||||||
class="w-auto h-auto rounded-md aspect-auto object-cover"
|
class='w-auto h-auto rounded-md aspect-auto object-cover'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const image = document.getElementById("img")!;
|
const image = document.getElementById('img')!;
|
||||||
|
|
||||||
image && image.setAttribute("loading", "eager");
|
image && image.setAttribute('loading', 'eager');
|
||||||
image && image.setAttribute("decoding", "sync");
|
image && image.setAttribute('decoding', 'sync');
|
||||||
image && image.setAttribute("fetchpriority", "high");
|
image && image.setAttribute('fetchpriority', 'high');
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,9 +3,16 @@ type TAnchor = {
|
|||||||
} & React.HTMLAttributes<HTMLAnchorElement>;
|
} & React.HTMLAttributes<HTMLAnchorElement>;
|
||||||
|
|
||||||
export default function CustomAnchor(props: TAnchor) {
|
export default function CustomAnchor(props: TAnchor) {
|
||||||
return props.href.startsWith("/") || props.href.startsWith("#") ? (
|
return props.href.startsWith('/') || props.href.startsWith('#') ? (
|
||||||
<a {...props} className="inline-flex outline-ring" />
|
<a
|
||||||
|
{...props}
|
||||||
|
className='inline-flex outline-ring'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<a {...props} className="inline-flex outline-ring" target="_blank" />
|
<a
|
||||||
|
{...props}
|
||||||
|
className='inline-flex outline-ring'
|
||||||
|
target='_blank'
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import AstroImage from "@/components/mdx/astro-image.astro";
|
import AstroImage from '@/components/mdx/astro-image.astro';
|
||||||
import CustomAnchor from "@/components/mdx/custom-anchor";
|
import CustomAnchor from '@/components/mdx/custom-anchor';
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
img: AstroImage,
|
img: AstroImage,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { marked } from "marked";
|
import { marked } from 'marked';
|
||||||
import formatDate from "@/utils/format-date";
|
import formatDate from '@/utils/format-date';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
|
|
||||||
const props = Astro.props;
|
const props = Astro.props;
|
||||||
const content = marked.parse(props.content);
|
const content = marked.parse(props.content);
|
||||||
@ -9,22 +9,22 @@ const content = marked.parse(props.content);
|
|||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="rounded-md border px-4 py-2">
|
<article class='rounded-md border px-4 py-2'>
|
||||||
<header class="mb-2">
|
<header class='mb-2'>
|
||||||
<section class="flex items-center justify-between text-sm">
|
<section class='flex items-center justify-between text-sm'>
|
||||||
<span class="font-light">
|
<span class='font-light'>
|
||||||
{formatDate(new Date(props.published), lang)}{" "}
|
{formatDate(new Date(props.published), lang)}{' '}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm font-thin">
|
<span class='text-sm font-thin'>
|
||||||
{new Date(props.published).toLocaleTimeString()}
|
{new Date(props.published).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
<section class="mt-1">
|
<section class='mt-1'>
|
||||||
{
|
{
|
||||||
props &&
|
props &&
|
||||||
props.expand.tags &&
|
props.expand.tags &&
|
||||||
props?.expand.tags.map(
|
props?.expand.tags.map(
|
||||||
(tag: any) => tag && <span class="text-sm">#{tag.name} </span>,
|
(tag: any) => tag && <span class='text-sm'>#{tag.name} </span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import LinkButton from "@/components/link-button";
|
import LinkButton from '@/components/link-button';
|
||||||
import {
|
import {
|
||||||
NotebookText,
|
NotebookText,
|
||||||
BriefcaseBusiness,
|
BriefcaseBusiness,
|
||||||
@ -7,8 +7,8 @@ import {
|
|||||||
PocketKnife,
|
PocketKnife,
|
||||||
Info,
|
Info,
|
||||||
Mail,
|
Mail,
|
||||||
} from "lucide-react";
|
} from 'lucide-react';
|
||||||
import { useTranslations, type lang } from "@/i18n/utils";
|
import { useTranslations, type lang } from '@/i18n/utils';
|
||||||
|
|
||||||
type TNavItem = {
|
type TNavItem = {
|
||||||
type: string;
|
type: string;
|
||||||
@ -17,28 +17,28 @@ type TNavItem = {
|
|||||||
|
|
||||||
export const navItems: TNavItem[] = [
|
export const navItems: TNavItem[] = [
|
||||||
{
|
{
|
||||||
type: "blog",
|
type: 'blog',
|
||||||
icon: <NotebookText />,
|
icon: <NotebookText />,
|
||||||
},
|
},
|
||||||
{ type: "portfolio", icon: <BriefcaseBusiness /> },
|
{ type: 'portfolio', icon: <BriefcaseBusiness /> },
|
||||||
{
|
{
|
||||||
type: "videos",
|
type: 'videos',
|
||||||
icon: <MonitorPlay />,
|
icon: <MonitorPlay />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "microblog",
|
type: 'microblog',
|
||||||
icon: <Newspaper />,
|
icon: <Newspaper />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "resources",
|
type: 'resources',
|
||||||
icon: <PocketKnife />,
|
icon: <PocketKnife />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "about",
|
type: 'about',
|
||||||
icon: <Info />,
|
icon: <Info />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "contact",
|
type: 'contact',
|
||||||
icon: <Mail />,
|
icon: <Mail />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -51,15 +51,18 @@ 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='px-4 sm:px-0 max-w-[65ch] mx-auto prose prose-invert pt-5 pb-20'>
|
||||||
<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) => (
|
||||||
<li key={index} className="m-0 p-0">
|
<li
|
||||||
|
key={index}
|
||||||
|
className='m-0 p-0'
|
||||||
|
>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="link"
|
variant='link'
|
||||||
href={t(`${navItem.type}.to` as any)}
|
href={t(`${navItem.type}.to` as any)}
|
||||||
className="p-0 text-base gap-1"
|
className='p-0 text-base gap-1'
|
||||||
>
|
>
|
||||||
{navItem.icon}
|
{navItem.icon}
|
||||||
{t(`${navItem.type}.label` as any)}
|
{t(`${navItem.type}.label` as any)}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
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 = {
|
type Props = {
|
||||||
slug: string;
|
id: string;
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
title: string;
|
title: string;
|
||||||
type: "blog" | "portfolio" | "videos";
|
type: 'blog' | 'portfolio' | 'videos';
|
||||||
lang: string;
|
lang: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,21 +14,21 @@ export default function PostItem(props: Props) {
|
|||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size={null}
|
size={null}
|
||||||
variant="link"
|
variant='link'
|
||||||
className="px-4 whitespace-normal py-2 hover:no-underline focus:no-underline flex flex-col items-start italic border border-secondary hover:border-foreground focus:border-foreground transition-colors rounded-md"
|
className='px-4 whitespace-normal py-2 hover:no-underline focus:no-underline flex flex-col items-start italic border border-secondary hover:border-foreground focus:border-foreground transition-colors rounded-md'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
className="no-underline"
|
className='no-underline'
|
||||||
href={
|
href={
|
||||||
props.lang === "en"
|
props.lang === 'en'
|
||||||
? `/${props.type}/${props.slug}`
|
? `/${props.type}/${props.id}`
|
||||||
: `/es/${props.type}/${[props.slug]}`
|
: `/es/${props.type}/${[props.id]}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="text-sm font-light no-underline">
|
<span className='text-sm font-light no-underline'>
|
||||||
{formatDate(props.date, props.lang)}
|
{formatDate(props.date, props.lang)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-primary text-underline text-lg font-semibold underline">
|
<span className='text-primary text-underline text-lg font-semibold underline'>
|
||||||
{props.title}
|
{props.title}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
import * as React from "react";
|
import * as React from 'react';
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||||
secondary: "bg-secondary text-foreground hover:bg-secondary/60",
|
secondary: 'bg-secondary text-foreground hover:bg-secondary/60',
|
||||||
ghost: "shadow-none hover:bg-foreground/10",
|
ghost: 'shadow-none hover:bg-foreground/10',
|
||||||
link: "shadow-none underline-offset-4 hover:underline focus-within:underline",
|
link: 'shadow-none underline-offset-4 hover:underline focus-within:underline',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: 'h-10 px-4 py-2',
|
||||||
sm: "h-9 rounded-md px-3",
|
sm: 'h-9 rounded-md px-3',
|
||||||
lg: "h-11 rounded-md px-8",
|
lg: 'h-11 rounded-md px-8',
|
||||||
icon: "h-10 w-10",
|
icon: 'h-10 w-10',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
size: "default",
|
size: 'default',
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
@ -40,7 +40,7 @@ export interface ButtonProps
|
|||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button";
|
const Comp = asChild ? Slot : 'button';
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
@ -48,8 +48,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
Button.displayName = "Button";
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants };
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { defineCollection, z } from "astro:content";
|
import { defineCollection, z } from 'astro:content';
|
||||||
|
import { glob } from 'astro/loaders';
|
||||||
|
|
||||||
const contentSchema = z.object({
|
const contentSchema = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@ -13,17 +14,20 @@ const contentSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const blog = defineCollection({
|
const blog = defineCollection({
|
||||||
type: "content",
|
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/blog' }),
|
||||||
schema: contentSchema,
|
schema: contentSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = defineCollection({
|
const portfolio = defineCollection({
|
||||||
type: "content",
|
loader: glob({
|
||||||
|
pattern: '**/[^_]*.{md,mdx}',
|
||||||
|
base: './src/content/portfolio',
|
||||||
|
}),
|
||||||
schema: contentSchema,
|
schema: contentSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pages = defineCollection({
|
const pages = defineCollection({
|
||||||
type: "content",
|
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/pages' }),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
@ -31,13 +35,13 @@ const pages = defineCollection({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const videos = defineCollection({
|
const videos = defineCollection({
|
||||||
type: "content",
|
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/videos' }),
|
||||||
schema: contentSchema
|
schema: contentSchema,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const collections = {
|
export const collections = {
|
||||||
blog,
|
blog,
|
||||||
portfolio,
|
portfolio,
|
||||||
pages,
|
pages,
|
||||||
videos
|
videos,
|
||||||
};
|
};
|
@ -1,44 +1,44 @@
|
|||||||
export const languages = {
|
export const languages = {
|
||||||
en: "English",
|
en: 'English',
|
||||||
es: "Español",
|
es: 'Español',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultLang = "en";
|
export const defaultLang = 'en';
|
||||||
export const showDefaultLang = false;
|
export const showDefaultLang = false;
|
||||||
|
|
||||||
export const ui = {
|
export const ui = {
|
||||||
en: {
|
en: {
|
||||||
navigation: "Navigation",
|
navigation: 'Navigation',
|
||||||
"blog.label": "Blog",
|
'blog.label': 'Blog',
|
||||||
"blog.to": "/blog",
|
'blog.to': '/blog',
|
||||||
"portfolio.label": "Portfolio",
|
'portfolio.label': 'Portfolio',
|
||||||
"portfolio.to": "/portfolio",
|
'portfolio.to': '/portfolio',
|
||||||
"videos.label": "Videos",
|
'videos.label': 'Videos',
|
||||||
"videos.to": "/es/videos",
|
'videos.to': '/es/videos',
|
||||||
"microblog.label": "Microblog",
|
'microblog.label': 'Microblog',
|
||||||
"microblog.to": "/microblog",
|
'microblog.to': '/microblog',
|
||||||
"resources.label": "Resources",
|
'resources.label': 'Resources',
|
||||||
"resources.to": "/resources",
|
'resources.to': '/resources',
|
||||||
"about.label": "About",
|
'about.label': 'About',
|
||||||
"about.to": "/about",
|
'about.to': '/about',
|
||||||
"contact.label": "Contact",
|
'contact.label': 'Contact',
|
||||||
"contact.to": "/contact",
|
'contact.to': '/contact',
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
navigation: "Navegación",
|
navigation: 'Navegación',
|
||||||
"blog.label": "Blog",
|
'blog.label': 'Blog',
|
||||||
"blog.to": "/es/blog",
|
'blog.to': '/es/blog',
|
||||||
"portfolio.label": "Portfolio",
|
'portfolio.label': 'Portfolio',
|
||||||
"portfolio.to": "/es/portfolio",
|
'portfolio.to': '/es/portfolio',
|
||||||
"videos.label": "Videos",
|
'videos.label': 'Videos',
|
||||||
"videos.to": "/es/videos",
|
'videos.to': '/es/videos',
|
||||||
"microblog.label": "Microblog",
|
'microblog.label': 'Microblog',
|
||||||
"microblog.to": "/microblog",
|
'microblog.to': '/microblog',
|
||||||
"resources.label": "Recursos",
|
'resources.label': 'Recursos',
|
||||||
"resources.to": "/es/recursos",
|
'resources.to': '/es/recursos',
|
||||||
"about.label": "Acerca de",
|
'about.label': 'Acerca de',
|
||||||
"about.to": "/es/acerca-de",
|
'about.to': '/es/acerca-de',
|
||||||
"contact.label": "Contacto",
|
'contact.label': 'Contacto',
|
||||||
"contact.to": "/es/contacto",
|
'contact.to': '/es/contacto',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { ui, defaultLang, showDefaultLang } from "@/i18n/ui";
|
import { ui, defaultLang, showDefaultLang } from '@/i18n/ui';
|
||||||
|
|
||||||
|
export type lang = 'en' | 'es';
|
||||||
|
|
||||||
export type lang = "en" | "es";
|
export type lang = "en" | "es";
|
||||||
|
|
||||||
export function getLangFromUrl(url: URL) {
|
export function getLangFromUrl(url: URL) {
|
||||||
const [, lang] = url.pathname.split("/");
|
const [, lang] = url.pathname.split('/');
|
||||||
if (lang in ui) return lang as keyof typeof ui;
|
if (lang in ui) return lang as keyof typeof ui;
|
||||||
return defaultLang;
|
return defaultLang;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import Header from "@/components/header.astro";
|
import Header from '@/components/header.astro';
|
||||||
import Navigation from "@/components/navigation";
|
import Navigation from '@/components/navigation';
|
||||||
import Footer from "@/components/footer";
|
import Footer from '@/components/footer';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import "@/styles/globals.css";
|
import '@/styles/globals.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@ -16,37 +16,54 @@ const { title, description } = Astro.props;
|
|||||||
|
|
||||||
<html lang={lang}>
|
<html lang={lang}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset='UTF-8' />
|
||||||
<meta name="description" content={description} />
|
<meta
|
||||||
<meta name="viewport" content="width=device-width" />
|
name='description'
|
||||||
<link rel="icon" type="image/png" href="/logo.png" sizes="16x16" />
|
content={description}
|
||||||
{
|
/>
|
||||||
lang === "en" && (
|
<meta
|
||||||
|
name='viewport'
|
||||||
|
content='width=device-width'
|
||||||
|
/>
|
||||||
<link
|
<link
|
||||||
rel="alternate"
|
rel='icon'
|
||||||
title="juancmandev"
|
type='image/png'
|
||||||
type="application/rss+xml"
|
href='/logo.png'
|
||||||
href={new URL("feed.xml", Astro.site)}
|
sizes='16x16'
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
lang === 'en' && (
|
||||||
|
<link
|
||||||
|
rel='alternate'
|
||||||
|
title='juancmandev'
|
||||||
|
type='application/rss+xml'
|
||||||
|
href={new URL('feed.xml', Astro.site)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
lang === "es" && (
|
lang === 'es' && (
|
||||||
<link
|
<link
|
||||||
rel="alternate"
|
rel='alternate'
|
||||||
title="juancmandev"
|
title='juancmandev'
|
||||||
type="application/rss+xml"
|
type='application/rss+xml'
|
||||||
href={new URL("feed.xml", `${Astro.site}/es/`)}
|
href={new URL('feed.xml', `${Astro.site}/es/`)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link
|
||||||
<meta name="generator" content={Astro.generator} />
|
rel='sitemap'
|
||||||
|
href='/sitemap-index.xml'
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name='generator'
|
||||||
|
content={Astro.generator}
|
||||||
|
/>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</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='px-4 sm:px-0 max-w-[65ch] pt-28 pb-5 mx-auto'>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Navigation lang={lang} />
|
<Navigation lang={lang} />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { type ClassValue, clsx } from "clsx";
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Not found" description="Error 404: Not found.">
|
<Layout
|
||||||
<div class="prose prose-invert">
|
title='Not found'
|
||||||
<h1 class="">Error 404: Not found</h1>
|
description='Error 404: Not found.'
|
||||||
|
>
|
||||||
|
<div class='prose prose-invert'>
|
||||||
|
<h1 class=''>Error 404: Not found</h1>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,28 +1,38 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
page: CollectionEntry<"pages">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allPages = await getCollection("pages");
|
const allPages = await getCollection('pages');
|
||||||
|
const filterEnPages = allPages.map((page) => {
|
||||||
|
const [lang, id] = page.id.split('/');
|
||||||
|
|
||||||
return allPages.map((page: CollectionEntry<"pages">) => ({
|
if (lang === 'en')
|
||||||
params: { slug: page.slug },
|
return {
|
||||||
props: { page },
|
...page,
|
||||||
|
id: id.split('.')[0],
|
||||||
|
};
|
||||||
|
else null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filterEnPages.map((page) => ({
|
||||||
|
params: { slug: page?.id },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const { Content } = await page.render();
|
const { slug } = Astro.params;
|
||||||
|
const project = await getEntry('pages', `${lang}/${slug}`)!;
|
||||||
|
const { Content, remarkPluginFrontmatter: data } = await render(project);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...page.data}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,51 +1,48 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import formatDate from '@/utils/format-date';
|
||||||
import formatDate from "@/utils/format-date";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
post: CollectionEntry<"blog">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allBlogPosts = await getCollection(
|
const allBlogPosts = await getCollection(
|
||||||
"blog",
|
'blog',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const filterEnPosts = allBlogPosts.map((post) => {
|
const filterEnPosts = allBlogPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang === "en")
|
if (lang === 'en')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return filterEnPosts.map((post) => ({
|
return filterEnPosts.map((post) => ({
|
||||||
params: { slug: post?.slug },
|
params: { slug: post?.id },
|
||||||
props: { post },
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
|
||||||
const { Content } = await post.render();
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
const { slug } = Astro.params;
|
||||||
|
const post = await getEntry('blog', `${lang}/${slug}`)!;
|
||||||
|
const { Content, remarkPluginFrontmatter: data } = await render(post);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={post.data.title} description={post.data.description}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
<h1>{post.data.title}</h1>
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<strong>Posted: </strong>
|
<strong>Posted: </strong>
|
||||||
{post.data.date && formatDate(new Date(post.data.date), lang)}
|
{data.date && formatDate(new Date(data.date), lang)}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
---
|
---
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "Blog",
|
title: 'Blog',
|
||||||
description: "Long format about thoughts and other topics.",
|
description: 'Long format about thoughts and other topics.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allPosts = await getCollection("blog", ({ data }) => data.draft !== true);
|
const allPosts = await getCollection('blog', ({ data }) => data.draft !== true);
|
||||||
const filterEnPosts = allPosts.map((post) => {
|
const filterEnPosts = allPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang === "en")
|
if (lang === 'en')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -27,25 +27,25 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class='prose prose-invert'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<ul class="mt-4 flex flex-col gap-4">
|
<ul class='mt-4 flex flex-col gap-4'>
|
||||||
{
|
{
|
||||||
filterEnPosts.map(
|
filterEnPosts.map(
|
||||||
(blogpost) =>
|
(blogpost) =>
|
||||||
blogpost && (
|
blogpost && (
|
||||||
<li>
|
<li>
|
||||||
<PostItem
|
<PostItem
|
||||||
type="blog"
|
type='blog'
|
||||||
lang={lang}
|
lang={lang}
|
||||||
slug={blogpost.slug}
|
id={blogpost.id}
|
||||||
date={blogpost.data.date!}
|
date={blogpost.data.date!}
|
||||||
title={blogpost.data.title!}
|
title={blogpost.data.title!}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,28 +1,38 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
page: CollectionEntry<"pages">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allPages = await getCollection("pages");
|
const allPages = await getCollection('pages');
|
||||||
|
const filterEsPages = allPages.map((page) => {
|
||||||
|
const [lang, id] = page.id.split('/');
|
||||||
|
|
||||||
return allPages.map((page: CollectionEntry<"pages">) => ({
|
if (lang === 'es')
|
||||||
params: { slug: page.slug },
|
return {
|
||||||
props: { page },
|
...page,
|
||||||
|
id: id.split('.')[0],
|
||||||
|
};
|
||||||
|
else null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filterEsPages.map((page) => ({
|
||||||
|
params: { slug: page?.id },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const { Content } = await page.render();
|
const { slug } = Astro.params;
|
||||||
|
const page = await getEntry('pages', `${lang}/${slug}`)!;
|
||||||
|
const { Content, remarkPluginFrontmatter: data } = await render(page);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...page.data}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,51 +1,48 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import formatDate from '@/utils/format-date';
|
||||||
import formatDate from "@/utils/format-date";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
post: CollectionEntry<"blog">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allBlogPosts = await getCollection(
|
const allBlogPosts = await getCollection(
|
||||||
"blog",
|
'blog',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const filterEsPosts = allBlogPosts.map((post) => {
|
const filterEsPosts = allBlogPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang === 'es')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return filterEsPosts.map((post) => ({
|
return filterEsPosts.map((post) => ({
|
||||||
params: { slug: post?.slug },
|
params: { slug: post?.id },
|
||||||
props: { post },
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
|
||||||
const { Content } = await post.render();
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
const { slug } = Astro.params;
|
||||||
|
const blog = await getEntry('blog', `${lang}/${slug}`)!;
|
||||||
|
const { Content, remarkPluginFrontmatter: data } = await render(blog);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={post.data.title} description={post.data.description}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
<h1>{post.data.title}</h1>
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<strong>Publicado: </strong>
|
<strong>Publicado: </strong>
|
||||||
{post.data.date && formatDate(new Date(post.data.date), lang)}
|
{data.date && formatDate(new Date(data.date), lang)}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
---
|
---
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "Blog",
|
title: 'Blog',
|
||||||
description: "Formato largo sobre pensamientos y otros temas.",
|
description: 'Formato largo sobre pensamientos y otros temas.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allPosts = await getCollection("blog", ({ data }) => data.draft !== true);
|
const allPosts = await getCollection('blog', ({ data }) => data.draft !== true);
|
||||||
const filterEsPosts = allPosts.map((post) => {
|
const filterEsPosts = allPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang === 'es')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -27,25 +27,25 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class='prose prose-invert'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<ul class="mt-4 flex flex-col gap-4">
|
<ul class='mt-4 flex flex-col gap-4'>
|
||||||
{
|
{
|
||||||
filterEsPosts.map(
|
filterEsPosts.map(
|
||||||
(post) =>
|
(post) =>
|
||||||
post && (
|
post && (
|
||||||
<li>
|
<li>
|
||||||
<PostItem
|
<PostItem
|
||||||
type="blog"
|
type='blog'
|
||||||
lang={lang}
|
lang={lang}
|
||||||
slug={post?.slug}
|
id={post.id}
|
||||||
date={post?.data.date!}
|
date={post.data.date}
|
||||||
title={post?.data.title!}
|
title={post.data.title}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,54 +1,54 @@
|
|||||||
import rss from "@astrojs/rss";
|
import rss from '@astrojs/rss';
|
||||||
import type { RSSFeedItem } from "@astrojs/rss";
|
import type { RSSFeedItem } from '@astrojs/rss';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from 'markdown-it';
|
||||||
import { parse as htmlParser } from "node-html-parser";
|
import { parse as htmlParser } from 'node-html-parser';
|
||||||
import { getImage } from "astro:assets";
|
import { getImage } from 'astro:assets';
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from 'astro';
|
||||||
|
|
||||||
const markdownParser = new MarkdownIt();
|
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 }>(
|
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}"
|
'/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[] = [];
|
||||||
|
|
||||||
const blog = await getCollection(
|
const blog = await getCollection(
|
||||||
"blog",
|
'blog',
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
({ data }) => data.draft !== true && data.rss === true
|
||||||
);
|
);
|
||||||
const filterBlog = blog.filter((post) => {
|
const filterBlog = blog.filter((post) => {
|
||||||
const [lang] = post.slug.split("/");
|
const [lang] = post.id.split('/');
|
||||||
|
|
||||||
return lang === "es" && post;
|
return lang === 'es' && post;
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = await getCollection(
|
const portfolio = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
({ data }) => data.draft !== true && data.rss === true
|
||||||
);
|
);
|
||||||
const filterPortfolio = portfolio.filter((project) => {
|
const filterPortfolio = portfolio.filter((project) => {
|
||||||
const [lang] = project.slug.split("/");
|
const [lang] = project.id.split('/');
|
||||||
|
|
||||||
return lang === "es" && project;
|
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);
|
||||||
const images = html.querySelectorAll("img");
|
const images = html.querySelectorAll('img');
|
||||||
|
|
||||||
for await (const img of images) {
|
for await (const img of images) {
|
||||||
const src = img.getAttribute("src")!;
|
const src = img.getAttribute('src')!;
|
||||||
|
|
||||||
if (src.startsWith("@/")) {
|
if (src.startsWith('@/')) {
|
||||||
const prefixRemoved = src.replace("@/", "");
|
const prefixRemoved = src.replace('@/', '');
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
|
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
|
||||||
(res: any) => res.default
|
(res: any) => res.default
|
||||||
@ -57,14 +57,14 @@ export async function GET(context: any) {
|
|||||||
if (imagePath) {
|
if (imagePath) {
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
img.setAttribute(
|
img.setAttribute(
|
||||||
"src",
|
'src',
|
||||||
context.site + optimizedImg.src.replace("/", "")
|
context.site + optimizedImg.src.replace('/', '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (src.startsWith("/images")) {
|
} else if (src.startsWith('/images')) {
|
||||||
img.setAttribute("src", context.site + src.replace("/", ""));
|
img.setAttribute('src', context.site + src.replace('/', ''));
|
||||||
} else {
|
} else {
|
||||||
throw Error("src unknown");
|
throw Error('src unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,23 +72,23 @@ export async function GET(context: any) {
|
|||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
pubDate: post.data.date,
|
pubDate: post.data.date,
|
||||||
description: post.data.description,
|
description: post.data.description,
|
||||||
link: `/blog/${post.slug}/`,
|
link: `/blog/${post.id.split('.')[0]}/`,
|
||||||
content: sanitizeHtml(html.toString(), {
|
content: sanitizeHtml(html.toString(), {
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const project of filterPortfolio) {
|
for await (const project of filterPortfolio) {
|
||||||
const body = markdownParser.render(project.body);
|
const body = markdownParser.render(project.body!);
|
||||||
const html = htmlParser.parse(body);
|
const html = htmlParser.parse(body);
|
||||||
const images = html.querySelectorAll("img");
|
const images = html.querySelectorAll('img');
|
||||||
|
|
||||||
for await (const img of images) {
|
for await (const img of images) {
|
||||||
const src = img.getAttribute("src")!;
|
const src = img.getAttribute('src')!;
|
||||||
|
|
||||||
if (src.startsWith("@/")) {
|
if (src.startsWith('@/')) {
|
||||||
const prefixRemoved = src.replace("@/", "");
|
const prefixRemoved = src.replace('@/', '');
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
||||||
(res: any) => res.default
|
(res: any) => res.default
|
||||||
@ -97,15 +97,15 @@ export async function GET(context: any) {
|
|||||||
if (imagePath) {
|
if (imagePath) {
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
img.setAttribute(
|
img.setAttribute(
|
||||||
"src",
|
'src',
|
||||||
context.site + optimizedImg.src.replace("/", "")
|
context.site + optimizedImg.src.replace('/', '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (src.startsWith("/images")) {
|
} else if (src.startsWith('/images')) {
|
||||||
// images starting with `/images/` is the public dir
|
// images starting with `/images/` is the public dir
|
||||||
img.setAttribute("src", context.site + src.replace("/", ""));
|
img.setAttribute('src', context.site + src.replace('/', ''));
|
||||||
} else {
|
} else {
|
||||||
throw Error("src unknown");
|
throw Error('src unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,27 +113,27 @@ export async function GET(context: any) {
|
|||||||
title: project.data.title,
|
title: project.data.title,
|
||||||
pubDate: project.data.date,
|
pubDate: project.data.date,
|
||||||
description: project.data.description,
|
description: project.data.description,
|
||||||
link: `/portfolio/${project.slug}/`,
|
link: `/portfolio/${project.id.split('.')[0]}/`,
|
||||||
content: sanitizeHtml(html.toString(), {
|
content: sanitizeHtml(html.toString(), {
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
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',
|
||||||
description: "Bienvenido a mi dominio, extraño.",
|
description: 'Bienvenido a mi dominio, extraño.',
|
||||||
site: `${context.site}es/`,
|
site: `${context.site}es/`,
|
||||||
customData: [
|
customData: [
|
||||||
"<language>es-mx</language>",
|
'<language>es-mx</language>',
|
||||||
`<image>
|
`<image>
|
||||||
<url>https://juancman.dev/logo.png</url>
|
<url>https://juancman.dev/logo.png</url>
|
||||||
<title>juancmandev</title>
|
<title>juancmandev</title>
|
||||||
<link>https://juancman.dev</link>
|
<link>https://juancman.dev</link>
|
||||||
</image>`,
|
</image>`,
|
||||||
`<atom:link href="${context.site}es/feed.xml" rel="self" type="application/rss+xml"/>`,
|
`<atom:link href="${context.site}es/feed.xml" rel="self" type="application/rss+xml"/>`,
|
||||||
].join(""),
|
].join(''),
|
||||||
items,
|
items,
|
||||||
trailingSlash: false,
|
trailingSlash: false,
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
---
|
---
|
||||||
import LinkButton from "@/components/link-button";
|
import LinkButton from '@/components/link-button';
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "juancmandev",
|
title: 'juancmandev',
|
||||||
description:
|
description:
|
||||||
"Bienvenido a mi dominio, extraño. Soy juancmandev; Desarrollador Web, entusiasta de Linux, y defensor de la privacidad.",
|
'Bienvenido a mi dominio, extraño. Soy juancmandev; Desarrollador Web, entusiasta de Linux, y defensor de la privacidad.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allPosts = await getCollection("blog", ({ data }) => data.draft !== true);
|
const allPosts = await getCollection('blog', ({ data }) => data.draft !== true);
|
||||||
const allEsPosts = allPosts.map((post) => {
|
const allEsPosts = allPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang !== 'en')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -27,16 +27,16 @@ sortContentByDate(allEsPosts);
|
|||||||
const last3Blogs = allEsPosts.slice(0, 3);
|
const last3Blogs = allEsPosts.slice(0, 3);
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const allEnProjects = allProjects.map((project) => {
|
const allEnProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang !== 'en')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -47,10 +47,10 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<div class="prose prose-invert">
|
<div class='prose prose-invert'>
|
||||||
<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
|
||||||
>Desarrollador Web</strong
|
>Desarrollador Web</strong
|
||||||
>, entusiasta de <strong>Linux</strong> y defensor de la <strong
|
>, entusiasta de <strong>Linux</strong> y defensor de la <strong
|
||||||
>privacidad.</strong
|
>privacidad.</strong
|
||||||
@ -63,52 +63,56 @@ 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">
|
<ul class='mt-0 p-0 list-none'>
|
||||||
{
|
{
|
||||||
last3Blogs.map(
|
last3Blogs.map(
|
||||||
(blogpost) =>
|
(blogpost) =>
|
||||||
blogpost && (
|
blogpost && (
|
||||||
<li class="p-0">
|
<li class='p-0'>
|
||||||
<PostItem
|
<PostItem
|
||||||
type="blog"
|
type='blog'
|
||||||
lang={lang}
|
lang={lang}
|
||||||
slug={blogpost?.slug}
|
id={blogpost.id}
|
||||||
date={blogpost.data.date}
|
date={blogpost.data.date}
|
||||||
title={blogpost.data.title}
|
title={blogpost.data.title}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<LinkButton variant="secondary" href="/es/blog" className="no-underline"
|
<LinkButton
|
||||||
|
variant='secondary'
|
||||||
|
href='/es/blog'
|
||||||
|
className='no-underline'
|
||||||
>Más posts</LinkButton
|
>Más posts</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Últimos proyectos</h2>
|
<h2>Últimos proyectos</h2>
|
||||||
<ul class="mt-0 p-0 list-none">
|
<ul class='mt-0 p-0 list-none'>
|
||||||
{
|
{
|
||||||
last3Projects.map(
|
last3Projects.map(
|
||||||
(project) =>
|
(project) =>
|
||||||
project && (
|
project && (
|
||||||
<li class="p-0">
|
<li class='p-0'>
|
||||||
<PostItem
|
<PostItem
|
||||||
lang={lang}
|
lang={lang}
|
||||||
type="portfolio"
|
type='portfolio'
|
||||||
slug={project.slug}
|
id={project.id}
|
||||||
date={project.data.date!}
|
date={project.data.date!}
|
||||||
title={project.data.title!}
|
title={project.data.title!}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="secondary"
|
variant='secondary'
|
||||||
href="/es/portfolio"
|
href='/es/portfolio'
|
||||||
className="no-underline">Más proyectos</LinkButton
|
className='no-underline'
|
||||||
|
>Más proyectos</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,51 +1,48 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import formatDate from '@/utils/format-date';
|
||||||
import formatDate from "@/utils/format-date";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
project: CollectionEntry<"portfolio">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const filterEnProjects = allProjects.map((project) => {
|
const filterEnProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang === 'es')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return filterEnProjects.map((project) => ({
|
return filterEnProjects.map((project) => ({
|
||||||
params: { slug: project?.slug },
|
params: { slug: project?.id },
|
||||||
props: { project },
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { project } = Astro.props;
|
|
||||||
const { Content } = await project.render();
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
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={project.data.title} description={project.data.description}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
<h1>{project.data.title}</h1>
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<strong>Publicado: </strong>
|
<strong>Publicado: </strong>
|
||||||
{project.data.date && formatDate(new Date(project.data.date), lang)}
|
{data.date && formatDate(new Date(data.date), lang)}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
---
|
---
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "Portfolio",
|
title: 'Portfolio',
|
||||||
description: "Revisa mis proyectos.",
|
description: 'Revisa mis proyectos.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const allEsProjects = allProjects.map((project) => {
|
const allEsProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang === "es")
|
if (lang === 'es')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -30,11 +30,11 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class='prose prose-invert'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<ul class="mt-4 flex flex-col gap-4">
|
<ul class='mt-4 flex flex-col gap-4'>
|
||||||
{
|
{
|
||||||
allEsProjects.map(
|
allEsProjects.map(
|
||||||
(project) =>
|
(project) =>
|
||||||
@ -42,13 +42,13 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
<li>
|
<li>
|
||||||
<PostItem
|
<PostItem
|
||||||
lang={lang}
|
lang={lang}
|
||||||
type="portfolio"
|
type='portfolio'
|
||||||
slug={project.slug}
|
id={project.id}
|
||||||
date={project.data.date!}
|
date={project.data.date!}
|
||||||
title={project.data.title!}
|
title={project.data.title!}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,41 +1,46 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import formatDate from '@/utils/format-date';
|
||||||
import formatDate from "@/utils/format-date";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
project: CollectionEntry<"videos">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allProjects = await getCollection(
|
const allVideos = await getCollection(
|
||||||
"videos",
|
'videos',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
|
const formatVideos = allVideos.map((video) => {
|
||||||
|
const [id] = video.id.split('/');
|
||||||
|
|
||||||
return allProjects.map((project) => ({
|
return {
|
||||||
params: { slug: project.slug },
|
...video,
|
||||||
props: { project },
|
id: id.split('.')[0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatVideos.map((video) => ({
|
||||||
|
params: { slug: video.id },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { project } = Astro.props;
|
|
||||||
const { Content } = await project.render();
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
const { slug } = Astro.params;
|
||||||
|
const video = await getEntry('videos', slug)!;
|
||||||
|
const { Content, remarkPluginFrontmatter: data } = await render(video);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={project.data.title} description={project.data.description}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
<h1>{project.data.title}</h1>
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<strong>Posted: </strong>
|
<strong>Posted: </strong>
|
||||||
{project.data.date && formatDate(new Date(project.data.date), lang)}
|
{data.date && formatDate(new Date(data.date), lang)}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
---
|
---
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "Videos",
|
title: 'Videos',
|
||||||
description: "Guiones de los videos de mi canal de YouTube.",
|
description: 'Guiones de los videos de mi canal de YouTube.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allVideos = await getCollection(
|
const allVideos = await getCollection(
|
||||||
"videos",
|
'videos',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
sortContentByDate(allVideos);
|
sortContentByDate(allVideos);
|
||||||
|
|
||||||
@ -20,18 +20,18 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class='prose prose-invert'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<ul class="mt-4 flex flex-col gap-4">
|
<ul class='mt-4 flex flex-col gap-4'>
|
||||||
{
|
{
|
||||||
allVideos.map((video: any) => (
|
allVideos.map((video: any) => (
|
||||||
<li>
|
<li>
|
||||||
<PostItem
|
<PostItem
|
||||||
lang={lang}
|
lang={lang}
|
||||||
type="videos"
|
type='videos'
|
||||||
slug={video.slug}
|
id={video.id.split('.')[0]}
|
||||||
date={video.data.date!}
|
date={video.data.date!}
|
||||||
title={video.data.title!}
|
title={video.data.title!}
|
||||||
/>
|
/>
|
||||||
|
@ -1,54 +1,54 @@
|
|||||||
import rss from "@astrojs/rss";
|
import rss from '@astrojs/rss';
|
||||||
import type { RSSFeedItem } from "@astrojs/rss";
|
import type { RSSFeedItem } from '@astrojs/rss';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from 'markdown-it';
|
||||||
import { parse as htmlParser } from "node-html-parser";
|
import { parse as htmlParser } from 'node-html-parser';
|
||||||
import { getImage } from "astro:assets";
|
import { getImage } from 'astro:assets';
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from 'astro';
|
||||||
|
|
||||||
const markdownParser = new MarkdownIt();
|
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 }>(
|
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}"
|
'/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[] = [];
|
||||||
|
|
||||||
const blog = await getCollection(
|
const blog = await getCollection(
|
||||||
"blog",
|
'blog',
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
({ data }) => data.draft !== true && data.rss === true
|
||||||
);
|
);
|
||||||
const filterBlog = blog.filter((post) => {
|
const filterBlog = blog.filter((post) => {
|
||||||
const [lang] = post.slug.split("/");
|
const [lang] = post.id.split('/');
|
||||||
|
|
||||||
return lang !== "es" && post;
|
return lang !== 'es' && post;
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolio = await getCollection(
|
const portfolio = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true && data.rss === true
|
({ data }) => data.draft !== true && data.rss === true
|
||||||
);
|
);
|
||||||
const filterPortfolio = portfolio.filter((project) => {
|
const filterPortfolio = portfolio.filter((project) => {
|
||||||
const [lang] = project.slug.split("/");
|
const [lang] = project.id.split('/');
|
||||||
|
|
||||||
return lang !== "es" && project;
|
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);
|
||||||
const images = html.querySelectorAll("img");
|
const images = html.querySelectorAll('img');
|
||||||
|
|
||||||
for await (const img of images) {
|
for await (const img of images) {
|
||||||
const src = img.getAttribute("src")!;
|
const src = img.getAttribute('src')!;
|
||||||
|
|
||||||
if (src.startsWith("@/")) {
|
if (src.startsWith('@/')) {
|
||||||
const prefixRemoved = src.replace("@/", "");
|
const prefixRemoved = src.replace('@/', '');
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
|
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
|
||||||
(res: any) => res.default
|
(res: any) => res.default
|
||||||
@ -57,14 +57,14 @@ export async function GET(context: any) {
|
|||||||
if (imagePath) {
|
if (imagePath) {
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
img.setAttribute(
|
img.setAttribute(
|
||||||
"src",
|
'src',
|
||||||
context.site + optimizedImg.src.replace("/", "")
|
context.site + optimizedImg.src.replace('/', '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (src.startsWith("/images")) {
|
} else if (src.startsWith('/images')) {
|
||||||
img.setAttribute("src", context.site + src.replace("/", ""));
|
img.setAttribute('src', context.site + src.replace('/', ''));
|
||||||
} else {
|
} else {
|
||||||
throw Error("src unknown");
|
throw Error('src unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,23 +72,23 @@ export async function GET(context: any) {
|
|||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
pubDate: post.data.date,
|
pubDate: post.data.date,
|
||||||
description: post.data.description,
|
description: post.data.description,
|
||||||
link: `/blog/${post.slug}/`,
|
link: `/blog/${post.id.split('.')[0]}/`,
|
||||||
content: sanitizeHtml(html.toString(), {
|
content: sanitizeHtml(html.toString(), {
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const project of filterPortfolio) {
|
for await (const project of filterPortfolio) {
|
||||||
const body = markdownParser.render(project.body);
|
const body = markdownParser.render(project.body!);
|
||||||
const html = htmlParser.parse(body);
|
const html = htmlParser.parse(body);
|
||||||
const images = html.querySelectorAll("img");
|
const images = html.querySelectorAll('img');
|
||||||
|
|
||||||
for await (const img of images) {
|
for await (const img of images) {
|
||||||
const src = img.getAttribute("src")!;
|
const src = img.getAttribute('src')!;
|
||||||
|
|
||||||
if (src.startsWith("@/")) {
|
if (src.startsWith('@/')) {
|
||||||
const prefixRemoved = src.replace("@/", "");
|
const prefixRemoved = src.replace('@/', '');
|
||||||
const imagePathPrefix = `/src/${prefixRemoved}`;
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
||||||
(res: any) => res.default
|
(res: any) => res.default
|
||||||
@ -97,15 +97,15 @@ export async function GET(context: any) {
|
|||||||
if (imagePath) {
|
if (imagePath) {
|
||||||
const optimizedImg = await getImage({ src: imagePath });
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
img.setAttribute(
|
img.setAttribute(
|
||||||
"src",
|
'src',
|
||||||
context.site + optimizedImg.src.replace("/", "")
|
context.site + optimizedImg.src.replace('/', '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (src.startsWith("/images")) {
|
} else if (src.startsWith('/images')) {
|
||||||
// images starting with `/images/` is the public dir
|
// images starting with `/images/` is the public dir
|
||||||
img.setAttribute("src", context.site + src.replace("/", ""));
|
img.setAttribute('src', context.site + src.replace('/', ''));
|
||||||
} else {
|
} else {
|
||||||
throw Error("src unknown");
|
throw Error('src unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,27 +113,27 @@ export async function GET(context: any) {
|
|||||||
title: project.data.title,
|
title: project.data.title,
|
||||||
pubDate: project.data.date,
|
pubDate: project.data.date,
|
||||||
description: project.data.description,
|
description: project.data.description,
|
||||||
link: `/portfolio/${project.slug}/`,
|
link: `/portfolio/${project.id.split('.')[0]}/`,
|
||||||
content: sanitizeHtml(html.toString(), {
|
content: sanitizeHtml(html.toString(), {
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
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',
|
||||||
description: "Welcome to my domain, stranger.",
|
description: 'Welcome to my domain, stranger.',
|
||||||
site: context.site,
|
site: context.site,
|
||||||
customData: [
|
customData: [
|
||||||
"<language>en-us</language>",
|
'<language>en-us</language>',
|
||||||
`<image>
|
`<image>
|
||||||
<url>https://juancman.dev/logo.png</url>
|
<url>https://juancman.dev/logo.png</url>
|
||||||
<title>juancmandev</title>
|
<title>juancmandev</title>
|
||||||
<link>https://juancman.dev</link>
|
<link>https://juancman.dev</link>
|
||||||
</image>`,
|
</image>`,
|
||||||
`<atom:link href="${context.site}feed.xml" rel="self" type="application/rss+xml"/>`,
|
`<atom:link href="${context.site}feed.xml" rel="self" type="application/rss+xml"/>`,
|
||||||
].join(""),
|
].join(''),
|
||||||
items,
|
items,
|
||||||
trailingSlash: false,
|
trailingSlash: false,
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
---
|
---
|
||||||
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 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';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "juancmandev",
|
title: 'juancmandev',
|
||||||
description:
|
description:
|
||||||
"Welcome to my domain, stranger. I am juancmandev; Web Developer, Linux enthusiast, and privacy defender.",
|
'Welcome to my domain, stranger. I am juancmandev; Web Developer, Linux enthusiast, and privacy defender.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allPosts = await getCollection("blog", ({ data }) => data.draft !== true);
|
const allPosts = await getCollection('blog', ({ data }) => data.draft !== true);
|
||||||
const allEnPosts = allPosts.map((post) => {
|
const allEnPosts = allPosts.map((post) => {
|
||||||
const [lang, ...slug] = post.slug.split("/");
|
const [lang, id] = post.id.split('/');
|
||||||
|
|
||||||
if (lang !== "es")
|
if (lang !== 'es')
|
||||||
return {
|
return {
|
||||||
...post,
|
...post,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -27,16 +27,16 @@ sortContentByDate(allEnPosts);
|
|||||||
const last3Blogs = allEnPosts.slice(0, 3);
|
const last3Blogs = allEnPosts.slice(0, 3);
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const allEnProjects = allProjects.map((project) => {
|
const allEnProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang !== "es")
|
if (lang !== 'es')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -47,10 +47,10 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<div class="prose prose-invert">
|
<div class='prose prose-invert'>
|
||||||
<h1 class="text-primary">Welcome to my domain, stranger.</h1>
|
<h1 class='text-primary'>Welcome to my domain, stranger.</h1>
|
||||||
<p>
|
<p>
|
||||||
I am <strong class="text-primary">juancmandev</strong>; <strong
|
I am <strong class='text-primary'>juancmandev</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,14 +61,14 @@ 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">
|
<ul class='mt-0 p-0 list-none'>
|
||||||
{
|
{
|
||||||
last3Blogs.map((blogpost: any) => (
|
last3Blogs.map((blogpost: any) => (
|
||||||
<li class="p-0">
|
<li class='p-0'>
|
||||||
<PostItem
|
<PostItem
|
||||||
type="blog"
|
type='blog'
|
||||||
lang={lang}
|
lang={lang}
|
||||||
slug={blogpost.slug}
|
id={blogpost.id}
|
||||||
date={blogpost.data.date!}
|
date={blogpost.data.date!}
|
||||||
title={blogpost.data.title!}
|
title={blogpost.data.title!}
|
||||||
/>
|
/>
|
||||||
@ -76,31 +76,37 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<LinkButton variant="secondary" href="/blog" className="no-underline"
|
<LinkButton
|
||||||
|
variant='secondary'
|
||||||
|
href='/blog'
|
||||||
|
className='no-underline'
|
||||||
>More posts</LinkButton
|
>More posts</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Latest projects</h2>
|
<h2>Latest projects</h2>
|
||||||
<ul class="mt-0 p-0 list-none">
|
<ul class='mt-0 p-0 list-none'>
|
||||||
{
|
{
|
||||||
last3Projects.map(
|
last3Projects.map(
|
||||||
(project) =>
|
(project) =>
|
||||||
project && (
|
project && (
|
||||||
<li class="p-0">
|
<li class='p-0'>
|
||||||
<PostItem
|
<PostItem
|
||||||
lang={lang}
|
lang={lang}
|
||||||
type="portfolio"
|
type='portfolio'
|
||||||
slug={project.slug}
|
id={project.id}
|
||||||
date={project.data.date!}
|
date={project.data.date!}
|
||||||
title={project.data.title!}
|
title={project.data.title!}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<LinkButton variant="secondary" href="/portfolio" className="no-underline"
|
<LinkButton
|
||||||
|
variant='secondary'
|
||||||
|
href='/portfolio'
|
||||||
|
className='no-underline'
|
||||||
>More projects</LinkButton
|
>More projects</LinkButton
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
---
|
---
|
||||||
import MicroblogItem from "@/components/microblog-item.astro";
|
import MicroblogItem from '@/components/microblog-item.astro';
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { createServerClient } from "@/utils/pocketbase";
|
import { createServerClient } from '@/utils/pocketbase';
|
||||||
|
|
||||||
const pb = createServerClient(import.meta.env.SECRET_POCKETBASE_API_URL);
|
const pb = createServerClient(import.meta.env.SECRET_POCKETBASE_API_URL);
|
||||||
const data = await pb.collection("microblogs").getFullList({
|
const data = await pb.collection('microblogs').getFullList({
|
||||||
expand: "tags",
|
expand: 'tags',
|
||||||
sort: "-published",
|
sort: '-published',
|
||||||
});
|
});
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
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'>
|
||||||
<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>
|
||||||
<ul class="mx-auto p-0 mt-10 flex flex-col gap-10 list-none">
|
<ul class='mx-auto p-0 mt-10 flex flex-col gap-10 list-none'>
|
||||||
{
|
{
|
||||||
data.map((item: any) => (
|
data.map((item: any) => (
|
||||||
<li>
|
<li>
|
||||||
|
@ -1,51 +1,48 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from '@/layouts/Layout.astro';
|
||||||
import { getCollection } from "astro:content";
|
import components from '@/components/mdx/wrapper';
|
||||||
import components from "@/components/mdx/wrapper";
|
import formatDate from '@/utils/format-date';
|
||||||
import formatDate from "@/utils/format-date";
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
import type { CollectionEntry } from "astro:content";
|
import { getCollection, getEntry, render } from 'astro:content';
|
||||||
import { getLangFromUrl } from "@/i18n/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
project: CollectionEntry<"portfolio">;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const filterEnProjects = allProjects.map((project) => {
|
const filterEnProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang === "en")
|
if (lang === 'en')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return filterEnProjects.map((project) => ({
|
return filterEnProjects.map((project) => ({
|
||||||
params: { slug: project?.slug },
|
params: { slug: project?.id },
|
||||||
props: { project },
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { project } = Astro.props;
|
|
||||||
const { Content } = await project.render();
|
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
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={project.data.title} description={project.data.description}>
|
<Layout
|
||||||
<article class="prose prose-invert">
|
title={data.title}
|
||||||
<h1>{project.data.title}</h1>
|
description={data.description}
|
||||||
|
>
|
||||||
|
<article class='prose prose-invert'>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<strong>Posted: </strong>
|
<strong>Posted: </strong>
|
||||||
{project.data.date && formatDate(new Date(project.data.date), lang)}
|
{data.date && formatDate(new Date(data.date), lang)}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
---
|
---
|
||||||
import PostItem from "@/components/post-item";
|
import PostItem from '@/components/post-item';
|
||||||
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';
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
title: "Portfolio",
|
title: 'Portfolio',
|
||||||
description: "Check my projects.",
|
description: 'Check my projects.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const allProjects = await getCollection(
|
const allProjects = await getCollection(
|
||||||
"portfolio",
|
'portfolio',
|
||||||
({ data }) => data.draft !== true,
|
({ data }) => data.draft !== true
|
||||||
);
|
);
|
||||||
const allEnProjects = allProjects.map((project) => {
|
const allEnProjects = allProjects.map((project) => {
|
||||||
const [lang, ...slug] = project.slug.split("/");
|
const [lang, id] = project.id.split('/');
|
||||||
|
|
||||||
if (lang === "en")
|
if (lang === 'en')
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
slug: slug.toString(),
|
id: id.split('.')[0],
|
||||||
};
|
};
|
||||||
else null;
|
else null;
|
||||||
});
|
});
|
||||||
@ -30,11 +30,11 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData}>
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class='prose prose-invert'>
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<ul class="mt-4 flex flex-col gap-4">
|
<ul class='mt-4 flex flex-col gap-4'>
|
||||||
{
|
{
|
||||||
allEnProjects.map(
|
allEnProjects.map(
|
||||||
(project) =>
|
(project) =>
|
||||||
@ -42,13 +42,13 @@ const lang = getLangFromUrl(Astro.url);
|
|||||||
<li>
|
<li>
|
||||||
<PostItem
|
<PostItem
|
||||||
lang={lang}
|
lang={lang}
|
||||||
type="portfolio"
|
type='portfolio'
|
||||||
slug={project.slug}
|
id={project.id}
|
||||||
date={project.data.date!}
|
date={project.data.date!}
|
||||||
title={project.data.title!}
|
title={project.data.title!}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
const robotsTxt = `
|
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', import.meta.env.SITE).href}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
export const GET: APIRoute = () => {
|
export const GET: APIRoute = () => {
|
||||||
return new Response(robotsTxt, {
|
return new Response(robotsTxt, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/plain; charset=utf-8",
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
const months = [
|
const months = [
|
||||||
"January",
|
'January',
|
||||||
"February",
|
'February',
|
||||||
"March",
|
'March',
|
||||||
"April",
|
'April',
|
||||||
"May",
|
'May',
|
||||||
"June",
|
'June',
|
||||||
"July",
|
'July',
|
||||||
"August",
|
'August',
|
||||||
"September",
|
'September',
|
||||||
"October",
|
'October',
|
||||||
"November",
|
'November',
|
||||||
"December",
|
'December',
|
||||||
];
|
];
|
||||||
const meses = [
|
const meses = [
|
||||||
"Enero",
|
'Enero',
|
||||||
"Febrero",
|
'Febrero',
|
||||||
"Marzo",
|
'Marzo',
|
||||||
"Abril",
|
'Abril',
|
||||||
"Mayo",
|
'Mayo',
|
||||||
"Junio",
|
'Junio',
|
||||||
"Julio",
|
'Julio',
|
||||||
"Agosto",
|
'Agosto',
|
||||||
"Septiembre",
|
'Septiembre',
|
||||||
"Octubre",
|
'Octubre',
|
||||||
"Noviembre",
|
'Noviembre',
|
||||||
"Diciembre",
|
'Diciembre',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function formatDate(date: Date | string, lang: string) {
|
export default function formatDate(date: Date | string, lang: string) {
|
||||||
@ -34,7 +34,7 @@ export default function formatDate(date: Date | string, lang: string) {
|
|||||||
const day = newDate.getDate();
|
const day = newDate.getDate();
|
||||||
const year = newDate.getFullYear();
|
const year = newDate.getFullYear();
|
||||||
|
|
||||||
return lang !== "es"
|
return lang !== 'es'
|
||||||
? `${month} ${day}, ${year}`
|
? `${month} ${day}, ${year}`
|
||||||
: `${day} de ${mes} del ${year}`;
|
: `${day} de ${mes} del ${year}`;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import PocketBase from "pocketbase";
|
import PocketBase from 'pocketbase';
|
||||||
import type { RecordService } from "pocketbase";
|
import type { RecordService } from 'pocketbase';
|
||||||
|
|
||||||
export enum Collections {
|
export enum Collections {
|
||||||
Microblogs = "microblogs",
|
Microblogs = 'microblogs',
|
||||||
Tags = "tags",
|
Tags = 'tags',
|
||||||
Users = "users",
|
Users = 'users',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IsoDateString = string;
|
export type IsoDateString = string;
|
||||||
@ -62,19 +62,19 @@ export type CollectionResponses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TypedPocketBase = PocketBase & {
|
export type TypedPocketBase = PocketBase & {
|
||||||
collection(idOrName: "microblogs"): RecordService<MicroblogsResponse>;
|
collection(idOrName: 'microblogs'): RecordService<MicroblogsResponse>;
|
||||||
collection(idOrName: "tags"): RecordService<TagsResponse>;
|
collection(idOrName: 'tags'): RecordService<TagsResponse>;
|
||||||
collection(idOrName: "users"): RecordService<UsersResponse>;
|
collection(idOrName: 'users'): RecordService<UsersResponse>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createServerClient(url: string) {
|
export function createServerClient(url: string) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error("Pocketbase API url not defined !");
|
throw new Error('Pocketbase API url not defined !');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"This method is only supposed to call from the Server environment",
|
'This method is only supposed to call from the Server environment'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export function sortContentByDate(array: any[]) {
|
export function sortContentByDate(array: any[]) {
|
||||||
array.sort(
|
array.sort(
|
||||||
(a: any, b: any) =>
|
(a: any, b: any) =>
|
||||||
Date.parse(b.data.date.toString()) - Date.parse(a.data.date.toString()),
|
Date.parse(b.data.date.toString()) - Date.parse(a.data.date.toString())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,61 +1,61 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ["class"],
|
darkMode: ['class'],
|
||||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||||
prefix: "",
|
prefix: '',
|
||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["Helvetica", "Arial", "sans-serif"],
|
sans: ['Helvetica', 'Arial', 'sans-serif'],
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "2rem",
|
padding: '2rem',
|
||||||
screens: {
|
screens: {
|
||||||
"2xl": "1400px",
|
'2xl': '1400px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "#eee",
|
border: '#eee',
|
||||||
input: "#00adb5",
|
input: '#00adb5',
|
||||||
ring: "#00adb5",
|
ring: '#00adb5',
|
||||||
background: "#222831",
|
background: '#222831',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "#00adb5",
|
DEFAULT: '#00adb5',
|
||||||
foreground: "#000",
|
foreground: '#000',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "#393e46",
|
DEFAULT: '#393e46',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "#ff2e63",
|
DEFAULT: '#ff2e63',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "#393e46",
|
DEFAULT: '#393e46',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "#00adb5",
|
DEFAULT: '#00adb5',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "#393e46",
|
DEFAULT: '#393e46',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "#393e46",
|
DEFAULT: '#393e46',
|
||||||
foreground: "#eee",
|
foreground: '#eee',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "8px",
|
lg: '8px',
|
||||||
md: "4px",
|
md: '4px',
|
||||||
sm: "2px",
|
sm: '2px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user