damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs | README | LICENSE

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("<","&lt;").replaceAll(">","&gt;")
    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 }