From 18f3e888a6eda283a655866c6af8b5f00f30ff8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 21 Oct 2024 00:06:15 +0200 Subject: [PATCH] Show list of linked articles on profile page --- handlers/naddr.ts | 12 ++++----- handlers/nprofile.ts | 2 +- handlers/npub.ts | 2 +- handlers/username.ts | 7 ++--- html.ts | 64 +++++++++++++++++++++++++++++++++++++------- ldap.ts | 16 +++++------ main.ts | 7 +++-- nostr.ts | 15 ++++++++++- 8 files changed, 93 insertions(+), 32 deletions(-) diff --git a/handlers/naddr.ts b/handlers/naddr.ts index b6716c5..cfd3ce7 100644 --- a/handlers/naddr.ts +++ b/handlers/naddr.ts @@ -1,11 +1,8 @@ import { Context } from "@oak/oak"; import { nip19 } from "@nostr/tools"; import { log } from "../log.ts"; -import { articleHtml } from "../html.ts" -import { - fetchReplaceableEvent, - fetchProfileEvent -} from "../nostr.ts"; +import { articleHtml } from "../html.ts"; +import { fetchProfileEvent, fetchReplaceableEvent } from "../nostr.ts"; const naddrHandler = async function (ctx: Context) { const { request } = ctx; @@ -13,7 +10,10 @@ const naddrHandler = async function (ctx: Context) { try { const r = nip19.decode(naddr); - const articleEvent = await fetchReplaceableEvent(r.data.pubkey, r.data.identifier); + const articleEvent = await fetchReplaceableEvent( + r.data.pubkey, + r.data.identifier, + ); const profileEvent = await fetchProfileEvent(r.data.pubkey); let profile; diff --git a/handlers/nprofile.ts b/handlers/nprofile.ts index 33703c2..5a889d9 100644 --- a/handlers/nprofile.ts +++ b/handlers/nprofile.ts @@ -3,7 +3,7 @@ import { nip19 } from "@nostr/tools"; import { log } from "../log.ts"; import { lookupUsernameByPubkey } from "../ldap.ts"; import { fetchProfileEvent } from "../nostr.ts"; -import { profilePageHtml } from "../html.ts" +import { profilePageHtml } from "../html.ts"; const nprofileHandler = async function (ctx: Context) { const { request } = ctx; diff --git a/handlers/npub.ts b/handlers/npub.ts index 19dcd22..9cb32f5 100644 --- a/handlers/npub.ts +++ b/handlers/npub.ts @@ -3,7 +3,7 @@ import { nip19 } from "@nostr/tools"; import { log } from "../log.ts"; import { lookupUsernameByPubkey } from "../ldap.ts"; import { fetchProfileEvent } from "../nostr.ts"; -import { profilePageHtml } from "../html.ts" +import { profilePageHtml } from "../html.ts"; const npubHandler = async function (ctx: Context) { const { request } = ctx; diff --git a/handlers/username.ts b/handlers/username.ts index c2581bb..f4d9f98 100644 --- a/handlers/username.ts +++ b/handlers/username.ts @@ -2,8 +2,8 @@ import { Context } from "@oak/oak"; import { nip19 } from "@nostr/tools"; import { log } from "../log.ts"; import { lookupPubkeyByUsername } from "../ldap.ts"; -import { fetchProfileEvent } from "../nostr.ts"; -import { profilePageHtml } from "../html.ts" +import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts"; +import { profilePageHtml } from "../html.ts"; const usernameHandler = async function (ctx: Context) { const { request } = ctx; @@ -20,7 +20,8 @@ const usernameHandler = async function (ctx: Context) { const profileEvent = await fetchProfileEvent(pubkey); if (profileEvent) { - const html = profilePageHtml(profileEvent); + const articleEvents = await fetchArticlesByAuthor(pubkey); + const html = profilePageHtml(profileEvent, articleEvents); ctx.response.body = html; } else { diff --git a/html.ts b/html.ts index 05707a2..00165d1 100644 --- a/html.ts +++ b/html.ts @@ -1,4 +1,5 @@ import { render as renderMarkdown } from "@deno/gfm"; +import { nip19 } from "@nostr/tools"; import { log } from "./log.ts"; export function htmlLayout(title: string, body: string) { @@ -47,6 +48,7 @@ export function htmlLayout(title: string, body: string) { h2, h3, h4 { margin-top: 2em; margin-bottom: 2rem; + line-height: 1.6em; } h1, h2, h3, h4 { @@ -110,6 +112,14 @@ export function htmlLayout(title: string, body: string) { p.meta .date { color: #888; } + + .article-list .item { + margin-bottom: 3rem; + } + + .article-list .item h3 { + margin-bottom: 1rem; + } @@ -120,7 +130,7 @@ export function htmlLayout(title: string, body: string) { } export function articleHtml(articleEvent: object, profile: object) { - const titleTag = articleEvent.tags.find(t => t[0] === "title"); + const titleTag = articleEvent.tags.find((t) => t[0] === "title"); const title = titleTag ? titleTag[1] : "Untitled"; const content = renderMarkdown(articleEvent.content); const date = new Date(articleEvent.created_at * 1000); @@ -149,16 +159,50 @@ export function articleHtml(articleEvent: object, profile: object) { return htmlLayout(title, body); } -export function profilePageHtml(profileEvent: object) { +function articleListItemHtml(articleEvent: object) { + const identifier = articleEvent.tags.find((t) => t[0] === "d")[1]; + const naddr = nip19.naddrEncode({ + identifier: identifier, + pubkey: articleEvent.pubkey, + kind: articleEvent.kind + }); + const titleTag = articleEvent.tags.find((t) => t[0] === "title"); + const title = titleTag ? titleTag[1] : "Untitled"; + const date = new Date(articleEvent.created_at * 1000); + const formattedDate = date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); + + return ` +
+

${title}

+

${formattedDate}

+
+ `; +} + +export function articleListHtml(articleEvents: object[]) { + if (articleEvents.length === 0) return ""; + let html = ""; + + for (const articleEvent of articleEvents) { + html += articleListItemHtml(articleEvent); + } + + return ` +

Articles

+
+ ${html} +
+ `; +} + +export function profilePageHtml(profileEvent: object, articleEvents: object[]) { const profile = JSON.parse(profileEvent.content); const name = profile.name || "Anonymous"; - const title = `${name} on Nostr` - // const date = new Date(articleEvent.created_at * 1000); - // const formattedDate = date.toLocaleDateString("en-US", { - // year: "numeric", - // month: "long", - // day: "numeric", - // }); + const title = `${name} on Nostr`; const body = `
@@ -171,7 +215,7 @@ export function profilePageHtml(profileEvent: object) {

-

Articles

+ ${articleListHtml(articleEvents)}
`; diff --git a/ldap.ts b/ldap.ts index 6131339..37cb118 100644 --- a/ldap.ts +++ b/ldap.ts @@ -1,20 +1,20 @@ import { load } from "@std/dotenv"; -import { Client } from 'ldapts'; +import { Client } from "ldapts"; import { log } from "./log.ts"; -const dirname = new URL('.', import.meta.url).pathname; +const dirname = new URL(".", import.meta.url).pathname; await load({ envPath: `${dirname}/.env`, export: true }); const config = { url: Deno.env.get("LDAP_URL"), bindDN: Deno.env.get("LDAP_BIND_DN"), password: Deno.env.get("LDAP_PASSWORD"), - searchDN: Deno.env.get("LDAP_SEARCH_DN") -} + searchDN: Deno.env.get("LDAP_SEARCH_DN"), +}; const client = new Client({ url: config.url }); -export async function lookupPubkeyByUsername (username: string) { +export async function lookupPubkeyByUsername(username: string) { let pubkey; try { @@ -22,7 +22,7 @@ export async function lookupPubkeyByUsername (username: string) { const { searchEntries } = await client.search(config.searchDN, { filter: `(cn=${username})`, - attributes: ['nostrKey'] + attributes: ["nostrKey"], }); pubkey = searchEntries[0]?.nostrKey; @@ -34,7 +34,7 @@ export async function lookupPubkeyByUsername (username: string) { } } -export async function lookupUsernameByPubkey (pubkey: string) { +export async function lookupUsernameByPubkey(pubkey: string) { let username; try { @@ -42,7 +42,7 @@ export async function lookupUsernameByPubkey (pubkey: string) { const { searchEntries } = await client.search(config.searchDN, { filter: `(nostrKey=${pubkey})`, - attributes: ['cn'] + attributes: ["cn"], }); username = searchEntries[0]?.cn; diff --git a/main.ts b/main.ts index 8f322b8..2d374b8 100644 --- a/main.ts +++ b/main.ts @@ -23,7 +23,10 @@ router.get("/:path", async (ctx: ctx) => { ctx.response.body = "Not Found"; } - log(`${ctx.request.method} ${ctx.request.url} - ${ctx.response.status}`, "gray"); + log( + `${ctx.request.method} ${ctx.request.url} - ${ctx.response.status}`, + "gray", + ); }); const app = new Application(); @@ -33,4 +36,4 @@ app.use(router.allowedMethods()); const PORT = 8000; app.listen({ port: PORT }); -console.log(`App listening on http://localhost:${PORT}`) +console.log(`App listening on http://localhost:${PORT}`); diff --git a/nostr.ts b/nostr.ts index 3480237..5d7c330 100644 --- a/nostr.ts +++ b/nostr.ts @@ -2,7 +2,10 @@ import { NRelay1 } from "@nostrify/nostrify"; export const relay = new NRelay1("wss://nostr.kosmos.org"); -export async function fetchReplaceableEvent(pubkey: string, identifier: string) { +export async function fetchReplaceableEvent( + pubkey: string, + identifier: string, +) { const events = await relay.query([{ authors: [pubkey], kinds: [30023], @@ -13,6 +16,16 @@ export async function fetchReplaceableEvent(pubkey: string, identifier: string) return events.length > 0 ? events[0] : null; } +export async function fetchArticlesByAuthor(pubkey: string) { + const events = await relay.query([{ + authors: [pubkey], + kinds: [30023], + limit: 10, + }]); + + return events; +} + export async function fetchProfileEvent(pubkey: string) { const events = await relay.query([{ authors: [pubkey],