feat: new article body stuffz

Signed-off-by: Filipe Medeiros <hello@filipesm.eu>
This commit is contained in:
Filipe Medeiros 2023-04-11 15:50:44 +01:00
parent d42271eb15
commit 09852de001
Signed by: filipe
GPG key ID: 9533BD5467CC1E78
23 changed files with 264 additions and 162 deletions

View file

@ -1,8 +1,7 @@
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
integrations: [react(), tailwind()],
integrations: [tailwind()],
});

View file

@ -6,7 +6,14 @@ export const forBlogPostPage = groq`
{
"slug": slug.current,
title,
content,
content[]{
_type == 'image' => {
...,
"lqip": @.asset->metadata.lqip,
"alt": @.asset->altText
},
_type != 'image' => @
},
headerImage,
publishDate
}`;

View file

@ -0,0 +1,10 @@
import urlBuilder from '@sanity/image-url';
import type { SanityImageSource } from '@sanity/image-url/lib/types/types';
import client from './client';
const builder = urlBuilder(client);
export default function urlFor(source: SanityImageSource) {
return builder.image(source);
}

View file

@ -11,19 +11,16 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^2.1.1",
"@astrojs/tailwind": "^3.1.1",
"@portabletext/react": "^2.0.2",
"@sanity/client": "^5.4.2",
"@sanity/image-url": "^1.0.2",
"astro": "^2.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"astro-portabletext": "^0.8.0"
},
"devDependencies": {
"@portabletext/types": "^2.0.2",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"autoprefixer": "^10.4.14",
"groq": "^3.8.3",
"postcss": "^8.4.21",

View file

@ -1,9 +1,6 @@
lockfileVersion: '6.0'
dependencies:
'@astrojs/react':
specifier: ^2.1.1
version: 2.1.1(@types/react-dom@18.0.11)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0)
'@astrojs/tailwind':
specifier: ^3.1.1
version: 3.1.1(astro@2.2.1)(tailwindcss@3.3.1)
@ -13,18 +10,15 @@ dependencies:
'@sanity/client':
specifier: ^5.4.2
version: 5.4.2
'@sanity/image-url':
specifier: ^1.0.2
version: 1.0.2
astro:
specifier: ^2.2.1
version: 2.2.1
groq:
specifier: ^3.8.3
version: 3.8.3
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
astro-portabletext:
specifier: ^0.8.0
version: 0.8.0
devDependencies:
'@portabletext/types':
@ -33,15 +27,12 @@ devDependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: ^4.1.1
version: 4.1.1(prettier@2.8.7)
'@types/react':
specifier: ^18.0.21
version: 18.0.33
'@types/react-dom':
specifier: ^18.0.6
version: 18.0.11
autoprefixer:
specifier: ^10.4.14
version: 10.4.14(postcss@8.4.21)
groq:
specifier: ^3.8.3
version: 3.8.3
postcss:
specifier: ^8.4.21
version: 8.4.21
@ -118,25 +109,6 @@ packages:
prismjs: 1.29.0
dev: false
/@astrojs/react@2.1.1(@types/react-dom@18.0.11)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-nIcDFnn5H4FKGoSBYXZr95RIQvpcTNRcVV1hvUQifO0F5hQsgb0PVyk6TG4JWxiPGY4Jt4MVQb5JaaDQHlHu4w==}
engines: {node: '>=16.12.0'}
peerDependencies:
'@types/react': ^17.0.50 || ^18.0.21
'@types/react-dom': ^17.0.17 || ^18.0.6
react: ^17.0.2 || ^18.0.0
react-dom: ^17.0.2 || ^18.0.0
dependencies:
'@babel/core': 7.21.4
'@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.4)
'@types/react': 18.0.33
'@types/react-dom': 18.0.11
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- supports-color
dev: false
/@astrojs/tailwind@3.1.1(astro@2.2.1)(tailwindcss@3.3.1):
resolution: {integrity: sha512-Wx/ZtVnmtfqHWGVzvUEYZm8rufVKVgDIef0q6fzwUxoT1EpTTwBkTbpnzooogewMLOh2eTscasGe3Ih2HC1wVA==}
peerDependencies:
@ -754,6 +726,11 @@ packages:
eventsource: 2.0.2
dev: false
/@sanity/image-url@1.0.2:
resolution: {integrity: sha512-C4+jb2ny3ZbMgEkLd7Z3C75DsxcTEoE+axXQJsQ75ou0AKWGdVsP351hqK6mJUUxn5HCSlu3vznoh7Yljye4cQ==}
engines: {node: '>=10.0.0'}
dev: false
/@trivago/prettier-plugin-sort-imports@4.1.1(prettier@2.8.7):
resolution: {integrity: sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw==}
peerDependencies:
@ -851,28 +828,10 @@ packages:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: false
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
/@types/react-dom@18.0.11:
resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==}
dependencies:
'@types/react': 18.0.33
/@types/react@18.0.33:
resolution: {integrity: sha512-sHxzVxeanvQyQ1lr8NSHaj0kDzcNiGpILEVt69g9S31/7PfMvNCKLKcsHw4lYKjs3cGNJjXSP4mYzX43QlnjNA==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
csstype: 3.1.2
/@types/resolve@1.20.2:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: false
/@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
/@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
@ -958,6 +917,13 @@ packages:
resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
dev: false
/astro-portabletext@0.8.0:
resolution: {integrity: sha512-2zA5TJ70Uhd4XfLlcTgxFMe202mEyB4V5R81KsjrP0nqBBHZLk1MaWxHPNq8Bz9KrF/u0mNYp+HFMQ/L/TDW7A==}
dependencies:
'@portabletext/toolkit': 2.0.1
'@portabletext/types': 2.0.2
dev: false
/astro@2.2.1:
resolution: {integrity: sha512-yYPRzh3su38bi3VBCKmYAUBkQSaFQMKFsu8JAVDzFRoGLbskJ/6JkDX2abSB9/iRug8GAKaH/FWxXOTzIsSQ7Q==}
engines: {node: '>=16.12.0', npm: '>=6.14.0'}
@ -1269,9 +1235,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
/csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@ -1629,7 +1592,7 @@ packages:
/groq@3.8.3:
resolution: {integrity: sha512-2zB3MseKzoZ+zySVJBB3u/tNYn4nEepF8O3fNa71ojD5UwG0Ra2kzNR9sbojF+eToazCHKcvPW73OfB9yYrYsQ==}
engines: {node: '>=14'}
dev: false
dev: true
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
@ -2817,16 +2780,6 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
react: ^18.2.0
dependencies:
loose-envify: 1.4.0
react: 18.2.0
scheduler: 0.23.0
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
@ -3038,12 +2991,6 @@ packages:
suf-log: 2.5.3
dev: false
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
loose-envify: 1.4.0
dev: false
/section-matter@1.0.0:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}

View file

@ -1,78 +0,0 @@
import { PortableText, PortableTextReactComponents } from '@portabletext/react';
import type { PortableTextBlock } from '@portabletext/types';
export interface Props {
content: PortableTextBlock[];
}
const components: Partial<PortableTextReactComponents> = {
block: {
h1: ({ children }) => (
<h2 className="mb-2 mt-4 text-3xl font-medium">{children}</h2>
),
h2: ({ children }) => (
<h3 className="mb-2 mt-3 text-2xl font-medium">{children}</h3>
),
h3: ({ children }) => (
<h4 className="mb-2 text-xl font-medium">{children}</h4>
),
h4: ({ children }) => (
<h5 className="mb-2 text-lg font-medium">{children}</h5>
),
h5: ({ children }) => (
<h6 className="mb-2 text-lg underline">{children}</h6>
),
h6: ({ children }) => <h6 className="mb-2 text-lg">{children}</h6>,
normal: ({ children }) => <p className="mb-2">{children}</p>,
},
marks: {
code: ({ children }) => (
<code className="bg-stone-800 p-0.5 px-1 text-white">{children}</code>
),
link: ({ children, value }) => {
return (
<a
className="text-primary-800 underline hover:text-primary-700"
href={value.href}
>
{children}
</a>
);
},
},
list: {
bullet: ({ children }) => (
<ul className="list-disc [counter-reset:list-counter]">{children}</ul>
),
number: ({ children }) => (
<ol className="[counter-reset:list-counter]">{children}</ol>
),
},
listItem: {
bullet: ({ children }) => <li className="mb-1 ml-5 pl-2">{children}</li>,
number: ({ children, value }) => (
<li
className={`mb-1 pl-2 [counter-increment:list-counter] before:pr-2 before:content-[counter(list-counter)'.'] data-[list-type=lower-latin]:before:content-[counter(list-counter,lower-latin)')'] data-[list-type=lower-roman]:before:content-[counter(list-counter,lower-roman)'.'] ${
(value.level ?? 1) !== 1 ? 'ml-5' : ''
}`}
data-list-type={
(value.level ?? 1) % 3 === 1
? 'decimal'
: (value.level ?? 1) % 3 === 2
? 'lower-latin'
: 'lower-roman'
}
>
{children}
</li>
),
},
};
export default function BlogPostContent({ content }: Props) {
return (
<div className="text-justify">
<PortableText components={components} value={content} />
</div>
);
}

View file

@ -0,0 +1,57 @@
---
import { PortableText } from 'astro-portabletext';
import type { PortableTextBlock } from '@portabletext/types';
import ShowableFigure from './components/ShowableFigure.astro';
import NumberListItem from './components/NumberListItem.astro';
import InlineLink from './components/InlineLink.astro';
import InlineCode from './components/InlineCode.astro';
import BulletList from './components/BulletList.astro';
import NumberList from './components/NumberList.astro';
import BulletListItem from './components/BulletListItem.astro';
import Heading1 from './components/Heading1.astro';
import Heading2 from './components/Heading2.astro';
import Heading3 from './components/Heading3.astro';
import Heading4 from './components/Heading4.astro';
import Heading5 from './components/Heading5.astro';
import Heading6 from './components/Heading6.astro';
import Paragraph from './components/Paragraph.astro';
export interface Props {
content: PortableTextBlock[];
}
const components = {
type: {
image: ShowableFigure,
},
block: {
h1: Heading1,
h2: Heading2,
h3: Heading3,
h4: Heading4,
h5: Heading5,
h6: Heading6,
normal: Paragraph,
},
marks: {
code: InlineCode,
link: InlineLink,
},
list: {
bullet: BulletList,
number: NumberList,
},
listItem: {
bullet: BulletListItem,
number: NumberListItem,
},
};
const { content } = Astro.props;
---
<div class="text-justify">
{/* @ts-expect-error */}
<PortableText components={components} value={content} />
</div>

View file

@ -0,0 +1,3 @@
<ul class="list-disc ps-2 list-inside [counter-reset:list-counter]">
<slot />
</ul>

View file

@ -0,0 +1,3 @@
<li>
<slot />
</li>

View file

@ -0,0 +1,3 @@
<h2 class="mb-2 mt-4 text-3xl font-medium">
<slot />
</h2>

View file

@ -0,0 +1,3 @@
<h3 class="mb-2 mt-3 text-2xl font-medium">
<slot />
</h3>

View file

@ -0,0 +1,3 @@
<h4 class="mb-2 text-xl font-medium">
<slot />
</h4>

View file

@ -0,0 +1,3 @@
<h5 class="mb-2 text-lg font-medium">
<slot />
</h5>

View file

@ -0,0 +1,3 @@
<h6 class="mb-2 text-lg underline">
<slot />
</h6>

View file

@ -0,0 +1,3 @@
<h6 class="mb-2 text-lg">
<slot />
</h6>

View file

@ -0,0 +1,3 @@
<code class="bg-stone-800 p-0.5 px-1 text-white">
<slot />
</code>

View file

@ -0,0 +1,13 @@
---
import type { PortableTextLink } from '@portabletext/types';
export interface Props {
value?: PortableTextLink;
}
const { value } = Astro.props;
---
<a class="text-primary-800 underline hover:text-primary-700" href={value?.href}>
<slot />
</a>

View file

@ -0,0 +1,3 @@
<ol class="[counter-reset:list-counter] ps-2 list-inside">
<slot />
</ol>

View file

@ -0,0 +1,22 @@
---
import type { PortableTextListItemBlock } from '@portabletext/types';
export interface Props {
node: PortableTextListItemBlock;
}
const { node: value } = Astro.props;
---
<li
class:list={[
"[counter-increment:list-counter] before:content-[counter(list-counter)'.'] data-[list-type=lower-latin]:before:content-[counter(list-counter,lower-latin)')'] data-[list-type=lower-roman]:before:content-[counter(list-counter,lower-roman)'.']",
]}
data-list-type={(value.level ?? 1) % 3 === 1
? 'decimal'
: (value.level ?? 1) % 3 === 2
? 'lower-latin'
: 'lower-roman'}
>
<slot />
</li>

View file

@ -0,0 +1,3 @@
<p class="mb-2">
<slot />
</p>

View file

@ -0,0 +1,31 @@
---
import { PortableText } from 'astro-portabletext';
import ShowableImage from './ShowableImage.astro';
import type { Props as ShowableImageProps } from './ShowableImage.astro';
import InlineLink from './InlineLink.astro';
export interface Props extends ShowableImageProps {}
const { node: value } = Astro.props;
---
{
!value.caption ? (
<ShowableImage {...Astro.props} />
) : (
<figure class="flex flex-col gap-1 mb-4 mt-4">
<ShowableImage {...Astro.props} />
<figcaption class="text-sm text-start">
<PortableText
components={{
mark: {
// @ts-expect-error
link: InlineLink,
},
}}
value={value.caption}
/>
</figcaption>
</figure>
)
}

View file

@ -0,0 +1,64 @@
---
import type { PortableTextBlock } from '@portabletext/types';
import urlFor from '../../../../lib/cms/urlFor';
export interface Props {
node: BlogPostContentImage;
}
export interface BlogPostContentImage {
lqip: string;
_type: 'image';
asset: {
_type: 'reference';
ref: string;
};
caption: PortableTextBlock[];
alt: string;
}
const { node: value } = Astro.props;
---
<showable-image>
<div class="relative" role="button" class="w-full" data-wrapper>
<img
src={value.lqip}
alt={`${value.alt} Quando esta página carrega, esta imagem está desfocada para poupar dados e energia. Podes carregar na imagem para mostrar a imagem completa.`}
class="w-full border-2 border-primary-800"
data-url={urlFor(value).url()}
/>
<div
data-tooltip
class="text-xs w-10/12 sm:w-1/2 left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 text-center absolute leading-tight bg-primary-900/80 text-white p-3"
>
<p class="mb-1">
Para poupar os teus dados e um bocadinho de energia, tens esta
pre-visualização da imagem.
</p>
<p class="text-sm">
Clica na pre-visualização para veres a versão completa.
</p>
</div>
</div>
</showable-image>
<script>
class ShowableImage extends HTMLElement {
constructor() {
super();
const image = this.querySelector('img')!;
const wrapper = this.querySelector('[data-wrapper]')!;
const tooltip = this.querySelector('[data-tooltip]')!;
wrapper.addEventListener('click', () => {
image.src = image.dataset.url!;
wrapper.role = 'none';
tooltip.classList.add('hidden');
});
}
}
customElements.define('showable-image', ShowableImage);
</script>

View file

@ -2,7 +2,7 @@
import type { PortableTextBlock } from '@portabletext/types';
import client from '../../../../lib/cms/client';
import { forBlogPostPage } from '../../../../lib/cms/queries/blogPosts';
import BlogPostContent from '../../../components/BlogPostContent';
import BlogPostContent from '../../../components/portableText/BlogPostContent.astro';
import ButtonLink from '../../../components/ButtonLink.astro';
import PageTitle from '../../../components/PageTitle.astro';
import Layout from '../../../layouts/Layout.astro';