@ -217,7 +217,7 @@ document.body.addEventListener('click', (e) => {
writeInput . blur ( ) ;
writeInput . blur ( ) ;
return ;
return ;
}
}
appendReplyForm ( button );
appendReplyForm ( button .closest ( '.buttons' ) );
localStorage . setItem ( 'reply_to' , id ) ;
localStorage . setItem ( 'reply_to' , id ) ;
return ;
return ;
}
}
@ -442,26 +442,24 @@ function createTextNote(evt, relay) {
... content ,
... content ,
( firstLink && validatePow ( evt ) ) ? linkPreview ( firstLink , evt . id , relay ) : '' ,
( firstLink && validatePow ( evt ) ) ? linkPreview ( firstLink , evt . id , relay ) : '' ,
] ) ,
] ) ,
elem ( 'button' , {
elem ( 'div' , { className : 'buttons' } , [
className : 'btn-inline' , name : 'star' , type : 'button' ,
elem ( 'button' , { name : 'reply' , type : 'button' } , [
data : { 'eventId' : evt . id , relay } ,
elem ( 'img' , { height : 24 , width : 24 , src : 'assets/comment.svg' } )
} , [
] ) ,
elem ( 'button' , { name : 'star' , type : 'button' } , [
elem ( 'img' , {
elem ( 'img' , {
alt : didReact ? '✭' : '✩' , // ♥
alt : didReact ? '✭' : '✩' , // ♥
height : 24 , width : 24 ,
height : 24 , width : 24 ,
src : ` assets/ ${ didReact ? 'star-fill' : 'star' } .svg ` ,
src : ` assets/ ${ didReact ? 'star-fill' : 'star' } .svg ` ,
title : getReactionList ( evt . id ) . join ( ' ' ) ,
title : getReactionList ( evt . id ) . join ( ' ' ) ,
} ) ,
} ) ,
elem ( 'small' , { data : { reactions : evt . id } } , hasReactions ? reactionMap [ evt . id ] . length : '' ) ,
elem ( 'small' , { data : { reactions : '' } } , hasReactions ? reactionMap [ evt . id ] . length : '' ) ,
] ) ,
] ) ,
] ) ,
elem ( 'button' , {
className : 'btn-inline' , name : 'reply' , type : 'button' ,
data : { 'eventId' : evt . id , relay } ,
} , [ elem ( 'img' , { height : 24 , width : 24 , src : 'assets/comment.svg' } ) ] ) ,
// replies[0] ? elem('div', {className: 'mobx-replies'}, replyFeed.reverse()) : '',
// replies[0] ? elem('div', {className: 'mobx-replies'}, replyFeed.reverse()) : '',
] ) ;
] ) ;
if ( restoredReplyTo === evt . id ) {
if ( restoredReplyTo === evt . id ) {
appendReplyForm ( body . querySelector ( ' button[name="reply"] ') ) ;
appendReplyForm ( body . querySelector ( '.buttons' ) ) ;
requestAnimationFrame ( ( ) => updateElemHeight ( writeInput ) ) ;
requestAnimationFrame ( ( ) => updateElemHeight ( writeInput ) ) ;
}
}
return renderArticle ( [
return renderArticle ( [
@ -770,7 +768,6 @@ miningTimeoutInput.addEventListener('input', (e) => {
miningTimeoutInput . value = timeout ;
miningTimeoutInput . value = timeout ;
async function upvote ( eventId , eventPubkey ) {
async function upvote ( eventId , eventPubkey ) {
const privatekey = localStorage . getItem ( 'private_key' ) ;
const note = replyList . find ( r => r . id === eventId ) || textNoteList . find ( n => n . id === ( eventId ) ) ;
const note = replyList . find ( r => r . id === eventId ) || textNoteList . find ( n => n . id === ( eventId ) ) ;
const tags = [
const tags = [
... note . tags
... note . tags
@ -778,16 +775,26 @@ async function upvote(eventId, eventPubkey) {
. map ( ( [ a , b ] ) => [ a , b ] ) , // drop optional (nip-10) relay and marker fields
. map ( ( [ a , b ] ) => [ a , b ] ) , // drop optional (nip-10) relay and marker fields
[ 'e' , eventId ] , [ 'p' , eventPubkey ] , // last e and p tag is the id and pubkey of the note being reacted to (nip-25)
[ 'e' , eventId ] , [ 'p' , eventPubkey ] , // last e and p tag is the id and pubkey of the note being reacted to (nip-25)
] ;
] ;
const article = ( feedDomMap [ eventId ] || replyDomMap [ eventId ] ) ;
const reactionBtn = article . querySelector ( '[name="star"]' ) ;
const statusElem = article . querySelector ( '[data-reactions]' ) ;
reactionBtn . disabled = true ;
const newReaction = await powEvent ( {
const newReaction = await powEvent ( {
kind : 7 ,
kind : 7 ,
pubkey , // TODO: lib could check that this is the pubkey of the key to sign with
pubkey , // TODO: lib could check that this is the pubkey of the key to sign with
content : '+' ,
content : '+' ,
tags ,
tags ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
} , difficulty , timeout ) . catch ( console . warn ) ;
} , { difficulty , statusElem , timeout } ) . catch ( console . warn ) ;
if ( newReaction ) {
if ( ! newReaction ) {
statusElem . textContent = reactionMap [ eventId ] ? . length ;
reactionBtn . disabled = false ;
return ;
}
const privatekey = localStorage . getItem ( 'private_key' ) ;
const sig = await signEvent ( newReaction , privatekey ) . catch ( console . error ) ;
const sig = await signEvent ( newReaction , privatekey ) . catch ( console . error ) ;
if ( sig ) {
if ( sig ) {
statusElem . textContent = 'publishing…' ;
const ev = await pool . publish ( { ... newReaction , sig } , ( status , url ) => {
const ev = await pool . publish ( { ... newReaction , sig } , ( status , url ) => {
if ( status === 0 ) {
if ( status === 0 ) {
console . info ( ` publish request sent to ${ url } ` ) ;
console . info ( ` publish request sent to ${ url } ` ) ;
@ -796,7 +803,7 @@ async function upvote(eventId, eventPubkey) {
console . info ( ` event published by ${ url } ` ) ;
console . info ( ` event published by ${ url } ` ) ;
}
}
} ) . catch ( console . error ) ;
} ) . catch ( console . error ) ;
}
reactionBtn . disabled = false ;
}
}
}
}
@ -816,6 +823,17 @@ writeForm.addEventListener('submit', async (e) => {
return onSendError ( new Error ( 'message is empty' ) ) ;
return onSendError ( new Error ( 'message is empty' ) ) ;
}
}
const replyTo = localStorage . getItem ( 'reply_to' ) ;
const replyTo = localStorage . getItem ( 'reply_to' ) ;
const close = ( ) => {
sendStatus . textContent = '' ;
writeInput . value = '' ;
writeInput . style . removeProperty ( 'height' ) ;
publish . disabled = true ;
if ( replyTo ) {
localStorage . removeItem ( 'reply_to' ) ;
newMessageDiv . append ( writeForm ) ;
}
hideNewMessage ( true ) ;
} ;
const tags = replyTo ? [ [ 'e' , replyTo , eventRelayMap [ replyTo ] [ 0 ] ] ] : [ ] ;
const tags = replyTo ? [ [ 'e' , replyTo , eventRelayMap [ replyTo ] [ 0 ] ] ] : [ ] ;
const newEvent = await powEvent ( {
const newEvent = await powEvent ( {
kind : 1 ,
kind : 1 ,
@ -823,29 +841,24 @@ writeForm.addEventListener('submit', async (e) => {
pubkey ,
pubkey ,
tags ,
tags ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
} , difficulty , timeout ) . catch ( console . warn ) ;
} , { difficulty , statusElem : sendStatus , timeout } ) . catch ( console . warn ) ;
if ( newEvent ) {
if ( ! newEvent ) {
close ( ) ;
return ;
}
const sig = await signEvent ( newEvent , privatekey ) . catch ( onSendError ) ;
const sig = await signEvent ( newEvent , privatekey ) . catch ( onSendError ) ;
if ( sig ) {
if ( sig ) {
sendStatus . textContent = 'publishing…' ;
const ev = await pool . publish ( { ... newEvent , sig } , ( status , url ) => {
const ev = await pool . publish ( { ... newEvent , sig } , ( status , url ) => {
if ( status === 0 ) {
if ( status === 0 ) {
console . info ( ` publish request sent to ${ url } ` ) ;
console . info ( ` publish request sent to ${ url } ` ) ;
}
}
if ( status === 1 ) {
if ( status === 1 ) {
sendStatus . textContent = '' ;
close ( ) ;
writeInput . value = '' ;
writeInput . style . removeProperty ( 'height' ) ;
publish . disabled = true ;
if ( replyTo ) {
localStorage . removeItem ( 'reply_to' ) ;
newMessageDiv . append ( writeForm ) ;
}
hideNewMessage ( true ) ;
// console.info(`event published by ${url}`, ev);
// console.info(`event published by ${url}`, ev);
}
}
} ) ;
} ) ;
}
}
}
} ) ;
} ) ;
writeInput . addEventListener ( 'input' , ( ) => {
writeInput . addEventListener ( 'input' , ( ) => {
@ -959,16 +972,19 @@ profileForm.addEventListener('input', (e) => {
profileForm . addEventListener ( 'submit' , async ( e ) => {
profileForm . addEventListener ( 'submit' , async ( e ) => {
e . preventDefault ( ) ;
e . preventDefault ( ) ;
const form = new FormData ( profileForm ) ;
const form = new FormData ( profileForm ) ;
const privatekey = localStorage . getItem ( 'private_key' ) ;
const newProfile = await powEvent ( {
const newProfile = await powEvent ( {
kind : 0 ,
kind : 0 ,
pubkey ,
pubkey ,
content : JSON . stringify ( Object . fromEntries ( form ) ) ,
content : JSON . stringify ( Object . fromEntries ( form ) ) ,
tags : [ ] ,
tags : [ ] ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
created _at : Math . floor ( Date . now ( ) * 0.001 ) ,
} , difficulty , timeout ) . catch ( console . warn ) ;
} , { difficulty , statusElem : profileStatus , timeout } ) . catch ( console . warn ) ;
if ( newProfile ) {
if ( ! newProfile ) {
profileStatus . textContent = 'publishing profile data canceled' ;
profileStatus . hidden = false ;
return ;
}
const privatekey = localStorage . getItem ( 'private_key' ) ;
const sig = await signEvent ( newProfile , privatekey ) . catch ( console . error ) ;
const sig = await signEvent ( newProfile , privatekey ) . catch ( console . error ) ;
if ( sig ) {
if ( sig ) {
const ev = await pool . publish ( { ... newProfile , sig } , ( status , url ) => {
const ev = await pool . publish ( { ... newProfile , sig } , ( status , url ) => {
@ -982,7 +998,6 @@ profileForm.addEventListener('submit', async (e) => {
}
}
} ) . catch ( console . error ) ;
} ) . catch ( console . error ) ;
}
}
}
} ) ;
} ) ;
const errorOverlay = document . querySelector ( '#errorOverlay' ) ;
const errorOverlay = document . querySelector ( '#errorOverlay' ) ;
@ -1041,20 +1056,31 @@ function validatePow(evt) {
* a zero timeout makes mineEvent run without a time limit .
* a zero timeout makes mineEvent run without a time limit .
* a zero difficulty target just resolves the promise without trying to find a 'nonce' .
* a zero difficulty target just resolves the promise without trying to find a 'nonce' .
* /
* /
function powEvent ( evt , difficulty , timeout ) {
function powEvent ( evt , options ) {
const { difficulty , statusElem , timeout } = options ;
if ( difficulty === 0 ) {
if ( difficulty === 0 ) {
return Promise . resolve ( evt ) ;
return Promise . resolve ( evt ) ;
}
}
const cancelBtn = elem ( 'button' , { className : 'btn-inline' } , [ elem ( 'small' , { } , 'cancel' ) ] ) ;
statusElem . replaceChildren ( 'working…' , cancelBtn ) ;
statusElem . hidden = false ;
return new Promise ( ( resolve , reject ) => {
return new Promise ( ( resolve , reject ) => {
const worker = new Worker ( './worker.js' ) ;
const worker = new Worker ( './worker.js' ) ;
const onCancel = ( ) => {
worker . terminate ( ) ;
reject ( 'canceled' ) ;
} ;
cancelBtn . addEventListener ( 'click' , onCancel ) ;
worker . onmessage = ( msg ) => {
worker . onmessage = ( msg ) => {
worker . terminate ( ) ;
worker . terminate ( ) ;
cancelBtn . removeEventListener ( 'click' , onCancel ) ;
if ( msg . data . error ) {
if ( msg . data . error ) {
promptError ( msg . data . error , {
promptError ( msg . data . error , {
onCancel : ( ) => reject ( 'canceled' ) ,
onCancel : ( ) => reject ( 'canceled' ) ,
onAgain : async ( ) => {
onAgain : async ( ) => {
const result = await powEvent ( evt , difficulty , timeout) . catch ( console . warn ) ;
const result = await powEvent ( evt , { difficulty , statusElem, timeout} ) . catch ( console . warn ) ;
resolve ( result ) ;
resolve ( result ) ;
}
}
} )
} )
@ -1065,6 +1091,7 @@ function powEvent(evt, difficulty, timeout) {
worker . onerror = ( err ) => {
worker . onerror = ( err ) => {
worker . terminate ( ) ;
worker . terminate ( ) ;
cancelBtn . removeEventListener ( 'click' , onCancel ) ;
reject ( err ) ;
reject ( err ) ;
} ;
} ;