Fixes a bunch of problems with how Nostr links are created and replaced in Markdown content
This commit is contained in:
parent
fb37db8583
commit
aeabf7725f
@ -3,7 +3,7 @@
|
||||
"dev": "deno run --allow-all --watch 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",
|
||||
"test": "deno test --allow-read --allow-env"
|
||||
"test": "DENO_ENV=test deno test --allow-read --allow-env"
|
||||
},
|
||||
"imports": {
|
||||
"@deno/gfm": "jsr:@deno/gfm@^0.10.0",
|
||||
@ -12,7 +12,7 @@
|
||||
"@oak/oak": "jsr:@oak/oak@^17.1.3",
|
||||
"@std/dotenv": "jsr:@std/dotenv@^0.225.2",
|
||||
"@std/expect": "jsr:@std/expect@^1.0.8",
|
||||
"@std/testing": "jsr:@std/testing@^1.0.5",
|
||||
"@std/testing": "jsr:@std/testing@^1.0.11",
|
||||
"@std/yaml": "jsr:@std/yaml@^1.0.5",
|
||||
"ldapts": "npm:ldapts@^7.2.2"
|
||||
},
|
||||
|
60
deno.lock
generated
60
deno.lock
generated
@ -3,34 +3,34 @@
|
||||
"specifiers": {
|
||||
"jsr:@deno/gfm@0.10": "0.10.0",
|
||||
"jsr:@denosaurs/emoji@0.3": "0.3.1",
|
||||
"jsr:@nostr/tools@^2.10.4": "2.10.4",
|
||||
"jsr:@nostr/tools@^2.10.4": "2.12.0",
|
||||
"jsr:@nostrify/nostrify@~0.36.2": "0.36.2",
|
||||
"jsr:@nostrify/types@0.35": "0.35.0",
|
||||
"jsr:@oak/commons@1": "1.0.0",
|
||||
"jsr:@oak/oak@^17.1.3": "17.1.3",
|
||||
"jsr:@std/assert@0.224": "0.224.0",
|
||||
"jsr:@std/assert@1": "1.0.8",
|
||||
"jsr:@std/assert@^1.0.12": "1.0.12",
|
||||
"jsr:@std/assert@^1.0.8": "1.0.8",
|
||||
"jsr:@std/bytes@1": "1.0.4",
|
||||
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
||||
"jsr:@std/crypto@0.224": "0.224.0",
|
||||
"jsr:@std/crypto@1": "1.0.3",
|
||||
"jsr:@std/data-structures@^1.0.4": "1.0.4",
|
||||
"jsr:@std/dotenv@~0.225.2": "0.225.2",
|
||||
"jsr:@std/dotenv@~0.225.2": "0.225.3",
|
||||
"jsr:@std/encoding@0.224": "0.224.3",
|
||||
"jsr:@std/encoding@1": "1.0.5",
|
||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||
"jsr:@std/encoding@~0.224.1": "0.224.3",
|
||||
"jsr:@std/expect@^1.0.8": "1.0.8",
|
||||
"jsr:@std/fs@^1.0.5": "1.0.5",
|
||||
"jsr:@std/expect@^1.0.8": "1.0.15",
|
||||
"jsr:@std/http@1": "1.0.10",
|
||||
"jsr:@std/internal@^1.0.5": "1.0.5",
|
||||
"jsr:@std/internal@^1.0.6": "1.0.6",
|
||||
"jsr:@std/io@0.224": "0.224.9",
|
||||
"jsr:@std/media-types@1": "1.1.0",
|
||||
"jsr:@std/path@1": "1.0.8",
|
||||
"jsr:@std/path@^1.0.7": "1.0.8",
|
||||
"jsr:@std/path@^1.0.8": "1.0.8",
|
||||
"jsr:@std/testing@^1.0.5": "1.0.5",
|
||||
"jsr:@std/testing@^1.0.11": "1.0.11",
|
||||
"jsr:@std/yaml@*": "1.0.5",
|
||||
"jsr:@std/yaml@^1.0.5": "1.0.5",
|
||||
"npm:@noble/ciphers@~0.5.1": "0.5.3",
|
||||
@ -90,6 +90,15 @@
|
||||
"npm:nostr-wasm"
|
||||
]
|
||||
},
|
||||
"@nostr/tools@2.12.0": {
|
||||
"integrity": "0584d5197682c6eabaded17bae10e765f215ef051ae70aa463f994abf90f295a",
|
||||
"dependencies": [
|
||||
"npm:@noble/ciphers",
|
||||
"npm:@noble/curves",
|
||||
"npm:@noble/hashes",
|
||||
"npm:@scure/base@1.1.1"
|
||||
]
|
||||
},
|
||||
"@nostrify/nostrify@0.36.2": {
|
||||
"integrity": "cc4787ca170b623a2e5dfed1baa4426077daa6143af728ea7dd325d58f4d04d6",
|
||||
"dependencies": [
|
||||
@ -139,7 +148,13 @@
|
||||
"@std/assert@1.0.8": {
|
||||
"integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal"
|
||||
"jsr:@std/internal@^1.0.5"
|
||||
]
|
||||
},
|
||||
"@std/assert@1.0.12": {
|
||||
"integrity": "08009f0926dda9cbd8bef3a35d3b6a4b964b0ab5c3e140a4e0351fbf34af5b9a",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/bytes@1.0.4": {
|
||||
@ -155,12 +170,12 @@
|
||||
"@std/crypto@1.0.3": {
|
||||
"integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
|
||||
},
|
||||
"@std/data-structures@1.0.4": {
|
||||
"integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0"
|
||||
},
|
||||
"@std/dotenv@0.225.2": {
|
||||
"integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23"
|
||||
},
|
||||
"@std/dotenv@0.225.3": {
|
||||
"integrity": "a95e5b812c27b0854c52acbae215856d9cce9d4bbf774d938c51d212711e8d4a"
|
||||
},
|
||||
"@std/encoding@0.224.3": {
|
||||
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
||||
},
|
||||
@ -171,13 +186,14 @@
|
||||
"integrity": "27e40d8f3aefb372fc6a703fb0b69e34560e72a2f78705178babdffa00119a5f",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^1.0.8",
|
||||
"jsr:@std/internal"
|
||||
"jsr:@std/internal@^1.0.5"
|
||||
]
|
||||
},
|
||||
"@std/fs@1.0.5": {
|
||||
"integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e",
|
||||
"@std/expect@1.0.15": {
|
||||
"integrity": "eca360007b5a7f13dbfa1294224baee7fb98dcd460d8461fe64eeae302902945",
|
||||
"dependencies": [
|
||||
"jsr:@std/path@^1.0.7"
|
||||
"jsr:@std/assert@^1.0.12",
|
||||
"jsr:@std/internal@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/http@1.0.10": {
|
||||
@ -189,6 +205,9 @@
|
||||
"@std/internal@1.0.5": {
|
||||
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
|
||||
},
|
||||
"@std/internal@1.0.6": {
|
||||
"integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4"
|
||||
},
|
||||
"@std/io@0.224.9": {
|
||||
"integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3",
|
||||
"dependencies": [
|
||||
@ -201,14 +220,11 @@
|
||||
"@std/path@1.0.8": {
|
||||
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||
},
|
||||
"@std/testing@1.0.5": {
|
||||
"integrity": "6e693cbec94c81a1ad3df668685c7ba8e20742bb10305bc7137faa5cf16d2ec4",
|
||||
"@std/testing@1.0.11": {
|
||||
"integrity": "12b3db12d34f0f385a26248933bde766c0f8c5ad8b6ab34d4d38f528ab852f48",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^1.0.8",
|
||||
"jsr:@std/data-structures",
|
||||
"jsr:@std/fs",
|
||||
"jsr:@std/internal",
|
||||
"jsr:@std/path@^1.0.8"
|
||||
"jsr:@std/assert@^1.0.12",
|
||||
"jsr:@std/internal@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/yaml@1.0.5": {
|
||||
@ -519,7 +535,7 @@
|
||||
"jsr:@oak/oak@^17.1.3",
|
||||
"jsr:@std/dotenv@~0.225.2",
|
||||
"jsr:@std/expect@^1.0.8",
|
||||
"jsr:@std/testing@^1.0.5",
|
||||
"jsr:@std/testing@^1.0.11",
|
||||
"jsr:@std/yaml@^1.0.5",
|
||||
"npm:ldapts@^7.2.2"
|
||||
]
|
||||
|
@ -1,7 +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 { replaceNostrUris } from "../nostr/links.ts";
|
||||
import config from "../config.ts";
|
||||
|
||||
export default class Article {
|
||||
|
44
nostr.ts
44
nostr.ts
@ -1,6 +1,5 @@
|
||||
import { NostrEvent, NostrFilter, NPool, NRelay1 } from "@nostrify/nostrify";
|
||||
import { nip19 } from "@nostr/tools";
|
||||
import { lookupUsernameByPubkey } from "./directory.ts";
|
||||
import config from "./config.ts";
|
||||
import Article from "./models/article.ts";
|
||||
|
||||
@ -79,49 +78,6 @@ 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|npub)[a-z0-9]+/g;
|
||||
const matches = markdown.match(nostrUriRegex);
|
||||
if (!matches) return markdown;
|
||||
|
||||
for (const uri of matches) {
|
||||
const url = await nostrUriToUrl(uri);
|
||||
markdown = markdown.replace(uri, url);
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
export async function verifyNip05Address(
|
||||
address: string,
|
||||
pubkey: string,
|
||||
|
130
nostr/links.ts
Normal file
130
nostr/links.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import { lookupUsernameByPubkey } from "../directory.ts";
|
||||
import { nip19 } from "@nostr/tools";
|
||||
import config from "../config.ts";
|
||||
|
||||
const nostrUriRegex = /(nostr:|@)(nprofile|naddr|nevent|npub)[a-z0-9]+/g;
|
||||
|
||||
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 protectedRegex = /(`{3,}[\s\S]*?`{3,})|(`[^`]*`)|(<pre>[\s\S]*?<\/pre>)|(https?:\/\/[^\s<>"']+)/gi;
|
||||
|
||||
// Split text into segments: unprotected text and protected areas (code blocks, URLs)
|
||||
const segments: string[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
markdown.replace(protectedRegex, (match, _fencedCode, _inlineCode, _preTag, _url, index) => {
|
||||
segments.push(markdown.slice(lastIndex, index));
|
||||
segments.push(match);
|
||||
lastIndex = index + match.length;
|
||||
return match;
|
||||
});
|
||||
segments.push(markdown.slice(lastIndex));
|
||||
|
||||
// Process each segment
|
||||
let result = '';
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
if (i % 2 === 1 || protectedRegex.test(segments[i])) {
|
||||
// Protected segment (code block or URL), leave unchanged
|
||||
result += segments[i];
|
||||
} else {
|
||||
// Unprotected text, replace URIs and handle markdown links
|
||||
result += await processUnprotectedText(segments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function processUnprotectedText(text: string): Promise<string> {
|
||||
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||
let modifiedText = text;
|
||||
let lastIndex = 0;
|
||||
const parts: string[] = [];
|
||||
|
||||
// Process markdown links first
|
||||
let match;
|
||||
while ((match = markdownLinkRegex.exec(text)) !== null) {
|
||||
const [fullMatch, linkText, target] = match;
|
||||
|
||||
// Add text before the link
|
||||
parts.push(await replaceUrisInText(text.slice(lastIndex, match.index)));
|
||||
|
||||
// Process the link target
|
||||
if (nostrUriRegex.test(target) && target.match(nostrUriRegex)![0] === target) {
|
||||
// Target is a Nostr URI, replace with resolved URL
|
||||
const resolvedUrl = await nostrUriToUrl(target);
|
||||
parts.push(`[${linkText}](${resolvedUrl})`);
|
||||
} else {
|
||||
// Not a Nostr URI, keep the original link
|
||||
parts.push(fullMatch);
|
||||
}
|
||||
|
||||
lastIndex = match.index + fullMatch.length;
|
||||
}
|
||||
|
||||
// Add any remaining text after the last link
|
||||
parts.push(await replaceUrisInText(text.slice(lastIndex)));
|
||||
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
async function replaceUrisInText(text: string): Promise<string> {
|
||||
let modifiedText = text;
|
||||
const replacements: { start: number; end: number; replacement: string }[] = [];
|
||||
|
||||
// Collect all replacements for bare Nostr URIs
|
||||
let match;
|
||||
while ((match = nostrUriRegex.exec(modifiedText)) !== null) {
|
||||
const fullUri = match[0];
|
||||
const url = await nostrUriToUrl(fullUri);
|
||||
const linkTitle = cleanUriForTitle(fullUri);
|
||||
const markdownLink = `[${linkTitle}](${url})`;
|
||||
replacements.push({
|
||||
start: match.index,
|
||||
end: match.index + fullUri.length,
|
||||
replacement: markdownLink,
|
||||
});
|
||||
}
|
||||
|
||||
// Apply replacements from right to left to avoid index shifting
|
||||
for (let i = replacements.length - 1; i >= 0; i--) {
|
||||
const { start, end, replacement } = replacements[i];
|
||||
modifiedText = modifiedText.slice(0, start) + replacement + modifiedText.slice(end);
|
||||
}
|
||||
|
||||
return modifiedText;
|
||||
}
|
||||
|
||||
function cleanUriForTitle(uri: string): string {
|
||||
// Remove "nostr:" prefix, keep "@" for the title
|
||||
return uri.startsWith('nostr:') ? uri.replace(/^nostr:/, '') : uri;
|
||||
}
|
41
tests/fixtures/article-2.md
vendored
Normal file
41
tests/fixtures/article-2.md
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
I think we should agree on an HTML attribute for pointing to the Nostr representation of a document/URL on the Web. We could use the existing one for link relations for example. Something like:
|
||||
|
||||
```html
|
||||
<link rel="alternate" type="application/nostr+json"
|
||||
href="nostr:naddr1qvzqqqr4..."
|
||||
title="This article on Nostr" />
|
||||
```
|
||||
|
||||
This would be useful in multiple ways:
|
||||
|
||||
1. Existing Web publications can retroactively create Nostr versions of their content and easily link the Nostr articles on all of their existing article pages.
|
||||
2. Nostr clients, when fetching meta/preview information for a URL that is linked in a note, can detect that there's a Nostr representation of the content, and then render it in Nostr-native ways (whatever that may be depending on the client)
|
||||
3. User agents, usually a browser or browser extension, when opening a URL on the Web, can offer opening the alternative representation of a page in a Nostr client. And/or they could offer to follow the author's pubkey on Nostr. And/or they could offer to zap the content.
|
||||
4. When publishing a new article on Nostr, authors can share their preferred Web URL everywhere, without having to consider if the recipients' clients support Nostr IDs and content or not. This makes it easy for the reader to share the author's preferred Web URL on any medium, instead of sharing a link to whatever their own Nostr client prefers (usually its own Web UI).
|
||||
|
||||
|
||||
### Testing Nostr IDs
|
||||
|
||||
Receive [special badges](https://badges.page/p/npub1cpmvpsqtzxl4px44dp4544xwgu0ryv2lscl3qexq42dfakuza02s4fsapc)
|
||||
|
||||
raucao scheme 1: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees
|
||||
raucao at 1: @npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees
|
||||
raucao scheme 2: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees
|
||||
raucao at 2: @npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees
|
||||
raucao scheme link 1: [raucao](nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees)
|
||||
raucao scheme link 2: [raucao](nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees)
|
||||
|
||||
Amber scheme 1: nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7
|
||||
Amber at 1: @npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7
|
||||
Amber scheme 2: nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7
|
||||
Amber at 2: @npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7
|
||||
Amber scheme link 1: [Amber](nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7)
|
||||
Amber scheme link 2: [Amber](nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7)
|
||||
|
||||
## More protected text
|
||||
|
||||
```
|
||||
Follow nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7
|
||||
```
|
||||
|
||||
Inline: `raucao: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees`
|
62
tests/nostr/links_test.ts
Normal file
62
tests/nostr/links_test.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { beforeAll, describe, it } from "@std/testing/bdd";
|
||||
import { expect } from "@std/expect";
|
||||
import { replaceNostrUris } from "../../nostr/links.ts";
|
||||
|
||||
describe("Nostr links", () => {
|
||||
|
||||
describe("#replaceNostrUris", () => {
|
||||
let mdContent: string;
|
||||
let result: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
mdContent = Deno.readTextFileSync("tests/fixtures/article-2.md"),
|
||||
result = await replaceNostrUris(mdContent);
|
||||
});
|
||||
|
||||
it("does not replace URIs in URLs", async () => {
|
||||
expect(result).toMatch(new RegExp("https://badges.page/p/npub1cpmvpsqtzxl4px44dp4544xwgu0ryv2lscl3qexq42dfakuza02s4fsapc"));
|
||||
});
|
||||
|
||||
it("does not replace URIs in fenced code blocks", async () => {
|
||||
expect(result).toMatch(new RegExp("Follow nostr:npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7"));
|
||||
});
|
||||
|
||||
it("does not replace URIs in inline code blocks", async () => {
|
||||
expect(result).toMatch(new RegExp("raucao: nostr:npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees"));
|
||||
});
|
||||
|
||||
describe("for unknown usernames", () => {
|
||||
it("replaces plain nostr:id URIs with a markdown link", async () => {
|
||||
expect(result).toMatch(/Amber scheme 1\: \[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 () => {
|
||||
expect(result).toMatch(/Amber at 1\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
|
||||
expect(result).toMatch(/Amber at 2\: \[@npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
|
||||
});
|
||||
|
||||
it("replaces nostr links with external links", async () => {
|
||||
expect(result).toMatch(/Amber scheme link 1\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
|
||||
expect(result).toMatch(/Amber scheme link 2\: \[Amber\]\(https\:\/\/njump\.me\/npub1am3ermkr250dywukzqnaug64cred3x5jht6f3kdhfp3h0rgtjlpqecxrv7\)/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("for known usernames", () => {
|
||||
it("replaces plain nostr:id URIs with a markdown link", async () => {
|
||||
expect(result).toMatch(/raucao scheme 1\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
|
||||
expect(result).toMatch(/raucao scheme 2\: \[npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
|
||||
});
|
||||
|
||||
it("replaces @id URIs with a markdown link", async () => {
|
||||
expect(result).toMatch(/raucao at 1\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
|
||||
expect(result).toMatch(/raucao at 2\: \[@npub1raustrrh5gjwt03zdj8syn9vmt2dwsv9t467m8c3gua636uxu89svgdees\]\(\/@raucao\)/);
|
||||
});
|
||||
|
||||
it("replaces scheme links with internal links", async () => {
|
||||
expect(result).toMatch(/raucao scheme link 1\: \[raucao\]\(\/@raucao\)/);
|
||||
expect(result).toMatch(/raucao scheme link 2\: \[raucao\]\(\/@raucao\)/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user