Implement Webmention functionality: add Webmentions component, update deploy script, and enhance UI with social links and localization
This commit is contained in:
parent
abbf2d9a0b
commit
934a9f2338
8 changed files with 460 additions and 10 deletions
|
|
@ -39,7 +39,7 @@ echo "Deploy done via $VPS (branch: $REMOTE_BRANCH)."
|
|||
WEBMENTION_APP_TOKEN="$(ssh "$VPS" "grep '^WEBMENTION_APP_TOKEN=' '$REMOTE_BASE/.env.production' | cut -d= -f2-" 2>/dev/null || true)"
|
||||
if [[ -n "$WEBMENTION_APP_TOKEN" ]]; then
|
||||
echo "Sending webmentions via webmention.app..."
|
||||
for feed in rss/blog.xml rss/fotos.xml; do
|
||||
for feed in rss.xml en/rss.xml; do
|
||||
curl -s -X POST "https://webmention.app/check?url=https://adrian-altner.de/${feed}&token=${WEBMENTION_APP_TOKEN}" \
|
||||
| grep -o '"status":"[^"]*"' || true
|
||||
done
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ const rssHref = new URL(locale === 'de' ? 'rss.xml' : 'en/rss.xml', Astro.site);
|
|||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Webmention endpoints (webmention.io) -->
|
||||
<link rel="webmention" href="https://webmention.io/adrian-altner.de/webmention" />
|
||||
<link rel="pingback" href="https://webmention.io/adrian-altner.de/xmlrpc" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
|
|
|
|||
|
|
@ -17,6 +17,31 @@ const today = new Date();
|
|||
<footer>
|
||||
<div class="footer__inner">
|
||||
<span>© {today.getFullYear()} Adrian Altner</span>
|
||||
<nav class="footer__social" aria-label="Social">
|
||||
<a
|
||||
href="https://mastodon.social/@altner"
|
||||
rel="me noopener"
|
||||
target="_blank"
|
||||
aria-label="Mastodon"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="currentColor">
|
||||
<path
|
||||
d="M21.58 13.91c-.29 1.48-2.58 3.1-5.21 3.41-1.37.17-2.72.32-4.16.25-2.35-.11-4.2-.56-4.2-.56 0 .23.01.45.04.65.3 2.28 2.26 2.42 4.11 2.48 1.87.07 3.54-.46 3.54-.46l.08 1.69s-1.31.7-3.64.83c-1.28.07-2.88-.03-4.74-.52-4.04-1.07-4.73-5.38-4.84-9.74-.03-1.3-.01-2.52-.01-3.54 0-4.46 2.92-5.77 2.92-5.77C6.95.89 9.48.72 12.11.7h.06c2.63.02 5.17.19 6.64.87 0 0 2.92 1.31 2.92 5.77 0 0 .04 3.29-.41 5.58"
|
||||
/>
|
||||
<path
|
||||
d="M18.66 7.63v5.45h-2.16V7.79c0-1.09-.46-1.64-1.38-1.64-1.01 0-1.52.65-1.52 1.95v2.82h-2.14V8.1c0-1.3-.51-1.95-1.52-1.95-.92 0-1.38.55-1.38 1.64v5.29H6.4V7.63c0-1.09.28-1.95.83-2.59.57-.64 1.32-.97 2.25-.97 1.08 0 1.9.41 2.43 1.24L12 6.08l.54-.77c.53-.83 1.35-1.24 2.43-1.24.93 0 1.68.33 2.25.97.55.64.83 1.5.83 2.59"
|
||||
fill="var(--bg, #fff)"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://github.com/altner" rel="me noopener" target="_blank" aria-label="GitHub">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="currentColor">
|
||||
<path
|
||||
d="M12 .5a12 12 0 0 0-3.79 23.4c.6.11.82-.26.82-.58v-2.03c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.73.08-.73 1.21.08 1.84 1.24 1.84 1.24 1.07 1.84 2.81 1.31 3.5 1 .11-.78.42-1.31.76-1.61-2.67-.31-5.47-1.34-5.47-5.95 0-1.31.47-2.39 1.24-3.23-.12-.31-.54-1.53.12-3.19 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.24 2.88.12 3.19.77.84 1.24 1.92 1.24 3.23 0 4.62-2.81 5.63-5.49 5.94.43.37.82 1.1.82 2.22v3.29c0 .32.22.7.83.58A12 12 0 0 0 12 .5Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
<nav class="footer__links" aria-label="Legal">
|
||||
<a href={localizePath(`/${contactSegment}`, locale)}>{t(locale, 'footer.contact')}</a>
|
||||
<a href={localizePath(`/${imprintSegment}`, locale)}>{t(locale, 'footer.imprint')}</a>
|
||||
|
|
@ -49,4 +74,18 @@ const today = new Date();
|
|||
.footer__links a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.footer__social {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75em;
|
||||
line-height: 1;
|
||||
}
|
||||
.footer__social a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: rgb(var(--gray));
|
||||
}
|
||||
.footer__social a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,18 @@ import { getLocaleFromUrl } from '~/i18n/ui';
|
|||
interface Props {
|
||||
date: Date;
|
||||
locale?: Locale;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { date, locale = getLocaleFromUrl(Astro.url) ?? DEFAULT_LOCALE } = Astro.props;
|
||||
const {
|
||||
date,
|
||||
locale = getLocaleFromUrl(Astro.url) ?? DEFAULT_LOCALE,
|
||||
class: className,
|
||||
} = Astro.props;
|
||||
const tag = locale === 'de' ? 'de-DE' : 'en-US';
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
<time datetime={date.toISOString()} class={className}>
|
||||
{
|
||||
date.toLocaleDateString(tag, {
|
||||
year: 'numeric',
|
||||
|
|
|
|||
247
src/components/Webmentions.astro
Normal file
247
src/components/Webmentions.astro
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
---
|
||||
import { DEFAULT_LOCALE, type Locale } from '~/consts';
|
||||
import { getLocaleFromUrl, t } from '~/i18n/ui';
|
||||
import { getMentionsFor, groupMentions, type WMEntry } from '~/lib/webmentions';
|
||||
|
||||
interface Props {
|
||||
target: string | URL;
|
||||
locale?: Locale;
|
||||
}
|
||||
|
||||
const { target, locale = getLocaleFromUrl(Astro.url) ?? DEFAULT_LOCALE } = Astro.props;
|
||||
|
||||
const all = await getMentionsFor(target);
|
||||
const { likes, reposts, replies, mentions } = groupMentions(all);
|
||||
|
||||
const facepile = [...likes, ...reposts];
|
||||
|
||||
function authorInitial(m: WMEntry) {
|
||||
return m.author?.name?.trim()?.[0]?.toUpperCase() ?? '?';
|
||||
}
|
||||
|
||||
function formatDate(iso?: string) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
const hasAny = facepile.length > 0 || replies.length > 0 || mentions.length > 0;
|
||||
---
|
||||
|
||||
{
|
||||
hasAny && (
|
||||
<section class="webmentions" aria-labelledby="webmentions-heading">
|
||||
<h2 id="webmentions-heading">{t(locale, 'webmentions.title')}</h2>
|
||||
|
||||
{facepile.length > 0 && (
|
||||
<div class="facepile-group">
|
||||
{likes.length > 0 && (
|
||||
<div class="facepile">
|
||||
<h3>
|
||||
{likes.length} {t(locale, likes.length === 1 ? 'webmentions.like' : 'webmentions.likes')}
|
||||
</h3>
|
||||
<ul>
|
||||
{likes.map((m) => (
|
||||
<li>
|
||||
<a
|
||||
href={m.url}
|
||||
title={m.author?.name ?? m.url}
|
||||
rel="noopener nofollow external"
|
||||
>
|
||||
{m.author?.photo ? (
|
||||
<img src={m.author.photo} alt="" loading="lazy" />
|
||||
) : (
|
||||
<span class="avatar-fallback">{authorInitial(m)}</span>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{reposts.length > 0 && (
|
||||
<div class="facepile">
|
||||
<h3>
|
||||
{reposts.length}{' '}
|
||||
{t(locale, reposts.length === 1 ? 'webmentions.repost' : 'webmentions.reposts')}
|
||||
</h3>
|
||||
<ul>
|
||||
{reposts.map((m) => (
|
||||
<li>
|
||||
<a
|
||||
href={m.url}
|
||||
title={m.author?.name ?? m.url}
|
||||
rel="noopener nofollow external"
|
||||
>
|
||||
{m.author?.photo ? (
|
||||
<img src={m.author.photo} alt="" loading="lazy" />
|
||||
) : (
|
||||
<span class="avatar-fallback">{authorInitial(m)}</span>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{replies.length > 0 && (
|
||||
<div class="replies">
|
||||
<h3>{t(locale, 'webmentions.replies')}</h3>
|
||||
<ol>
|
||||
{replies.map((m) => (
|
||||
<li>
|
||||
<div class="meta">
|
||||
{m.author?.photo && (
|
||||
<img src={m.author.photo} alt="" class="avatar" loading="lazy" />
|
||||
)}
|
||||
<a
|
||||
href={m.author?.url ?? m.url}
|
||||
rel="noopener nofollow external"
|
||||
class="author"
|
||||
>
|
||||
{m.author?.name ?? m.url}
|
||||
</a>
|
||||
<a href={m.url} rel="noopener nofollow external" class="permalink">
|
||||
<time datetime={m['wm-received'] ?? m.published}>
|
||||
{formatDate(m['wm-received'] ?? m.published)}
|
||||
</time>
|
||||
</a>
|
||||
</div>
|
||||
{m.content?.text && <p>{m.content.text}</p>}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mentions.length > 0 && (
|
||||
<div class="mentions">
|
||||
<h3>{t(locale, 'webmentions.mentions')}</h3>
|
||||
<ul>
|
||||
{mentions.map((m) => (
|
||||
<li>
|
||||
<a href={m.url} rel="noopener nofollow external">
|
||||
{m.author?.name ?? m.url}
|
||||
</a>
|
||||
{m['wm-received'] && (
|
||||
<>
|
||||
{' · '}
|
||||
<time datetime={m['wm-received']}>{formatDate(m['wm-received'])}</time>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
.webmentions {
|
||||
margin-top: 3em;
|
||||
padding-top: 2em;
|
||||
border-top: 1px solid rgba(var(--gray-light), 1);
|
||||
}
|
||||
.webmentions h2 {
|
||||
margin: 0 0 1em;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
.webmentions h3 {
|
||||
margin: 0 0 0.5em;
|
||||
font-size: 1em;
|
||||
color: rgb(var(--gray));
|
||||
font-weight: 600;
|
||||
}
|
||||
.facepile-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.facepile ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4em;
|
||||
}
|
||||
.facepile a {
|
||||
display: inline-block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: rgba(var(--gray-light), 1);
|
||||
}
|
||||
.facepile img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.avatar-fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 600;
|
||||
color: rgb(var(--gray-dark));
|
||||
}
|
||||
.replies ol {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25em;
|
||||
}
|
||||
.replies li {
|
||||
padding: 0.75em 1em;
|
||||
background: rgba(var(--gray-light), 0.4);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.replies .meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6em;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
.replies .avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.replies .author {
|
||||
font-weight: 600;
|
||||
}
|
||||
.replies .permalink {
|
||||
margin-left: auto;
|
||||
color: rgb(var(--gray));
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.replies p {
|
||||
margin: 0;
|
||||
}
|
||||
.mentions ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -22,6 +22,13 @@ export const ui = {
|
|||
'footer.contact': 'Kontakt',
|
||||
'footer.imprint': 'Impressum',
|
||||
'footer.privacy': 'Datenschutz',
|
||||
'webmentions.title': 'Reaktionen',
|
||||
'webmentions.like': 'Like',
|
||||
'webmentions.likes': 'Likes',
|
||||
'webmentions.repost': 'Repost',
|
||||
'webmentions.reposts': 'Reposts',
|
||||
'webmentions.replies': 'Antworten',
|
||||
'webmentions.mentions': 'Erwähnungen',
|
||||
'lang.de': 'Deutsch',
|
||||
'lang.en': 'English',
|
||||
},
|
||||
|
|
@ -46,6 +53,13 @@ export const ui = {
|
|||
'footer.contact': 'Contact',
|
||||
'footer.imprint': 'Imprint',
|
||||
'footer.privacy': 'Privacy',
|
||||
'webmentions.title': 'Reactions',
|
||||
'webmentions.like': 'Like',
|
||||
'webmentions.likes': 'Likes',
|
||||
'webmentions.repost': 'Repost',
|
||||
'webmentions.reposts': 'Reposts',
|
||||
'webmentions.replies': 'Replies',
|
||||
'webmentions.mentions': 'Mentions',
|
||||
'lang.de': 'Deutsch',
|
||||
'lang.en': 'English',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Image } from 'astro:assets';
|
|||
import { getEntry } from 'astro:content';
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import FormattedDate from '~/components/FormattedDate.astro';
|
||||
import Webmentions from '~/components/Webmentions.astro';
|
||||
import BaseLayout from '~/layouts/BaseLayout.astro';
|
||||
import { DEFAULT_LOCALE, type Locale } from '~/consts';
|
||||
import { categoryHref, entryHref, findTranslation, tagHref } from '~/i18n/posts';
|
||||
|
|
@ -31,11 +32,13 @@ const translation = entry ? await findTranslation(entry, otherLocale) : undefine
|
|||
---
|
||||
|
||||
<BaseLayout title={title} description={description} image={heroImage} locale={locale} entry={entry}>
|
||||
<article>
|
||||
<article class="h-entry">
|
||||
<a href={Astro.url.pathname} class="u-url" hidden></a>
|
||||
<div class="hero-image">
|
||||
{
|
||||
heroImage && (
|
||||
<Image
|
||||
class="u-photo"
|
||||
width={1020}
|
||||
height={510}
|
||||
src={heroImage}
|
||||
|
|
@ -48,21 +51,28 @@ const translation = entry ? await findTranslation(entry, otherLocale) : undefine
|
|||
<div class="prose">
|
||||
<div class="title">
|
||||
<div class="date">
|
||||
<FormattedDate date={pubDate} locale={locale} />
|
||||
<FormattedDate date={pubDate} locale={locale} class="dt-published" />
|
||||
{
|
||||
updatedDate && (
|
||||
<div class="last-updated-on">
|
||||
{t(locale, 'post.lastUpdated')} <FormattedDate date={updatedDate} locale={locale} />
|
||||
{t(locale, 'post.lastUpdated')}{' '}
|
||||
<FormattedDate date={updatedDate} locale={locale} class="dt-updated" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<h1>{title}</h1>
|
||||
<h1 class="p-name">{title}</h1>
|
||||
<p class="p-summary" hidden>{description}</p>
|
||||
<p class="p-author h-card" hidden>
|
||||
<a class="u-url p-name" href={new URL('/', Astro.site)}>Adrian Altner</a>
|
||||
</p>
|
||||
{
|
||||
categoryEntry && (
|
||||
<p class="category">
|
||||
{t(locale, 'post.category')}:{' '}
|
||||
<a href={categoryHref(categoryEntry)}>{categoryEntry.data.name}</a>
|
||||
<a href={categoryHref(categoryEntry)} class="p-category">
|
||||
{categoryEntry.data.name}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
|
@ -73,7 +83,9 @@ const translation = entry ? await findTranslation(entry, otherLocale) : undefine
|
|||
{tags.map((name, i) => (
|
||||
<>
|
||||
{i > 0 && ', '}
|
||||
<a href={tagHref(locale, name)}>{name}</a>
|
||||
<a href={tagHref(locale, name)} class="p-category">
|
||||
{name}
|
||||
</a>
|
||||
</>
|
||||
))}
|
||||
</p>
|
||||
|
|
@ -91,7 +103,10 @@ const translation = entry ? await findTranslation(entry, otherLocale) : undefine
|
|||
</aside>
|
||||
)
|
||||
}
|
||||
<slot />
|
||||
<div class="e-content">
|
||||
<slot />
|
||||
</div>
|
||||
<Webmentions target={new URL(Astro.url.pathname, Astro.site)} locale={locale} />
|
||||
</div>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
126
src/lib/webmentions.ts
Normal file
126
src/lib/webmentions.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Build-time Webmention fetcher via webmention.io API.
|
||||
*
|
||||
* Requires WEBMENTION_TOKEN in the environment (see .env.local / .env.production).
|
||||
* Token is read-only and is issued per registered webmention.io domain.
|
||||
*/
|
||||
|
||||
export type WMProperty = 'in-reply-to' | 'like-of' | 'repost-of' | 'bookmark-of' | 'mention-of';
|
||||
|
||||
export interface WMAuthor {
|
||||
type?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
photo?: string;
|
||||
}
|
||||
|
||||
export interface WMEntry {
|
||||
type: 'entry';
|
||||
author?: WMAuthor;
|
||||
url: string;
|
||||
published?: string;
|
||||
'wm-received'?: string;
|
||||
'wm-id'?: number;
|
||||
'wm-source'?: string;
|
||||
'wm-target'?: string;
|
||||
'wm-property'?: WMProperty;
|
||||
'wm-private'?: boolean;
|
||||
content?: { text?: string; html?: string };
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface WMResponse {
|
||||
type: 'feed';
|
||||
name?: string;
|
||||
children: WMEntry[];
|
||||
}
|
||||
|
||||
const API = 'https://webmention.io/api/mentions.jf2';
|
||||
|
||||
let cache: WMResponse | null = null;
|
||||
|
||||
async function fetchAll(): Promise<WMResponse> {
|
||||
if (cache) return cache;
|
||||
const token = import.meta.env.WEBMENTION_TOKEN;
|
||||
if (!token) {
|
||||
console.warn('[webmentions] WEBMENTION_TOKEN is not set — skipping fetch.');
|
||||
cache = { type: 'feed', children: [] };
|
||||
return cache;
|
||||
}
|
||||
const entries: WMEntry[] = [];
|
||||
let page = 0;
|
||||
const perPage = 100;
|
||||
while (true) {
|
||||
const url = new URL(API);
|
||||
url.searchParams.set('domain', 'adrian-altner.de');
|
||||
url.searchParams.set('token', token);
|
||||
url.searchParams.set('per-page', String(perPage));
|
||||
url.searchParams.set('page', String(page));
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
console.warn(`[webmentions] API ${res.status} ${res.statusText}`);
|
||||
break;
|
||||
}
|
||||
const json = (await res.json()) as WMResponse;
|
||||
entries.push(...(json.children ?? []));
|
||||
if (!json.children || json.children.length < perPage) break;
|
||||
page += 1;
|
||||
}
|
||||
cache = { type: 'feed', children: entries };
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all webmentions targeting a given absolute URL.
|
||||
* Matches both with and without trailing slash; ignores hash/query.
|
||||
*/
|
||||
export async function getMentionsFor(target: string | URL): Promise<WMEntry[]> {
|
||||
const { children } = await fetchAll();
|
||||
const wanted = normalize(target);
|
||||
return children.filter((m) => {
|
||||
const t = m['wm-target'];
|
||||
if (!t) return false;
|
||||
return normalize(t) === wanted;
|
||||
});
|
||||
}
|
||||
|
||||
function normalize(u: string | URL): string {
|
||||
const url = typeof u === 'string' ? new URL(u) : new URL(u.toString());
|
||||
url.hash = '';
|
||||
url.search = '';
|
||||
const path = url.pathname.replace(/\/+$/, '');
|
||||
return `${url.origin}${path}`;
|
||||
}
|
||||
|
||||
export function groupMentions(mentions: WMEntry[]): {
|
||||
likes: WMEntry[];
|
||||
reposts: WMEntry[];
|
||||
bookmarks: WMEntry[];
|
||||
replies: WMEntry[];
|
||||
mentions: WMEntry[];
|
||||
} {
|
||||
const likes: WMEntry[] = [];
|
||||
const reposts: WMEntry[] = [];
|
||||
const bookmarks: WMEntry[] = [];
|
||||
const replies: WMEntry[] = [];
|
||||
const others: WMEntry[] = [];
|
||||
for (const m of mentions) {
|
||||
switch (m['wm-property']) {
|
||||
case 'like-of':
|
||||
likes.push(m);
|
||||
break;
|
||||
case 'repost-of':
|
||||
reposts.push(m);
|
||||
break;
|
||||
case 'bookmark-of':
|
||||
bookmarks.push(m);
|
||||
break;
|
||||
case 'in-reply-to':
|
||||
replies.push(m);
|
||||
break;
|
||||
default:
|
||||
others.push(m);
|
||||
}
|
||||
}
|
||||
return { likes, reposts, bookmarks, replies, mentions: others };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue