3 Commits

Author SHA1 Message Date
5608176a20 Add some margin to list items
All checks were successful
CI / Test and lint (push) Successful in 16s
2025-04-23 15:30:07 +04:00
fa21e72b3f Add alternative nostr links to HTML meta tags
All checks were successful
CI / Test and lint (push) Successful in 19s
2025-04-22 13:09:03 +04:00
5b0397268b Improve Nostr link handling
Some checks failed
CI / Test and lint (push) Failing after 10m22s
Fixes a bunch of problems with how Nostr links are created and replaced
in Markdown content
2025-04-21 16:53:22 +04:00
7 changed files with 18 additions and 13 deletions

View File

@@ -21,6 +21,10 @@ p, pre, ul, ol, dl, blockquote, hr {
margin-bottom: 1.6em; margin-bottom: 1.6em;
} }
li {
margin-bottom: 0.8em;
}
a.anchor { a.anchor {
display: none; display: none;
} }

View File

@@ -3,7 +3,7 @@
"dev": "deno run --allow-all --watch server.ts", "dev": "deno run --allow-all --watch server.ts",
"server": "deno run --allow-all server.ts", "server": "deno run --allow-all server.ts",
"compile": "deno compile --allow-all --include ./assets/ --output ./build/substr_x86_64-unknown-linux-gnu server.ts", "compile": "deno compile --allow-all --include ./assets/ --output ./build/substr_x86_64-unknown-linux-gnu server.ts",
"test": "DENO_ENV=test deno test tests/nostr_test.ts --allow-read --allow-env" "test": "DENO_ENV=test deno test --allow-read --allow-env"
}, },
"imports": { "imports": {
"@deno/gfm": "jsr:@deno/gfm@^0.10.0", "@deno/gfm": "jsr:@deno/gfm@^0.10.0",

View File

@@ -1,3 +1,4 @@
// deno-lint-ignore-file require-await
import config from "./config.ts"; import config from "./config.ts";
import { lookupUsernameByPubkey as ldapLookupUsername } from "./ldap.ts"; import { lookupUsernameByPubkey as ldapLookupUsername } from "./ldap.ts";
import { lookupPubkeyByUsername as ldapLookupPubkey } from "./ldap.ts"; import { lookupPubkeyByUsername as ldapLookupPubkey } from "./ldap.ts";

View File

@@ -212,6 +212,7 @@ function feedLinksHtml(profile: Profile) {
function profileMetaHtml(profile: Profile) { function profileMetaHtml(profile: Profile) {
return ` return `
<link rel="icon" href="${profile.avatarImageUrl}" type="image/png"> <link rel="icon" href="${profile.avatarImageUrl}" type="image/png">
<link rel="alternate" type="application/nostr+json" href="nostr:${profile.npub}" title="${profile.name} on Nostr">
<meta property="og:url" content="${profile.profileUrl}"> <meta property="og:url" content="${profile.profileUrl}">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:title" content="${profile.name} on Nostr"> <meta property="og:title" content="${profile.name} on Nostr">
@@ -231,6 +232,7 @@ function articleMetaHtml(article: Article, profile: Profile) {
return ` return `
<link rel="icon" href="${profile.avatarImageUrl}" type="image/png"> <link rel="icon" href="${profile.avatarImageUrl}" type="image/png">
<link rel="alternate" type="application/nostr+json" href="nostr:${article.naddr}" title="This article on Nostr">
<meta property="og:url" content="${article.url}"> <meta property="og:url" content="${article.url}">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:title" content="${article.title}"> <meta property="og:title" content="${article.title}">

View File

@@ -1,5 +1,4 @@
import { NostrEvent, NostrFilter, NPool, NRelay1 } from "@nostrify/nostrify"; import { NostrEvent, NostrFilter, NPool, NRelay1 } from "@nostrify/nostrify";
import { nip19 } from "@nostr/tools";
import config from "./config.ts"; import config from "./config.ts";
import Article from "./models/article.ts"; import Article from "./models/article.ts";

View File

@@ -66,7 +66,6 @@ export async function replaceNostrUris(markdown: string): Promise<string> {
async function processUnprotectedText(text: string): Promise<string> { async function processUnprotectedText(text: string): Promise<string> {
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let modifiedText = text;
let lastIndex = 0; let lastIndex = 0;
const parts: string[] = []; const parts: string[] = [];

View File

@@ -1,6 +1,6 @@
import { beforeAll, describe, it } from "@std/testing/bdd"; import { beforeAll, describe, it } from "@std/testing/bdd";
import { expect } from "@std/expect"; import { expect } from "@std/expect";
import { replaceNostrUris } from "../nostr/links.ts"; import { replaceNostrUris } from "../../nostr/links.ts";
describe("Nostr links", () => { describe("Nostr links", () => {
@@ -13,47 +13,47 @@ describe("Nostr links", () => {
result = await replaceNostrUris(mdContent); result = await replaceNostrUris(mdContent);
}); });
it("does not replace URIs in URLs", async () => { it("does not replace URIs in URLs", () => {
expect(result).toMatch(new RegExp("https://badges.page/p/npub1cpmvpsqtzxl4px44dp4544xwgu0ryv2lscl3qexq42dfakuza02s4fsapc")); expect(result).toMatch(new RegExp("https://badges.page/p/npub1cpmvpsqtzxl4px44dp4544xwgu0ryv2lscl3qexq42dfakuza02s4fsapc"));
}); });
it("does not replace URIs in fenced code blocks", async () => { it("does not replace URIs in fenced code blocks", () => {
expect(result).toMatch(new RegExp("Follow nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7")); expect(result).toMatch(new RegExp("Follow nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7"));
}); });
it("does not replace URIs in inline code blocks", async () => { it("does not replace URIs in inline code blocks", () => {
expect(result).toMatch(new RegExp("raucao: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees")); expect(result).toMatch(new RegExp("raucao: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees"));
}); });
describe("for unknown usernames", () => { describe("for unknown usernames", () => {
it("replaces plain nostr:id URIs with a markdown link", async () => { it("replaces plain nostr:id URIs with a markdown link", () => {
expect(result).toMatch(/Amber scheme 1\: \[npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber scheme 1\: \[npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
expect(result).toMatch(/Amber scheme 2\: \[npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber scheme 2\: \[npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
}); });
it("replaces @id URIs with a markdown link", async () => { it("replaces @id URIs with a markdown link", () => {
expect(result).toMatch(/Amber at 1\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber at 1\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
expect(result).toMatch(/Amber at 2\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber at 2\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
}); });
it("replaces nostr links with external links", async () => { it("replaces nostr links with external links", () => {
expect(result).toMatch(/Amber scheme link 1\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber scheme link 1\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
expect(result).toMatch(/Amber scheme link 2\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/); expect(result).toMatch(/Amber scheme link 2\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
}); });
}); });
describe("for known usernames", () => { describe("for known usernames", () => {
it("replaces plain nostr:id URIs with a markdown link", async () => { it("replaces plain nostr:id URIs with a markdown link", () => {
expect(result).toMatch(/raucao scheme 1\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/); expect(result).toMatch(/raucao scheme 1\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
expect(result).toMatch(/raucao scheme 2\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/); expect(result).toMatch(/raucao scheme 2\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
}); });
it("replaces @id URIs with a markdown link", async () => { it("replaces @id URIs with a markdown link", () => {
expect(result).toMatch(/raucao at 1\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/); expect(result).toMatch(/raucao at 1\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
expect(result).toMatch(/raucao at 2\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/); expect(result).toMatch(/raucao at 2\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
}); });
it("replaces scheme links with internal links", async () => { it("replaces scheme links with internal links", () => {
expect(result).toMatch(/raucao scheme link 1\: \[raucao\]\(\/@raucao\)/); expect(result).toMatch(/raucao scheme link 1\: \[raucao\]\(\/@raucao\)/);
expect(result).toMatch(/raucao scheme link 2\: \[raucao\]\(\/@raucao\)/); expect(result).toMatch(/raucao scheme link 2\: \[raucao\]\(\/@raucao\)/);
}); });