Thread viewer for Bluesky

added folding/unfolding of thread sections

icons/add-square.png

This is a binary file and will not be displayed.

icons/subtract-square.png

This is a binary file and will not be displayed.

+1 -1
index.html
··· 9 9 <link href="style.css" rel="stylesheet"> 10 10 </head> 11 11 <body> 12 - <div id="loader"><img src="sunny.png"></div> 12 + <div id="loader"><img src="icons/sunny.png"></div> 13 13 14 14 <div id="search"> 15 15 <form method="get">
+46 -7
post.js
··· 23 23 let header = this.buildPostHeader(); 24 24 div.appendChild(header); 25 25 26 + let content = document.createElement('div'); 27 + content.className = 'content'; 28 + 29 + if (!this.isRoot) { 30 + let edge = document.createElement('div'); 31 + edge.className = 'edge'; 32 + div.appendChild(edge); 33 + 34 + let line = document.createElement('div'); 35 + line.className = 'line'; 36 + edge.appendChild(line); 37 + 38 + let plus = document.createElement('img'); 39 + plus.className = 'plus'; 40 + plus.src = 'icons/subtract-square.png'; 41 + div.appendChild(plus); 42 + 43 + for (let element of [edge, plus]) { 44 + element.addEventListener('click', (e) => { 45 + e.preventDefault(); 46 + this.toggleSectionFold(div); 47 + }); 48 + } 49 + } 50 + 26 51 let p = document.createElement('p'); 27 52 p.innerText = this.post.text; 28 - div.appendChild(p); 53 + content.appendChild(p); 29 54 30 55 if (this.post.embed) { 31 56 let embed = document.createElement('p'); 32 57 embed.innerText = `[${this.post.embed.$type}]`; 33 - div.appendChild(embed); 58 + content.appendChild(embed); 34 59 } 35 60 36 61 let stats = this.buildStatsFooter(); 37 - div.appendChild(stats); 62 + content.appendChild(stats); 38 63 39 64 if (this.post.replies.length == 1 && this.post.replies[0].author.did == this.post.author.did) { 40 65 let component = new PostComponent(this.post.replies[0], this.root); 41 66 let element = component.buildElement(); 42 67 element.classList.add('flat'); 43 - div.appendChild(element); 68 + content.appendChild(element); 44 69 } else { 45 70 for (let reply of this.post.replies) { 46 71 let component = new PostComponent(reply, this.root); 47 - div.appendChild(component.buildElement()); 72 + content.appendChild(component.buildElement()); 48 73 } 49 74 } 50 75 51 76 if (this.post.replyCount != this.post.replies.length) { 52 77 let loadMore = this.buildLoadMoreLink() 53 - div.appendChild(loadMore); 78 + content.appendChild(loadMore); 54 79 } 80 + 81 + div.appendChild(content); 55 82 56 83 return div; 84 + } 85 + 86 + toggleSectionFold(div) { 87 + let plus = div.querySelector('.plus'); 88 + 89 + if (div.classList.contains('collapsed')) { 90 + div.classList.remove('collapsed'); 91 + plus.src = 'icons/subtract-square.png' 92 + } else { 93 + div.classList.add('collapsed'); 94 + plus.src = 'icons/add-square.png' 95 + } 57 96 } 58 97 59 98 timeFormatForTimestamp() { ··· 142 181 143 182 link.addEventListener('click', (e) => { 144 183 e.preventDefault(); 145 - link.innerHTML = `<img class="loader" src="sunny.png">`; 184 + link.innerHTML = `<img class="loader" src="icons/sunny.png">`; 146 185 loadThread(this.post.author.handle, this.post.id, loadMore.parentNode.parentNode); 147 186 }); 148 187
+38 -4
style.css
··· 184 184 } 185 185 186 186 .post { 187 - padding-left: 20px; 187 + position: relative; 188 + padding-left: 21px; 188 189 margin-top: 30px; 190 + } 191 + 192 + .post .edge { 193 + position: absolute; 194 + left: -2px; 195 + top: 30px; 196 + bottom: 0px; 197 + width: 6px; 198 + } 199 + 200 + .post .edge .line { 201 + position: absolute; 202 + left: 2px; 203 + top: 0px; 204 + bottom: 0px; 189 205 border-left: 1px solid #aaa; 190 206 } 191 207 192 - body > .post { 193 - border-left: 0px; 208 + .post .edge:hover .line { 209 + border-left: 2px solid #888; 210 + } 211 + 212 + .post.collapsed .line { 213 + display: none; 214 + } 215 + 216 + .post.collapsed .content { 217 + display: none; 194 218 } 195 219 196 220 .post.flat { 197 221 padding-left: 0px; 198 - border-left: 0px; 199 222 margin-top: 25px; 200 223 } 201 224 225 + .post.flat .line { 226 + display: none; 227 + } 228 + 202 229 .post .avatar { 203 230 width: 32px; 204 231 height: 32px; ··· 214 241 border-radius: 16px; 215 242 vertical-align: middle; 216 243 margin-right: 8px; 244 + } 245 + 246 + .post .plus { 247 + position: absolute; 248 + top: 8px; 249 + left: -6px; 250 + width: 14px; 217 251 } 218 252 219 253 .post h2 {
sunny.png icons/sunny.png