WIP: adding microformat data
This commit is contained in:
parent
3bd197746b
commit
d77bd5d447
9 changed files with 75 additions and 54 deletions
examples/social-feed/src
|
@ -1,7 +1,5 @@
|
|||
---
|
||||
import ArticleHeader from './ArticleHeader.astro';
|
||||
import Icon from './Icon.astro';
|
||||
import Prose from './Prose.astro';
|
||||
import SplitLayout from './SplitLayout.astro';
|
||||
import type { Article, Post } from '../content/config.js';
|
||||
|
||||
|
@ -17,7 +15,7 @@ const { Content, headings } = await article.render();
|
|||
<article>
|
||||
<ArticleHeader {article} class="header" />
|
||||
<SplitLayout reverse>
|
||||
<div class="p-content">
|
||||
<div class="e-content">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
|
@ -46,11 +44,11 @@ const { Content, headings } = await article.render();
|
|||
padding-block: var(--theme-space-md);
|
||||
}
|
||||
|
||||
.p-content > :global(* + *) {
|
||||
.e-content > :global(* + *) {
|
||||
margin-block-start: 1em;
|
||||
}
|
||||
|
||||
.p-content > :global(p:first-child::first-letter) {
|
||||
.e-content > :global(p:first-child::first-letter) {
|
||||
float: left;
|
||||
font-size: 3.5rem;
|
||||
padding-inline-end: 0.5rem;
|
||||
|
@ -58,7 +56,7 @@ const { Content, headings } = await article.render();
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.p-content :global(img, video, figure) {
|
||||
.e-content :global(img, video, figure) {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
|
@ -86,7 +84,7 @@ const { Content, headings } = await article.render();
|
|||
display: block;
|
||||
}
|
||||
|
||||
.p-content > :global(* + *) {
|
||||
.e-content > :global(* + *) {
|
||||
margin-block-start: 1.5em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,19 @@ const { text: readingTime } = await getReadingTime(article.body);
|
|||
<div class="cover">
|
||||
{
|
||||
article.data.cover && (
|
||||
<Image src={article.data.cover.src} alt={article.data.cover.alt} />
|
||||
<Image src={article.data.cover.src} alt={article.data.cover.alt} class="u-photo" />
|
||||
)
|
||||
}
|
||||
|
||||
<div class="author">
|
||||
<Image src={settings.avatar.src} alt={settings.avatar.alt} width={80} />
|
||||
<p class="u-name">{settings.name}</p>
|
||||
<p class="u-nickname">{settings.username}</p>
|
||||
<div class="p-author h-card">
|
||||
<Image src={settings.avatar.src} alt={settings.avatar.alt} width={80} class="u-photo" />
|
||||
<p class="p-name">{settings.name}</p>
|
||||
<p class="p-nickname">{settings.username}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
<FormattedDate date={article.data.pubDate} />
|
||||
<FormattedDate date={article.data.pubDate} class="dt-published" />
|
||||
<span>•</span>
|
||||
<p>
|
||||
<Icon icon="clock" size="1rem" />
|
||||
|
@ -41,7 +41,7 @@ const { text: readingTime } = await getReadingTime(article.body);
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<h1>{article.data.title}</h1>
|
||||
<h1 class="p-name">{article.data.title}</h1>
|
||||
|
||||
{article.data.categories?.length > 0 && <TagList tags={article.data.categories} />}
|
||||
</header>
|
||||
|
@ -73,7 +73,7 @@ const { text: readingTime } = await getReadingTime(article.body);
|
|||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.author {
|
||||
.p-author {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
|
@ -96,23 +96,23 @@ const { text: readingTime } = await getReadingTime(article.body);
|
|||
margin-block-end: var(--theme-space-sm);
|
||||
}
|
||||
|
||||
.author {
|
||||
.p-author {
|
||||
justify-self: end;
|
||||
margin-inline-end: var(--theme-space-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.author img {
|
||||
.p-author img {
|
||||
width: var(--theme-space-lg);
|
||||
height: var(--theme-space-lg);
|
||||
}
|
||||
|
||||
.author .u-name {
|
||||
.p-author .p-name {
|
||||
font-family: var(--font-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.author .u-nickname {
|
||||
.p-author .p-nickname {
|
||||
color: var(--theme-gray-300);
|
||||
font-size: var(--theme-text-sm);
|
||||
font-weight: 600;
|
||||
|
|
|
@ -18,28 +18,28 @@ const cover = 'cover' in post.data && post.data.cover;
|
|||
const title = 'title' in post.data && post.data.title;
|
||||
---
|
||||
|
||||
<article {...attrs}>
|
||||
<header>
|
||||
<Image {...settings.avatar} width={120} class="u-image" />
|
||||
<strong class="u-name">{settings.name}</strong>
|
||||
<div class="u-nickname">
|
||||
<article class="h-entry" {...attrs}>
|
||||
<header class="p-author h-card">
|
||||
<Image {...settings.avatar} width={120} class="u-photo" />
|
||||
<strong class="p-name">{settings.name}</strong>
|
||||
<div class="p-nickname">
|
||||
{settings.username}
|
||||
•
|
||||
<FormattedDate date={pubDate} />
|
||||
<FormattedDate date={pubDate} class="dt-published" />
|
||||
</div>
|
||||
{categories.length > 0 && <TagList tags={categories} class="tags" />}
|
||||
</header>
|
||||
{cover && <Image {...cover} width={1060} />}
|
||||
{title && <h3>{title}</h3>}
|
||||
{cover && <Image {...cover} width={1060} class="u-photo" />}
|
||||
{title && <h3 class="p-name">{title}</h3>}
|
||||
{
|
||||
isArticle(post) ? (
|
||||
<p>{post.data.description}</p>
|
||||
<p class="p-summary">{post.data.description}</p>
|
||||
) : (
|
||||
post.render().then(({ Content }) => <Content />)
|
||||
post.render().then(({ Content }) => <div class="e-content"><Content /></div>)
|
||||
)
|
||||
}
|
||||
<footer>
|
||||
<a href={`/post/${post.slug}/`}>Full {post.data.type === 'article' ? 'article' : 'note'}</a>
|
||||
<a href={`/post/${post.slug}/`} class="u-url">Full {post.collection === 'articles' ? 'article' : 'note'}</a>
|
||||
<a href={`javascript: navigator.clipboard.writeText(window.location.href + "post/${post.slug}/");`}>
|
||||
<Icon icon="share" size="1.5rem" />
|
||||
<span class="sr-only">Share this post</span>
|
||||
|
@ -65,19 +65,19 @@ const title = 'title' in post.data && post.data.title;
|
|||
row-gap: var(--theme-space-2xs);
|
||||
column-gap: var(--theme-space-sm);
|
||||
}
|
||||
.u-image {
|
||||
.u-photo {
|
||||
grid-area: avatar;
|
||||
width: var(--theme-space-xl);
|
||||
height: var(--theme-space-xl);
|
||||
border-radius: var(--theme-radius-full);
|
||||
background-color: var(--theme-shade-subtle);
|
||||
}
|
||||
.u-name {
|
||||
.p-name {
|
||||
grid-area: name;
|
||||
font-family: var(--font-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
.u-nickname {
|
||||
.p-nickname {
|
||||
grid-area: nickname;
|
||||
font-size: var(--theme-text-sm);
|
||||
color: var(--theme-gray-300);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
interface Props {
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
|
||||
interface Props extends HTMLAttributes<'time'> {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,11 @@ const { tags, ...attrs } = Astro.props;
|
|||
---
|
||||
|
||||
<ul {...attrs}>
|
||||
{tags.map((tag) => <li>{tag}</li>)}
|
||||
{tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/category/${tag}/`} rel="category tag" class="p-category">{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,14 +6,14 @@ import Icon from './Icon.astro';
|
|||
const socialLinks = Object.entries(settings.social);
|
||||
---
|
||||
|
||||
<div class="profile">
|
||||
<div class="h-card">
|
||||
<div>
|
||||
<div class="avatar">
|
||||
<div class="u-photo">
|
||||
<Image {...settings.avatar} width={220} />
|
||||
</div>
|
||||
<h1>
|
||||
{settings.name}
|
||||
<small>{settings.username}</small>
|
||||
<span class="p-name">{settings.name}</span>
|
||||
<small class="p-nickname">{settings.username}</small>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bio-sections">
|
||||
|
@ -37,7 +37,7 @@ const socialLinks = Object.entries(settings.social);
|
|||
<p>
|
||||
<Icon icon="location-point" color="var(--theme-accent-dark)" size="1.75rem" />
|
||||
<span class="sr-only">Location</span>
|
||||
{settings.location}
|
||||
<span class="p-locality">{settings.location}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ const socialLinks = Object.entries(settings.social);
|
|||
<p>
|
||||
<Icon icon="link-h" color="var(--theme-accent-dark)" size="1.75rem" />
|
||||
<span class="sr-only">Homepage</span>
|
||||
<a href={settings.homepage}>{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}</a>
|
||||
<a href={settings.homepage} class="u-url">{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -57,9 +57,10 @@ const socialLinks = Object.entries(settings.social);
|
|||
</div>
|
||||
<ul class="social">
|
||||
{
|
||||
socialLinks.map(([key, { url }]) => (
|
||||
socialLinks.map(([key, { url, title }]) => (
|
||||
<li>
|
||||
<a href={url}>
|
||||
<a href={url} rel="me">
|
||||
<span class="sr-only">{title}</span>
|
||||
<Icon icon={`${key}`} size="1.75rem" />
|
||||
</a>
|
||||
</li>
|
||||
|
@ -70,18 +71,18 @@ const socialLinks = Object.entries(settings.social);
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.profile {
|
||||
.h-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--theme-space-md);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
.u-photo {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar::after {
|
||||
.u-photo::after {
|
||||
border-radius: var(--theme-radius-full);
|
||||
position: absolute;
|
||||
content: '';
|
||||
|
@ -89,7 +90,7 @@ const socialLinks = Object.entries(settings.social);
|
|||
border: 3px solid var(--theme-text);
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
.u-photo img {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ const articles = defineCollection({
|
|||
})
|
||||
.required({
|
||||
// requiring the description for articles, this will be shown as the short preview text on cards
|
||||
title: true,
|
||||
description: true
|
||||
})
|
||||
.strict(),
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { getCollection } from 'astro:content';
|
||||
import type { Article, Note } from '../content/config';
|
||||
|
||||
export function sortPosts(order: 'asc' | 'desc' = 'desc') {
|
||||
return function(a: Article | Note, b: Article | Note) {
|
||||
return order === 'asc'
|
||||
? a.data.pubDate.getTime() - b.data.pubDate.getTime()
|
||||
: b.data.pubDate.getTime() - a.data.pubDate.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
/** Get everything in your posts collection, sorted by date. */
|
||||
export async function getSortedPosts(order: 'asc' | 'desc' = 'desc') {
|
||||
|
@ -6,8 +15,10 @@ export async function getSortedPosts(order: 'asc' | 'desc' = 'desc') {
|
|||
getCollection('articles'),
|
||||
getCollection('notes'),
|
||||
])
|
||||
.then((collections) => collections.flat())
|
||||
posts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
||||
if (order === 'asc') posts.reverse();
|
||||
.then((collections) => collections
|
||||
.flat()
|
||||
.sort(sortPosts(order)
|
||||
));
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import type { APIContext } from 'astro';
|
||||
import { getSortedPosts } from '../helpers/getSortedPosts';
|
||||
import { sortPosts } from '../helpers/getSortedPosts';
|
||||
import settings from '../settings';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const { title, description } = settings.rss;
|
||||
|
||||
export async function get(context: APIContext) {
|
||||
const posts = await getSortedPosts();
|
||||
export async function GET(context: APIContext) {
|
||||
const posts = await getCollection('articles');
|
||||
|
||||
return rss({
|
||||
// `<title>` field in output xml
|
||||
title,
|
||||
|
@ -17,7 +19,9 @@ export async function get(context: APIContext) {
|
|||
site: context.site!.href,
|
||||
// Array of `<item>`s in output xml
|
||||
// See "Generating items" section for examples using content collections and glob imports
|
||||
items: posts.map(({ data, slug }) => ({ ...data, link: `/post/${slug}` })),
|
||||
items: posts
|
||||
.sort(sortPosts())
|
||||
.map(({ data, slug }) => ({ ...data, link: `/post/${slug}` })),
|
||||
stylesheet: '/rss/styles.xsl',
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue