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(),
|
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(),
|
||||||
|
})),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
title: "Беспечная Гляденовская гора"
|
title: "Беспечная Гляденовская гора"
|
||||||
description: "Путешествие до Гляденовской горы."
|
description: "Путешествие до Гляденовской горы."
|
||||||
pubDate: 2023-10-16
|
pubDate: 2023-10-16
|
||||||
|
tags: [{name: "Путешествия", slug: "traveling"}, {name: "Пермский край", slug: "perm-krai"}]
|
||||||
---
|
---
|
||||||
|
|
||||||
Гляденовская гора - это красивый лес в Перми, за которым прячется деревянная часовня.
|
Гляденовская гора - это красивый лес в Перми, за которым прячется деревянная часовня.
|
||||||
|
|
|
@ -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"}]
|
||||||
---
|
---
|
||||||
|
|
||||||
Это статья для тех, кто никогда не слышал про альтернативы современным социальным сетям и сервисам.
|
Это статья для тех, кто никогда не слышал про альтернативы современным социальным сетям и сервисам.
|
||||||
|
|
|
@ -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 утра в воскресенье.*
|
||||||
|
|
|
@ -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"}]
|
||||||
---
|
---
|
||||||
|
|
||||||
## Что случилось?
|
## Что случилось?
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
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