feed: render links in text notes
ci/woodpecker/pr/woodpecker Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details

Links should be rendered as anchors so that they are clickable.
For now only links starting with http:// https:// or www. are
supported.

Temporary disabled text notes shortening.
pull/29/head
OFF0 2 years ago
parent ec9945cf05
commit 8ec9420be8
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -24,16 +24,52 @@ export function elem(name = 'div', {data, ...props} = {}, children = []) {
return el;
}
/**
* Renders line breaks
*
* @param {string} text with newlines
* @return Array<TextNode | HTMLBRElement>
*/
export function multilineText(string) {
return string
.trimRight()
.replaceAll(/\n{3,}/g, '\n\n')
.split('\n')
.reduce((acc, next, i) => acc.concat(i === 0 ? next : [elem('br'), next]), []);
function isValidURL(url) {
if (!['http:', 'https:'].includes(url.protocol)) {
return false;
}
if (!['', '443', '80'].includes(url.port)) {
return false;
}
const lastDot = url.hostname.lastIndexOf('.');
if (lastDot < 1) {
return false;
}
if (url.hostname.slice(lastDot) === '.local') {
return false;
}
return true;
}
export function parseTextContent(string) {
return string
.trimRight()
.replaceAll(/\n{3,}/g, '\n\n')
.split('\n')
.map(line => {
const words = line.split(' ');
return words.map(word => {
if (!word.match(/(https?:\/\/|www\.)\S*/)) {
return word;
}
try {
if (!word.startsWith('http')) {
word = 'https://' + word;
}
const url = new URL(word);
if (!isValidURL(url)) {
return word;
}
return elem('a', {
href: url.href,
target: '_blank',
rel: 'noopener noreferrer'
}, url.href.slice(url.protocol.length + 2));
} catch (err) {
return word;
}
})
.reduce((acc, word) => [...acc, word, ' '], []);
})
.reduce((acc, words) => [...acc, ...words, elem('br')], []);
}

@ -1,5 +1,5 @@
import {relayPool, generatePrivateKey, getPublicKey, signEvent} from 'nostr-tools';
import {elem, multilineText} from './domutil.js';
import {elem, parseTextContent} from './domutil.js';
import {dateTime, formatTime} from './timeutil.js';
// curl -H 'accept: application/nostr+json' https://nostr.x1ddos.ch
const pool = relayPool();
@ -155,11 +155,12 @@ setInterval(() => {
function createTextNote(evt, relay) {
const {host, img, isReply, name, replies, time, userName} = getMetadata(evt, relay);
const isLongContent = evt.content.trimRight().length > 280;
const content = isLongContent ? evt.content.slice(0, 280) : evt.content;
// const isLongContent = evt.content.trimRight().length > 280;
// const content = isLongContent ? evt.content.slice(0, 280) : evt.content;
const hasReactions = reactionMap[evt.id]?.length > 0;
const didReact = hasReactions && !!reactionMap[evt.id].find(reaction => reaction.pubkey === pubkey);
const replyFeed = replies[0] ? replies.map(e => replyDomMap[e.id] = createTextNote(e, relay)) : [];
const content = parseTextContent(evt.content);
const body = elem('div', {className: 'mbox-body'}, [
elem('header', {
className: 'mbox-header',
@ -173,7 +174,7 @@ function createTextNote(evt, relay) {
elem('time', {dateTime: time.toISOString()}, formatTime(time)),
]),
]),
elem('div', {data: isLongContent ? {append: evt.content.slice(280)} : null}, multilineText(content)),
elem('div', {/* data: isLongContent ? {append: evt.content.slice(280)} : null*/}, content),
elem('button', {
className: 'btn-inline', name: 'star', type: 'button',
data: {'eventId': evt.id, relay},
@ -630,12 +631,12 @@ privateKeyInput.value = localStorage.getItem('private_key');
pubKeyInput.value = localStorage.getItem('pub_key');
document.body.addEventListener('click', (e) => {
const container = e.target.closest('[data-append]');
if (container) {
container.append(...multilineText(container.dataset.append));
delete container.dataset.append;
return;
}
// const container = e.target.closest('[data-append]');
// if (container) {
// container.append(...parseTextContent(container.dataset.append));
// delete container.dataset.append;
// return;
// }
const back = e.target.closest('[name="back"]')
if (back) {
hideNewMessage(true);

Loading…
Cancel
Save