a demonstration replicated social networking web app built with anproto wiredove.net/
social ed25519 protocols
at main 262 lines 7.7 kB view raw
1import { apds } from 'apds' 2import { h } from 'h' 3import { send } from './send.js' 4import { composer } from './composer.js' 5import { markdown } from './markdown.js' 6 7export const render = {} 8 9render.qr = async (hash, blob) => { 10 const qrcode = h('span', {id: 'qr' + hash, style: 'width: 50%; margin-right: auto; margin-left: auto;'}) 11 12 const link = h('a', {onclick: () => { 13 if (!qrcode.firstChild) { 14 const q = new QRCode('qr' + hash, { 15 text: location.href + blob, 16 }) 17 } else { 18 qrcode.firstChild.remove() 19 qrcode.firstChild.remove() 20 } 21 }, classList: 'material-symbols-outlined'}, ['Qr_Code']) 22 23 return h('span', [link, qrcode]) 24} 25 26render.meta = async (blob, opened, hash, div) => { 27 const ts = h('a', {href: '#' + hash}, [await apds.human(opened.substring(0, 13))]) 28 setInterval(async () => {ts.textContent = await apds.human(opened.substring(0, 13))}, 1000) 29 const author = blob.substring(0, 44) 30 31 const permalink = h('a', {href: '#' + blob, classList: 'material-symbols-outlined'}, ['Share']) 32 33 let show = true 34 35 const rawDiv = h('div') 36 37 let rawshow = true 38 39 const contentBlob = await apds.get(opened.substring(13)) 40 const rawContent = h('pre', {classList: 'hljs'}, [blob + '\n\n' + opened + '\n\n' + contentBlob]) 41 42 const raw = h('a', {classList: 'material-symbols-outlined', onclick: async () => { 43 if (rawshow) { 44 rawDiv.appendChild(rawContent) 45 rawshow = false 46 } else { 47 rawContent.parentNode.removeChild(rawContent) 48 rawshow = true 49 } 50 }}, ['Code']) 51 52 const right = h('span', {style: 'float: right;'}, [ 53 h('span', {classList: 'pubkey'}, [author.substring(0, 6)]), 54 ' ', 55 await render.qr(hash, blob), 56 ' ', 57 permalink, 58 ' ', 59 raw, 60 ' ', 61 ts, 62 ]) 63 64 const contentHash = opened.substring(13) 65 66 const img = await apds.visual(author) 67 img.classList = 'avatar' 68 img.id = 'image' + contentHash 69 img.style = 'float: left;' 70 71 const name = h('span', {id: 'name' + contentHash, classList: 'avatarlink'}, [author.substring(0, 10)]) 72 73 const content = h('div', {id: contentHash, classList: 'material-symbols-outlined content'}, ['Notes']) 74 75 const meta = h('div', {id: div.id, classList: div.classList}, [ 76 right, 77 h('a', {href: '#' + author}, [ 78 img, 79 name, 80 ]), 81 h('div', {style: 'margin-left: 43px;'}, [ 82 h('div', {id: 'reply' + contentHash}), 83 content, 84 rawDiv 85 ]) 86 ]) 87 88 div.replaceWith(meta) 89 //div.appendChild(meta) 90 await render.comments(hash, blob, meta) 91 const getContent = await apds.get(contentHash) 92 if (getContent) { 93 await render.content(contentHash, getContent, content) 94 } else { 95 await send(contentHash) 96 } 97} 98 99render.comments = async (hash, blob, div) => { 100 const num = h('span') 101 102 const log = await apds.getOpenedLog() 103 const src = document.location.hash.substring(1) 104 105 let nume = 0 106 log.forEach(async msg => { 107 const yaml = await apds.parseYaml(msg.text) 108 if (yaml.replyHash) { yaml.reply = yaml.replyHash} 109 if (yaml.reply === hash) { 110 ++nume 111 num.textContent = nume 112 //if (src === yaml.reply) { 113 const replyContain = h('div', {classList: 'reply'}, [ 114 await render.hash(msg.hash) 115 ]) 116 div.after(replyContain) 117 await render.blob(await apds.get(msg.hash)) 118 //} 119 } 120 }) 121 122 const reply = h('a', { 123 classList: 'material-symbols-outlined', 124 onclick: async () => { 125 if (await apds.pubkey()) { 126 div.after(await composer(blob)) 127 } 128 } 129 }, ['Chat_Bubble']) 130 131 div.appendChild(h('div', {style: 'margin-left: 43px;'}, [ 132 reply, ' ', num 133 ])) 134} 135 136const cache = new Map() 137 138render.content = async (hash, blob, div) => { 139 const contentHash = await apds.hash(blob) 140 const yaml = await apds.parseYaml(blob) 141 142 if (yaml && yaml.body) { 143 div.classList = 'content' 144 let html = await markdown(yaml.body) 145 if (yaml.reply) { html = "<span class='material-symbols-outlined'>Subdirectory_Arrow_left</span><a href='#" + yaml.reply + "'> " + yaml.reply.substring(0, 10) + "...</a>" + html } 146 147 div.innerHTML = html 148 149 if (yaml.image) { 150 const get = await document.getElementById('image' + contentHash) 151 if (get) { 152 if (cache.get(yaml.image)) { 153 get.src = cache.get(yaml.image) 154 } else { 155 const image = await apds.get(yaml.image) 156 cache.set(yaml.image, image) 157 if (image) { 158 get.src = image 159 } else { send(yaml.image)} 160 } 161 } 162 } 163 164 if (yaml.name) { 165 const get = await document.getElementById('name' + contentHash) 166 if (get) { get.textContent = yaml.name} 167 } 168 169 if (yaml.previous) { 170 const check = await apds.query(yaml.previous) 171 if (!check[0]) { 172 await send(yaml.previous) 173 } 174 } 175 176 //if (yaml.reply || yaml.replyHash) { 177 // if (yaml.replyHash) { yaml.reply = yaml.replyHash} 178 // try { 179 // const get = await document.getElementById('reply' + contentHash) 180 // const query = await apds.query(yaml.reply) 181 // if (get && query && query[0]) { 182 // const replyYaml = await apds.parseYaml(query[0].text) 183 // const replyDiv = h('div', {classList: 'breadcrumbs'}, [ 184 // h('span', {classList: 'material-symbols-outlined'}, ['Subdirectory_Arrow_left']), 185 // ' ', 186 // h('a', {href: '#' + query[0].author}, [replyYaml.name || query[0].author.substring(0, 10)]), 187 // ' | ', 188 // h('a', {href: '#' + query[0].hash}, [replyYaml.body.substring(0, 24) || query[0].hash.substring(0, 10)]) 189 // ]) 190 // get.appendChild(replyDiv) 191 // } 192 // } catch (err) { 193 // //console.log(err) 194 // } 195 //} 196 } 197} 198 199render.blob = async (blob) => { 200 const hash = await apds.hash(blob) 201 202 const div = await document.getElementById(hash) 203 204 const opened = await apds.open(blob) 205 const getimg = await document.getElementById('inlineimage' + hash) 206 if (opened && div && !div.childNodes[1]) { 207 await render.meta(blob, opened, hash, div) 208 //await render.comments(hash, blob, div) 209 } else if (div && !div.childNodes[1]) { 210 await render.content(hash, blob, div) 211 } else if (getimg) { 212 getimg.src = blob 213 } 214} 215 216render.shouldWe = async (blob) => { 217 const opened = await apds.open(blob) 218 const hash = await apds.hash(blob) 219 const already = await apds.get(hash) 220 if (!already) { 221 await apds.make(blob) 222 } 223 if (opened && !already) { 224 const src = window.location.hash.substring(1) 225 const al = [] 226 const aliases = localStorage.getItem(src) 227 if (aliases) { 228 const parse = JSON.parse(aliases) 229 al.push(...parse) 230 console.log(al) 231 } 232 const hash = await apds.hash(blob) 233 const msg = await apds.get(opened.substring(13)) 234 const yaml = await apds.parseYaml(msg) 235 // this should detect whether the syncing message is newer or older and place the msg in the right spot 236 if (blob.substring(0, 44) === src || hash === src || yaml.author === src || al.includes(blob.substring(0, 44))) { 237 const scroller = document.getElementById('scroller') 238 const div = await render.hash(hash) 239 if (div) { 240 scroller.appendChild(div) 241 //scroller.insertBefore(div, scroller.firstChild) 242 await render.blob(blob) 243 } 244 } 245 if (src === '') { 246 const scroller = document.getElementById('scroller') 247 const div = await render.hash(hash) 248 if (div) { 249 //scroller.appendChild(div) 250 scroller.insertBefore(div, scroller.firstChild) 251 await render.blob(blob) 252 } 253 } 254 } 255} 256 257render.hash = async (hash) => { 258 if (!await document.getElementById(hash)) { 259 const div = h('div', {id: hash, classList: 'message'}) 260 return div 261 } 262}