WIP Nostr links
This commit is contained in:
parent
6ec9f51d77
commit
eadc40392a
@ -1,4 +1,3 @@
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ button {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
|
||||
code {
|
||||
background-color: #e8e3da;
|
||||
color: #027739;
|
||||
@ -49,7 +48,7 @@ pre code {
|
||||
}
|
||||
|
||||
dl dt {
|
||||
color: var(--text-color-discreet)
|
||||
color: var(--text-color-discreet);
|
||||
}
|
||||
|
||||
hr {
|
||||
@ -62,7 +61,7 @@ main header .draft-label {
|
||||
}
|
||||
|
||||
main header .meta .date {
|
||||
color: var(--text-color-discreet)
|
||||
color: var(--text-color-discreet);
|
||||
}
|
||||
|
||||
main header .meta .name a {
|
||||
@ -70,7 +69,7 @@ main header .meta .name a {
|
||||
}
|
||||
|
||||
main.profile-page .pubkey {
|
||||
color: var(--text-color-discreet)
|
||||
color: var(--text-color-discreet);
|
||||
}
|
||||
|
||||
/* Dropdown menu */
|
||||
|
@ -45,6 +45,7 @@ const config = {
|
||||
password: Deno.env.get("LDAP_PASSWORD"),
|
||||
searchDN: Deno.env.get("LDAP_SEARCH_DN"),
|
||||
},
|
||||
njump_url: Deno.env.get("NJUMP_URL") || "https://njump.me",
|
||||
};
|
||||
|
||||
const staticUsersConfigured = Object.keys(staticUsers).length > 0;
|
||||
|
15
feeds.ts
15
feeds.ts
@ -2,15 +2,20 @@ import Article from "./models/article.ts";
|
||||
import Profile from "./models/profile.ts";
|
||||
import { isoDate } from "./dates.ts";
|
||||
|
||||
export function profileAtomFeed(profile: Profile, articles: Article[]) {
|
||||
export async function profileAtomFeed(
|
||||
profile: Profile,
|
||||
articles: Article[],
|
||||
): Promise<string> {
|
||||
const feedId = `tag:${profile.nip05},nostr-p-${profile.pubkey}-k-30023`;
|
||||
const lastUpdate = articles.sort((a, b) => b.updatedAt - a.updatedAt)[0]
|
||||
?.updatedAt;
|
||||
let articlesXml = "";
|
||||
|
||||
const articlesXml = articles.map((article) => {
|
||||
for (const article of articles) {
|
||||
const contentHtml = await article.buildContentHtml();
|
||||
const articleId =
|
||||
`tag:${profile.nip05},nostr-d-${article.identifier}-k-30023`;
|
||||
return `
|
||||
articlesXml += `
|
||||
<entry>
|
||||
<id>${articleId}</id>
|
||||
<title>${article.title}</title>
|
||||
@ -19,11 +24,11 @@ export function profileAtomFeed(profile: Profile, articles: Article[]) {
|
||||
<published>${isoDate(article.publishedAt)}</published>
|
||||
<summary>${article.summary}</summary>
|
||||
<content type="html"><![CDATA[
|
||||
${cleanContentHtml(article.html)}
|
||||
${cleanContentHtml(contentHtml)}
|
||||
]]></content>
|
||||
</entry>
|
||||
`;
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
return `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
@ -27,7 +27,7 @@ const userEventHandler = async function (ctx: Context) {
|
||||
if (articleEvent && profileEvent) {
|
||||
const article = new Article(articleEvent);
|
||||
const profile = new Profile(profileEvent, username);
|
||||
const html = articleHtml(article, profile);
|
||||
const html = await articleHtml(article, profile);
|
||||
generateOgProfileImage(profile);
|
||||
|
||||
ctx.response.body = html;
|
||||
|
7
html.ts
7
html.ts
@ -38,7 +38,10 @@ export function errorPageHtml(statusCode: number, title: string): string {
|
||||
return htmlLayout({ title, body });
|
||||
}
|
||||
|
||||
export function articleHtml(article: Article, profile: Profile): string {
|
||||
export async function articleHtml(
|
||||
article: Article,
|
||||
profile: Profile,
|
||||
): Promise<string> {
|
||||
const publishedAtFormatted = localizeDate(article.publishedAt);
|
||||
const pageTitle = article.isDraft ? `Draft: ${article.title}` : article.title;
|
||||
let draftLabel = ``;
|
||||
@ -60,7 +63,7 @@ export function articleHtml(article: Article, profile: Profile): string {
|
||||
</div>
|
||||
</header>
|
||||
<article>
|
||||
${article.html}
|
||||
${await article.buildContentHtml()}
|
||||
<footer>
|
||||
${openWithNostrAppHtml(article.naddr)}
|
||||
</footer>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { render as renderMarkdown } from "@deno/gfm";
|
||||
import { nip19 } from "@nostr/tools";
|
||||
import { NostrEvent as NEvent } from "@nostrify/nostrify";
|
||||
import { replaceNostrUris } from "../nostr.ts";
|
||||
import config from "../config.ts";
|
||||
|
||||
export default class Article {
|
||||
@ -47,10 +48,6 @@ export default class Article {
|
||||
return this.event.created_at;
|
||||
}
|
||||
|
||||
get html(): string {
|
||||
return renderMarkdown(this.event.content);
|
||||
}
|
||||
|
||||
get naddr(): string {
|
||||
return nip19.naddrEncode({
|
||||
identifier: this.identifier,
|
||||
@ -59,4 +56,11 @@ export default class Article {
|
||||
relays: [config.relay_urls[0]],
|
||||
});
|
||||
}
|
||||
|
||||
async buildContentHtml(): Promise<string> {
|
||||
let md = this.event.content.trim();
|
||||
md = md.replace(`# ${this.title}\n`, "");
|
||||
md = await replaceNostrUris(md);
|
||||
return renderMarkdown(md);
|
||||
}
|
||||
}
|
||||
|
47
nostr.ts
47
nostr.ts
@ -1,4 +1,6 @@
|
||||
import { NPool, NRelay1 } from "@nostrify/nostrify";
|
||||
import { nip19 } from "@nostr/tools";
|
||||
import { lookupUsernameByPubkey } from "./directory.ts";
|
||||
import config from "./config.ts";
|
||||
|
||||
const relayPool = new NPool({
|
||||
@ -56,3 +58,48 @@ export async function fetchProfileEvent(pubkey: string) {
|
||||
|
||||
return events.length > 0 ? events[0] : null;
|
||||
}
|
||||
|
||||
export async function nostrUriToUrl(uri: string): Promise<string> {
|
||||
const bech32 = uri.replace(/^nostr:/, "");
|
||||
|
||||
if (bech32.match(/^(naddr|nprofile|npub)/)) {
|
||||
try {
|
||||
const r = nip19.decode(bech32);
|
||||
let username;
|
||||
|
||||
switch (r.type) {
|
||||
case "naddr":
|
||||
username = await lookupUsernameByPubkey(r.data.pubkey);
|
||||
if (username) return `/${bech32}`;
|
||||
break;
|
||||
case "nprofile":
|
||||
username = await lookupUsernameByPubkey(r.data.pubkey);
|
||||
if (username) return `/@${username}`;
|
||||
break;
|
||||
case "npub":
|
||||
username = await lookupUsernameByPubkey(r.data);
|
||||
if (username) return `/@${username}`;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
return `${config.njump_url}/${bech32}`;
|
||||
}
|
||||
|
||||
export async function replaceNostrUris(markdown: string): Promise<string> {
|
||||
const nostrUriRegex =
|
||||
/\((nostr:|nprofile|naddr|nevent|nrelay|npub)[a-z0-9]+\)/g;
|
||||
const matches = markdown.match(nostrUriRegex);
|
||||
if (!matches) return markdown;
|
||||
|
||||
for (const match of matches) {
|
||||
const uri = match.slice(1, -1);
|
||||
const url = await nostrUriToUrl(uri);
|
||||
markdown = markdown.replace(match, `(${url})`);
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
@ -58,12 +58,6 @@ describe("Article", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#html", () => {
|
||||
it("returns a rendered HTML version of the 'content'", () => {
|
||||
expect(article.html).toMatch(/<h2 id="the-solution">/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#naddr", () => {
|
||||
it("returns a bech32 addressable event ID", () => {
|
||||
expect(article.naddr).toMatch(
|
||||
@ -71,4 +65,11 @@ describe("Article", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#buildContentHtml", () => {
|
||||
it("returns a rendered HTML version of the 'content'", async () => {
|
||||
const html = await article.buildContentHtml();
|
||||
expect(html).toMatch(/<h2 id="the-solution">/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user