feat: tags
This commit is contained in:
parent
487925f613
commit
5a7e8cd230
11 changed files with 159 additions and 23 deletions
1
public/icons/tag.svg
Normal file
1
public/icons/tag.svg
Normal 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
38
src/components/Note.astro
Normal 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
25
src/components/Tag.astro
Normal 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>
|
|
@ -7,6 +7,10 @@ const notesCollection = defineCollection({
|
|||
description: z.string(),
|
||||
pubDate: z.date(),
|
||||
updatedAt: z.date().optional(),
|
||||
tags: z.array(z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
})),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
title: "Беспечная Гляденовская гора"
|
||||
description: "Путешествие до Гляденовской горы."
|
||||
pubDate: 2023-10-16
|
||||
tags: [{name: "Путешествия", slug: "traveling"}, {name: "Пермский край", slug: "perm-krai"}]
|
||||
---
|
||||
|
||||
Гляденовская гора - это красивый лес в Перми, за которым прячется деревянная часовня.
|
||||
|
|
|
@ -3,6 +3,7 @@ title: "Свободные альтернативы популярным сер
|
|||
description: "Список самых популярных независимых альтернатив коммерческим социальным сетям и сервисам."
|
||||
pubDate: 2023-08-27
|
||||
updatedAt: 2023-10-01
|
||||
tags: [{name: "Интернет", slug: "internet"}]
|
||||
---
|
||||
|
||||
Это статья для тех, кто никогда не слышал про альтернативы современным социальным сетям и сервисам.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
title: "Мыс Стрелка"
|
||||
description: "Фотографии с путешествия на мыс Стрелка."
|
||||
pubDate: 2023-11-11
|
||||
tags: [{name: "Путешествия", slug: "traveling"}, {name: "Пермский край", slug: "perm-krai"}]
|
||||
---
|
||||
|
||||
*Некоторые вещи стоят того, чтобы встать в 6 утра в воскресенье.*
|
||||
|
|
|
@ -3,6 +3,7 @@ title: "Интернет №1 продолжают ломать?"
|
|||
description: "Расскажу о событиях, которые побудили меня завести зеркало этого сайта в Yggdrasil."
|
||||
pubDate: 2024-02-02
|
||||
updatedAt: 2024-02-12
|
||||
tags: [{name: "Интернет", slug: "internet"}]
|
||||
---
|
||||
|
||||
## Что случилось?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro'
|
||||
import Note from '../components/Note.astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
const notes = await getCollection('notes')
|
||||
|
@ -33,11 +34,12 @@ notes.sort((a, b) => {
|
|||
</a>
|
||||
</div>
|
||||
{notes.map(note => (
|
||||
<div class="note">
|
||||
<div class="date">{note.data.pubDate.toLocaleDateString("ru", {year: "numeric", month: "long", day: "numeric"})}</div>
|
||||
<div class="dash">—</div>
|
||||
<a href={`/notes/${note.slug}/`}>{ note.data.title }</a>
|
||||
</div>
|
||||
<Note
|
||||
pubDate={note.data.pubDate}
|
||||
slug={note.slug}
|
||||
title={note.data.title}
|
||||
tags={note.data.tags}
|
||||
/>
|
||||
))}
|
||||
</Layout>
|
||||
|
||||
|
@ -59,22 +61,4 @@ notes.sort((a, b) => {
|
|||
align-items: center;
|
||||
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>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content'
|
||||
import Layout from '../../layouts/Layout.astro'
|
||||
import Tag from '../../components/Tag.astro'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection('notes')
|
||||
|
@ -23,6 +24,10 @@ const formatDate = (date: Date) => {
|
|||
{ entry.data.updatedAt !== undefined && entry.data.updatedAt !== entry.data.pubDate && (
|
||||
<p>Последнее обновление: { formatDate(entry.data.updatedAt) }</p>
|
||||
)}
|
||||
|
||||
<div class="tags">
|
||||
{entry.data.tags.map(tag => <Tag slug={tag.slug} name={tag.name} />)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Content />
|
||||
|
@ -39,6 +44,10 @@ const formatDate = (date: Date) => {
|
|||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.metadata .tags {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
.content {
|
||||
img {
|
||||
width: 100%;
|
||||
|
|
71
src/pages/tags/[...slug].astro
Normal file
71
src/pages/tags/[...slug].astro
Normal 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>
|
Loading…
Reference in a new issue