|
1 | 1 | // ==UserScript==
|
2 | 2 | // @name Unedit and Undelete for Reddit
|
3 | 3 | // @namespace http://tampermonkey.net/
|
4 |
| -// @version 3.8.0 |
| 4 | +// @version 3.9.0 |
5 | 5 | // @description Creates the option next to edited and deleted Reddit comments/posts to show the original comment from before it was edited
|
6 | 6 | // @author Jonah Lawrence (DenverCoder1)
|
7 | 7 | // @match *://*reddit.com/*
|
|
37 | 37 | */
|
38 | 38 | let currentLoading = null;
|
39 | 39 |
|
| 40 | + /** |
| 41 | + * List of submission ids of edited posts. |
| 42 | + * Used on Reddit redesign since the submissions are not marked as such. |
| 43 | + * This is set in the "load" event listener from the Reddit JSON API. |
| 44 | + * @type {Array<{id: string, edited: float}>} |
| 45 | + */ |
| 46 | + let editedSubmissions = []; |
| 47 | + |
| 48 | + /** |
| 49 | + * The current URL that is being viewed. |
| 50 | + * On Redesign, this can change without the user leaving page, |
| 51 | + * so we want to look for new edited submissions if it changes. |
| 52 | + * @type {string} |
| 53 | + */ |
| 54 | + let currentURL = window.location.href; |
| 55 | + |
40 | 56 | /**
|
41 | 57 | * Showdown markdown converter
|
42 | 58 | * @type {showdown.Converter}
|
|
149 | 165 | // redesign
|
150 | 166 | if (!isOldReddit) {
|
151 | 167 | baseEl = document.querySelector(`#${postId}, .Comment.${postId}`);
|
| 168 | + // in post preview popups, the id will appear again but in #overlayScrollContainer |
| 169 | + const popupEl = document.querySelector(`#overlayScrollContainer .Post.${postId}`); |
| 170 | + baseEl = popupEl ? popupEl : baseEl; |
152 | 171 | if (baseEl) {
|
153 | 172 | if (baseEl.getElementsByClassName("RichTextJSON-root").length > 0) {
|
154 | 173 | bodyEl = baseEl.getElementsByClassName("RichTextJSON-root")[0];
|
|
238 | 257 | origBodyEl.scrollIntoView({ behavior: "smooth" });
|
239 | 258 | }
|
240 | 259 | }, 500);
|
241 |
| - // on old reddit, if the comment is collapsed, expand it so the original comment is visible |
242 |
| - if (isOldReddit) { |
| 260 | + // Redesign |
| 261 | + if (!isOldReddit) { |
| 262 | + // Make sure collapsed submission previews are expanded to not hide the original comment. |
| 263 | + commentBodyElement.parentElement.style.maxHeight = "unset"; |
| 264 | + } |
| 265 | + // Old reddit |
| 266 | + else { |
| 267 | + // If the comment is collapsed, expand it so the original comment is visible |
243 | 268 | expandComment(commentBodyElement);
|
244 | 269 | }
|
245 | 270 | }
|
|
347 | 372 | showOriginalComment(commentBodyElement, "comment", post.body);
|
348 | 373 | // remove loading status from comment
|
349 | 374 | loading.innerHTML = "";
|
| 375 | + logging.info("Successfully loaded comment."); |
350 | 376 | } else if (post?.selftext) {
|
351 | 377 | // check if result has selftext instead of body (it is a submission post)
|
352 | 378 | // create new paragraph containing the selftext of the original submission
|
353 | 379 | showOriginalComment(commentBodyElement, "post", post.selftext);
|
354 | 380 | // remove loading status from post
|
355 | 381 | loading.innerHTML = "";
|
| 382 | + logging.info("Successfully loaded post."); |
356 | 383 | } else if (out?.data?.length === 0) {
|
357 | 384 | // data was returned empty
|
358 | 385 | loading.innerHTML = "not found";
|
|
379 | 406 | );
|
380 | 407 | }
|
381 | 408 |
|
| 409 | + /** |
| 410 | + * Convert unix timestamp in seconds to a relative time string (e.g. "2 hours ago"). |
| 411 | + * @param {number} timestamp A unix timestamp in seconds. |
| 412 | + * @returns {string} A relative time string. |
| 413 | + */ |
| 414 | + function getRelativeTime(timestamp) { |
| 415 | + const time = new Date(timestamp * 1000); |
| 416 | + const now = new Date(); |
| 417 | + const seconds = Math.round((now.getTime() - time.getTime()) / 1000); |
| 418 | + const minutes = Math.round(seconds / 60); |
| 419 | + const hours = Math.round(minutes / 60); |
| 420 | + const days = Math.round(hours / 24); |
| 421 | + const months = Math.round(days / 30.5); |
| 422 | + const years = Math.round(days / 365); |
| 423 | + if (years > 0 && months >= 12) { |
| 424 | + return `${years} ${years === 1 ? "year" : "years"} ago`; |
| 425 | + } |
| 426 | + if (months > 0 && days >= 30) { |
| 427 | + return `${months} ${months === 1 ? "month" : "months"} ago`; |
| 428 | + } |
| 429 | + if (days > 0 && hours >= 24) { |
| 430 | + return `${days} ${days === 1 ? "day" : "days"} ago`; |
| 431 | + } |
| 432 | + if (hours > 0 && minutes >= 60) { |
| 433 | + return `${hours} ${hours === 1 ? "hour" : "hours"} ago`; |
| 434 | + } |
| 435 | + if (minutes > 0 && seconds >= 60) { |
| 436 | + return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago`; |
| 437 | + } |
| 438 | + return "just now"; |
| 439 | + } |
| 440 | + |
382 | 441 | /**
|
383 | 442 | * Locate comments and add links to each.
|
384 | 443 | */
|
|
393 | 452 | editedComments = [];
|
394 | 453 | // redesign
|
395 | 454 | if (!isOldReddit) {
|
| 455 | + // check for edited/deleted comments and deleted submissions |
396 | 456 | selectors = [
|
397 |
| - ".Comment div:first-of-type span span:not(.found)", // Comments "edited..." or "Comment deleted/removed..." |
| 457 | + ".Comment div:first-of-type span:not(.found)", // Comments "edited..." or "Comment deleted/removed..." |
398 | 458 | ".Post div div div:last-of-type div ~ div:last-of-type:not(.found)", // Submissions "It doesn't appear in any feeds..." message
|
399 | 459 | ];
|
400 | 460 | elementsToCheck = Array.from(document.querySelectorAll(selectors.join(", ")));
|
401 | 461 | editedComments = elementsToCheck.filter(function (el) {
|
402 | 462 | el.classList.add("found");
|
403 | 463 | return (
|
404 |
| - el.innerText.substring(0, 6) === "edited" || // include edited comments |
405 |
| - el.innerText.substring(0, 15) === "Comment deleted" || // include comments deleted by user |
406 |
| - el.innerText.substring(0, 15) === "Comment removed" || // include comments removed by moderator |
407 |
| - el.innerText.substring(0, 30) === "It doesn't appear in any feeds" || // include deleted submissions |
408 |
| - el.innerText.substring(0, 23) == "Moderators remove posts" // include submissions removed by moderators |
| 464 | + !el.children.length && // we only care about the element if it has no children |
| 465 | + (el.innerText.substring(0, 6) === "edited" || // include edited comments |
| 466 | + el.innerText.substring(0, 15) === "Comment deleted" || // include comments deleted by user |
| 467 | + el.innerText.substring(0, 15) === "Comment removed" || // include comments removed by moderator |
| 468 | + el.innerText.substring(0, 30) === "It doesn't appear in any feeds" || // include deleted submissions |
| 469 | + el.innerText.substring(0, 23) == "Moderators remove posts") // include submissions removed by moderators |
409 | 470 | );
|
410 | 471 | });
|
| 472 | + // Edited submissions found using the Reddit API |
| 473 | + editedSubmissions.forEach((submission) => { |
| 474 | + const postId = submission.id; |
| 475 | + const editedAt = submission.edited; |
| 476 | + selectors = [ |
| 477 | + `#t3_${postId} > div:first-of-type > div:nth-of-type(2) > div:first-of-type > div:first-of-type > span:nth-of-type(3):not(.found)`, // Submission page |
| 478 | + `#t3_${postId} > div:last-of-type[data-click-id] > div:first-of-type > div:first-of-type > div:first-of-type:not(.found)`, // Subreddit listing view |
| 479 | + `.Post.t3_${postId} > div:last-of-type[data-click-id] > div:first-of-type > div:nth-of-type(2) > div:first-of-type:not(.found)`, // Profile/home listing view |
| 480 | + `.Post.t3_${postId}:not(.scrollerItem) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(2) > div:first-of-type > div:first-of-type:not(.found)`, // Preview popup |
| 481 | + ]; |
| 482 | + Array.from(document.querySelectorAll(selectors.join(", "))).forEach((el) => { |
| 483 | + el.classList.add("found"); |
| 484 | + editedComments.push(el); |
| 485 | + // display when the post was edited |
| 486 | + const editedDateElement = document.createElement("span"); |
| 487 | + editedDateElement.classList.add("edited-date"); |
| 488 | + editedDateElement.style.fontStyle = "italic"; |
| 489 | + editedDateElement.innerText = ` \u00b7 edited ${getRelativeTime(editedAt)}`; // middle-dot = \u00b7 |
| 490 | + el.parentElement.appendChild(editedDateElement); |
| 491 | + }); |
| 492 | + }); |
| 493 | + // If the url has changed, check for edited submissions again |
| 494 | + // This is an async fetch that will check for edited submissions again when it is done |
| 495 | + if (currentURL !== window.location.href) { |
| 496 | + logging.info(`URL changed from ${currentURL} to ${window.location.href}`); |
| 497 | + currentURL = window.location.href; |
| 498 | + checkForEditedSubmissions(); |
| 499 | + } |
411 | 500 | }
|
412 | 501 | // old Reddit
|
413 | 502 | else {
|
|
428 | 517 | });
|
429 | 518 | }
|
430 | 519 | // create links
|
431 |
| - editedComments.forEach(function (x) { |
432 |
| - createLink(x); |
| 520 | + editedComments.forEach(function (el) { |
| 521 | + // for removed submissions, add the link to an element in the tagline instead of the body |
| 522 | + if (el.closest(".usertext-body") && el.innerText === "[removed]") { |
| 523 | + el = el.closest(".entry")?.querySelector("p.tagline span:first-of-type") || el; |
| 524 | + } |
| 525 | + createLink(el); |
433 | 526 | });
|
434 | 527 | }
|
435 | 528 |
|
| 529 | + /** |
| 530 | + * If the script timeout is not already set, set it and |
| 531 | + * run the findEditedComments in a second, otherwise do nothing. |
| 532 | + */ |
| 533 | + function waitAndFindEditedComments() { |
| 534 | + if (!scriptTimeout) { |
| 535 | + scriptTimeout = setTimeout(findEditedComments, 1000); |
| 536 | + } |
| 537 | + } |
| 538 | + |
| 539 | + /** |
| 540 | + * Check for edited submissions using the Reddit JSON API. |
| 541 | + * |
| 542 | + * Since the Reddit Redesign website does not show if a submission was edited, |
| 543 | + * we will check the data in the Reddit JSON API for the information. |
| 544 | + */ |
| 545 | + function checkForEditedSubmissions() { |
| 546 | + // don't need to check if we're not on a submission page or list view |
| 547 | + if (!document.querySelector(".Post, .ListingLayout-backgroundContainer")) { |
| 548 | + return; |
| 549 | + } |
| 550 | + const pattern = new URLPattern(window.location.href); |
| 551 | + const jsonUrl = `https://www.reddit.com${pattern.pathname}.json?${pattern.search}`; |
| 552 | + logging.info(`Fetching additional info from ${jsonUrl}`); |
| 553 | + fetch(jsonUrl) |
| 554 | + .then(function (response) { |
| 555 | + if (!response.ok) { |
| 556 | + throw new Error(`${response.status} ${response.statusText}`); |
| 557 | + } |
| 558 | + return response.json(); |
| 559 | + }) |
| 560 | + .then(function (data) { |
| 561 | + logging.info("Response:", data); |
| 562 | + const out = data?.length ? data[0] : data; |
| 563 | + const children = out?.data?.children; |
| 564 | + if (children) { |
| 565 | + editedSubmissions = children |
| 566 | + .filter(function (post) { |
| 567 | + return post.kind === "t3" && post.data.edited; |
| 568 | + }) |
| 569 | + .map(function (post) { |
| 570 | + return { |
| 571 | + id: post.data.id, |
| 572 | + edited: post.data.edited, |
| 573 | + }; |
| 574 | + }); |
| 575 | + logging.info("Edited submissions:", editedSubmissions); |
| 576 | + setTimeout(findEditedComments, 1000); |
| 577 | + } |
| 578 | + }) |
| 579 | + .catch(function (error) { |
| 580 | + logging.error("Error fetching additional info:", error); |
| 581 | + }); |
| 582 | + } |
| 583 | + |
436 | 584 | // check for new comments when you scroll
|
437 |
| - window.addEventListener( |
438 |
| - "scroll", |
439 |
| - function () { |
440 |
| - if (!scriptTimeout) { |
441 |
| - scriptTimeout = setTimeout(findEditedComments, 1000); |
442 |
| - } |
443 |
| - }, |
444 |
| - true |
445 |
| - ); |
| 585 | + window.addEventListener("scroll", waitAndFindEditedComments, true); |
| 586 | + |
| 587 | + // check for new comments when you click |
| 588 | + document.body.addEventListener("click", waitAndFindEditedComments, true); |
446 | 589 |
|
447 | 590 | // add additional styling, find edited comments, and set old reddit status on page load
|
448 | 591 | window.addEventListener("load", function () {
|
449 | 592 | // determine if reddit is old or redesign
|
450 | 593 | isOldReddit = /old\.reddit/.test(window.location.href) || !!document.querySelector("#header-img");
|
451 |
| - // fix styling of created paragraphs in new reddit |
| 594 | + // Reddit redesign |
452 | 595 | if (!isOldReddit) {
|
| 596 | + // fix styling of created paragraphs in new reddit |
453 | 597 | document.head.insertAdjacentHTML(
|
454 | 598 | "beforeend",
|
455 | 599 | "<style>p.og pre { font-family: monospace; background: #fff59d; padding: 6px; margin: 6px 0; color: black; } p.og h1 { font-size: 2em; } p.og h2 { font-size: 1.5em; } p.og > h3:first-child { font-weight: bold; margin-bottom: 0.5em; } p.og h3 { font-size: 1.17em; } p.og h4 { font-size: 1em; } p.og h5 { font-size: 0.83em; } p.og h6 { font-size: 0.67em; } p.og a { color: lightblue; text-decoration: underline; }</style>"
|
456 | 600 | );
|
| 601 | + // check for edited submissions |
| 602 | + checkForEditedSubmissions(); |
457 | 603 | }
|
458 | 604 | // find edited comments
|
459 | 605 | findEditedComments();
|
|
0 commit comments