a demonstration replicated social networking web app built with anproto
wiredove.net/
social
ed25519
protocols
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}