Last active 1 month ago

sometimes when a post is really big trying to manage it via inbox is futile. now also checks your threads for AC viability + teammate threads

veryroundbird's Avatar veryroundbird revised this gist 1 month ago. Go to revision

No changes

veryroundbird's Avatar 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's Avatar 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's Avatar 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's Avatar 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's Avatar 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's Avatar 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's Avatar veryroundbird revised this gist 1 month ago. Go to revision

No changes

veryroundbird's Avatar 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 + })();
Newer Older