comments.js (4112B)
1 2 function uuidv4() { 3 return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => 4 (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 5 ); 6 } 7 8 9 async function comments_init(thread) 10 { 11 const relay = await Relay("wss://relay.damus.io") 12 const now = (new Date().getTime()) / 1000 13 const model = {events: [], profiles: {}} 14 const comments_id = uuidv4() 15 const profiles_id = uuidv4() 16 17 model.pool = relay 18 model.el = document.querySelector("#comments") 19 20 relay.subscribe(comments_id, {kinds: [1], "#e": [thread]}) 21 22 relay.event = (sub_id, ev) => { 23 if (sub_id === comments_id) { 24 if (ev.content !== "") 25 insert_event_sorted(model.events, ev) 26 if (model.realtime) 27 render_home_view(model) 28 } else if (sub_id === profiles_id) { 29 try { 30 model.profiles[ev.pubkey] = JSON.parse(ev.content) 31 } catch { 32 console.log("failed to parse", ev.content) 33 } 34 } 35 } 36 37 relay.eose = async (sub_id) => { 38 if (sub_id === comments_id) { 39 handle_comments_loaded(profiles_id, model) 40 } else if (sub_id === profiles_id) { 41 handle_profiles_loaded(profiles_id, model) 42 } 43 } 44 45 return relay 46 } 47 48 function handle_profiles_loaded(profiles_id, model) { 49 // stop asking for profiles 50 model.pool.unsubscribe(profiles_id) 51 model.realtime = true 52 render_home_view(model) 53 } 54 55 // load profiles after comment notes are loaded 56 function handle_comments_loaded(profiles_id, model) 57 { 58 const pubkeys = model.events.reduce((s, ev) => { 59 s.add(ev.pubkey) 60 return s 61 }, new Set()) 62 const authors = Array.from(pubkeys) 63 64 // load profiles 65 model.pool.subscribe(profiles_id, {kinds: [0], authors: authors}) 66 } 67 68 function render_home_view(model) { 69 model.el.innerHTML = render_events(model) 70 } 71 72 function render_events(model) { 73 const render = render_event.bind(null, model) 74 return model.events.map(render).join("\n") 75 } 76 77 function render_event(model, ev) { 78 const profile = model.profiles[ev.pubkey] || { 79 name: "anon", 80 display_name: "Anonymous", 81 } 82 const delta = time_delta(new Date().getTime(), ev.created_at*1000) 83 return ` 84 <div class="comment"> 85 <div class="info"> 86 ${render_name(ev.pubkey, profile)} 87 <span>${delta}</span> 88 </div> 89 <img class="pfp" src="${get_picture(ev.pubkey, profile)}"> 90 <p> 91 ${format_content(ev.content)} 92 </p> 93 </div> 94 ` 95 } 96 97 function convert_quote_blocks(content) 98 { 99 const split = content.split("\n") 100 let blockin = false 101 return split.reduce((str, line) => { 102 if (line !== "" && line[0] === '>') { 103 if (!blockin) { 104 str += "<span class='quote'>" 105 blockin = true 106 } 107 str += sanitize(line.slice(1)) 108 } else { 109 if (blockin) { 110 blockin = false 111 str += "</span>" 112 } 113 str += sanitize(line) 114 } 115 return str + "<br/>" 116 }, "") 117 } 118 119 function format_content(content) 120 { 121 return convert_quote_blocks(content) 122 } 123 124 function sanitize(content) 125 { 126 if (!content) 127 return "" 128 return content.replaceAll("<","<").replaceAll(">",">") 129 } 130 131 function get_picture(pk, profile) 132 { 133 return sanitize(profile.picture) || "https://robohash.org/" + pk 134 } 135 136 function render_name(pk, profile={}) 137 { 138 const display_name = profile.display_name || profile.user 139 const username = profile.name || "anon" 140 const name = display_name || username 141 142 return `<div class="username">${sanitize(name)}</div>` 143 } 144 145 function time_delta(current, previous) { 146 var msPerMinute = 60 * 1000; 147 var msPerHour = msPerMinute * 60; 148 var msPerDay = msPerHour * 24; 149 var msPerMonth = msPerDay * 30; 150 var msPerYear = msPerDay * 365; 151 152 var elapsed = current - previous; 153 154 if (elapsed < msPerMinute) { 155 return Math.round(elapsed/1000) + ' seconds ago'; 156 } 157 158 else if (elapsed < msPerHour) { 159 return Math.round(elapsed/msPerMinute) + ' minutes ago'; 160 } 161 162 else if (elapsed < msPerDay ) { 163 return Math.round(elapsed/msPerHour ) + ' hours ago'; 164 } 165 166 else if (elapsed < msPerMonth) { 167 return Math.round(elapsed/msPerDay) + ' days ago'; 168 } 169 170 else if (elapsed < msPerYear) { 171 return Math.round(elapsed/msPerMonth) + ' months ago'; 172 } 173 174 else { 175 return Math.round(elapsed/msPerYear ) + ' years ago'; 176 } 177 }