feat: tags

This commit is contained in:
Ivan R. 2024-03-03 14:05:33 +05:00
parent 487925f613
commit 5a7e8cd230
No known key found for this signature in database
GPG key ID: 56C7BAAE859B302C
11 changed files with 159 additions and 23 deletions

1
public/icons/tag.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tag"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>

After

Width:  |  Height:  |  Size: 349 B

38
src/components/Note.astro Normal file
View file

@ -0,0 +1,38 @@
---
import Tag from './Tag.astro'
interface Props {
pubDate: Date
slug: string
title: string
tags: { name: string, slug: string }[]
}
const { pubDate, slug, title, tags } = Astro.props
---
<div class="note">
<div class="date">{pubDate.toLocaleDateString("ru", {year: "numeric", month: "long", day: "numeric"})}</div>
<div class="dash">—</div>
<a href={`/notes/${slug}/`}>{ title }</a>
{tags.map(tag => <Tag slug={tag.slug} name={tag.name} />)}
</div>
<style>
.note {
margin-bottom: 15px;
}
.dash {
display: none;
}
@media screen and (min-width: 600px) {
.note {
display: flex;
gap: 6px;
margin-bottom: 5px;
}
.dash {
display: block;
}
}
</style>

25
src/components/Tag.astro Normal file
View file

@ -0,0 +1,25 @@
---
interface Props {
name: string
slug: string
}
const { name, slug } = Astro.props
---
<a class="tag" href={`/tags/${slug}`}>
<img width="12" height="12" src="/icons/tag.svg" />
{name}
</a>
<style>
.tag {
font-size: 10px;
background-color: #323232;
padding: 2px 8px;
border-radius: 15px;
display: flex;
gap: 5px;
align-items: center;
color: #ddd;
}
</style>

View file

@ -7,6 +7,10 @@ const notesCollection = defineCollection({
description: z.string(), description: z.string(),
pubDate: z.date(), pubDate: z.date(),
updatedAt: z.date().optional(), updatedAt: z.date().optional(),
tags: z.array(z.object({
name: z.string(),
slug: z.string(),
})),
}), }),
}) })

View file

@ -2,6 +2,7 @@
title: "Беспечная Гляденовская гора" title: "Беспечная Гляденовская гора"
description: "Путешествие до Гляденовской горы." description: "Путешествие до Гляденовской горы."
pubDate: 2023-10-16 pubDate: 2023-10-16
tags: [{name: "Путешествия", slug: "traveling"}, {name: "Пермский край", slug: "perm-krai"}]
--- ---
Гляденовская гора - это красивый лес в Перми, за которым прячется деревянная часовня. Гляденовская гора - это красивый лес в Перми, за которым прячется деревянная часовня.

View file

@ -3,6 +3,7 @@ title: "Свободные альтернативы популярным сер
description: "Список самых популярных независимых альтернатив коммерческим социальным сетям и сервисам." description: "Список самых популярных независимых альтернатив коммерческим социальным сетям и сервисам."
pubDate: 2023-08-27 pubDate: 2023-08-27
updatedAt: 2023-10-01 updatedAt: 2023-10-01
tags: [{name: "Интернет", slug: "internet"}]
--- ---
Это статья для тех, кто никогда не слышал про альтернативы современным социальным сетям и сервисам. Это статья для тех, кто никогда не слышал про альтернативы современным социальным сетям и сервисам.

View file

@ -2,6 +2,7 @@
title: "Мыс Стрелка" title: "Мыс Стрелка"
description: "Фотографии с путешествия на мыс Стрелка." description: "Фотографии с путешествия на мыс Стрелка."
pubDate: 2023-11-11 pubDate: 2023-11-11
tags: [{name: "Путешествия", slug: "traveling"}, {name: "Пермский край", slug: "perm-krai"}]
--- ---
*Некоторые вещи стоят того, чтобы встать в 6 утра в воскресенье.* *Некоторые вещи стоят того, чтобы встать в 6 утра в воскресенье.*

View file

@ -3,6 +3,7 @@ title: "Интернет №1 продолжают ломать?"
description: "Расскажу о событиях, которые побудили меня завести зеркало этого сайта в Yggdrasil." description: "Расскажу о событиях, которые побудили меня завести зеркало этого сайта в Yggdrasil."
pubDate: 2024-02-02 pubDate: 2024-02-02
updatedAt: 2024-02-12 updatedAt: 2024-02-12
tags: [{name: "Интернет", slug: "internet"}]
--- ---
## Что случилось? ## Что случилось?

View file

@ -1,5 +1,6 @@
--- ---
import Layout from '../layouts/Layout.astro' import Layout from '../layouts/Layout.astro'
import Note from '../components/Note.astro'
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
const notes = await getCollection('notes') const notes = await getCollection('notes')
@ -33,11 +34,12 @@ notes.sort((a, b) => {
</a> </a>
</div> </div>
{notes.map(note => ( {notes.map(note => (
<div class="note"> <Note
<div class="date">{note.data.pubDate.toLocaleDateString("ru", {year: "numeric", month: "long", day: "numeric"})}</div> pubDate={note.data.pubDate}
<div class="dash">—</div> slug={note.slug}
<a href={`/notes/${note.slug}/`}>{ note.data.title }</a> title={note.data.title}
</div> tags={note.data.tags}
/>
))} ))}
</Layout> </Layout>
@ -59,22 +61,4 @@ notes.sort((a, b) => {
align-items: center; align-items: center;
gap: 5px; gap: 5px;
} }
.note {
margin-bottom: 15px;
}
.dash {
display: none;
}
@media screen and (min-width: 600px) {
.note {
display: flex;
gap: 6px;
margin-bottom: 5px;
}
.dash {
display: block;
}
}
</style> </style>

View file

@ -1,6 +1,7 @@
--- ---
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro' import Layout from '../../layouts/Layout.astro'
import Tag from '../../components/Tag.astro'
export async function getStaticPaths() { export async function getStaticPaths() {
const blogEntries = await getCollection('notes') const blogEntries = await getCollection('notes')
@ -23,6 +24,10 @@ const formatDate = (date: Date) => {
{ entry.data.updatedAt !== undefined && entry.data.updatedAt !== entry.data.pubDate && ( { entry.data.updatedAt !== undefined && entry.data.updatedAt !== entry.data.pubDate && (
<p>Последнее обновление: { formatDate(entry.data.updatedAt) }</p> <p>Последнее обновление: { formatDate(entry.data.updatedAt) }</p>
)} )}
<div class="tags">
{entry.data.tags.map(tag => <Tag slug={tag.slug} name={tag.name} />)}
</div>
</div> </div>
<div class="content"> <div class="content">
<Content /> <Content />
@ -39,6 +44,10 @@ const formatDate = (date: Date) => {
margin-top: 6px; margin-top: 6px;
margin-bottom: 6px; margin-bottom: 6px;
} }
.metadata .tags {
display: flex;
gap: 5px;
}
.content { .content {
img { img {
width: 100%; width: 100%;

View file

@ -0,0 +1,71 @@
---
import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro'
import Note from '../../components/Note.astro'
export async function getStaticPaths() {
const blogEntries = await getCollection('notes')
const tags = blogEntries
.map(entry => entry.data.tags)
.flat()
.filter((tag, i, self) => self.findIndex(t => t.slug === tag.slug) === i)
return tags.map(tag => {
const notes = blogEntries
.filter(entry => entry.data.tags.find(t => t.slug === tag.slug) !== undefined)
.toSorted((a, b) => {
if (a.data.pubDate < b.data.pubDate) return 1
if (a.data.pubDate > b.data.pubDate) return -1
return 0
})
return {
params: { slug: tag.slug },
props: { tag, notes },
}
})
}
const { notes, tag } = Astro.props
---
<Layout title={`Заметки с тегом "${tag.name}"`}>
<h1>Заметки с тегом "{ tag.name }"</h1>
{notes.map(note => (
<Note
pubDate={note.data.pubDate}
slug={note.slug}
title={note.data.title}
tags={note.data.tags}
/>
))}
</Layout>
<style>
.note {
margin-bottom: 15px;
}
.dash {
display: none;
}
.tag {
font-size: 10px;
background-color: #323232;
padding: 2px 8px;
border-radius: 15px;
display: flex;
gap: 5px;
align-items: center;
color: #ddd;
}
@media screen and (min-width: 600px) {
.note {
display: flex;
gap: 6px;
margin-bottom: 5px;
}
.dash {
display: block;
}
}
</style>