veryroundbird revised this gist 1 month ago. Go to revision
No changes
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 306 insertions, 66 deletions
newcomments.user.js
| @@ -13,67 +13,167 @@ | |||
| 13 | 13 | // @grant GM.setValue | |
| 14 | 14 | // ==/UserScript== | |
| 15 | 15 | ||
| 16 | - | // make the settings | |
| 17 | - | let gmc = new GM_config( | |
| 18 | - | { | |
| 19 | - | 'id': 'veryroundbirdhouse_newreplies', | |
| 20 | - | 'title': 'Settings', | |
| 21 | - | 'fields': | |
| 22 | - | { | |
| 23 | - | 'newReplyText': | |
| 24 | - | { | |
| 25 | - | 'label': 'New Reply Text', | |
| 26 | - | 'type': 'text', | |
| 27 | - | 'default': '✨ NEW REPLIES ✨' | |
| 28 | - | }, | |
| 29 | - | 'markDoneText': | |
| 30 | - | { | |
| 31 | - | 'label': 'Mark Thread Done Text', | |
| 32 | - | 'type': 'text', | |
| 33 | - | 'default': '✅ Mark Thread Done' | |
| 34 | - | }, | |
| 35 | - | 'highlightColor': | |
| 36 | - | { | |
| 37 | - | 'label': 'Highlight Color', | |
| 38 | - | 'type': 'text', | |
| 39 | - | 'default': 'yellow' | |
| 40 | - | } | |
| 41 | - | }, | |
| 42 | - | 'init': () => { | |
| 43 | - | gmc.css.basic = ` | |
| 44 | - | label { | |
| 45 | - | display: block; | |
| 46 | - | } | |
| 47 | - | `; | |
| 48 | - | } | |
| 49 | - | }); | |
| 50 | - | ||
| 51 | 16 | (function() { | |
| 17 | + | 'use strict'; | |
| 52 | 18 | if (window.location.href.match(/https:\/\/[a-z\-]+\.dreamwidth.org\/(?:[0-9]+|(?:[0-9]{4}\/[0-9]{2}\/[0-9]{2}\/[a-z\-]+))\.html/)) { // checks that this is like, an actual entry | |
| 53 | - | 'use strict'; | |
| 19 | + | // make the settings | |
| 20 | + | let gmc = new GM_config( | |
| 21 | + | { | |
| 22 | + | 'id': 'veryroundbirdhouse_newreplies', | |
| 23 | + | 'title': 'Settings', | |
| 24 | + | 'fields': | |
| 25 | + | { | |
| 26 | + | 'altJournals': | |
| 27 | + | { | |
| 28 | + | 'label': 'Alternate Journals', | |
| 29 | + | 'type': 'text', | |
| 30 | + | 'default': '', | |
| 31 | + | 'labelPos': 'above' | |
| 32 | + | }, | |
| 33 | + | 'newReplyText': | |
| 34 | + | { | |
| 35 | + | 'label': 'New Reply Text', | |
| 36 | + | 'type': 'text', | |
| 37 | + | 'default': '✨ NEW REPLIES ✨', | |
| 38 | + | 'labelPos': 'above' | |
| 39 | + | }, | |
| 40 | + | 'markDoneText': | |
| 41 | + | { | |
| 42 | + | 'label': 'Mark Thread Done Text', | |
| 43 | + | 'type': 'text', | |
| 44 | + | 'default': '✅ Mark Thread Done', | |
| 45 | + | 'labelPos': 'above' | |
| 46 | + | }, | |
| 47 | + | 'acPassIndicator': | |
| 48 | + | { | |
| 49 | + | 'label': 'AC Passing Thread Indicator', | |
| 50 | + | 'type': 'text', | |
| 51 | + | 'default': '🌟', | |
| 52 | + | 'labelPos': 'above' | |
| 53 | + | }, | |
| 54 | + | 'highlightColor': | |
| 55 | + | { | |
| 56 | + | 'label': 'Highlight Color', | |
| 57 | + | 'type': 'text', | |
| 58 | + | 'default': 'yellow', | |
| 59 | + | 'labelPos': 'above' | |
| 60 | + | }, | |
| 61 | + | 'showACInfo': | |
| 62 | + | { | |
| 63 | + | 'label': 'Show AC Info?', | |
| 64 | + | 'type': 'checkbox', | |
| 65 | + | 'default': true | |
| 66 | + | }, | |
| 67 | + | 'threadLengthThreshold': | |
| 68 | + | { | |
| 69 | + | 'label': 'Thread Length Threshold for AC', | |
| 70 | + | 'type': 'int', | |
| 71 | + | 'min': 0, | |
| 72 | + | 'default': 15, | |
| 73 | + | 'labelPos': 'above' | |
| 74 | + | }, | |
| 75 | + | 'threadLengthCountType': | |
| 76 | + | { | |
| 77 | + | 'label': 'How to Count Comments for AC', | |
| 78 | + | 'type': 'radio', | |
| 79 | + | 'options': ['Total Comments', 'Comments from Me'], | |
| 80 | + | 'default': 'Total Comments' | |
| 81 | + | }, | |
| 82 | + | 'showTeamThreads': | |
| 83 | + | { | |
| 84 | + | 'label': 'Show Team Thread Info?', | |
| 85 | + | 'type': 'checkbox', | |
| 86 | + | 'default': true | |
| 87 | + | }, | |
| 88 | + | 'teammateUsernames': | |
| 89 | + | { | |
| 90 | + | 'label': 'Teammate Usernames', | |
| 91 | + | 'type': 'text', | |
| 92 | + | 'default': '', | |
| 93 | + | 'labelPos': 'above' | |
| 94 | + | } | |
| 95 | + | }, | |
| 96 | + | 'events': { | |
| 97 | + | 'init': () => { | |
| 98 | + | main(); | |
| 99 | + | updateUI(); | |
| 100 | + | }, | |
| 101 | + | 'save': () => { | |
| 102 | + | updateUI(); | |
| 103 | + | } | |
| 104 | + | }, | |
| 105 | + | 'css': ` | |
| 106 | + | #veryroundbirdhouse_newreplies { | |
| 107 | + | font-size: 16px; | |
| 108 | + | font-family: 'Atkinson Hyperlegible', sans-serif; | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | #veryroundbirdhouse_newreplies .config_var { | |
| 112 | + | display: flex; | |
| 113 | + | gap: 10px; | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | #veryroundbirdhouse_newreplies .config_var br { | |
| 117 | + | display: none; | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | #veryroundbirdhouse_newreplies .config_var label { | |
| 121 | + | min-width: 200px; | |
| 122 | + | font-size: 14px; | |
| 123 | + | line-height: 16px; | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | #veryroundbirdhouse_newreplies label { | |
| 127 | + | display: block; | |
| 128 | + | white-space: nowrap; | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | #veryroundbirdhouse_newreplies [type="radio"] + label { | |
| 132 | + | display: inline-block; | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | #veryroundbirdhouse_newreplies input { | |
| 136 | + | font-family: 'Atkinson Hyperlegible', sans-serif; | |
| 137 | + | } | |
| 138 | + | ` | |
| 139 | + | }); | |
| 54 | 140 | ||
| 55 | 141 | // set up globals | |
| 56 | 142 | let readReplies = []; | |
| 57 | 143 | let linkList = []; | |
| 144 | + | let teamList = []; | |
| 145 | + | let passList = []; | |
| 58 | 146 | ||
| 59 | 147 | // get username from account links | |
| 60 | 148 | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 61 | 149 | ||
| 62 | - | // initialize user settings | |
| 63 | - | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 64 | - | const markDoneText = "✅ Mark Thread Done"; | |
| 65 | - | const highlightColor = "yellow"; | |
| 66 | - | ||
| 67 | 150 | // create elements for jump links menu | |
| 68 | 151 | const jumpLinks = document.createElement('div'); | |
| 69 | 152 | const jumpDetails = document.createElement('details'); | |
| 70 | 153 | const jumpSummary = document.createElement('summary'); | |
| 71 | 154 | const jumpLinksOl = document.createElement('ol'); | |
| 155 | + | const jumpPassingDetails = document.createElement('details'); | |
| 156 | + | const jumpPassingSummary = document.createElement('summary'); | |
| 157 | + | const jumpPassingOl = document.createElement('ol'); | |
| 158 | + | const jumpTeamDetails = document.createElement('details'); | |
| 159 | + | const jumpTeamSummary = document.createElement('summary'); | |
| 160 | + | const jumpTeamOl = document.createElement('ol'); | |
| 72 | 161 | const settingsLink = document.createElement('a'); | |
| 73 | 162 | jumpSummary.textContent = "🧵 New Comments"; | |
| 74 | 163 | jumpDetails.append(jumpSummary); | |
| 75 | 164 | jumpDetails.append(jumpLinksOl); | |
| 76 | 165 | jumpLinks.append(jumpDetails); | |
| 166 | + | jumpPassingDetails.id = 'jump-ac-passing'; | |
| 167 | + | jumpPassingSummary.textContent = "✅ AC Length OK"; | |
| 168 | + | jumpPassingDetails.append(jumpPassingSummary); | |
| 169 | + | jumpPassingDetails.append(jumpPassingOl); | |
| 170 | + | jumpTeamDetails.id = 'jump-team-threads'; | |
| 171 | + | jumpTeamSummary.textContent = "👯 Team Threads"; | |
| 172 | + | jumpTeamDetails.append(jumpTeamSummary); | |
| 173 | + | jumpTeamDetails.append(jumpTeamOl); | |
| 174 | + | jumpTeamDetails.style.display = "none"; | |
| 175 | + | jumpLinks.append(jumpPassingDetails); | |
| 176 | + | jumpLinks.append(jumpTeamDetails); | |
| 77 | 177 | settingsLink.textContent = "⚙️ Settings"; | |
| 78 | 178 | settingsLink.href = "#"; | |
| 79 | 179 | settingsLink.addEventListener('click', (e) => { | |
| @@ -84,6 +184,95 @@ let gmc = new GM_config( | |||
| 84 | 184 | jumpLinks.id = 'jump-links'; | |
| 85 | 185 | document.body.append(jumpLinks); | |
| 86 | 186 | ||
| 187 | + | const updateUI = () => { | |
| 188 | + | if (gmc.get('showTeamThreads', true)) { | |
| 189 | + | document.getElementById('jump-team-threads').style.display = "block"; | |
| 190 | + | } else { | |
| 191 | + | document.getElementById('jump-team-threads').style.display = "none"; | |
| 192 | + | } | |
| 193 | + | ||
| 194 | + | if (gmc.get('showACInfo', true)) { | |
| 195 | + | document.getElementById('jump-ac-passing').style.display = "block"; | |
| 196 | + | } else { | |
| 197 | + | document.getElementById('jump-ac-passing').style.display = "none"; | |
| 198 | + | } | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | const sliceNodeList = (id1, id2) => { | |
| 202 | + | let id1idx = null; | |
| 203 | + | let id2idx = null; | |
| 204 | + | const nodes = Array.from(document.querySelectorAll('.comment-thread')); | |
| 205 | + | for (let i = 0; i < nodes.length; i++) { | |
| 206 | + | if (!id1idx && nodes[i].querySelector('.dwexpcomment').id === `cmt${id1}`) id1idx = i; | |
| 207 | + | if (!id2idx && nodes[i].querySelector('.dwexpcomment').id === `cmt${id2}`) id2idx = i; | |
| 208 | + | if (id1idx !== null && id2idx !== null) break; | |
| 209 | + | } | |
| 210 | + | return nodes.slice(id1idx, id2idx+1); | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | const getCommentCount = (thread) => { | |
| 214 | + | return thread.filter((elt) => { | |
| 215 | + | if (elt.querySelector('.ljuser').textContent === username) return true; | |
| 216 | + | return false; | |
| 217 | + | }).length | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | const getParticipantList = (thread) => { | |
| 221 | + | const usernames = thread.map((elt) => { | |
| 222 | + | return elt.querySelector('.ljuser').textContent; | |
| 223 | + | }) | |
| 224 | + | return usernames.filter((un, i) => { | |
| 225 | + | if (usernames.indexOf(un) === i) return true; | |
| 226 | + | return false; | |
| 227 | + | }); | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | const isTeamThread = (thread) => { | |
| 231 | + | const participants = getParticipantList(thread); | |
| 232 | + | const teammates = getTeammates(); | |
| 233 | + | for (let i = 0; i < participants.length; i++) { | |
| 234 | + | if (teammates.indexOf(participants[i]) !== -1) { | |
| 235 | + | return true; | |
| 236 | + | } | |
| 237 | + | } | |
| 238 | + | return false; | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | const passesAc = (thread) => { | |
| 242 | + | if (gmc.get("threadLengthCountType", "Comments from Me") === "Comments from Me") { | |
| 243 | + | const myComments = thread.filter((elt) => { | |
| 244 | + | if (elt.querySelector('.ljuser').textContent === username) return true; | |
| 245 | + | return false; | |
| 246 | + | }); | |
| 247 | + | if (myComments.length >= gmc.get("threadLengthThreshold", 20)) return true; | |
| 248 | + | return false; | |
| 249 | + | } else if (gmc.get("threadLengthCountType", "Comments from Me") === "Total Comments") { | |
| 250 | + | if (thread.length >= gmc.get("threadLengthThreshold", 20)) return true; | |
| 251 | + | return false; | |
| 252 | + | } | |
| 253 | + | return false; | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | const checkThread = (cmt, topOfThread, prevComment) => { | |
| 257 | + | const startId = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 258 | + | const endId = prevComment ? parseInt(prevComment.querySelector('.comment').id.replace("comment-cmt", "")) : null; | |
| 259 | + | const threadComments = sliceNodeList(startId, endId); | |
| 260 | + | const commentCount = getCommentCount(threadComments); | |
| 261 | + | const participants = getParticipantList(threadComments); | |
| 262 | + | ||
| 263 | + | if (isTeamThread(threadComments)) teamList.push({id: startId, count: commentCount}); | |
| 264 | + | if (passesAc(threadComments)) { | |
| 265 | + | topOfThread.classList.add("ac-pass"); | |
| 266 | + | passList.push({id: startId, count: commentCount}); | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 270 | + | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(startId) === -1 && cmt !== topOfThread) { | |
| 271 | + | highlightCmt(topOfThread); | |
| 272 | + | linkList.push(startId); | |
| 273 | + | } | |
| 274 | + | } | |
| 275 | + | ||
| 87 | 276 | // add an item to the list of threads we're ignoring | |
| 88 | 277 | const addReadReply = (id) => { | |
| 89 | 278 | if (readReplies.indexOf(parseInt(id)) === -1) { | |
| @@ -93,8 +282,7 @@ let gmc = new GM_config( | |||
| 93 | 282 | }; | |
| 94 | 283 | ||
| 95 | 284 | // adds UI elements to highlighted comments | |
| 96 | - | const highlightCmt = (cmt, top) => { | |
| 97 | - | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 285 | + | const highlightCmt = (top) => { | |
| 98 | 286 | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 99 | 287 | const info = top.querySelector('.comment-info'); | |
| 100 | 288 | const markReadBtn = document.createElement('button'); | |
| @@ -102,8 +290,8 @@ let gmc = new GM_config( | |||
| 102 | 290 | let expandListener = null; | |
| 103 | 291 | markReadBtn.setAttribute('type', 'button'); | |
| 104 | 292 | markReadBtn.classList.add("markRead"); | |
| 105 | - | markReadBtn.textContent = markDoneText; | |
| 106 | - | newCmtMarker.textContent = newReplyText; | |
| 293 | + | markReadBtn.textContent = gmc.get("markDoneText", "✅ Mark Thread Done"); | |
| 294 | + | newCmtMarker.textContent = gmc.get("newReplyText", "✨ NEW REPLIES ✨"); | |
| 107 | 295 | newCmtMarker.classList.add('newComment'); | |
| 108 | 296 | top.classList.add('highlight'); | |
| 109 | 297 | ||
| @@ -140,16 +328,52 @@ let gmc = new GM_config( | |||
| 140 | 328 | } | |
| 141 | 329 | }; | |
| 142 | 330 | ||
| 143 | - | const main = () => { | |
| 331 | + | const getTeammates = () => { | |
| 332 | + | const teammateUsernames = gmc.get('teammateUsernames', ''); | |
| 333 | + | return teammateUsernames !== '' ? teammateUsernames.split(",").map(x => x.trim()) : []; | |
| 334 | + | } | |
| 335 | + | ||
| 336 | + | const getAltJournals = () => { | |
| 337 | + | const altJournals = gmc.get('altJournals', ''); | |
| 338 | + | return altJournals !== '' ? altJournals.split(",").map(x => x.trim()) : []; | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | const getHighlightColor = () => gmc.get('highlightColor', ''); | |
| 342 | + | const getAcPassIndicator = () => gmc.get('acPassIndicator', ''); | |
| 343 | + | ||
| 344 | + | const main = async () => { | |
| 144 | 345 | // create styles for stuff we're adding to the UI | |
| 145 | 346 | const newStyles = document.createElement('style'); | |
| 347 | + | const teammates = getTeammates(); | |
| 348 | + | console.log(teammates); | |
| 349 | + | const altjournals = getAltJournals(); | |
| 350 | + | const highlightColor = getHighlightColor(); | |
| 351 | + | const acPassIndicator = getAcPassIndicator(); | |
| 146 | 352 | newStyles.setAttribute('type', 'text/css'); | |
| 353 | + | newStyles.id = 'new-replies-styles'; | |
| 147 | 354 | newStyles.textContent = ` | |
| 148 | 355 | .highlight .partial > .comment, | |
| 149 | 356 | .highlight .visible .comment-info { | |
| 150 | 357 | background-color: ${highlightColor} !important; | |
| 151 | 358 | } | |
| 152 | 359 | ||
| 360 | + | .ac-pass .partial > .comment .inner:before { | |
| 361 | + | content: "${acPassIndicator}"; | |
| 362 | + | margin-right: 5px; | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | .ac-pass .visible .comment-info { | |
| 366 | + | position: relative; | |
| 367 | + | } | |
| 368 | + | ||
| 369 | + | .ac-pass .visible .comment-info:after { | |
| 370 | + | content: "${acPassIndicator}"; | |
| 371 | + | position: absolute; | |
| 372 | + | top: 5px; | |
| 373 | + | right: 5px; | |
| 374 | + | font-size: 1.4em; | |
| 375 | + | } | |
| 376 | + | ||
| 153 | 377 | button.markRead { | |
| 154 | 378 | padding: 2px; | |
| 155 | 379 | margin-bottom: 0; | |
| @@ -171,6 +395,7 @@ let gmc = new GM_config( | |||
| 171 | 395 | max-width: 200px; | |
| 172 | 396 | overflow: auto; | |
| 173 | 397 | opacity: .5; | |
| 398 | + | z-index: 99; | |
| 174 | 399 | } | |
| 175 | 400 | ||
| 176 | 401 | #jump-links summary { | |
| @@ -193,12 +418,17 @@ let gmc = new GM_config( | |||
| 193 | 418 | opacity: 1; | |
| 194 | 419 | } | |
| 195 | 420 | `; | |
| 196 | - | document.head.append(newStyles); | |
| 421 | + | if (document.getElementById('new-replies-styles')) { | |
| 422 | + | document.getElementById('new-replies-styles').innerHTML = newStyles; | |
| 423 | + | } else { | |
| 424 | + | document.head.append(newStyles); | |
| 425 | + | } | |
| 197 | 426 | ||
| 198 | 427 | readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; | |
| 199 | 428 | ||
| 200 | 429 | let prevDepth = 1; | |
| 201 | 430 | let topOfThread = null; | |
| 431 | + | let topOfThreadDepth = null; | |
| 202 | 432 | let prevComment = null; | |
| 203 | 433 | ||
| 204 | 434 | // loop over all comments | |
| @@ -213,6 +443,7 @@ let gmc = new GM_config( | |||
| 213 | 443 | return; | |
| 214 | 444 | } | |
| 215 | 445 | topOfThread = cmt; | |
| 446 | + | topOfThreadDepth = parseInt(Array.from(topOfThread.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 216 | 447 | prevComment = cmt; | |
| 217 | 448 | } | |
| 218 | 449 | ||
| @@ -224,22 +455,8 @@ let gmc = new GM_config( | |||
| 224 | 455 | } | |
| 225 | 456 | ||
| 226 | 457 | // if the depth is lower than it was in the last comment, the thread has ended | |
| 227 | - | if (depth < prevDepth) { | |
| 228 | - | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 229 | - | ||
| 230 | - | console.log(`depth: ${depth}; prevDepth: ${prevDepth}`); | |
| 231 | - | console.log('comment'); | |
| 232 | - | console.log(cmt); | |
| 233 | - | console.log('top of thread'); | |
| 234 | - | console.log(topOfThread); | |
| 235 | - | console.log('prev comment'); | |
| 236 | - | console.log(prevComment); | |
| 237 | - | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 238 | - | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1 && cmt !== topOfThread) { | |
| 239 | - | ||
| 240 | - | highlightCmt(cmt, topOfThread); | |
| 241 | - | linkList.push(id); | |
| 242 | - | } | |
| 458 | + | if (depth < prevDepth || prevDepth !== topOfThreadDepth) { | |
| 459 | + | checkThread(cmt, topOfThread, prevComment); | |
| 243 | 460 | ||
| 244 | 461 | // regardless, unset the thread variables | |
| 245 | 462 | topOfThread = null; | |
| @@ -253,6 +470,13 @@ let gmc = new GM_config( | |||
| 253 | 470 | prevDepth = depth; | |
| 254 | 471 | }); | |
| 255 | 472 | ||
| 473 | + | if (topOfThread) { | |
| 474 | + | const startId = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 475 | + | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(startId) === -1 && prevComment !== topOfThread) { | |
| 476 | + | checkThread(prevComment, topOfThread, prevComment); | |
| 477 | + | } | |
| 478 | + | } | |
| 479 | + | ||
| 256 | 480 | // add comments to the floating bottom left menu | |
| 257 | 481 | linkList.forEach((id) => { | |
| 258 | 482 | const newLi = document.createElement('li'); | |
| @@ -262,9 +486,25 @@ let gmc = new GM_config( | |||
| 262 | 486 | newLi.append(newA); | |
| 263 | 487 | jumpLinksOl.append(newLi); | |
| 264 | 488 | }); | |
| 489 | + | jumpSummary.append(` (${linkList.length})`); | |
| 490 | + | passList.forEach((p) => { | |
| 491 | + | const newLi = document.createElement('li'); | |
| 492 | + | const newA = document.createElement('a'); | |
| 493 | + | newA.href = `#cmt${p.id}`; | |
| 494 | + | newA.textContent = `#${p.id} (${p.count})`; | |
| 495 | + | newLi.append(newA); | |
| 496 | + | jumpPassingOl.append(newLi); | |
| 497 | + | }); | |
| 498 | + | jumpPassingSummary.append(` (${passList.length})`); | |
| 499 | + | teamList.forEach((t) => { | |
| 500 | + | const newLi = document.createElement('li'); | |
| 501 | + | const newA = document.createElement('a'); | |
| 502 | + | newA.href = `#cmt${t.id}`; | |
| 503 | + | newA.textContent = `#${t.id} (${t.count})`; | |
| 504 | + | newLi.append(newA); | |
| 505 | + | jumpTeamOl.append(newLi); | |
| 506 | + | }); | |
| 507 | + | jumpTeamSummary.append(` (${teamList.length})`); | |
| 265 | 508 | }; | |
| 266 | - | ||
| 267 | - | ||
| 268 | - | main(); | |
| 269 | 509 | } | |
| 270 | 510 | })(); | |
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 189 insertions, 188 deletions
newcomments.user.js
| @@ -49,221 +49,222 @@ let gmc = new GM_config( | |||
| 49 | 49 | }); | |
| 50 | 50 | ||
| 51 | 51 | (function() { | |
| 52 | - | 'use strict'; | |
| 53 | - | ||
| 54 | - | // set up globals | |
| 55 | - | let readReplies = []; | |
| 56 | - | let linkList = []; | |
| 57 | - | ||
| 58 | - | // get username from account links | |
| 59 | - | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 60 | - | ||
| 61 | - | // initialize user settings | |
| 62 | - | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 63 | - | const markDoneText = "✅ Mark Thread Done"; | |
| 64 | - | const highlightColor = "yellow"; | |
| 65 | - | ||
| 66 | - | // create elements for jump links menu | |
| 67 | - | const jumpLinks = document.createElement('div'); | |
| 68 | - | const jumpDetails = document.createElement('details'); | |
| 69 | - | const jumpSummary = document.createElement('summary'); | |
| 70 | - | const jumpLinksOl = document.createElement('ol'); | |
| 71 | - | const settingsLink = document.createElement('a'); | |
| 72 | - | jumpSummary.textContent = "🧵 New Comments"; | |
| 73 | - | jumpDetails.append(jumpSummary); | |
| 74 | - | jumpDetails.append(jumpLinksOl); | |
| 75 | - | jumpLinks.append(jumpDetails); | |
| 76 | - | settingsLink.textContent = "⚙️ Settings"; | |
| 77 | - | settingsLink.href = "#"; | |
| 78 | - | settingsLink.addEventListener('click', (e) => { | |
| 79 | - | e.preventDefault(); | |
| 80 | - | gmc.open(); | |
| 81 | - | }); | |
| 82 | - | jumpLinks.append(settingsLink); | |
| 83 | - | jumpLinks.id = 'jump-links'; | |
| 84 | - | document.body.append(jumpLinks); | |
| 85 | - | ||
| 86 | - | // add an item to the list of threads we're ignoring | |
| 87 | - | const addReadReply = (id) => { | |
| 88 | - | if (readReplies.indexOf(parseInt(id)) === -1) { | |
| 89 | - | readReplies.push(parseInt(id)); | |
| 90 | - | localStorage.setItem("readComments", JSON.stringify(readReplies)); | |
| 91 | - | } | |
| 92 | - | }; | |
| 93 | - | ||
| 94 | - | // adds UI elements to highlighted comments | |
| 95 | - | const highlightCmt = (cmt, top) => { | |
| 96 | - | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 97 | - | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 98 | - | const info = top.querySelector('.comment-info'); | |
| 99 | - | const markReadBtn = document.createElement('button'); | |
| 100 | - | const newCmtMarker = document.createElement('span'); | |
| 101 | - | let expandListener = null; | |
| 102 | - | markReadBtn.setAttribute('type', 'button'); | |
| 103 | - | markReadBtn.classList.add("markRead"); | |
| 104 | - | markReadBtn.textContent = markDoneText; | |
| 105 | - | newCmtMarker.textContent = newReplyText; | |
| 106 | - | newCmtMarker.classList.add('newComment'); | |
| 107 | - | top.classList.add('highlight'); | |
| 108 | - | ||
| 109 | - | const readListener = (e) => { | |
| 52 | + | if (window.location.href.match(/https:\/\/[a-z\-]+\.dreamwidth.org\/(?:[0-9]+|(?:[0-9]{4}\/[0-9]{2}\/[0-9]{2}\/[a-z\-]+))\.html/)) { // checks that this is like, an actual entry | |
| 53 | + | 'use strict'; | |
| 54 | + | ||
| 55 | + | // set up globals | |
| 56 | + | let readReplies = []; | |
| 57 | + | let linkList = []; | |
| 58 | + | ||
| 59 | + | // get username from account links | |
| 60 | + | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 61 | + | ||
| 62 | + | // initialize user settings | |
| 63 | + | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 64 | + | const markDoneText = "✅ Mark Thread Done"; | |
| 65 | + | const highlightColor = "yellow"; | |
| 66 | + | ||
| 67 | + | // create elements for jump links menu | |
| 68 | + | const jumpLinks = document.createElement('div'); | |
| 69 | + | const jumpDetails = document.createElement('details'); | |
| 70 | + | const jumpSummary = document.createElement('summary'); | |
| 71 | + | const jumpLinksOl = document.createElement('ol'); | |
| 72 | + | const settingsLink = document.createElement('a'); | |
| 73 | + | jumpSummary.textContent = "🧵 New Comments"; | |
| 74 | + | jumpDetails.append(jumpSummary); | |
| 75 | + | jumpDetails.append(jumpLinksOl); | |
| 76 | + | jumpLinks.append(jumpDetails); | |
| 77 | + | settingsLink.textContent = "⚙️ Settings"; | |
| 78 | + | settingsLink.href = "#"; | |
| 79 | + | settingsLink.addEventListener('click', (e) => { | |
| 110 | 80 | e.preventDefault(); | |
| 111 | - | addReadReply(id); | |
| 112 | - | top.classList.remove("highlight"); | |
| 113 | - | newCmtMarker.remove(); | |
| 114 | - | markReadBtn.remove(); | |
| 115 | - | jumpLinks.querySelector(`a[href="#cmt${id}"]`).parentNode.remove(); | |
| 116 | - | if (expandListener) { | |
| 117 | - | removeEventListener('click', expandListener); | |
| 81 | + | gmc.open(); | |
| 82 | + | }); | |
| 83 | + | jumpLinks.append(settingsLink); | |
| 84 | + | jumpLinks.id = 'jump-links'; | |
| 85 | + | document.body.append(jumpLinks); | |
| 86 | + | ||
| 87 | + | // add an item to the list of threads we're ignoring | |
| 88 | + | const addReadReply = (id) => { | |
| 89 | + | if (readReplies.indexOf(parseInt(id)) === -1) { | |
| 90 | + | readReplies.push(parseInt(id)); | |
| 91 | + | localStorage.setItem("readComments", JSON.stringify(readReplies)); | |
| 118 | 92 | } | |
| 119 | 93 | }; | |
| 120 | 94 | ||
| 121 | - | markReadBtn.addEventListener('click', readListener); | |
| 95 | + | // adds UI elements to highlighted comments | |
| 96 | + | const highlightCmt = (cmt, top) => { | |
| 97 | + | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 98 | + | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 99 | + | const info = top.querySelector('.comment-info'); | |
| 100 | + | const markReadBtn = document.createElement('button'); | |
| 101 | + | const newCmtMarker = document.createElement('span'); | |
| 102 | + | let expandListener = null; | |
| 103 | + | markReadBtn.setAttribute('type', 'button'); | |
| 104 | + | markReadBtn.classList.add("markRead"); | |
| 105 | + | markReadBtn.textContent = markDoneText; | |
| 106 | + | newCmtMarker.textContent = newReplyText; | |
| 107 | + | newCmtMarker.classList.add('newComment'); | |
| 108 | + | top.classList.add('highlight'); | |
| 122 | 109 | ||
| 123 | - | if (top.querySelector('.partial')) { | |
| 124 | - | top.querySelector('.comment .inner').append(markReadBtn); | |
| 125 | - | expandListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 110 | + | const readListener = (e) => { | |
| 111 | + | e.preventDefault(); | |
| 112 | + | addReadReply(id); | |
| 113 | + | top.classList.remove("highlight"); | |
| 126 | 114 | newCmtMarker.remove(); | |
| 127 | 115 | markReadBtn.remove(); | |
| 128 | - | const waitForExpansion = setTimeout(() => { | |
| 129 | - | if (top.querySelector('.comment-info h4')) { | |
| 130 | - | top.querySelector('.comment-info h4').append(newCmtMarker); | |
| 131 | - | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 132 | - | markReadBtn.addEventListener('click', readListener); | |
| 133 | - | clearInterval(waitForExpansion); | |
| 134 | - | } | |
| 135 | - | }, 1000); | |
| 136 | - | }); | |
| 137 | - | } else { | |
| 138 | - | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 139 | - | } | |
| 140 | - | }; | |
| 141 | - | ||
| 142 | - | const main = () => { | |
| 143 | - | // create styles for stuff we're adding to the UI | |
| 144 | - | const newStyles = document.createElement('style'); | |
| 145 | - | newStyles.setAttribute('type', 'text/css'); | |
| 146 | - | newStyles.textContent = ` | |
| 147 | - | .highlight .partial > .comment, | |
| 148 | - | .highlight .visible .comment-info { | |
| 149 | - | background-color: ${highlightColor} !important; | |
| 150 | - | } | |
| 116 | + | jumpLinks.querySelector(`a[href="#cmt${id}"]`).parentNode.remove(); | |
| 117 | + | if (expandListener) { | |
| 118 | + | removeEventListener('click', expandListener); | |
| 119 | + | } | |
| 120 | + | }; | |
| 151 | 121 | ||
| 152 | - | button.markRead { | |
| 153 | - | padding: 2px; | |
| 154 | - | margin-bottom: 0; | |
| 155 | - | font-size: .8em; | |
| 156 | - | } | |
| 122 | + | markReadBtn.addEventListener('click', readListener); | |
| 157 | 123 | ||
| 158 | - | .comment .inner > button.markRead { | |
| 159 | - | margin-left: 5px; | |
| 160 | - | } | |
| 124 | + | if (top.querySelector('.partial')) { | |
| 125 | + | top.querySelector('.comment .inner').append(markReadBtn); | |
| 126 | + | expandListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 127 | + | newCmtMarker.remove(); | |
| 128 | + | markReadBtn.remove(); | |
| 129 | + | const waitForExpansion = setTimeout(() => { | |
| 130 | + | if (top.querySelector('.comment-info h4')) { | |
| 131 | + | top.querySelector('.comment-info h4').append(newCmtMarker); | |
| 132 | + | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 133 | + | markReadBtn.addEventListener('click', readListener); | |
| 134 | + | clearInterval(waitForExpansion); | |
| 135 | + | } | |
| 136 | + | }, 1000); | |
| 137 | + | }); | |
| 138 | + | } else { | |
| 139 | + | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 140 | + | } | |
| 141 | + | }; | |
| 161 | 142 | ||
| 162 | - | #jump-links { | |
| 163 | - | position: fixed; | |
| 164 | - | bottom: 10px; | |
| 165 | - | left: 10px; | |
| 166 | - | padding: 5px; | |
| 167 | - | border: 1px gray solid; | |
| 168 | - | background-color: #FFFFFF66; | |
| 169 | - | max-height: 150px; | |
| 170 | - | max-width: 200px; | |
| 171 | - | overflow: auto; | |
| 172 | - | opacity: .5; | |
| 173 | - | } | |
| 143 | + | const main = () => { | |
| 144 | + | // create styles for stuff we're adding to the UI | |
| 145 | + | const newStyles = document.createElement('style'); | |
| 146 | + | newStyles.setAttribute('type', 'text/css'); | |
| 147 | + | newStyles.textContent = ` | |
| 148 | + | .highlight .partial > .comment, | |
| 149 | + | .highlight .visible .comment-info { | |
| 150 | + | background-color: ${highlightColor} !important; | |
| 151 | + | } | |
| 174 | 152 | ||
| 175 | - | #jump-links summary { | |
| 176 | - | cursor: pointer; | |
| 177 | - | list-style: none; | |
| 178 | - | } | |
| 153 | + | button.markRead { | |
| 154 | + | padding: 2px; | |
| 155 | + | margin-bottom: 0; | |
| 156 | + | font-size: .8em; | |
| 157 | + | } | |
| 179 | 158 | ||
| 180 | - | #jump-links > a { | |
| 181 | - | text-decoration: none; | |
| 182 | - | color: inherit; | |
| 183 | - | } | |
| 159 | + | .comment .inner > button.markRead { | |
| 160 | + | margin-left: 5px; | |
| 161 | + | } | |
| 184 | 162 | ||
| 185 | - | #jump-links ol { | |
| 186 | - | font-size: .8em; | |
| 187 | - | margin-bottom: 0; | |
| 188 | - | } | |
| 163 | + | #jump-links { | |
| 164 | + | position: fixed; | |
| 165 | + | bottom: 10px; | |
| 166 | + | left: 10px; | |
| 167 | + | padding: 5px; | |
| 168 | + | border: 1px gray solid; | |
| 169 | + | background-color: #FFFFFF66; | |
| 170 | + | max-height: 150px; | |
| 171 | + | max-width: 200px; | |
| 172 | + | overflow: auto; | |
| 173 | + | opacity: .5; | |
| 174 | + | } | |
| 189 | 175 | ||
| 190 | - | #jump-links:hover, | |
| 191 | - | #jump-links:focus-within { | |
| 192 | - | opacity: 1; | |
| 193 | - | } | |
| 194 | - | `; | |
| 195 | - | document.head.append(newStyles); | |
| 176 | + | #jump-links summary { | |
| 177 | + | cursor: pointer; | |
| 178 | + | list-style: none; | |
| 179 | + | } | |
| 196 | 180 | ||
| 197 | - | readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; | |
| 181 | + | #jump-links > a { | |
| 182 | + | text-decoration: none; | |
| 183 | + | color: inherit; | |
| 184 | + | } | |
| 198 | 185 | ||
| 199 | - | let prevDepth = 1; | |
| 200 | - | let topOfThread = null; | |
| 201 | - | let prevComment = null; | |
| 186 | + | #jump-links ol { | |
| 187 | + | font-size: .8em; | |
| 188 | + | margin-bottom: 0; | |
| 189 | + | } | |
| 202 | 190 | ||
| 203 | - | // loop over all comments | |
| 204 | - | document.querySelectorAll('.comment-thread').forEach((cmt) => { | |
| 205 | - | // get the depth from the comment classes | |
| 206 | - | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 191 | + | #jump-links:hover, | |
| 192 | + | #jump-links:focus-within { | |
| 193 | + | opacity: 1; | |
| 194 | + | } | |
| 195 | + | `; | |
| 196 | + | document.head.append(newStyles); | |
| 207 | 197 | ||
| 208 | - | // if we're not currently in a thread, check if this is the top of a thread (either belongs to us or is a toplevel) | |
| 209 | - | if (!topOfThread || depth === 1) { | |
| 210 | - | if (!cmt.querySelector('.comment-wrapper').classList.contains(`poster-${username}`)) { | |
| 211 | - | prevComment = null; | |
| 198 | + | readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; | |
| 199 | + | ||
| 200 | + | let prevDepth = 1; | |
| 201 | + | let topOfThread = null; | |
| 202 | + | let prevComment = null; | |
| 203 | + | ||
| 204 | + | // loop over all comments | |
| 205 | + | document.querySelectorAll('.comment-thread').forEach((cmt) => { | |
| 206 | + | // get the depth from the comment classes | |
| 207 | + | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 208 | + | ||
| 209 | + | // if we're not currently in a thread, check if this is the top of a thread (either belongs to us or is a toplevel) | |
| 210 | + | if (!topOfThread || depth === 1) { | |
| 211 | + | if (!cmt.querySelector('.comment-wrapper').classList.contains(`poster-${username}`)) { | |
| 212 | + | prevComment = null; | |
| 213 | + | return; | |
| 214 | + | } | |
| 215 | + | topOfThread = cmt; | |
| 216 | + | prevComment = cmt; | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | // if it's just a reply in the same thread but not the end keep going | |
| 220 | + | if (depth >= prevDepth) { | |
| 221 | + | prevComment = cmt; | |
| 222 | + | prevDepth = depth; | |
| 212 | 223 | return; | |
| 213 | 224 | } | |
| 214 | - | topOfThread = cmt; | |
| 215 | - | prevComment = cmt; | |
| 216 | - | } | |
| 217 | 225 | ||
| 218 | - | // if it's just a reply in the same thread but not the end keep going | |
| 219 | - | if (depth >= prevDepth) { | |
| 220 | - | prevComment = cmt; | |
| 221 | - | prevDepth = depth; | |
| 222 | - | return; | |
| 223 | - | } | |
| 226 | + | // if the depth is lower than it was in the last comment, the thread has ended | |
| 227 | + | if (depth < prevDepth) { | |
| 228 | + | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 224 | 229 | ||
| 225 | - | // if the depth is lower than it was in the last comment, the thread has ended | |
| 226 | - | if (depth < prevDepth) { | |
| 227 | - | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 228 | - | ||
| 229 | - | console.log(`depth: ${depth}; prevDepth: ${prevDepth}`); | |
| 230 | - | console.log('comment'); | |
| 231 | - | console.log(cmt); | |
| 232 | - | console.log('top of thread'); | |
| 233 | - | console.log(topOfThread); | |
| 234 | - | console.log('prev comment'); | |
| 235 | - | console.log(prevComment); | |
| 236 | - | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 237 | - | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1 && cmt !== topOfThread) { | |
| 238 | - | ||
| 239 | - | highlightCmt(cmt, topOfThread); | |
| 240 | - | linkList.push(id); | |
| 230 | + | console.log(`depth: ${depth}; prevDepth: ${prevDepth}`); | |
| 231 | + | console.log('comment'); | |
| 232 | + | console.log(cmt); | |
| 233 | + | console.log('top of thread'); | |
| 234 | + | console.log(topOfThread); | |
| 235 | + | console.log('prev comment'); | |
| 236 | + | console.log(prevComment); | |
| 237 | + | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 238 | + | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1 && cmt !== topOfThread) { | |
| 239 | + | ||
| 240 | + | highlightCmt(cmt, topOfThread); | |
| 241 | + | linkList.push(id); | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | // regardless, unset the thread variables | |
| 245 | + | topOfThread = null; | |
| 246 | + | prevComment = null; | |
| 247 | + | prevDepth = 1; | |
| 248 | + | return; | |
| 241 | 249 | } | |
| 242 | 250 | ||
| 243 | - | // regardless, unset the thread variables | |
| 244 | - | topOfThread = null; | |
| 245 | - | prevComment = null; | |
| 246 | - | prevDepth = 1; | |
| 247 | - | return; | |
| 248 | - | } | |
| 251 | + | // set variables for next loop | |
| 252 | + | prevComment = cmt; | |
| 253 | + | prevDepth = depth; | |
| 254 | + | }); | |
| 249 | 255 | ||
| 250 | - | // set variables for next loop | |
| 251 | - | prevComment = cmt; | |
| 252 | - | prevDepth = depth; | |
| 253 | - | }); | |
| 256 | + | // add comments to the floating bottom left menu | |
| 257 | + | linkList.forEach((id) => { | |
| 258 | + | const newLi = document.createElement('li'); | |
| 259 | + | const newA = document.createElement('a'); | |
| 260 | + | newA.href = `#cmt${id}`; | |
| 261 | + | newA.textContent = `#${id}`; | |
| 262 | + | newLi.append(newA); | |
| 263 | + | jumpLinksOl.append(newLi); | |
| 264 | + | }); | |
| 265 | + | }; | |
| 254 | 266 | ||
| 255 | - | // add comments to the floating bottom left menu | |
| 256 | - | linkList.forEach((id) => { | |
| 257 | - | const newLi = document.createElement('li'); | |
| 258 | - | const newA = document.createElement('a'); | |
| 259 | - | newA.href = `#cmt${id}`; | |
| 260 | - | newA.textContent = `#${id}`; | |
| 261 | - | newLi.append(newA); | |
| 262 | - | jumpLinksOl.append(newLi); | |
| 263 | - | }); | |
| 264 | - | }; | |
| 265 | 267 | ||
| 266 | - | if (window.location.href.match(/https:\/\/[a-z\-]+\.dreamwidth.org\/(?:[0-9]+|(?:[0-9]{4}\/[0-9]{2}\/[0-9]{2}\/[a-z\-]+))\.html/)) { // checks that this is like, an actual entry | |
| 267 | 268 | main(); | |
| 268 | 269 | } | |
| 269 | 270 | })(); | |
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 9 insertions, 2 deletions
newcomments.user.js
| @@ -213,7 +213,6 @@ let gmc = new GM_config( | |||
| 213 | 213 | } | |
| 214 | 214 | topOfThread = cmt; | |
| 215 | 215 | prevComment = cmt; | |
| 216 | - | return; | |
| 217 | 216 | } | |
| 218 | 217 | ||
| 219 | 218 | // if it's just a reply in the same thread but not the end keep going | |
| @@ -227,8 +226,16 @@ let gmc = new GM_config( | |||
| 227 | 226 | if (depth < prevDepth) { | |
| 228 | 227 | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 229 | 228 | ||
| 229 | + | console.log(`depth: ${depth}; prevDepth: ${prevDepth}`); | |
| 230 | + | console.log('comment'); | |
| 231 | + | console.log(cmt); | |
| 232 | + | console.log('top of thread'); | |
| 233 | + | console.log(topOfThread); | |
| 234 | + | console.log('prev comment'); | |
| 235 | + | console.log(prevComment); | |
| 230 | 236 | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 231 | - | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1) { | |
| 237 | + | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1 && cmt !== topOfThread) { | |
| 238 | + | ||
| 232 | 239 | highlightCmt(cmt, topOfThread); | |
| 233 | 240 | linkList.push(id); | |
| 234 | 241 | } | |
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 32 insertions, 1 deletion
newcomments.user.js
| @@ -13,6 +13,7 @@ | |||
| 13 | 13 | // @grant GM.setValue | |
| 14 | 14 | // ==/UserScript== | |
| 15 | 15 | ||
| 16 | + | // make the settings | |
| 16 | 17 | let gmc = new GM_config( | |
| 17 | 18 | { | |
| 18 | 19 | 'id': 'veryroundbirdhouse_newreplies', | |
| @@ -49,12 +50,20 @@ let gmc = new GM_config( | |||
| 49 | 50 | ||
| 50 | 51 | (function() { | |
| 51 | 52 | 'use strict'; | |
| 53 | + | ||
| 54 | + | // set up globals | |
| 52 | 55 | let readReplies = []; | |
| 53 | 56 | let linkList = []; | |
| 57 | + | ||
| 58 | + | // get username from account links | |
| 54 | 59 | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 60 | + | ||
| 61 | + | // initialize user settings | |
| 55 | 62 | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 56 | 63 | const markDoneText = "✅ Mark Thread Done"; | |
| 57 | 64 | const highlightColor = "yellow"; | |
| 65 | + | ||
| 66 | + | // create elements for jump links menu | |
| 58 | 67 | const jumpLinks = document.createElement('div'); | |
| 59 | 68 | const jumpDetails = document.createElement('details'); | |
| 60 | 69 | const jumpSummary = document.createElement('summary'); | |
| @@ -74,6 +83,7 @@ let gmc = new GM_config( | |||
| 74 | 83 | jumpLinks.id = 'jump-links'; | |
| 75 | 84 | document.body.append(jumpLinks); | |
| 76 | 85 | ||
| 86 | + | // add an item to the list of threads we're ignoring | |
| 77 | 87 | const addReadReply = (id) => { | |
| 78 | 88 | if (readReplies.indexOf(parseInt(id)) === -1) { | |
| 79 | 89 | readReplies.push(parseInt(id)); | |
| @@ -81,6 +91,7 @@ let gmc = new GM_config( | |||
| 81 | 91 | } | |
| 82 | 92 | }; | |
| 83 | 93 | ||
| 94 | + | // adds UI elements to highlighted comments | |
| 84 | 95 | const highlightCmt = (cmt, top) => { | |
| 85 | 96 | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 86 | 97 | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| @@ -129,6 +140,7 @@ let gmc = new GM_config( | |||
| 129 | 140 | }; | |
| 130 | 141 | ||
| 131 | 142 | const main = () => { | |
| 143 | + | // create styles for stuff we're adding to the UI | |
| 132 | 144 | const newStyles = document.createElement('style'); | |
| 133 | 145 | newStyles.setAttribute('type', 'text/css'); | |
| 134 | 146 | newStyles.textContent = ` | |
| @@ -183,11 +195,17 @@ let gmc = new GM_config( | |||
| 183 | 195 | document.head.append(newStyles); | |
| 184 | 196 | ||
| 185 | 197 | readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; | |
| 198 | + | ||
| 186 | 199 | let prevDepth = 1; | |
| 187 | 200 | let topOfThread = null; | |
| 188 | 201 | let prevComment = null; | |
| 202 | + | ||
| 203 | + | // loop over all comments | |
| 189 | 204 | document.querySelectorAll('.comment-thread').forEach((cmt) => { | |
| 205 | + | // get the depth from the comment classes | |
| 190 | 206 | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 207 | + | ||
| 208 | + | // if we're not currently in a thread, check if this is the top of a thread (either belongs to us or is a toplevel) | |
| 191 | 209 | if (!topOfThread || depth === 1) { | |
| 192 | 210 | if (!cmt.querySelector('.comment-wrapper').classList.contains(`poster-${username}`)) { | |
| 193 | 211 | prevComment = null; | |
| @@ -197,26 +215,37 @@ let gmc = new GM_config( | |||
| 197 | 215 | prevComment = cmt; | |
| 198 | 216 | return; | |
| 199 | 217 | } | |
| 218 | + | ||
| 219 | + | // if it's just a reply in the same thread but not the end keep going | |
| 200 | 220 | if (depth >= prevDepth) { | |
| 201 | 221 | prevComment = cmt; | |
| 202 | 222 | prevDepth = depth; | |
| 203 | 223 | return; | |
| 204 | 224 | } | |
| 225 | + | ||
| 226 | + | // if the depth is lower than it was in the last comment, the thread has ended | |
| 205 | 227 | if (depth < prevDepth) { | |
| 206 | 228 | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 229 | + | ||
| 230 | + | // if this comment isn't ours and it's not in our ignored threads, highlight it | |
| 207 | 231 | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1) { | |
| 208 | 232 | highlightCmt(cmt, topOfThread); | |
| 209 | 233 | linkList.push(id); | |
| 210 | 234 | } | |
| 235 | + | ||
| 236 | + | // regardless, unset the thread variables | |
| 211 | 237 | topOfThread = null; | |
| 212 | 238 | prevComment = null; | |
| 213 | 239 | prevDepth = 1; | |
| 214 | 240 | return; | |
| 215 | 241 | } | |
| 242 | + | ||
| 243 | + | // set variables for next loop | |
| 216 | 244 | prevComment = cmt; | |
| 217 | 245 | prevDepth = depth; | |
| 218 | 246 | }); | |
| 219 | 247 | ||
| 248 | + | // add comments to the floating bottom left menu | |
| 220 | 249 | linkList.forEach((id) => { | |
| 221 | 250 | const newLi = document.createElement('li'); | |
| 222 | 251 | const newA = document.createElement('a'); | |
| @@ -227,5 +256,7 @@ let gmc = new GM_config( | |||
| 227 | 256 | }); | |
| 228 | 257 | }; | |
| 229 | 258 | ||
| 230 | - | main(); | |
| 259 | + | if (window.location.href.match(/https:\/\/[a-z\-]+\.dreamwidth.org\/(?:[0-9]+|(?:[0-9]{4}\/[0-9]{2}\/[0-9]{2}\/[a-z\-]+))\.html/)) { // checks that this is like, an actual entry | |
| 260 | + | main(); | |
| 261 | + | } | |
| 231 | 262 | })(); | |
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 1 insertion, 1 deletion
newcomments.user.js
| @@ -109,7 +109,7 @@ let gmc = new GM_config( | |||
| 109 | 109 | ||
| 110 | 110 | markReadBtn.addEventListener('click', readListener); | |
| 111 | 111 | ||
| 112 | - | if (cmt.querySelector('.partial')) { | |
| 112 | + | if (top.querySelector('.partial')) { | |
| 113 | 113 | top.querySelector('.comment .inner').append(markReadBtn); | |
| 114 | 114 | expandListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 115 | 115 | newCmtMarker.remove(); | |
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 92 insertions, 19 deletions
newcomments.user.js
| @@ -6,20 +6,71 @@ | |||
| 6 | 6 | // @author Carly Smallbird | |
| 7 | 7 | // @match https://*.dreamwidth.org/* | |
| 8 | 8 | // @icon https://www.google.com/s2/favicons?sz=64&domain=dreamwidth.org | |
| 9 | - | // @grant none | |
| 9 | + | // @require https://openuserjs.org/src/libs/sizzle/GM_config.js | |
| 10 | + | // @grant GM_getValue | |
| 11 | + | // @grant GM_setValue | |
| 12 | + | // @grant GM.getValue | |
| 13 | + | // @grant GM.setValue | |
| 10 | 14 | // ==/UserScript== | |
| 11 | 15 | ||
| 16 | + | let gmc = new GM_config( | |
| 17 | + | { | |
| 18 | + | 'id': 'veryroundbirdhouse_newreplies', | |
| 19 | + | 'title': 'Settings', | |
| 20 | + | 'fields': | |
| 21 | + | { | |
| 22 | + | 'newReplyText': | |
| 23 | + | { | |
| 24 | + | 'label': 'New Reply Text', | |
| 25 | + | 'type': 'text', | |
| 26 | + | 'default': '✨ NEW REPLIES ✨' | |
| 27 | + | }, | |
| 28 | + | 'markDoneText': | |
| 29 | + | { | |
| 30 | + | 'label': 'Mark Thread Done Text', | |
| 31 | + | 'type': 'text', | |
| 32 | + | 'default': '✅ Mark Thread Done' | |
| 33 | + | }, | |
| 34 | + | 'highlightColor': | |
| 35 | + | { | |
| 36 | + | 'label': 'Highlight Color', | |
| 37 | + | 'type': 'text', | |
| 38 | + | 'default': 'yellow' | |
| 39 | + | } | |
| 40 | + | }, | |
| 41 | + | 'init': () => { | |
| 42 | + | gmc.css.basic = ` | |
| 43 | + | label { | |
| 44 | + | display: block; | |
| 45 | + | } | |
| 46 | + | `; | |
| 47 | + | } | |
| 48 | + | }); | |
| 49 | + | ||
| 12 | 50 | (function() { | |
| 13 | 51 | 'use strict'; | |
| 14 | 52 | let readReplies = []; | |
| 15 | 53 | let linkList = []; | |
| 16 | 54 | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 17 | 55 | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 18 | - | const markDoneText = "Mark Thread Done"; | |
| 56 | + | const markDoneText = "✅ Mark Thread Done"; | |
| 19 | 57 | const highlightColor = "yellow"; | |
| 20 | 58 | const jumpLinks = document.createElement('div'); | |
| 59 | + | const jumpDetails = document.createElement('details'); | |
| 60 | + | const jumpSummary = document.createElement('summary'); | |
| 21 | 61 | const jumpLinksOl = document.createElement('ol'); | |
| 22 | - | jumpLinks.append(jumpLinksOl); | |
| 62 | + | const settingsLink = document.createElement('a'); | |
| 63 | + | jumpSummary.textContent = "🧵 New Comments"; | |
| 64 | + | jumpDetails.append(jumpSummary); | |
| 65 | + | jumpDetails.append(jumpLinksOl); | |
| 66 | + | jumpLinks.append(jumpDetails); | |
| 67 | + | settingsLink.textContent = "⚙️ Settings"; | |
| 68 | + | settingsLink.href = "#"; | |
| 69 | + | settingsLink.addEventListener('click', (e) => { | |
| 70 | + | e.preventDefault(); | |
| 71 | + | gmc.open(); | |
| 72 | + | }); | |
| 73 | + | jumpLinks.append(settingsLink); | |
| 23 | 74 | jumpLinks.id = 'jump-links'; | |
| 24 | 75 | document.body.append(jumpLinks); | |
| 25 | 76 | ||
| @@ -35,34 +86,46 @@ | |||
| 35 | 86 | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 36 | 87 | const info = top.querySelector('.comment-info'); | |
| 37 | 88 | const markReadBtn = document.createElement('button'); | |
| 38 | - | let readListener = null; | |
| 89 | + | const newCmtMarker = document.createElement('span'); | |
| 90 | + | let expandListener = null; | |
| 39 | 91 | markReadBtn.setAttribute('type', 'button'); | |
| 40 | 92 | markReadBtn.classList.add("markRead"); | |
| 41 | - | markReadBtn.textContent = newReplyText; | |
| 93 | + | markReadBtn.textContent = markDoneText; | |
| 94 | + | newCmtMarker.textContent = newReplyText; | |
| 95 | + | newCmtMarker.classList.add('newComment'); | |
| 42 | 96 | top.classList.add('highlight'); | |
| 43 | 97 | ||
| 98 | + | const readListener = (e) => { | |
| 99 | + | e.preventDefault(); | |
| 100 | + | addReadReply(id); | |
| 101 | + | top.classList.remove("highlight"); | |
| 102 | + | newCmtMarker.remove(); | |
| 103 | + | markReadBtn.remove(); | |
| 104 | + | jumpLinks.querySelector(`a[href="#cmt${id}"]`).parentNode.remove(); | |
| 105 | + | if (expandListener) { | |
| 106 | + | removeEventListener('click', expandListener); | |
| 107 | + | } | |
| 108 | + | }; | |
| 109 | + | ||
| 110 | + | markReadBtn.addEventListener('click', readListener); | |
| 111 | + | ||
| 44 | 112 | if (cmt.querySelector('.partial')) { | |
| 45 | 113 | top.querySelector('.comment .inner').append(markReadBtn); | |
| 46 | - | readListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 114 | + | expandListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 115 | + | newCmtMarker.remove(); | |
| 47 | 116 | markReadBtn.remove(); | |
| 48 | - | setTimeout(() => { | |
| 49 | - | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 117 | + | const waitForExpansion = setTimeout(() => { | |
| 118 | + | if (top.querySelector('.comment-info h4')) { | |
| 119 | + | top.querySelector('.comment-info h4').append(newCmtMarker); | |
| 120 | + | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 121 | + | markReadBtn.addEventListener('click', readListener); | |
| 122 | + | clearInterval(waitForExpansion); | |
| 123 | + | } | |
| 50 | 124 | }, 1000); | |
| 51 | 125 | }); | |
| 52 | 126 | } else { | |
| 53 | 127 | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 54 | 128 | } | |
| 55 | - | ||
| 56 | - | markReadBtn.addEventListener('click', (e) => { | |
| 57 | - | e.preventDefault(); | |
| 58 | - | addReadReply(id); | |
| 59 | - | top.classList.remove("highlight"); | |
| 60 | - | markReadBtn.remove(); | |
| 61 | - | jumpLinks.querySelector(`a[href="#cmt${id}"]`).remove(); | |
| 62 | - | if (readListener) { | |
| 63 | - | removeEventListener('click', readListener); | |
| 64 | - | } | |
| 65 | - | }); | |
| 66 | 129 | }; | |
| 67 | 130 | ||
| 68 | 131 | const main = () => { | |
| @@ -97,6 +160,16 @@ | |||
| 97 | 160 | opacity: .5; | |
| 98 | 161 | } | |
| 99 | 162 | ||
| 163 | + | #jump-links summary { | |
| 164 | + | cursor: pointer; | |
| 165 | + | list-style: none; | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | #jump-links > a { | |
| 169 | + | text-decoration: none; | |
| 170 | + | color: inherit; | |
| 171 | + | } | |
| 172 | + | ||
| 100 | 173 | #jump-links ol { | |
| 101 | 174 | font-size: .8em; | |
| 102 | 175 | margin-bottom: 0; | |
veryroundbird revised this gist 1 month ago. Go to revision
No changes
veryroundbird revised this gist 1 month ago. Go to revision
1 file changed, 158 insertions
newcomments.user.js(file created)
| @@ -0,0 +1,158 @@ | |||
| 1 | + | // ==UserScript== | |
| 2 | + | // @name Highlight New Replies | |
| 3 | + | // @namespace http://veryroundbird.house | |
| 4 | + | // @version 2026-06-03 | |
| 5 | + | // @description highlights your threads where you're not the most recent reply. | |
| 6 | + | // @author Carly Smallbird | |
| 7 | + | // @match https://*.dreamwidth.org/* | |
| 8 | + | // @icon https://www.google.com/s2/favicons?sz=64&domain=dreamwidth.org | |
| 9 | + | // @grant none | |
| 10 | + | // ==/UserScript== | |
| 11 | + | ||
| 12 | + | (function() { | |
| 13 | + | 'use strict'; | |
| 14 | + | let readReplies = []; | |
| 15 | + | let linkList = []; | |
| 16 | + | const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); | |
| 17 | + | const newReplyText = "✨ NEW REPLIES ✨"; | |
| 18 | + | const markDoneText = "Mark Thread Done"; | |
| 19 | + | const highlightColor = "yellow"; | |
| 20 | + | const jumpLinks = document.createElement('div'); | |
| 21 | + | const jumpLinksOl = document.createElement('ol'); | |
| 22 | + | jumpLinks.append(jumpLinksOl); | |
| 23 | + | jumpLinks.id = 'jump-links'; | |
| 24 | + | document.body.append(jumpLinks); | |
| 25 | + | ||
| 26 | + | const addReadReply = (id) => { | |
| 27 | + | if (readReplies.indexOf(parseInt(id)) === -1) { | |
| 28 | + | readReplies.push(parseInt(id)); | |
| 29 | + | localStorage.setItem("readComments", JSON.stringify(readReplies)); | |
| 30 | + | } | |
| 31 | + | }; | |
| 32 | + | ||
| 33 | + | const highlightCmt = (cmt, top) => { | |
| 34 | + | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 35 | + | const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 36 | + | const info = top.querySelector('.comment-info'); | |
| 37 | + | const markReadBtn = document.createElement('button'); | |
| 38 | + | let readListener = null; | |
| 39 | + | markReadBtn.setAttribute('type', 'button'); | |
| 40 | + | markReadBtn.classList.add("markRead"); | |
| 41 | + | markReadBtn.textContent = newReplyText; | |
| 42 | + | top.classList.add('highlight'); | |
| 43 | + | ||
| 44 | + | if (cmt.querySelector('.partial')) { | |
| 45 | + | top.querySelector('.comment .inner').append(markReadBtn); | |
| 46 | + | readListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { | |
| 47 | + | markReadBtn.remove(); | |
| 48 | + | setTimeout(() => { | |
| 49 | + | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 50 | + | }, 1000); | |
| 51 | + | }); | |
| 52 | + | } else { | |
| 53 | + | top.querySelector('.comment-info h4').append(markReadBtn); | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | markReadBtn.addEventListener('click', (e) => { | |
| 57 | + | e.preventDefault(); | |
| 58 | + | addReadReply(id); | |
| 59 | + | top.classList.remove("highlight"); | |
| 60 | + | markReadBtn.remove(); | |
| 61 | + | jumpLinks.querySelector(`a[href="#cmt${id}"]`).remove(); | |
| 62 | + | if (readListener) { | |
| 63 | + | removeEventListener('click', readListener); | |
| 64 | + | } | |
| 65 | + | }); | |
| 66 | + | }; | |
| 67 | + | ||
| 68 | + | const main = () => { | |
| 69 | + | const newStyles = document.createElement('style'); | |
| 70 | + | newStyles.setAttribute('type', 'text/css'); | |
| 71 | + | newStyles.textContent = ` | |
| 72 | + | .highlight .partial > .comment, | |
| 73 | + | .highlight .visible .comment-info { | |
| 74 | + | background-color: ${highlightColor} !important; | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | button.markRead { | |
| 78 | + | padding: 2px; | |
| 79 | + | margin-bottom: 0; | |
| 80 | + | font-size: .8em; | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | .comment .inner > button.markRead { | |
| 84 | + | margin-left: 5px; | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | #jump-links { | |
| 88 | + | position: fixed; | |
| 89 | + | bottom: 10px; | |
| 90 | + | left: 10px; | |
| 91 | + | padding: 5px; | |
| 92 | + | border: 1px gray solid; | |
| 93 | + | background-color: #FFFFFF66; | |
| 94 | + | max-height: 150px; | |
| 95 | + | max-width: 200px; | |
| 96 | + | overflow: auto; | |
| 97 | + | opacity: .5; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | #jump-links ol { | |
| 101 | + | font-size: .8em; | |
| 102 | + | margin-bottom: 0; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | #jump-links:hover, | |
| 106 | + | #jump-links:focus-within { | |
| 107 | + | opacity: 1; | |
| 108 | + | } | |
| 109 | + | `; | |
| 110 | + | document.head.append(newStyles); | |
| 111 | + | ||
| 112 | + | readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; | |
| 113 | + | let prevDepth = 1; | |
| 114 | + | let topOfThread = null; | |
| 115 | + | let prevComment = null; | |
| 116 | + | document.querySelectorAll('.comment-thread').forEach((cmt) => { | |
| 117 | + | const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); | |
| 118 | + | if (!topOfThread || depth === 1) { | |
| 119 | + | if (!cmt.querySelector('.comment-wrapper').classList.contains(`poster-${username}`)) { | |
| 120 | + | prevComment = null; | |
| 121 | + | return; | |
| 122 | + | } | |
| 123 | + | topOfThread = cmt; | |
| 124 | + | prevComment = cmt; | |
| 125 | + | return; | |
| 126 | + | } | |
| 127 | + | if (depth >= prevDepth) { | |
| 128 | + | prevComment = cmt; | |
| 129 | + | prevDepth = depth; | |
| 130 | + | return; | |
| 131 | + | } | |
| 132 | + | if (depth < prevDepth) { | |
| 133 | + | const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); | |
| 134 | + | if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1) { | |
| 135 | + | highlightCmt(cmt, topOfThread); | |
| 136 | + | linkList.push(id); | |
| 137 | + | } | |
| 138 | + | topOfThread = null; | |
| 139 | + | prevComment = null; | |
| 140 | + | prevDepth = 1; | |
| 141 | + | return; | |
| 142 | + | } | |
| 143 | + | prevComment = cmt; | |
| 144 | + | prevDepth = depth; | |
| 145 | + | }); | |
| 146 | + | ||
| 147 | + | linkList.forEach((id) => { | |
| 148 | + | const newLi = document.createElement('li'); | |
| 149 | + | const newA = document.createElement('a'); | |
| 150 | + | newA.href = `#cmt${id}`; | |
| 151 | + | newA.textContent = `#${id}`; | |
| 152 | + | newLi.append(newA); | |
| 153 | + | jumpLinksOl.append(newLi); | |
| 154 | + | }); | |
| 155 | + | }; | |
| 156 | + | ||
| 157 | + | main(); | |
| 158 | + | })(); | |