Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name LowEndTalk Compact Full-Width Layout
- // @namespace https://lowendtalk.com/
- // @version 3.3
- // @description Full-width layout with single-line discussion rows
- // @match *://lowendtalk.com/*
- // @grant none
- // @run-at document-start
- // ==/UserScript==
- (function () {
- "use strict";
- var IS_IFRAME = window !== window.top;
- // --- Userscript Settings (persisted via localStorage) ---
- var LET_DEFAULTS = {
- titleFontSize: 14,
- metaFontSize: 12,
- hcInterval: 30,
- hcAutoOn: false,
- hideSidebar: false,
- pushEnabled: false,
- pushoverUser: "",
- pushoverToken: "",
- autoRefreshOn: false,
- autoRefreshSec: 120,
- readingPane: false,
- };
- var LET_STORAGE_KEY = "let-compact-settings";
- function loadSettings() {
- try {
- var s = JSON.parse(localStorage.getItem(LET_STORAGE_KEY));
- if (s && typeof s === "object") {
- return {
- titleFontSize: Number(s.titleFontSize) || LET_DEFAULTS.titleFontSize,
- metaFontSize: Number(s.metaFontSize) || LET_DEFAULTS.metaFontSize,
- hcInterval: Number(s.hcInterval) || LET_DEFAULTS.hcInterval,
- hcAutoOn: s.hcAutoOn === true,
- hideSidebar: s.hideSidebar === true,
- pushEnabled: s.pushEnabled === true,
- pushoverUser: s.pushoverUser || "",
- pushoverToken: s.pushoverToken || "",
- autoRefreshOn: s.autoRefreshOn === true,
- autoRefreshSec:
- Number(s.autoRefreshSec) || LET_DEFAULTS.autoRefreshSec,
- readingPane: s.readingPane === true,
- };
- }
- } catch (e) {}
- return Object.assign({}, LET_DEFAULTS);
- }
- function saveSettings(obj) {
- localStorage.setItem(LET_STORAGE_KEY, JSON.stringify(obj));
- }
- var letSettings = loadSettings();
- // Dynamic style element for user-configurable font sizes
- var dynStyle = document.createElement("style");
- dynStyle.id = "let-dynamic-styles";
- document.documentElement.appendChild(dynStyle);
- function applyDynamicStyles() {
- var titleSize = letSettings.titleFontSize;
- var metaSize = letSettings.metaFontSize;
- var titleLine = Math.round(titleSize * 1.55);
- var metaLine = Math.round(metaSize * 1.55);
- var css =
- ".let-title { font-size: " +
- (titleSize - 1) +
- "px !important; line-height: " +
- titleLine +
- "px !important; }" +
- ".let-title a { font-size: " +
- titleSize +
- "px !important; }" +
- ".let-meta { font-size: " +
- metaSize +
- "px !important; line-height: " +
- Math.max(metaLine, 20) +
- "px !important; }" +
- ".let-meta span { font-size: " +
- metaSize +
- "px !important; }" +
- ".let-meta a { font-size: " +
- metaSize +
- "px !important; }";
- var isProfilePage = /\/profile\//i.test(window.location.pathname);
- var isDiscussionPage = /\/discussion\/\d+/i.test(window.location.pathname);
- var isListingPage = !isProfilePage && !isDiscussionPage;
- if (!IS_IFRAME && letSettings.readingPane && isListingPage) {
- css += ".Column.PanelColumn { display: none !important; }";
- css +=
- ".Column.ContentColumn { border-right: none !important; padding-right: 0 !important; margin-right: 0 !important; flex: 0 0 50% !important; max-width: 50% !important; overflow-y: auto !important; overflow-x: hidden !important; height: calc(100vh - 50px) !important; }";
- css +=
- "ul.DataList.Discussions > li.ItemDiscussion { flex-wrap: wrap !important; }";
- css +=
- ".let-meta { flex-wrap: wrap !important; white-space: normal !important; }";
- css +=
- "#let-reading-pane { display: block !important; flex: 0 0 50% !important; max-width: 50% !important; }";
- } else if (letSettings.hideSidebar && !isProfilePage) {
- css += ".Column.PanelColumn { display: none !important; }";
- css +=
- ".Column.ContentColumn { border-right: none !important; padding-right: 0 !important; margin-right: 0 !important; }";
- }
- if (!letSettings.readingPane || !isListingPage) {
- css += "#let-reading-pane { display: none !important; }";
- }
- dynStyle.textContent = css;
- }
- applyDynamicStyles();
- // Reading pane functions
- function createReadingPane() {
- if (document.getElementById("let-reading-pane")) return;
- var row = document.querySelector("#Body .Row");
- if (!row) return;
- var pane = document.createElement("div");
- pane.id = "let-reading-pane";
- pane.innerHTML =
- '<div id="let-reading-pane-placeholder">Click a post to read it here</div>';
- row.appendChild(pane);
- }
- function loadInReadingPane(url) {
- var pane = document.getElementById("let-reading-pane");
- if (!pane) return;
- pane.innerHTML = '<div id="let-reading-pane-placeholder">Loading...</div>';
- fetch(url, { credentials: "same-origin" })
- .then(function (r) {
- return r.text();
- })
- .then(function (html) {
- var doc = new DOMParser().parseFromString(html, "text/html");
- var content =
- doc.querySelector(".Column.ContentColumn") ||
- doc.querySelector("main.Content.MainContent") ||
- doc.querySelector("#Body");
- if (!content) {
- pane.innerHTML =
- '<div id="let-reading-pane-placeholder">Failed to load content</div>';
- return;
- }
- content
- .querySelectorAll(".Column.PanelColumn, aside.Panel, .Panel-main")
- .forEach(function (el) {
- el.remove();
- });
- pane.innerHTML = "";
- var wrapper = document.createElement("div");
- wrapper.className = "let-reading-content";
- wrapper.innerHTML = content.innerHTML;
- pane.appendChild(wrapper);
- interceptPaneLinks(pane);
- })
- .catch(function () {
- pane.innerHTML =
- '<div id="let-reading-pane-placeholder">Failed to load</div>';
- });
- }
- function interceptPaneLinks(pane) {
- pane.addEventListener("click", function (e) {
- var a = e.target.closest("a");
- if (!a) return;
- var href = a.getAttribute("href");
- if (!href || href.startsWith("#") || href.startsWith("javascript:"))
- return;
- e.preventDefault();
- e.stopPropagation();
- try {
- var linkUrl = new URL(href, location.origin);
- if (linkUrl.origin !== location.origin) {
- window.open(href, "_blank");
- } else {
- loadInReadingPane(linkUrl.href);
- }
- } catch (ex) {
- window.open(href, "_blank");
- }
- });
- pane.addEventListener("submit", function (e) {
- var form = e.target.closest("form");
- if (!form) return;
- e.preventDefault();
- e.stopPropagation();
- var action = form.action || location.href;
- var method = (form.method || "GET").toUpperCase();
- var data = new FormData(form);
- var opts = { credentials: "same-origin", method: method };
- if (method === "POST") {
- opts.body = data;
- } else {
- action +=
- (action.indexOf("?") > -1 ? "&" : "?") +
- new URLSearchParams(data).toString();
- }
- fetch(action, opts)
- .then(function (r) {
- if (r.redirected) {
- loadInReadingPane(r.url);
- } else {
- loadInReadingPane(action);
- }
- })
- .catch(function () {
- loadInReadingPane(action);
- });
- });
- }
- function toggleReadingPane(enabled) {
- if (enabled) {
- createReadingPane();
- } else {
- var pane = document.getElementById("let-reading-pane");
- if (pane) {
- pane.innerHTML =
- '<div id="let-reading-pane-placeholder">Click a post to read it here</div>';
- }
- }
- }
- // Intercept post title clicks for reading pane
- if (!IS_IFRAME) {
- document.addEventListener("click", function (e) {
- if (!letSettings.readingPane) return;
- var link = e.target.closest(".let-title a");
- if (!link) return;
- e.preventDefault();
- e.stopPropagation();
- document.querySelectorAll("li.let-reading-active").forEach(function (li) {
- li.classList.remove("let-reading-active");
- });
- var li = link.closest("li");
- if (li) li.classList.add("let-reading-active");
- loadInReadingPane(link.href);
- });
- }
- const style = document.createElement("style");
- style.textContent = `
- /* Override native dark theme's green #Head and #Frame backgrounds */
- #Frame {
- background-color: #080810 !important;
- display: flex !important;
- flex-direction: column !important;
- min-height: 100vh !important;
- }
- #Body {
- flex: 1 !important;
- }
- /* Dark theme base - force on everything */
- html, body,
- #Frame,
- #Body,
- #Foot,
- #Body .Row,
- .Column.ContentColumn,
- #Content,
- .Section,
- .Section-Content,
- .DataList,
- ul.DataList,
- .CategoryList,
- .DataTable,
- .PageBox {
- background: #080810 !important;
- background-color: #080810 !important;
- color: #ccc !important;
- }
- /* Full width - override all constraining containers */
- #Frame,
- #Body,
- #Head,
- #Foot,
- #Head .Row,
- #Body .Row,
- #Foot .Row,
- .Row,
- .Column.ContentColumn,
- #Content {
- max-width: none !important;
- width: 100% !important;
- box-sizing: border-box !important;
- }
- #Frame {
- margin: 0 !important;
- box-shadow: none !important;
- border: none !important;
- }
- html {
- overflow-x: hidden !important;
- }
- .Row {
- padding: 0 10px !important;
- }
- #Body .Row {
- display: flex !important;
- }
- .Column.PanelColumn {
- width: 220px !important;
- min-width: 220px !important;
- max-width: 220px !important;
- flex-shrink: 0 !important;
- order: 2 !important;
- }
- .Column.ContentColumn {
- flex: 1 !important;
- min-width: 0 !important;
- order: 1 !important;
- border-right: 1px solid #373741 !important;
- padding-right: 14px !important;
- margin-right: 10px !important;
- }
- /* Dark header - single green bottom border only */
- #Head {
- background: #15151b !important;
- border: none !important;
- border-bottom: 1px solid #373741 !important;
- padding: 0 !important;
- margin: 0 !important;
- position: sticky !important;
- top: 0 !important;
- z-index: 1000 !important;
- }
- /* Kill ALL borders inside #Head */
- #Head .Row,
- #Head .Row > *,
- #Head .Title,
- #Head .SiteMenu,
- #Head .SiteMenu li,
- #Head .SiteMenu ul,
- #Head .MeBox,
- #Head #let-head-mebox {
- border: none !important;
- border-top: none !important;
- border-bottom: none !important;
- background: transparent !important;
- }
- /* Kill borders/gaps on elements right below header */
- #Body,
- #Body > .Row,
- #Body > .Row:before,
- #Body > .Row:after {
- border-top: none !important;
- }
- #Body,
- #Body > .Row,
- .Column.ContentColumn,
- #Content,
- .Section,
- .Section-Content {
- padding-top: 0 !important;
- margin-top: 0 !important;
- }
- /* Kill native theme's green borders on Headings/Tabs */
- div.Headings,
- div.Tabs,
- .DiscussionsTabs,
- .Column.ContentColumn > div.Tabs,
- .Column.ContentColumn > div.Headings {
- border-bottom: none !important;
- border-top: none !important;
- margin-bottom: 0 !important;
- }
- #Head a {
- color: #ccc !important;
- }
- #Head a:hover {
- color: #22ad5d !important;
- }
- /* Nav link colors to match LES */
- #Head .SiteMenu a {
- color: #22ad5d !important;
- font-size: 14px !important;
- font-weight: 400 !important;
- }
- #Head .SiteMenu a:hover {
- color: orange !important;
- }
- /* Logo styling to match LES */
- #Head .Title a {
- font-size: 20px !important;
- font-weight: 700 !important;
- letter-spacing: 2px !important;
- color: #fff !important;
- text-transform: uppercase !important;
- }
- /* Center header content vertically */
- #Head .Row {
- display: flex !important;
- align-items: center !important;
- padding: 8px 10px !important;
- border: none !important;
- background: #15151b !important;
- }
- /* Hide search in header, show in sidebar */
- #Head .SiteSearch {
- display: none !important;
- }
- .Column.PanelColumn > .SiteSearch {
- padding: 4px 8px !important;
- margin-bottom: 4px !important;
- display: block !important;
- visibility: visible !important;
- opacity: 1 !important;
- height: auto !important;
- overflow: visible !important;
- }
- .SiteSearch div {
- display: flex !important;
- }
- .SiteSearch input.InputBox {
- font-size: 11px !important;
- padding: 2px 6px !important;
- flex: 1 !important;
- box-sizing: border-box !important;
- border: 1px solid #373741 !important;
- background: #222225 !important;
- color: #ccc !important;
- }
- .SiteSearch .Button {
- font-size: 10px !important;
- padding: 2px 6px !important;
- filter: none !important;
- }
- /* MeBox icons in header bar */
- #let-head-mebox {
- display: inline-flex !important;
- align-items: center !important;
- gap: 8px !important;
- margin-left: auto !important;
- padding: 0 !important;
- margin-right: 0 !important;
- position: relative !important;
- }
- #let-head-mebox a {
- color: #ccc !important;
- padding: 2px !important;
- }
- #let-head-mebox a:hover {
- color: #22ad5d !important;
- }
- #let-head-mebox .PhotoWrap {
- width: 20px !important;
- height: 20px !important;
- padding: 0 !important;
- margin: 0 !important;
- }
- #let-head-mebox .PhotoWrap img {
- width: 20px !important;
- height: 20px !important;
- border-radius: 3px !important;
- }
- #let-head-mebox .Alert {
- font-size: 9px !important;
- padding: 0 3px !important;
- }
- #let-head-mebox a[title="Account Options"],
- #let-head-mebox > a.MeButton:not([title]) {
- display: none !important;
- }
- /* Custom flyout panels */
- .let-flyout-wrap {
- position: relative !important;
- display: inline-flex !important;
- align-items: center !important;
- }
- .let-flyout {
- display: none;
- position: absolute;
- top: 100%;
- left: 0;
- width: 350px;
- max-height: 400px;
- overflow-y: auto;
- background: #1a1a22 !important;
- border: 1px solid #373741 !important;
- border-radius: 4px !important;
- box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
- z-index: 10001 !important;
- font-size: 12px !important;
- color: #ccc !important;
- padding: 0 !important;
- }
- .let-flyout.let-flyout-open {
- display: block !important;
- }
- .let-flyout a {
- color: #22ad5d !important;
- font-size: 12px !important;
- }
- .let-flyout a:hover {
- color: orange !important;
- }
- .let-flyout .Item,
- .let-flyout li {
- padding: 8px 12px !important;
- border-bottom: 1px solid #252530 !important;
- list-style: none !important;
- background: transparent !important;
- }
- .let-flyout .Item:hover,
- .let-flyout li:hover {
- background: #222230 !important;
- }
- .let-flyout .PhotoWrap img {
- width: 32px !important;
- height: 32px !important;
- border-radius: 3px !important;
- }
- .let-flyout-footer {
- padding: 8px 12px !important;
- text-align: center !important;
- border-top: 1px solid #373741 !important;
- }
- .let-flyout-loading {
- padding: 16px !important;
- text-align: center !important;
- color: #666 !important;
- }
- /* Settings flyout */
- #let-hostc-banner .let-flyout-wrap {
- margin-left: auto !important;
- }
- .let-settings-link {
- color: #555 !important;
- font-size: 10px !important;
- cursor: pointer !important;
- text-decoration: none !important;
- flex-shrink: 0 !important;
- }
- .let-settings-link:hover {
- color: #22ad5d !important;
- }
- .let-settings-flyout {
- width: 260px !important;
- padding: 12px 16px !important;
- max-height: none !important;
- z-index: 100000 !important;
- left: auto !important;
- right: 0 !important;
- }
- .let-settings-flyout label {
- display: flex !important;
- align-items: center !important;
- justify-content: space-between !important;
- color: #aaa !important;
- font-size: 11px !important;
- margin-bottom: 8px !important;
- }
- .let-settings-flyout input[type="range"] {
- width: 100px !important;
- margin: 0 8px !important;
- accent-color: #22ad5d !important;
- }
- .let-settings-flyout .let-size-val {
- color: #ccc !important;
- font-size: 11px !important;
- min-width: 30px !important;
- text-align: right !important;
- }
- .let-settings-flyout .let-settings-reset {
- display: block !important;
- margin-top: 8px !important;
- padding: 4px 10px !important;
- background: #252530 !important;
- border: 1px solid #373741 !important;
- color: #888 !important;
- font-size: 10px !important;
- border-radius: 3px !important;
- cursor: pointer !important;
- text-align: center !important;
- width: 100% !important;
- }
- .let-settings-flyout .let-settings-reset:hover {
- background: #333340 !important;
- color: #ccc !important;
- }
- /* Hide original sidebar MeBox after move */
- .Column.PanelColumn .MeBox.MeBox-SignedIn,
- .Column.PanelColumn .MeBox,
- .Column.PanelColumn .Box.MeBox,
- .Panel .MeBox {
- display: none !important;
- }
- /* Dark navigation */
- .SiteMenu,
- .SiteSearch,
- .MeBox,
- .Breadcrumbs {
- background: transparent !important;
- }
- /* Dark content area */
- .Content,
- .DataList,
- .MessageList,
- .Item {
- background: transparent !important;
- color: #ccc !important;
- }
- /* Hide sidebar ads */
- .Column.PanelColumn [id^="bsap_"],
- .Column.PanelColumn [class*="bsap"],
- .Column.PanelColumn [class*="bsa_"],
- .Column.PanelColumn a[rel="nofollow noopener"][target="_blank"] {
- display: none !important;
- }
- /* Dark sidebar */
- .Column.PanelColumn {
- background: #15151b !important;
- color: #aaa !important;
- }
- .Column.PanelColumn h4,
- .PanelInfo h4 {
- color: #e0e0e0 !important;
- }
- .Column.PanelColumn a {
- color: #22ad5d !important;
- }
- .Column.PanelColumn a:hover {
- color: orange !important;
- }
- /* Dark footer - condensed 3-column layout */
- #Foot {
- background: #15151b !important;
- border-top: 1px solid #373741 !important;
- color: #666 !important;
- padding: 0 !important;
- margin: 0 !important;
- min-height: 0 !important;
- height: auto !important;
- line-height: 1 !important;
- }
- #Foot div {
- background: #15151b !important;
- color: #666 !important;
- border: none !important;
- margin: 0 !important;
- padding: 2px 10px !important;
- display: flex !important;
- align-items: center !important;
- height: auto !important;
- min-height: 0 !important;
- width: 100% !important;
- }
- #Foot a {
- color: #888 !important;
- }
- .let-foot-left,
- .let-foot-center,
- .let-foot-right {
- flex: 1 !important;
- font-size: 11px !important;
- }
- .let-foot-left {
- text-align: left !important;
- }
- .let-foot-center {
- text-align: center !important;
- }
- .let-foot-center a {
- color: #888 !important;
- margin: 0 6px !important;
- }
- .let-foot-center a:hover {
- color: #22ad5d !important;
- }
- .let-foot-right {
- text-align: right !important;
- }
- /* Hide breadcrumb "Home" line — wastes a full row */
- .BreadcrumbsWrapper,
- .Breadcrumbs,
- #Body .HomeCrumb,
- .Column.ContentColumn > .Breadcrumbs {
- display: none !important;
- }
- /* Compact sidebar user profile box */
- .MeBox .PhotoWrap,
- .Column.PanelColumn .PhotoWrap {
- width: 32px !important;
- height: 32px !important;
- }
- .MeBox .PhotoWrap img,
- .Column.PanelColumn .PhotoWrap img {
- width: 32px !important;
- height: 32px !important;
- }
- .Column.PanelColumn .MeBox {
- padding: 4px !important;
- margin-bottom: 4px !important;
- }
- .Column.PanelColumn .MeBox h3 {
- font-size: 12px !important;
- margin: 0 !important;
- }
- .Column.PanelColumn .MeBox .WhoIs {
- font-size: 11px !important;
- }
- /* Compact sidebar sections */
- .Column.PanelColumn .Box,
- .Column.PanelColumn .PanelInfo,
- .Column.PanelColumn .BoxButtons {
- padding: 4px 8px !important;
- margin-bottom: 2px !important;
- }
- .Column.PanelColumn h4 {
- font-size: 11px !important;
- margin: 2px 0 !important;
- padding: 0 !important;
- }
- .Column.PanelColumn .PanelInfo li {
- padding: 1px 0 !important;
- font-size: 11px !important;
- }
- .Column.PanelColumn .PanelInfo li a {
- font-size: 11px !important;
- }
- /* Compact sidebar buttons (New Discussion, etc.) */
- .Column.PanelColumn .BigButton,
- .Column.PanelColumn .Button.Action.Primary {
- padding: 4px 8px !important;
- font-size: 11px !important;
- margin-bottom: 2px !important;
- }
- /* Compact the filter/view dropdown area */
- .BoxFilter,
- .FilterMenu,
- .DiscussionsTabs {
- padding: 2px 0 !important;
- margin: 0 !important;
- font-size: 11px !important;
- }
- /* Tabs/breadcrumbs full width */
- .Tabs,
- .PageControls,
- .FilterMenu,
- #content {
- max-width: none !important;
- width: 100% !important;
- background: transparent !important;
- }
- .Tabs a,
- .PageControls a {
- color: #ccc !important;
- font-size: 11px !important;
- }
- .Tabs a:hover,
- .PageControls a:hover {
- color: #22ad5d !important;
- }
- .Tabs .Active a,
- .Tabs .Active {
- color: #22ad5d !important;
- border-color: #22ad5d !important;
- }
- /* Dark form elements */
- input, textarea, select, .TextBoxWrapper {
- background: #222225 !important;
- color: #ccc !important;
- border-color: #373741 !important;
- }
- /* Dark buttons */
- .Button, .btn, .Btn {
- background: #222225 !important;
- color: #ccc !important;
- border-color: #373741 !important;
- }
- .Button.Primary, .btn-primary {
- background: #22ad5d !important;
- color: #fff !important;
- border-color: #22ad5d !important;
- }
- /* host_c latest post banner */
- #let-hostc-banner {
- display: flex !important;
- align-items: center !important;
- background: #15151b !important;
- padding: 2px 10px !important;
- font-size: 11px !important;
- color: #888 !important;
- border-bottom: 1px solid #222225 !important;
- gap: 6px !important;
- white-space: nowrap !important;
- overflow: visible !important;
- }
- #let-hostc-banner .let-hc-label {
- color: #888 !important;
- flex-shrink: 0 !important;
- }
- #let-hostc-banner .let-hc-label a {
- color: #22ad5d !important;
- font-weight: 700 !important;
- }
- #let-hostc-banner .let-hc-title {
- color: #ccc !important;
- overflow: hidden !important;
- text-overflow: ellipsis !important;
- }
- #let-hostc-banner .let-hc-title a {
- color: #ccc !important;
- }
- #let-hostc-banner .let-hc-title a:hover {
- color: #22ad5d !important;
- }
- #let-hostc-banner .let-hc-time {
- flex-shrink: 0 !important;
- color: #666 !important;
- }
- #let-hostc-banner .let-hc-toggle {
- display: flex !important;
- align-items: center !important;
- gap: 4px !important;
- flex-shrink: 0 !important;
- cursor: pointer !important;
- color: #555 !important;
- font-size: 10px !important;
- }
- #let-hostc-banner .let-hc-toggle:hover {
- color: #888 !important;
- }
- #let-hostc-banner .let-hc-switch {
- position: relative !important;
- width: 24px !important;
- height: 12px !important;
- background: #333 !important;
- border-radius: 6px !important;
- transition: background 0.2s !important;
- }
- #let-hostc-banner .let-hc-switch.on {
- background: #22ad5d !important;
- }
- #let-hostc-banner .let-hc-switch::after {
- content: '' !important;
- position: absolute !important;
- top: 1px !important;
- left: 1px !important;
- width: 10px !important;
- height: 10px !important;
- background: #ccc !important;
- border-radius: 50% !important;
- transition: left 0.2s !important;
- }
- #let-hostc-banner .let-hc-switch.on::after {
- left: 13px !important;
- }
- #let-hostc-banner .let-hc-interval {
- width: 28px !important;
- background: #222 !important;
- border: 1px solid #444 !important;
- color: #ccc !important;
- font-size: 10px !important;
- padding: 0 2px !important;
- text-align: center !important;
- border-radius: 3px !important;
- height: 14px !important;
- line-height: 14px !important;
- }
- #let-hostc-banner .let-hc-interval:focus {
- border-color: #22ad5d !important;
- outline: none !important;
- }
- #let-hostc-banner .let-hc-unit {
- color: #555 !important;
- font-size: 10px !important;
- }
- #let-hostc-banner .let-hc-divider {
- width: 1px !important;
- height: 10px !important;
- background: #444 !important;
- margin: 0 4px !important;
- flex-shrink: 0 !important;
- }
- #let-hostc-banner .let-hc-updated {
- color: #555 !important;
- font-size: 10px !important;
- flex-shrink: 0 !important;
- }
- /* host_c new post toast notification */
- .let-hc-toast {
- position: fixed !important;
- top: 60px !important;
- right: 20px !important;
- background: #1a1a22 !important;
- border: 1px solid #22ad5d !important;
- border-radius: 6px !important;
- padding: 12px 16px !important;
- max-width: 350px !important;
- z-index: 100001 !important;
- box-shadow: 0 4px 16px rgba(0,0,0,0.6) !important;
- font-size: 12px !important;
- color: #ccc !important;
- animation: let-toast-in 0.3s ease-out !important;
- cursor: pointer !important;
- }
- .let-hc-toast:hover {
- border-color: orange !important;
- }
- .let-hc-toast-label {
- color: #22ad5d !important;
- font-weight: 700 !important;
- font-size: 11px !important;
- margin-bottom: 4px !important;
- display: block !important;
- }
- .let-hc-toast-title {
- color: #e0e0e0 !important;
- font-weight: 700 !important;
- display: block !important;
- margin-bottom: 2px !important;
- }
- .let-hc-toast-title a {
- color: #e0e0e0 !important;
- text-decoration: none !important;
- }
- .let-hc-toast-title a:hover {
- color: #22ad5d !important;
- }
- .let-hc-toast-time {
- color: #666 !important;
- font-size: 10px !important;
- }
- .let-hc-toast-close {
- position: absolute !important;
- top: 4px !important;
- right: 8px !important;
- color: #555 !important;
- cursor: pointer !important;
- font-size: 14px !important;
- background: none !important;
- border: none !important;
- padding: 2px !important;
- }
- .let-hc-toast-close:hover {
- color: #ccc !important;
- }
- @keyframes let-toast-in {
- from { opacity: 0; transform: translateX(50px); }
- to { opacity: 1; transform: translateX(0); }
- }
- /* Pager - right-aligned, compact */
- .PageControls {
- text-align: right !important;
- padding: 2px 0 !important;
- margin: 0 !important;
- }
- .Pager {
- padding: 0 !important;
- margin: 0 !important;
- }
- .Pager a,
- .Pager span {
- background: #222225 !important;
- color: #ccc !important;
- border-color: #373741 !important;
- font-size: 10px !important;
- padding: 1px 5px !important;
- min-width: 0 !important;
- }
- .Pager .Active,
- .Pager .Highlight {
- background: #22ad5d !important;
- color: #fff !important;
- }
- /* Compact page controls row */
- .PageControls {
- padding: 2px 0 !important;
- margin: 0 !important;
- display: flex !important;
- align-items: center !important;
- justify-content: flex-end !important;
- }
- .let-sort-wrap {
- margin-right: 8px !important;
- display: flex !important;
- align-items: center !important;
- gap: 3px !important;
- }
- .let-sort-select {
- display: inline-block !important;
- visibility: visible !important;
- opacity: 1 !important;
- width: auto !important;
- height: auto !important;
- max-height: none !important;
- overflow: visible !important;
- background: #222225 !important;
- color: #ccc !important;
- border: 1px solid #373741 !important;
- font-size: 10px !important;
- padding: 1px 4px !important;
- border-radius: 3px !important;
- cursor: pointer !important;
- -webkit-appearance: menulist !important;
- appearance: menulist !important;
- }
- .let-sort-dir {
- display: inline-block !important;
- visibility: visible !important;
- opacity: 1 !important;
- width: auto !important;
- height: auto !important;
- background: #222225 !important;
- color: #ccc !important;
- border: 1px solid #373741 !important;
- font-size: 10px !important;
- padding: 1px 5px !important;
- border-radius: 3px !important;
- cursor: pointer !important;
- line-height: 1 !important;
- }
- .let-sort-select:hover, .let-sort-dir:hover {
- border-color: #22ad5d !important;
- }
- /* Reading pane */
- #let-reading-pane {
- display: none;
- order: 3 !important;
- height: calc(100vh - 50px) !important;
- position: sticky !important;
- top: 50px !important;
- border: none !important;
- border-left: 1px solid #373741 !important;
- background: #080810 !important;
- box-sizing: border-box !important;
- overflow-y: auto !important;
- overflow-x: hidden !important;
- }
- .let-reading-content {
- padding: 10px !important;
- color: #ccc !important;
- }
- .let-reading-content a {
- color: #22ad5d !important;
- }
- .let-reading-content img {
- max-width: 100% !important;
- height: auto !important;
- }
- #let-reading-pane-placeholder {
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- height: 100% !important;
- color: #555 !important;
- font-size: 14px !important;
- font-style: italic !important;
- }
- li.let-reading-active {
- background: #1a1a28 !important;
- border-left: 3px solid #22ad5d !important;
- }
- /* Dark category headers */
- .CategoryHeading,
- h2.H {
- color: #e0e0e0 !important;
- }
- /* Generic link colors */
- a {
- color: #22ad5d !important;
- }
- a:hover {
- color: orange !important;
- }
- /* Fix green-on-green button text */
- .Button.Primary,
- .Button.Primary a,
- .BigButton,
- .BigButton a,
- .btn-primary a,
- a.Button.Primary,
- a.BigButton {
- color: #fff !important;
- }
- /* Kill all default borders/shadows on list items */
- .DataList li,
- .DataList .Item,
- ul.DataList > li,
- ul.DataList.Discussions > li,
- ul.DataList.Discussions > li.ItemDiscussion,
- li.ItemDiscussion {
- border: none !important;
- border-top: none !important;
- border-bottom: none !important;
- box-shadow: none !important;
- outline: none !important;
- }
- /* Compact discussion rows */
- ul.DataList.Discussions {
- padding: 0 !important;
- margin: 0 !important;
- overflow: visible !important;
- }
- .DataList,
- .Section,
- .Section-Content,
- #Content,
- .Column.ContentColumn {
- overflow: visible !important;
- }
- ul.DataList.Discussions > li.ItemDiscussion {
- display: flex !important;
- flex-wrap: nowrap !important;
- align-items: center !important;
- padding: 1px 6px !important;
- gap: 0 6px !important;
- min-height: 0 !important;
- border-bottom: 1px solid #151518 !important;
- border-top: none !important;
- background: transparent !important;
- font-size: 13px !important;
- line-height: 20px !important;
- overflow: visible !important;
- }
- /* Restructured row: .let-avatar, .let-title, .let-meta, .let-bookmark */
- .let-avatar {
- flex-shrink: 0 !important;
- width: 16px !important;
- height: 16px !important;
- order: 1 !important;
- }
- .let-avatar img {
- width: 16px !important;
- height: 16px !important;
- border-radius: 3px !important;
- vertical-align: middle !important;
- }
- .let-title {
- order: 2 !important;
- white-space: nowrap !important;
- overflow: hidden !important;
- text-overflow: ellipsis !important;
- flex: 1 1 0 !important;
- min-width: 100px !important;
- }
- .let-title a {
- color: inherit !important;
- }
- .let-meta {
- order: 3 !important;
- display: flex !important;
- align-items: center !important;
- gap: 5px !important;
- white-space: nowrap !important;
- flex-shrink: 0 !important;
- color: #aaa !important;
- }
- .let-meta .let-tag {
- font-size: 9px !important;
- padding: 0 3px !important;
- border-radius: 2px !important;
- font-weight: 700 !important;
- }
- .let-meta .Tag-Announcement {
- background: #aaa !important;
- color: #000 !important;
- }
- .let-meta .let-cat {
- border: 1px solid #22ad5d !important;
- padding: 0 3px !important;
- border-radius: 2px !important;
- font-size: 9px !important;
- }
- .let-meta .let-cat a {
- color: inherit !important;
- text-decoration: none !important;
- }
- .let-title .let-new {
- background: #dcb727 !important;
- color: #000 !important;
- padding: 0 3px !important;
- border-radius: 2px !important;
- font-size: 9px !important;
- font-weight: 700 !important;
- margin-left: 6px !important;
- vertical-align: middle !important;
- line-height: 1.2 !important;
- display: inline !important;
- }
- .let-meta .let-author a,
- .let-meta .let-recent a {
- color: #22ad5d !important;
- text-decoration: none !important;
- }
- .let-meta .let-author a:hover,
- .let-meta .let-recent a:hover {
- color: orange !important;
- }
- .let-bookmark {
- order: 4 !important;
- flex-shrink: 0 !important;
- display: flex !important;
- align-items: center !important;
- gap: 3px !important;
- }
- .let-bookmark a {
- display: inline-flex !important;
- align-items: center !important;
- justify-content: center !important;
- width: 16px !important;
- height: 16px !important;
- font-size: 0 !important;
- vertical-align: middle !important;
- flex-shrink: 0 !important;
- }
- .let-bookmark .OptionsMenu {
- display: none !important;
- }
- .let-bookmark .Sprite.SpBookmarks,
- .let-bookmark .Bookmark {
- filter: invert(0.8) !important;
- }
- .let-bookmark .OptionsMenu {
- filter: invert(0.8) !important;
- }
- /* Hide original children after restructure */
- ul.DataList.Discussions > li.ItemDiscussion.let-done > a.IndexPhoto,
- ul.DataList.Discussions > li.ItemDiscussion.let-done > .ItemContent,
- ul.DataList.Discussions > li.ItemDiscussion.let-done > .Options {
- display: none !important;
- }
- /* Unread vs read styling */
- ul.DataList.Discussions > li.ItemDiscussion.Read .let-title {
- opacity: 0.6 !important;
- }
- ul.DataList.Discussions > li.ItemDiscussion.Read .let-meta {
- opacity: 0.6 !important;
- }
- /* Remove backgrounds that add visual noise */
- ul.DataList.Discussions > li.Announcement {
- background: transparent !important;
- }
- .Discussions .DataList .Item,
- .Item {
- background: transparent !important;
- }
- .Discussions .DataList .Item.Read,
- .Item.Read {
- background: transparent !important;
- }
- /* Dark post/comment content in thread view */
- .ItemComment, .Message {
- background: transparent !important;
- color: #ccc !important;
- }
- .ItemComment .Header {
- background: #15151b !important;
- color: #aaa !important;
- }
- .CommentHeader,
- .Comment .Header {
- background: #15151b !important;
- }
- .AuthorWrap a,
- .Username a {
- color: #22ad5d !important;
- }
- blockquote {
- background: #1a1a20 !important;
- border-left: 3px solid #373741 !important;
- color: #aaa !important;
- }
- code, pre {
- background: #1a1a20 !important;
- color: #ccc !important;
- border-color: #373741 !important;
- }
- /* When narrow, allow wrapping */
- @media (max-width: 1000px) {
- #Body .Row {
- flex-direction: column !important;
- }
- .Column.PanelColumn {
- width: 100% !important;
- min-width: 0 !important;
- }
- }
- @media (max-width: 800px) {
- ul.DataList.Discussions > li.ItemDiscussion {
- flex-wrap: wrap !important;
- }
- .let-title {
- flex: 1 1 100% !important;
- min-width: 0 !important;
- }
- .let-meta {
- flex-wrap: wrap !important;
- }
- }
- `;
- document.documentElement.appendChild(style);
- // Re-append dynamic style AFTER main style so it wins the cascade
- document.documentElement.appendChild(dynStyle);
- applyDynamicStyles();
- document.addEventListener("DOMContentLoaded", function () {
- if (!IS_IFRAME) {
- // Move MeBox icons from sidebar into header bar
- const headRow = document.querySelector("#Head .Row");
- if (headRow && !document.getElementById("let-head-mebox")) {
- // Try multiple selectors to find the sidebar MeBox
- const sidebarMeBox =
- document.querySelector(".Column.PanelColumn .MeBox") ||
- document.querySelector(".Panel .MeBox") ||
- document.querySelector(".PanelColumn .MeBox");
- if (sidebarMeBox) {
- const container = document.createElement("span");
- container.id = "let-head-mebox";
- // Grab all links and photo wraps from the MeBox
- sidebarMeBox.querySelectorAll("a, .PhotoWrap").forEach(function (el) {
- container.appendChild(el.cloneNode(true));
- });
- // Insert into the header
- headRow.appendChild(container);
- // Hide original
- sidebarMeBox.style.display = "none";
- // Attach custom flyout panels to notification/inbox links
- var flyoutMap = [
- {
- match: /notifications/i,
- popin: "/profile/notificationspopin",
- label: "All Notifications",
- full: "/profile/notifications",
- },
- {
- match: /messages|inbox/i,
- popin: "/messages/popin",
- label: "All Messages",
- full: "/messages/inbox",
- },
- {
- match: /bookmarks/i,
- popin: "/discussions/bookmarkedpopin",
- label: "All Bookmarks",
- full: "/discussions/bookmarked",
- },
- ];
- container.querySelectorAll("a").forEach(function (link) {
- var href = link.getAttribute("href") || "";
- var title = link.getAttribute("title") || "";
- var text = link.textContent || "";
- var cls = link.className || "";
- var inner = link.innerHTML || "";
- var matchStr =
- href + " " + title + " " + text + " " + cls + " " + inner;
- var config = null;
- for (var i = 0; i < flyoutMap.length; i++) {
- if (flyoutMap[i].match.test(matchStr)) {
- config = flyoutMap[i];
- break;
- }
- }
- if (!config) return;
- // Wrap link in a positioned container
- var wrap = document.createElement("span");
- wrap.className = "let-flyout-wrap";
- link.parentNode.insertBefore(wrap, link);
- wrap.appendChild(link);
- // Create flyout panel inside the wrapper
- var flyout = document.createElement("div");
- flyout.className = "let-flyout";
- flyout.innerHTML =
- '<div class="let-flyout-loading">Loading…</div>';
- wrap.appendChild(flyout);
- var loaded = false;
- link.addEventListener("click", function (e) {
- // Allow ctrl/cmd/middle click to navigate normally
- if (e.ctrlKey || e.metaKey || e.button === 1) return;
- e.preventDefault();
- e.stopPropagation();
- // Close other flyouts
- container.querySelectorAll(".let-flyout").forEach(function (f) {
- if (f !== flyout) f.classList.remove("let-flyout-open");
- });
- // Toggle this flyout
- var isOpen = flyout.classList.toggle("let-flyout-open");
- // Fetch content on first open (and refresh each time)
- if (isOpen) {
- flyout.innerHTML =
- '<div class="let-flyout-loading">Loading…</div>';
- var tk =
- typeof gdn !== "undefined" && gdn.definition
- ? gdn.definition("TransientKey")
- : "";
- var popinUrl =
- config.popin +
- (config.popin.indexOf("?") > -1 ? "&" : "?") +
- "DeliveryType=VIEW" +
- (tk ? "&TransientKey=" + encodeURIComponent(tk) : "");
- fetch(popinUrl, {
- credentials: "same-origin",
- })
- .then(function (r) {
- return r.text();
- })
- .then(function (html) {
- flyout.innerHTML =
- html.trim() +
- '<div class="let-flyout-footer"><a href="' +
- config.full +
- '">' +
- config.label +
- "</a></div>";
- // Restyle the title row inline to beat theme CSS
- var titleLi = flyout.querySelector("li.Item.Title");
- if (titleLi) {
- var prefLink = titleLi.querySelector("a");
- var heading = titleLi.querySelector("strong");
- titleLi.innerHTML = "";
- titleLi.style.cssText =
- "display:flex!important;align-items:baseline!important;justify-content:space-between!important;padding:6px 12px!important;border-bottom:1px solid #252530!important;";
- if (heading) {
- heading.style.cssText =
- "font-size:12px!important;color:#ccc!important;font-weight:700!important;order:1!important;";
- titleLi.appendChild(heading);
- }
- if (prefLink) {
- prefLink.style.cssText =
- "font-size:10px!important;color:#666!important;font-style:italic!important;order:2!important;text-decoration:none!important;";
- titleLi.appendChild(prefLink);
- }
- }
- // Remove duplicate "All Notifications" from popin (we add our own footer)
- var centerItem = flyout.querySelector("li.Item.Center");
- if (centerItem) centerItem.style.display = "none";
- })
- .catch(function () {
- flyout.innerHTML =
- '<div class="let-flyout-loading">Failed to load</div>' +
- '<div class="let-flyout-footer"><a href="' +
- config.full +
- '">' +
- config.label +
- "</a></div>";
- });
- }
- });
- });
- // Close flyouts on outside click
- document.addEventListener("click", function (e) {
- if (!container.contains(e.target)) {
- container.querySelectorAll(".let-flyout").forEach(function (f) {
- f.classList.remove("let-flyout-open");
- });
- }
- });
- }
- }
- // Move search from header to top of sidebar, or ensure sidebar search is visible
- const sidebar = document.querySelector(".Column.PanelColumn");
- if (sidebar) {
- const headSearch = document.querySelector("#Head .SiteSearch");
- if (headSearch) {
- // Move header search to sidebar
- sidebar.insertBefore(headSearch, sidebar.firstChild);
- }
- // Find whichever search is now in sidebar and force visible at top
- const search = sidebar.querySelector(".SiteSearch");
- if (search) {
- search.style.cssText =
- "display: block !important; padding: 4px 8px; margin-bottom: 4px;";
- if (search !== sidebar.firstElementChild) {
- sidebar.insertBefore(search, sidebar.firstChild);
- }
- }
- }
- // Fetch host_c's latest comment and display in banner
- (function () {
- var banner = document.createElement("div");
- banner.id = "let-hostc-banner";
- var head = document.getElementById("Head");
- if (head) {
- head.appendChild(banner);
- }
- var intervalId = null;
- var tickerId = null;
- var autoOn = letSettings.hcAutoOn;
- var refreshSec = letSettings.hcInterval;
- var lastFetchTime = null;
- var lastPostHref = null;
- function showNewPostToast(prefix, title, href, time) {
- var existing = document.querySelector(".let-hc-toast");
- if (existing) existing.remove();
- var toast = document.createElement("div");
- toast.className = "let-hc-toast";
- toast.innerHTML =
- '<button class="let-hc-toast-close">×</button>' +
- '<span class="let-hc-toast-label">New post from host_c</span>' +
- '<span class="let-hc-toast-title"><a href="' +
- href +
- '">' +
- prefix +
- title +
- "</a></span>" +
- '<span class="let-hc-toast-time">' +
- time +
- "</span>";
- document.body.appendChild(toast);
- toast
- .querySelector(".let-hc-toast-close")
- .addEventListener("click", function (e) {
- e.stopPropagation();
- toast.remove();
- });
- toast.addEventListener("click", function () {
- window.location.href = href;
- });
- setTimeout(function () {
- if (toast.parentNode) toast.remove();
- }, 15000);
- // Send Pushover notification if enabled and configured
- if (
- letSettings.pushEnabled &&
- letSettings.pushoverUser &&
- letSettings.pushoverToken
- ) {
- var fullUrl = href.startsWith("http")
- ? href
- : window.location.origin + href;
- var formData = new FormData();
- formData.append("token", letSettings.pushoverToken);
- formData.append("user", letSettings.pushoverUser);
- formData.append("title", "host_c — " + prefix + title);
- formData.append("message", prefix + title + "\n" + time);
- formData.append("url", fullUrl);
- formData.append("url_title", "View Post");
- fetch("https://api.pushover.net/1/messages.json", {
- method: "POST",
- body: formData,
- }).catch(function () {});
- }
- }
- function getInterval() {
- return refreshSec * 1000;
- }
- function startInterval() {
- clearInterval(intervalId);
- intervalId = setInterval(fetchLatest, getInterval());
- }
- function startTicker() {
- clearInterval(tickerId);
- tickerId = setInterval(updateTicker, 1000);
- }
- function updateTicker() {
- var el = banner.querySelector(".let-hc-updated");
- if (!el || !lastFetchTime) return;
- var ago = Math.round((Date.now() - lastFetchTime) / 1000);
- el.textContent = "updated " + ago + "s ago";
- }
- function buildBanner(titleHtml, timeText) {
- banner.innerHTML =
- '<span class="let-hc-label">Latest from <a href="/profile/host_c">host_c</a>:</span>' +
- '<span class="let-hc-title">' +
- titleHtml +
- "</span>" +
- '<span class="let-hc-time">' +
- timeText +
- "</span>" +
- '<span class="let-hc-divider"></span>' +
- '<span class="let-hc-toggle">' +
- '<span class="let-hc-unit">host_c monitor settings: Auto Update interval (seconds):</span>' +
- '<input type="text" class="let-hc-interval" value="' +
- refreshSec +
- '" title="Refresh interval in seconds" />' +
- '<span class="let-hc-divider"></span>' +
- '<span class="let-hc-unit">Enabled:</span> ' +
- '<span class="let-hc-switch' +
- (autoOn ? " on" : "") +
- '"></span>' +
- "</span>" +
- '<span class="let-hc-divider"></span>' +
- '<span class="let-hc-updated">' +
- (lastFetchTime ? "updated 0s ago" : "") +
- "</span>" +
- ' <a href="#" class="let-hc-refresh" style="color:#22ad5d!important;font-size:10px!important;margin-left:4px!important">update now</a>';
- banner
- .querySelector(".let-hc-switch")
- .addEventListener("click", toggleAuto);
- banner
- .querySelector(".let-hc-refresh")
- .addEventListener("click", function (e) {
- e.preventDefault();
- fetchLatest();
- });
- var input = banner.querySelector(".let-hc-interval");
- input.addEventListener("click", function (e) {
- e.stopPropagation();
- });
- input.addEventListener("change", function () {
- var val = parseInt(this.value, 10);
- if (val && val >= 5) {
- refreshSec = val;
- letSettings.hcInterval = val;
- saveSettings(letSettings);
- } else {
- this.value = refreshSec;
- }
- if (autoOn) startInterval();
- });
- }
- function parseComment(doc) {
- var item = doc.querySelector("ul.DataList.SearchResults li.Item");
- if (!item) return null;
- var titleLink = item.querySelector(".Meta .MItem b a");
- var timeEl = item.querySelector(".Meta .MItem:last-child a");
- // Comments page: time may be a <time> tag or just text in an <a>
- var timeTag = item.querySelector(".Meta time");
- var title = titleLink ? titleLink.textContent.trim() : "";
- var href = titleLink ? titleLink.getAttribute("href") : "#";
- var time = timeEl ? timeEl.textContent.trim() : "";
- var datetime = timeTag ? timeTag.getAttribute("datetime") : null;
- // If no <time> tag, the comment ID in the li id tells us order;
- // but more reliably, extract from the comment permalink's title attr
- if (!datetime) {
- // Try the <time> inside the last MItem link
- var innerTime = timeEl ? timeEl.querySelector("time") : null;
- if (innerTime) {
- datetime = innerTime.getAttribute("datetime");
- } else {
- // Comments without <time> tag — parse displayed time text
- var timeText = time; // e.g. "12:56PM", "March 5", "March 6, 2025"
- var todayMatch = timeText.match(/^(\d{1,2}):(\d{2})(AM|PM)$/i);
- if (todayMatch) {
- var h = parseInt(todayMatch[1], 10);
- var m = parseInt(todayMatch[2], 10);
- var ampm = todayMatch[3].toUpperCase();
- if (ampm === "PM" && h !== 12) h += 12;
- if (ampm === "AM" && h === 12) h = 0;
- var now = new Date();
- datetime = new Date(
- now.getFullYear(),
- now.getMonth(),
- now.getDate(),
- h,
- m,
- 0,
- ).toISOString();
- } else {
- // Parse "Month Day" or "Month Day, Year" format
- var months = {
- January: 0,
- February: 1,
- March: 2,
- April: 3,
- May: 4,
- June: 5,
- July: 6,
- August: 7,
- September: 8,
- October: 9,
- November: 10,
- December: 11,
- };
- var dateMatch = timeText.match(
- /^(\w+)\s+(\d{1,2})(?:,?\s*(\d{4}))?$/,
- );
- if (dateMatch && months[dateMatch[1]] !== undefined) {
- var yr = dateMatch[3]
- ? parseInt(dateMatch[3], 10)
- : new Date().getFullYear();
- datetime = new Date(
- yr,
- months[dateMatch[1]],
- parseInt(dateMatch[2], 10),
- ).toISOString();
- }
- }
- }
- }
- return { title: title, href: href, time: time, datetime: datetime };
- }
- function parseDiscussion(doc) {
- var item = doc.querySelector(
- "ul.DataList.Discussions li.ItemDiscussion",
- );
- if (!item) return null;
- var titleLink = item.querySelector(".Title a");
- var timeTag = item.querySelector(".LastCommentDate time");
- var title = titleLink ? titleLink.textContent.trim() : "";
- var href = titleLink ? titleLink.getAttribute("href") : "#";
- var time = timeTag ? timeTag.textContent.trim() : "";
- var datetime = timeTag ? timeTag.getAttribute("datetime") : null;
- return { title: title, href: href, time: time, datetime: datetime };
- }
- function fetchLatest() {
- var results = { comments: null, discussions: null };
- var done = 0;
- function finish() {
- done++;
- if (done < 2) return;
- lastFetchTime = Date.now();
- var c = results.comments;
- var d = results.discussions;
- if (!c && !d) {
- buildBanner("no posts found", "");
- return;
- }
- // Compare timestamps to find which is more recent
- var useComment = false;
- if (c && d) {
- var cDate = c.datetime ? new Date(c.datetime).getTime() : 0;
- var dDate = d.datetime ? new Date(d.datetime).getTime() : 0;
- useComment = cDate >= dDate;
- } else if (c) {
- useComment = true;
- }
- var latest = useComment ? c : d;
- var prefix = useComment ? "RE: " : "";
- // Show toast if this is a new post (not the first load)
- if (lastPostHref !== null && latest.href !== lastPostHref) {
- showNewPostToast(prefix, latest.title, latest.href, latest.time);
- }
- lastPostHref = latest.href;
- buildBanner(
- '<a href="' + latest.href + '">' + prefix + latest.title + "</a>",
- latest.time,
- );
- }
- function fetchPage(url, key, parser) {
- var xhr = new XMLHttpRequest();
- xhr.open("GET", url, true);
- xhr.onload = function () {
- if (xhr.status === 200) {
- var doc = new DOMParser().parseFromString(
- xhr.responseText,
- "text/html",
- );
- results[key] = parser(doc);
- }
- finish();
- };
- xhr.onerror = function () {
- finish();
- };
- xhr.send();
- }
- fetchPage("/profile/comments/host_c", "comments", parseComment);
- fetchPage(
- "/profile/discussions/host_c",
- "discussions",
- parseDiscussion,
- );
- }
- function toggleAuto() {
- autoOn = !autoOn;
- letSettings.hcAutoOn = autoOn;
- saveSettings(letSettings);
- if (autoOn) {
- fetchLatest();
- startInterval();
- } else {
- clearInterval(intervalId);
- intervalId = null;
- }
- var sw = banner.querySelector(".let-hc-switch");
- if (sw) sw.classList.toggle("on", autoOn);
- }
- // After building banner, re-append the settings wrap if it exists
- var settingsWrap = null;
- var origBuildBanner = buildBanner;
- buildBanner = function (titleHtml, timeText) {
- origBuildBanner(titleHtml, timeText);
- if (settingsWrap) banner.appendChild(settingsWrap);
- };
- // Create settings link + flyout, appended to the banner
- (function () {
- settingsWrap = document.createElement("span");
- settingsWrap.className = "let-flyout-wrap";
- settingsWrap.style.cssText =
- "flex-shrink:0!important;display:inline-flex!important;align-items:center!important;gap:6px!important;";
- var divider = document.createElement("span");
- divider.className = "let-hc-divider";
- settingsWrap.insertBefore(divider, settingsWrap.firstChild);
- // Auto-refresh page toggle
- var arLabel = document.createElement("span");
- arLabel.className = "let-hc-unit";
- arLabel.textContent = "Auto Refresh (seconds):";
- settingsWrap.appendChild(arLabel);
- var arInput = document.createElement("input");
- arInput.type = "text";
- arInput.className = "let-hc-interval";
- arInput.value = letSettings.autoRefreshSec;
- arInput.title = "Page refresh interval in seconds";
- settingsWrap.appendChild(arInput);
- var arSwitch = document.createElement("span");
- arSwitch.className =
- "let-hc-switch" + (letSettings.autoRefreshOn ? " on" : "");
- var arTimerId = null;
- function startAutoRefresh() {
- if (arTimerId) clearInterval(arTimerId);
- arTimerId = setInterval(function () {
- window.location.reload();
- }, letSettings.autoRefreshSec * 1000);
- }
- arSwitch.addEventListener("click", function () {
- letSettings.autoRefreshOn = !letSettings.autoRefreshOn;
- arSwitch.classList.toggle("on", letSettings.autoRefreshOn);
- saveSettings(letSettings);
- if (letSettings.autoRefreshOn) {
- startAutoRefresh();
- } else {
- clearInterval(arTimerId);
- arTimerId = null;
- }
- });
- settingsWrap.appendChild(arSwitch);
- arInput.addEventListener("change", function () {
- var val = parseInt(this.value, 10);
- if (!val || val < 5) val = 5;
- letSettings.autoRefreshSec = val;
- this.value = val;
- saveSettings(letSettings);
- if (letSettings.autoRefreshOn) startAutoRefresh();
- });
- if (letSettings.autoRefreshOn) startAutoRefresh();
- var dividerAR = document.createElement("span");
- dividerAR.className = "let-hc-divider";
- settingsWrap.appendChild(dividerAR);
- // Push notifications toggle
- var pushLabel = document.createElement("span");
- pushLabel.className = "let-hc-unit";
- pushLabel.textContent = "Push:";
- settingsWrap.appendChild(pushLabel);
- var pushSwitch = document.createElement("span");
- pushSwitch.className =
- "let-hc-switch let-push-switch" +
- (letSettings.pushEnabled ? " on" : "");
- pushSwitch.addEventListener("click", function () {
- letSettings.pushEnabled = !letSettings.pushEnabled;
- pushSwitch.classList.toggle("on", letSettings.pushEnabled);
- saveSettings(letSettings);
- });
- settingsWrap.appendChild(pushSwitch);
- var divider2 = document.createElement("span");
- divider2.className = "let-hc-divider";
- settingsWrap.appendChild(divider2);
- var link = document.createElement("a");
- link.className = "let-settings-link";
- link.href = "#";
- link.textContent = "Userscript Settings";
- settingsWrap.appendChild(link);
- var flyout = document.createElement("div");
- flyout.className = "let-flyout let-settings-flyout";
- flyout.innerHTML =
- "<label>Post Title Size " +
- '<input type="range" min="8" max="20" value="' +
- letSettings.titleFontSize +
- '" class="let-range-title" />' +
- '<span class="let-size-val let-val-title">' +
- letSettings.titleFontSize +
- "px</span>" +
- "</label>" +
- "<label>Meta Info Size " +
- '<input type="range" min="6" max="16" value="' +
- letSettings.metaFontSize +
- '" class="let-range-meta" />' +
- '<span class="let-size-val let-val-meta">' +
- letSettings.metaFontSize +
- "px</span>" +
- "</label>" +
- "<label>Hide Sidebar " +
- '<input type="checkbox" class="let-chk-sidebar"' +
- (letSettings.hideSidebar ? " checked" : "") +
- " />" +
- "</label>" +
- "<label>Reading Pane " +
- '<input type="checkbox" class="let-chk-readpane"' +
- (letSettings.readingPane ? " checked" : "") +
- " />" +
- "</label>" +
- '<hr style="border:none;border-top:1px solid #333;margin:8px 0" />' +
- '<label style="font-size:11px;color:#888;margin-bottom:4px;display:block">Pushover User Key</label>' +
- '<input type="text" class="let-push-user" value="' +
- (letSettings.pushoverUser || "") +
- '" placeholder="User key" style="width:100%;padding:3px 6px;background:#111;color:#ccc;border:1px solid #333;border-radius:3px;font-size:11px;margin-bottom:6px;box-sizing:border-box" />' +
- '<label style="font-size:11px;color:#888;margin-bottom:4px;display:block">Pushover API Token</label>' +
- '<input type="password" class="let-push-token" value="' +
- (letSettings.pushoverToken || "") +
- '" placeholder="API token" style="width:100%;padding:3px 6px;background:#111;color:#ccc;border:1px solid #333;border-radius:3px;font-size:11px;margin-bottom:6px;box-sizing:border-box" />' +
- '<button class="let-settings-reset">Reset to Defaults</button>' +
- '<button class="let-settings-reset let-test-toast">Test Notification</button>';
- settingsWrap.appendChild(flyout);
- flyout
- .querySelector(".let-test-toast")
- .addEventListener("click", function () {
- showNewPostToast(
- "RE: ",
- "Test Post — This is a sample host_c notification!",
- "#",
- "just now",
- );
- });
- var rangeTitle = flyout.querySelector(".let-range-title");
- var rangeMeta = flyout.querySelector(".let-range-meta");
- var chkSidebar = flyout.querySelector(".let-chk-sidebar");
- var chkReadPane = flyout.querySelector(".let-chk-readpane");
- var valTitle = flyout.querySelector(".let-val-title");
- var valMeta = flyout.querySelector(".let-val-meta");
- var pushUserInput = flyout.querySelector(".let-push-user");
- var pushTokenInput = flyout.querySelector(".let-push-token");
- pushUserInput.addEventListener("change", function () {
- letSettings.pushoverUser = this.value.trim();
- saveSettings(letSettings);
- });
- pushTokenInput.addEventListener("change", function () {
- letSettings.pushoverToken = this.value.trim();
- saveSettings(letSettings);
- });
- rangeTitle.addEventListener("input", function () {
- var v = parseInt(this.value, 10);
- letSettings.titleFontSize = v;
- valTitle.textContent = v + "px";
- applyDynamicStyles();
- saveSettings(letSettings);
- });
- rangeMeta.addEventListener("input", function () {
- var v = parseInt(this.value, 10);
- letSettings.metaFontSize = v;
- valMeta.textContent = v + "px";
- applyDynamicStyles();
- saveSettings(letSettings);
- });
- chkSidebar.addEventListener("change", function () {
- letSettings.hideSidebar = this.checked;
- applyDynamicStyles();
- saveSettings(letSettings);
- });
- chkReadPane.addEventListener("change", function () {
- letSettings.readingPane = this.checked;
- applyDynamicStyles();
- saveSettings(letSettings);
- toggleReadingPane(this.checked);
- });
- flyout
- .querySelector(".let-settings-reset")
- .addEventListener("click", function () {
- letSettings.titleFontSize = LET_DEFAULTS.titleFontSize;
- letSettings.metaFontSize = LET_DEFAULTS.metaFontSize;
- letSettings.hideSidebar = LET_DEFAULTS.hideSidebar;
- letSettings.pushEnabled = LET_DEFAULTS.pushEnabled;
- letSettings.pushoverUser = LET_DEFAULTS.pushoverUser;
- letSettings.pushoverToken = LET_DEFAULTS.pushoverToken;
- rangeTitle.value = LET_DEFAULTS.titleFontSize;
- rangeMeta.value = LET_DEFAULTS.metaFontSize;
- valTitle.textContent = LET_DEFAULTS.titleFontSize + "px";
- valMeta.textContent = LET_DEFAULTS.metaFontSize + "px";
- chkSidebar.checked = LET_DEFAULTS.hideSidebar;
- letSettings.readingPane = LET_DEFAULTS.readingPane;
- chkReadPane.checked = LET_DEFAULTS.readingPane;
- toggleReadingPane(false);
- pushUserInput.value = "";
- pushTokenInput.value = "";
- pushSwitch.classList.remove("on");
- letSettings.autoRefreshOn = LET_DEFAULTS.autoRefreshOn;
- letSettings.autoRefreshSec = LET_DEFAULTS.autoRefreshSec;
- arInput.value = LET_DEFAULTS.autoRefreshSec;
- arSwitch.classList.remove("on");
- clearInterval(arTimerId);
- arTimerId = null;
- applyDynamicStyles();
- saveSettings(letSettings);
- });
- link.addEventListener("click", function (e) {
- e.preventDefault();
- e.stopPropagation();
- flyout.classList.toggle("let-flyout-open");
- });
- document.addEventListener("click", function (e) {
- if (!settingsWrap.contains(e.target)) {
- flyout.classList.remove("let-flyout-open");
- }
- });
- banner.appendChild(settingsWrap);
- })();
- buildBanner('<span style="color:#666!important">loading...</span>', "");
- fetchLatest();
- startTicker();
- if (autoOn) startInterval();
- })();
- } // end !IS_IFRAME
- document
- .querySelectorAll("ul.DataList.Discussions > li.ItemDiscussion")
- .forEach(function (li) {
- if (li.classList.contains("let-done")) return;
- // Extract pieces
- const photoLink = li.querySelector(":scope > a.IndexPhoto");
- const content = li.querySelector(":scope > .ItemContent.Discussion");
- const options = li.querySelector(":scope > .Options");
- if (!content) return;
- const titleDiv = content.querySelector(".Title");
- const meta = content.querySelector(".Meta.Meta-Discussion");
- // 1. Avatar
- const avatar = document.createElement("span");
- avatar.className = "let-avatar";
- if (photoLink) {
- const img = photoLink.querySelector("img");
- if (img) {
- const newImg = document.createElement("img");
- newImg.src = img.src;
- newImg.alt = img.alt || "";
- avatar.appendChild(newImg);
- }
- }
- // 2. Title
- const title = document.createElement("span");
- title.className = "let-title";
- if (titleDiv) {
- const link = titleDiv.querySelector("a");
- if (link) {
- title.appendChild(link.cloneNode(true));
- }
- }
- // 3. Meta - build in desired order
- const metaSpan = document.createElement("span");
- metaSpan.className = "let-meta";
- if (meta) {
- // Tag (Announcement)
- const tag = meta.querySelector(".Tag");
- if (tag) {
- const t = document.createElement("span");
- t.className = "let-tag " + tag.className;
- t.textContent = tag.textContent;
- metaSpan.appendChild(t);
- }
- // Author
- const author = meta.querySelector(".DiscussionAuthor");
- if (author) {
- const a = document.createElement("span");
- a.className = "let-author";
- a.innerHTML = author.innerHTML;
- metaSpan.appendChild(a);
- }
- // Views
- const views = meta.querySelector(".ViewCount");
- if (views) {
- const v = document.createElement("span");
- const num = views.querySelector(".Number");
- v.textContent = (num ? num.textContent : "") + " views";
- metaSpan.appendChild(v);
- }
- // Comments
- const comments = meta.querySelector(".CommentCount");
- if (comments) {
- const c = document.createElement("span");
- const num = comments.querySelector(".Number");
- c.textContent = (num ? num.textContent : "") + " comments";
- metaSpan.appendChild(c);
- }
- // New - append to title instead of meta
- const hasNew = meta.querySelector(".HasNew");
- if (hasNew) {
- const n = document.createElement("span");
- n.className = "let-new";
- n.textContent = hasNew.textContent.trim();
- title.appendChild(n);
- }
- // Most recent by / Started by
- const lastBy = meta.querySelector(".LastCommentBy");
- if (lastBy) {
- const r = document.createElement("span");
- r.className = "let-recent";
- r.innerHTML = lastBy.innerHTML;
- metaSpan.appendChild(r);
- }
- // Time
- const lastDate = meta.querySelector(".LastCommentDate");
- if (lastDate) {
- const d = document.createElement("span");
- d.textContent = lastDate.textContent.trim();
- metaSpan.appendChild(d);
- }
- // Category
- const cat = meta.querySelector(".Category");
- if (cat) {
- const g = document.createElement("span");
- g.className = "let-cat";
- g.innerHTML = cat.querySelector("a")
- ? cat.querySelector("a").outerHTML
- : cat.textContent;
- metaSpan.appendChild(g);
- }
- }
- // 4. Bookmark
- const bookmark = document.createElement("span");
- bookmark.className = "let-bookmark";
- if (options) {
- const bmLink = options.querySelector("a.Bookmark");
- if (bmLink) {
- bookmark.appendChild(bmLink);
- }
- // Also grab dismiss for announcements
- const optMenu = options.querySelector(".OptionsMenu");
- if (optMenu) {
- bookmark.appendChild(optMenu);
- }
- }
- // Store sortable data on li
- if (meta) {
- var vc = meta.querySelector(".ViewCount .Number");
- li.setAttribute(
- "data-views",
- vc ? parseInt(vc.textContent.replace(/,/g, ""), 10) || 0 : 0,
- );
- var cc = meta.querySelector(".CommentCount .Number");
- li.setAttribute(
- "data-comments",
- cc ? parseInt(cc.textContent.replace(/,/g, ""), 10) || 0 : 0,
- );
- var ldt = meta.querySelector(".LastCommentDate time");
- li.setAttribute(
- "data-lastpost",
- ldt ? ldt.getAttribute("datetime") || "" : "",
- );
- }
- // Append in order
- li.appendChild(avatar);
- li.appendChild(title);
- li.appendChild(metaSpan);
- li.appendChild(bookmark);
- li.classList.add("let-done");
- });
- // Create reading pane if enabled
- if (!IS_IFRAME && letSettings.readingPane) createReadingPane();
- // Sort dropdown for discussions
- (function () {
- var list = document.querySelector("ul.DataList.Discussions");
- if (!list) return;
- // Find the PageControls nearest to the discussion list (before or after)
- var pager =
- list.parentNode.querySelector(".PageControls") ||
- document.querySelector(".PageControls");
- if (!pager) {
- // Fallback: create a controls row above the list
- pager = document.createElement("div");
- pager.className = "PageControls";
- pager.style.cssText =
- "display:flex!important;align-items:center!important;justify-content:flex-end!important;padding:2px 0!important;";
- list.parentNode.insertBefore(pager, list);
- }
- // Store original order
- var items = list.querySelectorAll("li.ItemDiscussion.let-done");
- items.forEach(function (li, i) {
- li.setAttribute("data-sort-index", i);
- });
- // Build sort UI
- var wrap = document.createElement("span");
- wrap.className = "let-sort-wrap";
- var sel = document.createElement("select");
- sel.className = "let-sort-select";
- [
- { value: "default", text: "Default Order" },
- { value: "lastpost", text: "Last Post" },
- { value: "views", text: "Views" },
- { value: "comments", text: "Comments" },
- ].forEach(function (o) {
- var opt = document.createElement("option");
- opt.value = o.value;
- opt.textContent = o.text;
- sel.appendChild(opt);
- });
- var dirBtn = document.createElement("button");
- dirBtn.className = "let-sort-dir";
- dirBtn.textContent = "\u25BC";
- dirBtn.title = "Descending";
- var ascending = false;
- dirBtn.addEventListener("click", function () {
- ascending = !ascending;
- dirBtn.textContent = ascending ? "\u25B2" : "\u25BC";
- dirBtn.title = ascending ? "Ascending" : "Descending";
- doSort();
- });
- function doSort() {
- var key = sel.value;
- var rows = Array.from(
- list.querySelectorAll("li.ItemDiscussion.let-done"),
- );
- if (key === "default") {
- rows.sort(function (a, b) {
- return (
- parseInt(a.getAttribute("data-sort-index")) -
- parseInt(b.getAttribute("data-sort-index"))
- );
- });
- if (!ascending) rows.reverse();
- } else if (key === "views" || key === "comments") {
- rows.sort(function (a, b) {
- var va = parseInt(a.getAttribute("data-" + key)) || 0;
- var vb = parseInt(b.getAttribute("data-" + key)) || 0;
- return ascending ? va - vb : vb - va;
- });
- } else if (key === "lastpost") {
- rows.sort(function (a, b) {
- var da = new Date(a.getAttribute("data-lastpost") || 0).getTime();
- var db = new Date(b.getAttribute("data-lastpost") || 0).getTime();
- return ascending ? da - db : db - da;
- });
- }
- rows.forEach(function (li) {
- list.appendChild(li);
- });
- }
- sel.addEventListener("change", doSort);
- wrap.appendChild(sel);
- wrap.appendChild(dirBtn);
- pager.insertBefore(wrap, pager.firstChild);
- })();
- });
- // Condense footer into 3-column layout
- function condenseFooter() {
- var foot = document.getElementById("Foot");
- if (!foot) return;
- foot.innerHTML = "";
- var row = document.createElement("div");
- row.className = "Row";
- row.style.cssText =
- "display:flex!important;align-items:center!important;padding:2px 10px!important;height:auto!important;min-height:0!important;";
- var left = document.createElement("div");
- left.className = "let-foot-left";
- left.style.cssText =
- "flex:1!important;text-align:left!important;font-size:11px!important;";
- left.textContent = "2008-2025 \u00A9 LowEndBox & LowEndTalk.";
- var center = document.createElement("div");
- center.className = "let-foot-center";
- center.style.cssText =
- "flex:1!important;text-align:center!important;font-size:11px!important;";
- [
- { text: "Back to Top", href: "#top" },
- { text: "LowEndTalk", href: "https://lowendtalk.com" },
- { text: "LowEndBox", href: "https://www.lowendbox.com" },
- {
- text: "Dark Theme Config",
- href: "https://lowendtalk.com/page/dark-theme-config",
- },
- { text: "Advertise", href: "https://lowendbox.com/advertise/" },
- ].forEach(function (l, i) {
- if (i > 0) center.appendChild(document.createTextNode(" | "));
- var a = document.createElement("a");
- a.href = l.href;
- a.textContent = l.text;
- center.appendChild(a);
- });
- var right = document.createElement("div");
- right.className = "let-foot-right";
- right.style.cssText =
- "flex:1!important;text-align:right!important;font-size:11px!important;";
- row.appendChild(left);
- row.appendChild(center);
- row.appendChild(right);
- foot.appendChild(row);
- }
- // Run multiple times to survive late-running page scripts that modify the footer
- if (!IS_IFRAME) {
- document.addEventListener("DOMContentLoaded", condenseFooter);
- window.addEventListener("load", function () {
- condenseFooter();
- setTimeout(condenseFooter, 500);
- setTimeout(condenseFooter, 1500);
- setTimeout(condenseFooter, 3000);
- });
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment