Use the node short description in link preview metadata
Hop-State: A_06FPEC5CSW59V0KHWX0W1ER Hop-Proposal: R_06FPEC4XSJJ03M62S8AA9P0 Hop-Task: T_06FPEBTP53BQJHKGSPK894G Hop-Attempt: AT_06FPEBTP50KTCJ3YT789R78
This commit is contained in:
+9
-11
@@ -13,26 +13,24 @@ const sairaCondensed = Saira_Condensed({
|
|||||||
variable: "--font-saira",
|
variable: "--font-saira",
|
||||||
});
|
});
|
||||||
|
|
||||||
import { db } from "@/db";
|
import { getLocalNode } from "@/lib/node/local-node";
|
||||||
|
import { buildNodeLinkMetadata, DEFAULT_NODE_DESCRIPTION } from "@/lib/node/metadata";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
let title = "Synapsis";
|
let node = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const node = await db.query.nodes.findFirst();
|
node = await getLocalNode();
|
||||||
if (node?.name) {
|
|
||||||
title = node.name;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch node info for metadata", e);
|
console.error("Failed to fetch node info for metadata", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: {
|
...buildNodeLinkMetadata(
|
||||||
default: title,
|
node,
|
||||||
template: `%s | ${title}`,
|
process.env.NEXT_PUBLIC_NODE_NAME || "Synapsis",
|
||||||
},
|
process.env.NEXT_PUBLIC_NODE_DESCRIPTION || DEFAULT_NODE_DESCRIPTION
|
||||||
description: "Synapsis is designed to function like a global signal layer rather than a culture-bound platform. Anyone can run their own node and still participate in a shared, interconnected network, with global identity, clean terminology, and a modern interface that feels current rather than experimental.",
|
),
|
||||||
manifest: "/manifest.json",
|
manifest: "/manifest.json",
|
||||||
icons: {
|
icons: {
|
||||||
icon: "/api/favicon",
|
icon: "/api/favicon",
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
|
|
||||||
export async function isLocalNodeNsfw(): Promise<boolean> {
|
export async function getLocalNode() {
|
||||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || process.env.NODE_DOMAIN || 'localhost:43821';
|
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || process.env.NODE_DOMAIN || 'localhost:43821';
|
||||||
|
let node = await db.query.nodes.findFirst({ where: { domain } });
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
const localNodes = await db.query.nodes.findMany({ limit: 2 });
|
||||||
|
if (localNodes.length === 1) node = localNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return node ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isLocalNodeNsfw(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
let node = await db.query.nodes.findFirst({ where: { domain } });
|
const node = await getLocalNode();
|
||||||
if (!node) {
|
|
||||||
const localNodes = await db.query.nodes.findMany({ limit: 2 });
|
|
||||||
if (localNodes.length === 1) node = localNodes[0];
|
|
||||||
}
|
|
||||||
return node?.isNsfw === true;
|
return node?.isNsfw === true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Local node NSFW lookup failed:', error);
|
console.error('Local node NSFW lookup failed:', error);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { buildNodeLinkMetadata } from './metadata';
|
||||||
|
|
||||||
|
describe('buildNodeLinkMetadata', () => {
|
||||||
|
it('uses the node short description everywhere link previews read it', () => {
|
||||||
|
const metadata = buildNodeLinkMetadata({
|
||||||
|
name: 'Example Node',
|
||||||
|
description: 'A concise description of this community.',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(metadata.description).toBe('A concise description of this community.');
|
||||||
|
expect(metadata.openGraph).toMatchObject({
|
||||||
|
title: 'Example Node',
|
||||||
|
description: 'A concise description of this community.',
|
||||||
|
});
|
||||||
|
expect(metadata.twitter).toMatchObject({
|
||||||
|
title: 'Example Node',
|
||||||
|
description: 'A concise description of this community.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses configured fallbacks when node branding is blank', () => {
|
||||||
|
const metadata = buildNodeLinkMetadata(
|
||||||
|
{ name: ' ', description: ' ' },
|
||||||
|
'Fallback Node',
|
||||||
|
'Fallback description'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(metadata.description).toBe('Fallback description');
|
||||||
|
expect(metadata.openGraph).toMatchObject({
|
||||||
|
title: 'Fallback Node',
|
||||||
|
description: 'Fallback description',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const DEFAULT_NODE_DESCRIPTION = 'A swarm social network node.';
|
||||||
|
|
||||||
|
type NodeBranding = {
|
||||||
|
name?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildNodeLinkMetadata(
|
||||||
|
node?: NodeBranding | null,
|
||||||
|
fallbackName = 'Synapsis',
|
||||||
|
fallbackDescription = DEFAULT_NODE_DESCRIPTION
|
||||||
|
): Pick<Metadata, 'title' | 'description' | 'openGraph' | 'twitter'> {
|
||||||
|
const title = node?.name?.trim() || fallbackName;
|
||||||
|
const description = node?.description?.trim() || fallbackDescription;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
default: title,
|
||||||
|
template: `%s | ${title}`,
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
siteName: title,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary',
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user