Browse Source

implemented emoji rendering and cleaned up nostr address rendering

master
Silberengel 10 months ago
parent
commit
68ab68bede
  1. 66
      package-lock.json
  2. 1
      package.json
  3. 18
      src/lib/utils/markdown/basicMarkdownParser.ts
  4. 12
      src/lib/utils/markdown/markdownTestfile.md
  5. 28
      src/lib/utils/nostrUtils.ts

66
package-lock.json generated

@ -17,6 +17,7 @@
"d3": "^7.9.0", "d3": "^7.9.0",
"he": "1.2.x", "he": "1.2.x",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"node-emoji": "^2.2.0",
"nostr-tools": "2.10.x" "nostr-tools": "2.10.x"
}, },
"devDependencies": { "devDependencies": {
@ -24,7 +25,7 @@
"@sveltejs/adapter-auto": "3.x", "@sveltejs/adapter-auto": "3.x",
"@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/adapter-static": "3.x", "@sveltejs/adapter-static": "3.x",
"@sveltejs/kit": "2.x", "@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "4.x", "@sveltejs/vite-plugin-svelte": "4.x",
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@types/he": "1.2.x", "@types/he": "1.2.x",
@ -1364,6 +1365,18 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sveltejs/adapter-auto": { "node_modules/@sveltejs/adapter-auto": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz",
@ -2454,6 +2467,15 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/character-parser": { "node_modules/character-parser": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@ -3162,6 +3184,12 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/emojilib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
"integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==",
"license": "MIT"
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -4775,6 +4803,21 @@
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/node-emoji": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
"integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==",
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^4.6.0",
"char-regex": "^1.0.2",
"emojilib": "^2.4.0",
"skin-tone": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/node-gyp-build": { "node_modules/node-gyp-build": {
"version": "4.8.4", "version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
@ -5806,6 +5849,18 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/skin-tone": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
"integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
"license": "MIT",
"dependencies": {
"unicode-emoji-modifier-base": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -6542,6 +6597,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unicode-emoji-modifier-base": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
"integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unxhr": { "node_modules/unxhr": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.2.0.tgz", "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.2.0.tgz",

1
package.json

@ -23,6 +23,7 @@
"d3": "^7.9.0", "d3": "^7.9.0",
"he": "1.2.x", "he": "1.2.x",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"node-emoji": "^2.2.0",
"nostr-tools": "2.10.x" "nostr-tools": "2.10.x"
}, },
"devDependencies": { "devDependencies": {

18
src/lib/utils/markdown/basicMarkdownParser.ts

@ -1,4 +1,5 @@
import { processNostrIdentifiers } from '../nostrUtils'; import { processNostrIdentifiers } from '../nostrUtils';
import * as emoji from 'node-emoji';
// Regular expressions for basic markdown elements // Regular expressions for basic markdown elements
const BOLD_REGEX = /(\*\*|[*])((?:[^*\n]|\*(?!\*))+)\1/g; const BOLD_REGEX = /(\*\*|[*])((?:[^*\n]|\*(?!\*))+)\1/g;
@ -25,6 +26,8 @@ const VIDEO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp4|webm|mov|avi)(?:[^\s<]*)?/i;
const AUDIO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp3|wav|ogg|m4a)(?:[^\s<]*)?/i; const AUDIO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp3|wav|ogg|m4a)(?:[^\s<]*)?/i;
const YOUTUBE_URL_REGEX = /https?:\/\/(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/|youtube-nocookie\.com\/embed\/)([a-zA-Z0-9_-]{11})(?:[^\s<]*)?/i; const YOUTUBE_URL_REGEX = /https?:\/\/(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/|youtube-nocookie\.com\/embed\/)([a-zA-Z0-9_-]{11})(?:[^\s<]*)?/i;
// Emoji shortcut pattern
const EMOJI_SHORTCUT_REGEX = /:([a-zA-Z0-9_+-]+):/g;
function processBasicFormatting(content: string): string { function processBasicFormatting(content: string): string {
if (!content) return ''; if (!content) return '';
@ -133,6 +136,18 @@ function processBlockquotes(content: string): string {
} }
} }
function processEmojiShortcuts(content: string): string {
try {
return emoji.emojify(content, { fallback: (name: string) => {
const emojiChar = emoji.get(name);
return emojiChar || `:${name}:`;
}});
} catch (error) {
console.error('Error in processEmojiShortcuts:', error);
return content;
}
}
export async function parseBasicMarkdown(text: string): Promise<string> { export async function parseBasicMarkdown(text: string): Promise<string> {
if (!text) return ''; if (!text) return '';
@ -140,6 +155,9 @@ export async function parseBasicMarkdown(text: string): Promise<string> {
// Process basic text formatting first // Process basic text formatting first
let processedText = processBasicFormatting(text); let processedText = processBasicFormatting(text);
// Process emoji shortcuts
processedText = processEmojiShortcuts(processedText);
// Process lists - handle ordered lists first // Process lists - handle ordered lists first
processedText = processedText processedText = processedText
// Process ordered lists // Process ordered lists

12
src/lib/utils/markdown/markdownTestfile.md

@ -70,6 +70,16 @@ Here with a naddr:
nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqzasj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwmsu0ktnz nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqzasj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwmsu0ktnz
Here's a nonsense one:
nevent123
And some Nostr addresses that should be ignored:
https://lumina.rocks/note/note1sd0hkhxr49jsetkcrjkvf2uls5m8frkue6f5huj8uv4964p2d8fs8dn68z
https://primal.net/e/nevent1qqsqum7j25p9z8vcyn93dsd7edx34w07eqav50qnde3vrfs466q558gdd02yr
This is an implementation of [Nostr-flavored Markdown](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes. This is an implementation of [Nostr-flavored Markdown](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes.
You can even include `code inline`, like `<div class="leather min-h-full w-full flex flex-col items-center">` or You can even include `code inline`, like `<div class="leather min-h-full w-full flex flex-col items-center">` or
@ -162,6 +172,8 @@ content starts at 4-columns in.
> if you like. > if you like.
``` ```
Test out some emojis :heart: and :trophy:
#### Here is an image! #### Here is an image!
![Nostr logo](https://user-images.githubusercontent.com/99301796/219900773-d6d02038-e2a0-4334-9f28-c14d40ab6fe7.png) ![Nostr logo](https://user-images.githubusercontent.com/99301796/219900773-d6d02038-e2a0-4334-9f28-c14d40ab6fe7.png)

28
src/lib/utils/nostrUtils.ts

@ -118,13 +118,27 @@ function createNoteLink(identifier: string): string {
* Process Nostr identifiers in text * Process Nostr identifiers in text
*/ */
export async function processNostrIdentifiers(content: string): Promise<string> { export async function processNostrIdentifiers(content: string): Promise<string> {
console.log('Processing Nostr identifiers:', { input: content });
let processedContent = content; let processedContent = content;
// Helper to check if a match is part of a URL
function isPartOfUrl(text: string, index: number): boolean {
// Look for http(s):// or www. before the match
const before = text.slice(Math.max(0, index - 12), index);
return /https?:\/\/$|www\.$/i.test(before);
}
// Process profiles (npub and nprofile) // Process profiles (npub and nprofile)
const profileMatches = Array.from(content.matchAll(NOSTR_PROFILE_REGEX)); const profileMatches = Array.from(content.matchAll(NOSTR_PROFILE_REGEX));
for (const match of profileMatches) { for (const match of profileMatches) {
const [fullMatch, identifier] = match; const [fullMatch] = match;
const matchIndex = match.index ?? 0;
if (isPartOfUrl(content, matchIndex)) {
continue; // skip if part of a URL
}
let identifier = fullMatch;
if (!identifier.startsWith('nostr:')) {
identifier = 'nostr:' + identifier;
}
const metadata = await getUserMetadata(identifier); const metadata = await getUserMetadata(identifier);
const displayText = metadata.displayName || metadata.name; const displayText = metadata.displayName || metadata.name;
const link = createProfileLink(identifier, displayText); const link = createProfileLink(identifier, displayText);
@ -134,7 +148,15 @@ export async function processNostrIdentifiers(content: string): Promise<string>
// Process notes (nevent, note, naddr) // Process notes (nevent, note, naddr)
const noteMatches = Array.from(processedContent.matchAll(NOSTR_NOTE_REGEX)); const noteMatches = Array.from(processedContent.matchAll(NOSTR_NOTE_REGEX));
for (const match of noteMatches) { for (const match of noteMatches) {
const [fullMatch, identifier] = match; const [fullMatch] = match;
const matchIndex = match.index ?? 0;
if (isPartOfUrl(processedContent, matchIndex)) {
continue; // skip if part of a URL
}
let identifier = fullMatch;
if (!identifier.startsWith('nostr:')) {
identifier = 'nostr:' + identifier;
}
const link = createNoteLink(identifier); const link = createNoteLink(identifier);
processedContent = processedContent.replace(fullMatch, link); processedContent = processedContent.replace(fullMatch, link);
} }

Loading…
Cancel
Save