// ==UserScript== // @name redditmod // @namespace derv82 // @description Subset of RES features I like. // @include https://*.reddit.com/* // @version 1.5.8 // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @downloadURL none // ==/UserScript== /* TODO Features: -> UX Overhaul: Hovering a post has /large/ buttons to 1. Expand the content, 2. Expand the comments, 3. Hide this post, 4. Filter the subreddit -> Media overhaul: Don't rely on max-height, allow resizing of images -> Customizable UI colors. Or at least different themes. */ var Redditmod = {}; Redditmod.CSS = (function() { var self = this; this.colors = { bg: { base: "#121234", input: "#232356", filterHover: "#f00", flair: "#18f", thingHover: "#18f" }, fg: { text: "#aaa", title: "#18f", visited: "#88f", filter: "#f00", filterHover: "#fff", flair: "#000", spoiler: "#f80" }, border: { input: "#555", flair: "#000" }, shadow: { thingHover: "#18f" } }; this.selectorsAndStyles = [{ selectors: [ "body", ".side", "#header", "#sr-header-area", "#header-bottom-right", ".drop-choices", ".tabmenu li.selected a", ".link .usertext-body .md", ".subreddit .usertext .md", ".morelink", ".morelink .nub", ".linkinfo", ".server-seconds", ".trophy-area .content", ".sidebox .spacer" ], styles: {"background-color": this.colors.bg.base} }, { selectors: [".tabmenu li a", "input", "textarea", ".infobar", ".reddit-infobar", ".link .md :not(pre) > code", ".link .md pre"], styles: {"background-color": this.colors.bg.input} }, { selectors: [".morelink", ".morelink .nub"], styles: {"background-image":"none"} }, { selectors: [ ".pagename a", ".pagename.selected", ".sr-bar a", ".dropdown.srdrop .selected", ".md", "h1", "h2", "h3", "h4", "h5", "h6", ".eddit-content", "input", "textarea", ".titlebox h1 a", ".side", ".lightdrop", ".content" ], styles: {color: this.colors.fg.text} }, { selectors: [".thing .title", ".tagline a"], styles: {color: this.colors.fg.title} }, { selectors: [".thing a.title:visited"], styles: {color: this.colors.fg.visited} // + " !important"} }, { selectors: [".tabmenu li.selected a"], styles: {"border-bottom-color": this.colors.bg.base} }, { selectors: [".pagename"], styles: { position:"relative !important", bottom: "0px !important" } }, { selectors: ["a.thumbnail.self", "a.thumbnail.default"], styles: {visibility: "hidden"} }, { selectors: [".midcol"], styles: {"margin-left": "0px"} }, { selectors: ["input", "textarea", "button", ".subreddit .usertext .md", ".link .md :not(pre) > code", ".link .md pre"], styles: {border: "solid 0.5px " + this.colors.border.input} }, { selectors: [".side"], styles: { position: "absolute", right: "0px", "z-index": "1111", } }, { selectors: [".side", "#header-bottom-right"], styles: { opacity: "0.0", transition: "opacity 0.3s linear" } }, { selectors: [".side:hover", "#header-bottom-right:hover"], styles: { opacity: "1.0" } }, { selectors: [".eddit-filter-subreddit-link"], styles: { color: this.colors.fg.filter + " !important", "border-radius": "5px", padding: "0 0.1rem 0 0.1rem", "font-size": "0.5rem", border: "solid 1px " + this.colors.fg.filter + " !important", "margin-left": "0.2rem" } }, { selectors: [".eddit-filter-subreddit-link:hover"], styles: { "background-color": this.colors.bg.filterHover + " !important", color: this.colors.fg.filterHover + " !important", "text-decoration": "none !important" } }, { selectors: [".eddit-content-other"], styles: { color: this.colors.fg.text, display: "block", width: "100%", height: "100%" } }, { selectors: ["#siteTable .thing.link", ".thing.comment"], styles: { cursor: "pointer", padding: "5px", "border-radius": "10px" } }, { selectors: ["#siteTable .thing.link:hover", ".eddit-comment-hover"], styles: {"box-shadow": "0px 0px 5px " + this.colors.shadow.thingHover} }, { selectors: [".linkflairlabel"], styles: { border: "solid 0.5px " + this.colors.border.flair, "background-color": this.colors.bg.flair, color: this.colors.fg.flair } }, { selectors: [".spoiler-stamp"], styles: { "color": this.colors.fg.spoiler, "border-color": this.colors.fg.spoiler } }, { selectors: ["a:hover"], styles: {"text-decoration": "underline"} }, { selectors: [ ".organic-listing", ".listing-chooser", "#sr-more-link", "#header-img", ".rank", "li.share", "li.give-gold-button", ".footer-parent", ".eddit-duplicate", "#sr-bar", ".sr-list > ul:nth-child(3n)", ".sr-list > .separator", ".filtered-details", ".titlebox.rounded", ".domain", //".expando-button", ".goldvertisement", ".titlebox form.toggle", ".side .tagline", ".eddit-filtered-post", ".recommended-link" ], styles: {display: "none !important"} }]; this.cssText = function() { return self.selectorsAndStyles.map(function(ss) { var styles = "", key; for (key in ss.styles) { if (styles !== "") styles += ";"; styles += key + ":" + ss.styles[key]; } return ss.selectors.join(",") + "{" + styles + "}"; }).join(""); }; GM_addStyle(this.cssText()); return this; })(); Redditmod.Error = function(message, url) { var div = document.createElement("div"); // TODO: Move style to stylesheet. div.style = "background-color: #800; border: solid 0.5px #c00; color: #fff; font-weight: bold; font-size: 1.2rem; padding: 5px;"; div.classList.add("eddit-content-error"); div.textContent = message; if (url) { // TODO: Move style to stylesheet. div.innerHTML += '' + url + ''; } return div; }; Redditmod.ImagePromise = function(sourceURLs) { if (!(this instanceof Redditmod.ImagePromise)) return new Redditmod.ImagePromise(sourceURLs); var self = this; this.sourceURLs = (sourceURLs instanceof String || typeof(sourceURLs) === "string") ? [sourceURLs] : sourceURLs; this.currentIndex = 0; this.img = null; this.createAlbumNav = function() { var albumStatus = document.createElement("span"); var albumPrevButton = document.createElement("a"); albumPrevButton.textContent = "<"; albumPrevButton.style = "cursor: pointer; font-size: 1.4rem;"; albumPrevButton.addEventListener("click", function(e) { e.stopPropagation(); if (self.currentIndex === 0) { self.currentIndex = index = self.sourceURLs.length; } self.currentIndex--; albumStatus.textContent = (self.currentIndex + 1) + "/" + self.sourceURLs.length; self.img.src = self.sourceURLs[self.currentIndex]; }, true); var albumNextButton = document.createElement("a"); albumNextButton.textContent = ">"; albumNextButton.style = "cursor: pointer; font-size: 1.4rem;"; albumNextButton.addEventListener("click", function(e) { e.stopPropagation(); if (self.currentIndex === self.sourceURLs.length - 1) { self.currentIndex = -1; } self.currentIndex++; albumStatus.textContent = (self.currentIndex + 1) + "/" + self.sourceURLs.length; self.img.src = self.sourceURLs[self.currentIndex]; }, true); albumStatus.textContent = "1/" + self.sourceURLs.length; albumStatus.style = "cursor: default; font-size: 1.4rem;"; var albumNav = document.createElement("div"); albumNav.appendChild(albumPrevButton); albumNav.appendChild(albumStatus); albumNav.appendChild(albumNextButton); return albumNav; }; return new Promise(function(resolve, reject) { var imageContainer = document.createElement("div"); if (self.sourceURLs.length > 1) { imageContainer.appendChild(self.createAlbumNav()); } self.img = document.createElement("img"); self.img.src = self.sourceURLs[0]; self.img.style["max-height"] = window.innerHeight + "px"; imageContainer.appendChild(self.img); resolve(imageContainer); }); }; Redditmod.VideoPromise = function(sourceURLs) { return new Promise(function(resolve, reject) { var video = document.createElement("video"); video.controls = false; video.autoplay = true; video.loop = true; video.classList.add("eddit-content-video"); video.style.display = "block"; video.style.width = "auto"; video.style.height = "auto"; sourceURLs.forEach(function(sourceURL) { var source = document.createElement("source"); source.src = sourceURL; video.appendChild(source); }); resolve(video); }); }; Redditmod.GiphyPromise = function(url) { // https://giphy.com/gifs/xUPGctxgaSqOpZx9zW // https://media.giphy.com/media/xUPGctxgaSqOpZx9zW/giphy.gif // https://media.giphy.com/media/xUPGctxgaSqOpZx9zW/giphy.mp4 var matches = url.href.match(/giphy\.com\/(?:gifs|media)\/(?:[a-z0-9\-]*-)?([a-z0-9]+)/i); if (!matches) return null; var shortcode = matches[1]; return Redditmod.VideoPromise([ "https://media.giphy.com/media/" + shortcode + "/giphy.mp4" ]); }; Redditmod.GfycatPromise = function(url) { var gfycatUrl, shortCode = url.href.match(/gfycat\.com\/.*\/([a-z0-9]*)/i); if (!shortCode) { gfycatUrl = url.href; } else { gfycatUrl = "https://gfycat.com/" + shortCode[1]; } return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: gfycatUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var videoSource = html.querySelector("#webmSource").src; Redditmod.VideoPromise([videoSource]).then(resolve, reject); } catch (error) { reject("Error (" + error + "): Failed to read " + gfycatUrl); } } }); }); }; Redditmod.StreamablePromise = function(url) { var matches = url.href.match(/streamable\.com\/([a-zA-Z0-9]*)/); if (!matches) return; var shortcode = matches[1]; var apiUrl = "https://api.streamable.com/videos/" + shortcode; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: apiUrl, onabort: reject, onerror: reject, onload: function(response) { try { var json = JSON.parse(response.responseText); Redditmod.VideoPromise([json.files.mp4.url]).then(resolve, reject); } catch (error) { reject("Error (" + error + "): Failed to read " + apiUrl); } } }); }); }; Redditmod.TwitchClipPromise = function(url) { var twitchUrl = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: twitchUrl, onabort: reject, onerror: reject, onload: function(response) { try { var matches = response.responseText.match(/quality_options: (\[.*\]),/); if (!matches) { reject("Error: No 'quality_options' at " + twitchUrl); } else { var json = JSON.parse(matches[1]); Redditmod.VideoPromise([json[0].source]).then(resolve, reject); } } catch (error) { reject("Error (" + error + "): Failed to read " + twitchUrl); } } }); }); }; Redditmod.XkcdPromise = function(url) { var matches = url.href.match(/xkcd\.com\/([0-9]+)/); if (!matches) return; var shortcode = matches[1]; var apiUrl = "https://xkcd.com/" + shortcode + "/info.0.json"; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: apiUrl, onabort: reject, onerror: reject, onload: function(response) { try { var json = JSON.parse(response.responseText); var xkcdDiv = document.createElement("div"); var h3 = document.createElement("h3"); h3.textContent = json.title; var img = document.createElement("img"); img.src = json.img; img.title = json.alt; var h5 = document.createElement("h5"); h5.textContent = json.alt; xkcdDiv.appendChild(h3); xkcdDiv.appendChild(img); xkcdDiv.appendChild(h5); resolve(xkcdDiv); } catch (error) { reject("Error (" + error + "): Failed to read " + apiUrl); } } }); }); }; Redditmod.DeviantartPromise = function(url) { var theUrl = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var fullImg = html.querySelector('img[dev-content-full]'); var smallImg = html.querySelector('meta[property="og:image"]'); if (fullImg) { Redditmod.ImagePromise([fullImg.getAttribute("src")]).then(resolve, reject); } else if (smallImg) { Redditmod.ImagePromise([smallImg.getAttribute("content")]).then(resolve, reject); } else { reject("No images found ", theUrl); } } catch (error) { reject("Error (" + error + "): Failed to read ", theUrl); } } }); }); }; Redditmod.TwitterPromise = function(url) { var theUrl = url.href.replace(/[^\/]*\.twitter.com/, "twitter.com"); return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var result = document.createElement("div"); var domTweet = html.querySelector("div.tweet"); if (domTweet) { var resultUser = document.createElement("div"); resultUser.textContent = domTweet.dataset.screenName + " (" + domTweet.dataset.name + ")"; result.appendChild(resultUser); } var domDescription = html.querySelector('meta[property="og:description"]'); if (domDescription) { var resultDescription = document.createElement("div"); resultDescription.textContent = domDescription.getAttribute("content"); result.appendChild(resultDescription); } var domVideo = html.querySelector('meta[property="og:video:url"]'); var domPicture = html.querySelector('meta[property="og:image:user_generated"]'); if (domVideo) { var resultVideo = document.createElement("iframe"); resultVideo.width = html.querySelector('meta[property="og:video:width"]').getAttribute("content"); resultVideo.height = html.querySelector('meta[property="og:video:height"]').getAttribute("content"); resultVideo.src = domVideo.getAttribute("content"); resultVideo.cssText = "border: none"; result.appendChild(resultVideo); } else if (domPicture) { var resultPicture = document.createElement("img"); resultPicture.src = html.querySelector('meta[property="og:image"]').getAttribute("content"); result.appendChild(resultPicture); } resolve(result); } catch (error) { reject("Error (" + error + "): Failed to read ", theUrl); } } }); }); }; Redditmod.LightshotPromise = function(url) { var lightshotUrl = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: lightshotUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var imageMeta = html.querySelector('meta[property="og:image"]'); if (imageMeta) { Redditmod.ImagePromise([imageMeta.getAttribute("content")]).then(resolve, reject); } else { reject("No images found ", lightshotUrl); } } catch (error) { reject("Error (" + error + "): Failed to read " + lightshotUrl); } } }); }); }; Redditmod.InstagramPromise = function(url) { var theUrl = url.href; var matches = theUrl.match(/instagram\.com\/p\/([a-zA-Z0-9_\-]*)/); if (!matches) reject("No images found", theUrl); var shortcode = matches[1]; var apiUrl = "https://instagram.com/p/" + shortcode + "/"; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: apiUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var videoMeta = html.querySelector('meta[property="og:video"]'); var imageMeta = html.querySelector('meta[property="og:image"]'); if (videoMeta) { Redditmod.VideoPromise([videoMeta.getAttribute("content")]).then(resolve, reject); } else if (imageMeta) { Redditmod.ImagePromise([imageMeta.getAttribute("content")]).then(resolve, reject); } else { reject("No images found ", apiUrl); } } catch (error) { reject("Error (" + error + "): Failed to read " + apiUrl); } } }); }); }; Redditmod.ImgurPromise = function(url) { var href = url.href.replace(/\?.*/, ""); if (href.indexOf("/a/") >= 0 || href.indexOf("/gallery/") >= 0) { return Redditmod.ImgurAlbumPromise(href); } else if (/\.gifv$/.test(href) || /\.gif$/.test(href) || /\.mp4$/.test(href)) { // it's a GIF/video. href = href.replace(/\.(gifv|gif|mp4)$/, ".mp4"); return Redditmod.VideoPromise([href]); } else { href = href.replace(/[^/]*\.imgur\.com/, "i.imgur.com"); href = href.replace(/_[a-z]./, "."); href = href.replace(/\.(gif|jpg|jpeg|png)$/i, ""); href = href + ".jpg"; return Redditmod.ImagePromise([href]); } }; Redditmod.ImgurAlbumPromise = function(url) { var theUrlForReal = url; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrlForReal, onabort: reject, onerror: reject, onload: function(response) { // Parsing imgur album HTML for Javascript via Regex. Lord'avmercy try { var jsonChunks = response.response.match(/\s*image\s*:\s*(.*),\s*/); var json = JSON.parse(jsonChunks[1] || "{}"); var album_images = json.album_images || {}; var images = album_images.images || []; if (images.length === 0) { // No images, it might be a "gallery" link. if (/imgur\.com\/gallery/.test(theUrlForReal)) { var imgurHtml = document.createElement("html"); imgurHtml.innerHTML = response.responseText; var imgurImage = imgurHtml.querySelector('link[rel="image_src"]'); if (imgurImage) { Redditmod.ImagePromise([imgurImage.getAttribute("href")]).then(resolve, reject); } else { reject("No images found ", theUrlForReal); } } else { reject("No images found ", theUrlForReal); } } else { var urls = images.map(function(image) { return "https://i.imgur.com/" + image.hash + image.ext; }); Redditmod.ImagePromise(urls).then(resolve, reject); } } catch (error) { reject("Error (" + error + "): Failed to load imgur album "); } } }); }); }; Redditmod.FlickrPromise = function(url) { var theUrlForReal = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrlForReal, onabort: reject, onerror: reject, onload: function(response) { // Parsing flickr HTML for Javascript via Regex. Lord'avmercy try { var jsonChunks = response.response.match(/modelExport: (\{.*})/); var json = JSON.parse(jsonChunks[1] || "{}"); var photo_models = json["photo-models"] || []; var images = photo_models.map(function(model) { var imageObjs = []; for (var key in model.sizes) { imageObjs.push(model.sizes[key]); } imageObjs = imageObjs.sort(function(a,b) { return a.width < b.width; }); if (imageObjs.length > 0) { return window.location.protocol + imageObjs[0].url; } else { return null; } }).filter(function(imageUrl) { return imageUrl !== null; }); if (images.length === 0) { reject("No images found ", theUrlForReal); } else { Redditmod.ImagePromise(images).then(resolve, reject); } } catch (error) { reject("Error (" + error + "): Failed to load Flickr page "); } } }); }); }; Redditmod.RedditCommentsPromise = function(url) { var theUrlForReal = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrlForReal, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var commentContainer = html.querySelector(".commentarea > .sitetable"); if (commentContainer) { // Process incoming comments commentContainer.querySelectorAll(".thing.comment").forEach(Redditmod.Comments.add); resolve(commentContainer); } else { reject("Failed to find commentarea at ", theUrlForReal); } } catch (error) { reject("Error (" + error + "): Failed to load page ", theUrlForReal); } } }); }); }; Redditmod.CustomPromise = function(url) { var theUrl = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", url: theUrl, onabort: reject, onerror: reject, onload: function(response) { try { var html = document.createElement("html"); html.innerHTML = response.responseText; var domTitle = html.querySelector('meta[property="og:title"]'); var domBody = html.querySelector('meta[property="og:description"]'); var domDate = html.querySelector('meta[property="article:published_time"]'); if (!domTitle && !domBody) { Redditmod.OtherPromise(url).then(resolve, reject); return; } var result = document.createElement("div"); result.cssText = "max-width: 33%"; var resultTitleBox = document.createElement("h3"); var resultTitle = document.createElement("a"); resultTitle.textContent = domTitle.getAttribute("content"); resultTitle.href = theUrl; resultTitle.target = "_BLANK"; resultTitle.cssText = "font-style: italic"; resultTitleBox.appendChild(resultTitle); result.appendChild(resultTitleBox); if (domDate) { var resultDate = document.createElement("div"); resultDate.textContent = domDate.getAttribute("content"); result.appendChild(resultDate); } var resultBody = document.createElement("div"); resultBody.textContent = domBody.getAttribute("content"); result.appendChild(resultBody); resolve(result); } catch (error) { reject("Error (" + error + "): Failed to load page ", theUrl); } } }); }); } Redditmod.OtherPromise = function(url) { var theUrl = url.href; return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: "GET", headers: {"X-api-key": "NtFdFjTYzQXF4WUWBivfsnTj0zXZyvwCKbSQeuAB"}, url: "https://mercury.postlight.com/parser?url=" + encodeURIComponent(theUrl), onload: function(response) { try { var json = JSON.parse(response.response); var otherContent = document.createElement("div"); otherContent.innerHTML = json.content; otherContent.classList.add("eddit-content-other"); resolve(otherContent); } catch (error) { reject("Error (" + error + "): Failed to load page "); } }, onerror: function(xhr) { reject("Error (status:" + xhr.status + " " + xhr.statusText + ") "); }, onabort: function(xhr) { reject("Error (status:" + xhr.status + " " + xhr.statusText + ") "); } }); }); }; Redditmod.VisitedLinks = (function() { var self = this; this._visitedLinks = GM_getValue("eddit-visited-links", {}); this.contains = function(link) { return (self._visitedLinks[link] === true); }; this.add = function(link) { if (!self._visitedLinks[link]) { self._visitedLinks[link] = true; GM_setValue("eddit-visited-links", self._visitedLinks); } }; return { contains: this.contains, add: this.add }; })(); Redditmod.MediaHandler = function(domPost) { if (!(this instanceof Redditmod.MediaHandler)) return new Redditmod.MediaHandler(domPost); var self = this; this._domPost = domPost; this._domCommentsLink = domPost.querySelector(".buttons a.comments"); this._loadedMedia = false; this._loadedComments = false; this._expandedMedia = false; this._expandedComments = false; this._mediaObj = null; this._commentsObj = null; this._shouldUseExpando = self._domPost.classList.contains("self"); this.url = (function() { var thisUrl = self._domPost.getAttribute("data-url"); if (!thisUrl) { return null; } if (thisUrl && thisUrl.indexOf("/") === 0) { thisUrl = window.location.protocol + "//" + window.location.host + thisUrl; } try { return new URL(thisUrl); } catch (e) { return thisUrl; } })(); this._loadMedia = function() { if (self._loadedMedia) return; self._loadedMedia = true; self._expandedMedia = true; if (self._expandedComments) self._hideComments(); if (self._domPost.classList.contains("self")) { self._shouldUseExpando = true; return; } var mediaPromise = Redditmod.MediaPromise(self.url); if (mediaPromise instanceof Promise) { mediaPromise.then(function(mediaDiv) { mediaDiv.style["max-width"] = self._domPost.clientWidth + "px"; self._mediaObj = mediaDiv; self._domPost.appendChild(mediaDiv); }).catch(Redditmod.Error); } else { self._shouldUseExpando = true; } }; this._showMedia = function() { self._loadMedia(); self._expandedMedia = true; if (self._shouldUseExpando) { self._clickExpando(); } else if (self._mediaObj) { self._mediaObj.style.display = "block"; if (self._mediaObj.tagName === "VIDEO") self._mediaObj.load(); } if (self._expandoButton && self._expandoButton.classList.contains("collapsed")) { self._expandoButton.classList.add("expanded"); self._expandoButton.classList.remove("collapsed"); } }; this._hideMedia = function() { if (!self._loadedMedia) return; self._loadMedia(); self._expandedMedia = false; if (self._shouldUseExpando) { self._clickExpando(); } else if (self._mediaObj) { self._mediaObj.style.display = "none"; if (self._mediaObj.tagName === "VIDEO") self._mediaObj.pause(); } if (self._expandoButton && self._expandoButton.classList.contains("expanded")) { self._expandoButton.classList.add("collapsed"); self._expandoButton.classList.remove("expanded"); } }; this._loadComments = function() { if (self._loadedComments) return; if (!self._domCommentsLink) return; self._loadedComments = true; self._expandedComments = true; var commentsUrl = new URL("https://reddit.com" + self._domCommentsLink.getAttribute("href")); var commentsPromise = Redditmod.RedditCommentsPromise(commentsUrl); commentsPromise.then(function(commentsDiv) { commentsDiv.style["max-width"] = self._domPost.clientWidth + "px"; self._commentsObj = commentsDiv; self._domPost.appendChild(commentsDiv); }).catch(function(reason) { self._domPost.appendChild(Redditmod.Error(reason, commentsUrl)); }); }; this._showComments = function() { self._loadComments(); self._expandedComments = true; if (self._commentsObj) { self._commentsObj.style.display = "block"; } }; this._hideComments = function() { if (!self._loadedComments) return; self._loadComments(); self._expandedComments = false; if (self._commentsObj) { self._commentsObj.style.display = "none"; } }; this.markVisited = function() { var linkTitle = self._domPost.querySelector("a.title"); if (!linkTitle) return; linkTitle.style.color = Redditmod.CSS.colors.fg.visited; }; this._clickExpando = function() { if (self._expandoButton) { self._expandoButton.click(); } }; this._postClick = function(event) { var target = Redditmod.Utils.findThing(event); if (!target) return; target.scrollIntoView({behavior: "smooth"}); self._toggleMedia(event); }; this._toggleComments = function(e) { e.stopPropagation(); e.preventDefault(); if (self._expandedMedia) self._hideMedia(); if (self._expandedComments) { self._hideComments(); } else { self._showComments(); } }; this._toggleMedia = function(e) { e.stopPropagation(); e.preventDefault(); if (self._expandedComments) self._hideComments(); if (!self.url) return; Redditmod.VisitedLinks.add(self.url.href); self.markVisited(); if (self._expandedMedia) { self._hideMedia(); } else { self._showMedia(); } }; this._expandoButton = (function() { var button = self._domPost.querySelector(".expando-button"); if (!button) { button = document.createElement("a"); button.classList.add("expando-button"); button.classList.add("collapsed"); button.classList.add("video"); button.onclick = self._toggleMedia; var entry = self._domPost.querySelector(".entry"); var tagline = self._domPost.querySelector(".tagline"); // TODO: Apparently this doesn't work because button isn't a child of entry, or tagline, or something. //entry.insertBefore(button, tagline); } return button; })(); if (self._domPost) { self._domPost.addEventListener("click", self._postClick); } if (self._domCommentsLink) { self._domCommentsLink.addEventListener("click", self._toggleComments); } }; // top-level domain name (no subdomains) var DOMAIN_NAME_REGEX = RegExp(/([a-z0-9\-]+\.[a-z]{2,}$)/); /** * @returns Promise for a