feat: add blog with archives, pagination, search, sharing and RSS
- Content collection (one folder per post) with zod schema - /blog, category and tag archives with /sayfa/<n>/ pagination - Post page: breadcrumbs, date badges, share bar, TOC, DIY notice, code copy, license line, prev/next, BlogPosting + BreadcrumbList JSON-LD - Sidebar: Pagefind search, categories, featured fallback, tag cloud - Sätteri HAST plugins for figure/figcaption and external links - Dual-theme Shiki, themed prose, RSS feed, latest posts on home - Three sample posts marked as examples Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { blog, site } from '../../lib/data';
|
||||
|
||||
export interface Props {
|
||||
url: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { url, title } = Astro.props;
|
||||
const fill = (tpl: string) =>
|
||||
tpl.replace('{url}', encodeURIComponent(url)).replace('{title}', encodeURIComponent(title));
|
||||
const btn =
|
||||
'border-line hover:border-fg hover:bg-fg hover:text-bg inline-flex size-11 cursor-pointer items-center justify-center border transition-colors';
|
||||
---
|
||||
|
||||
<div data-share data-url={url} data-title={title} data-copied={blog.post.copied}>
|
||||
<p class="font-display mb-3 text-sm font-bold tracking-wide uppercase" id="share-label">
|
||||
{blog.post.shareLabel}
|
||||
</p>
|
||||
<ul class="flex flex-wrap gap-2" aria-labelledby="share-label">
|
||||
{blog.share.map((s) => (
|
||||
<li>
|
||||
<a
|
||||
href={fill(s.url)}
|
||||
target={s.id === 'email' ? undefined : '_blank'}
|
||||
rel={s.id === 'email' ? undefined : 'noopener noreferrer'}
|
||||
title={s.label}
|
||||
class={btn}
|
||||
>
|
||||
<Icon name={s.icon} class="size-5" aria-hidden="true" />
|
||||
<span class="sr-only">
|
||||
{s.label}
|
||||
{s.id !== 'email' && ` ${site.ui.newTab}`}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<button type="button" data-share-copy title={blog.post.copyLink} class={btn}>
|
||||
<Icon name="tabler:link" class="size-5" aria-hidden="true" />
|
||||
<span class="sr-only">{blog.post.copyLink}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li hidden data-share-native-item>
|
||||
<button type="button" data-share-native title={blog.post.nativeShare} class={btn}>
|
||||
<Icon name="tabler:share" class="size-5" aria-hidden="true" />
|
||||
<span class="sr-only">{blog.post.nativeShare}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p
|
||||
data-share-status
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
class="text-accent-text mt-2 min-h-5 text-sm font-semibold"
|
||||
></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
for (const root of document.querySelectorAll<HTMLElement>('[data-share]')) {
|
||||
const { url = location.href, title = document.title, copied = '' } = root.dataset;
|
||||
const status = root.querySelector<HTMLElement>('[data-share-status]')!;
|
||||
|
||||
root.querySelector('[data-share-copy]')?.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
status.textContent = copied;
|
||||
setTimeout(() => (status.textContent = ''), 2500);
|
||||
} catch {
|
||||
prompt('', url);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof navigator.share === 'function') {
|
||||
root.querySelector<HTMLElement>('[data-share-native-item]')!.hidden = false;
|
||||
root.querySelector('[data-share-native]')!.addEventListener('click', () => {
|
||||
navigator.share({ url, title }).catch(() => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user