@commonpub/layer 0.3.11 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -45,14 +45,14 @@
45
45
  "vue-router": "^4.3.0",
46
46
  "zod": "^4.3.6",
47
47
  "@commonpub/auth": "0.5.0",
48
- "@commonpub/config": "0.7.0",
49
48
  "@commonpub/docs": "0.5.2",
50
- "@commonpub/learning": "0.5.0",
51
- "@commonpub/editor": "0.5.0",
49
+ "@commonpub/config": "0.7.0",
52
50
  "@commonpub/protocol": "0.9.4",
51
+ "@commonpub/editor": "0.5.0",
52
+ "@commonpub/learning": "0.5.0",
53
53
  "@commonpub/schema": "0.8.10",
54
54
  "@commonpub/ui": "0.7.1",
55
- "@commonpub/server": "2.9.0"
55
+ "@commonpub/server": "2.10.0"
56
56
  },
57
57
  "scripts": {}
58
58
  }
@@ -208,11 +208,12 @@ async function deleteItem(id: string, title: string): Promise<void> {
208
208
  <div class="cpub-dash-list">
209
209
  <div v-for="bm in bookmarkData?.items ?? []" :key="bm.id" class="cpub-dash-row">
210
210
  <template v-if="bm.content">
211
- <NuxtLink :to="`/${bm.content.type}/${bm.content.slug}`" class="cpub-dash-row-title">
211
+ <NuxtLink :to="bm.isFederated ? `/mirror/${bm.targetId}` : `/${bm.content.type}/${bm.content.slug}`" class="cpub-dash-row-title">
212
212
  {{ bm.content.title }}
213
213
  </NuxtLink>
214
214
  <span class="cpub-dash-row-meta">
215
215
  <ContentTypeBadge :type="bm.content.type" />
216
+ <span v-if="bm.isFederated && bm.content.originDomain" class="cpub-dash-row-fed"><i class="fa-solid fa-globe"></i> {{ bm.content.originDomain }}</span>
216
217
  <span v-if="bm.content.author">by {{ bm.content.author.displayName || bm.content.author.username }}</span>
217
218
  </span>
218
219
  </template>
@@ -0,0 +1,72 @@
1
+ import { getFederatedHub, listFederatedHubPosts } from '@commonpub/server';
2
+
3
+ function escapeXml(str: string): string {
4
+ return str
5
+ .replace(/&/g, '&amp;')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, '&quot;')
9
+ .replace(/'/g, '&apos;');
10
+ }
11
+
12
+ function stripHtml(html: string): string {
13
+ return html.replace(/<[^>]*>/g, '').trim();
14
+ }
15
+
16
+ export default defineEventHandler(async (event) => {
17
+ requireFeature('federation');
18
+ requireFeature('federateHubs');
19
+
20
+ const db = useDB();
21
+ const config = useRuntimeConfig();
22
+ const siteUrl = config.public.siteUrl as string;
23
+ const { id } = parseParams(event, { id: 'uuid' });
24
+
25
+ const hub = await getFederatedHub(db, id);
26
+ if (!hub) {
27
+ throw createError({ statusCode: 404, statusMessage: 'Federated hub not found' });
28
+ }
29
+
30
+ const { items } = await listFederatedHubPosts(db, id, { limit: 50 });
31
+
32
+ const lastBuildDate = items.length > 0 && items[0]!.publishedAt
33
+ ? new Date(items[0]!.publishedAt).toUTCString()
34
+ : new Date().toUTCString();
35
+
36
+ const rssItems = items.map((item) => {
37
+ const title = item.sharedContentMeta?.title
38
+ ? `Shared: ${item.sharedContentMeta.title}`
39
+ : stripHtml(item.content).slice(0, 100) || 'Post';
40
+ const link = item.objectUri;
41
+ const pubDate = item.publishedAt ? new Date(item.publishedAt).toUTCString() : new Date(item.receivedAt).toUTCString();
42
+ const authorName = item.author.displayName ?? item.author.preferredUsername ?? 'Unknown';
43
+ const desc = stripHtml(item.content).slice(0, 300);
44
+
45
+ return ` <item>
46
+ <title>${escapeXml(title)}</title>
47
+ <link>${escapeXml(link)}</link>
48
+ <guid isPermaLink="false">${escapeXml(link)}</guid>
49
+ <pubDate>${pubDate}</pubDate>
50
+ <description>${escapeXml(desc)}</description>
51
+ <author>${escapeXml(authorName)}@${escapeXml(item.author.instanceDomain)}</author>
52
+ <category>${escapeXml(item.postType)}</category>
53
+ </item>`;
54
+ });
55
+
56
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
57
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
58
+ <channel>
59
+ <title>${escapeXml(hub.name)} (via ${escapeXml(hub.originDomain)})</title>
60
+ <link>${escapeXml(hub.url ?? `${siteUrl}/federated-hubs/${id}`)}</link>
61
+ <description>${escapeXml(hub.description ?? `Posts from ${hub.name} on ${hub.originDomain}`)}</description>
62
+ <language>en</language>
63
+ <lastBuildDate>${lastBuildDate}</lastBuildDate>
64
+ <atom:link href="${escapeXml(siteUrl)}/api/federated-hubs/${id}/feed.xml" rel="self" type="application/rss+xml"/>
65
+ ${rssItems.join('\n')}
66
+ </channel>
67
+ </rss>`;
68
+
69
+ setResponseHeader(event, 'Content-Type', 'application/rss+xml; charset=utf-8');
70
+ setResponseHeader(event, 'Cache-Control', 'public, max-age=600, stale-while-revalidate=300');
71
+ return xml;
72
+ });