Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name OddworldForums Necrum Theme + Fixes
- // @namespace owforums-necrum-theme
- // @version 1.0
- // @match https://owforums.net/*
- // @run-at document-start
- // ==/UserScript==
- (function () {
- 'use strict';
- // Immediately hide the page and set background
- const preStyle = document.createElement('style');
- preStyle.id = 'pre-theme-style';
- preStyle.textContent = `
- html, body {
- background: linear-gradient(to bottom, #171309, #000000); /* flash color */
- visibility: hidden !important; /* hide content until ready */
- }
- `;
- document.head.appendChild(preStyle);
- const NEW_LOGO_URL = 'https://i.imgur.com/xYhdQy1.png';
- const TOP_GRADIENT = 'https://i.imgur.com/THjrmF5.png';
- const GRADIENT_TCAT = 'https://i.imgur.com/f3Y762Z.png';
- const TOP_BG = 'https://i.imgur.com/vRScjRf.png';
- const TOP_LEFT1 = 'https://i.imgur.com/ipJ6dw2.png';
- const TOP_LEFT2 = 'https://i.imgur.com/a4XaQCn.png';
- const TOP_LEFT3 = 'https://i.imgur.com/e8r6tjH.png';
- const BOTTOM_BG = 'https://i.imgur.com/wV2o6Cw.png';
- const BOTTOM_LEFT1 = 'https://i.imgur.com/o3VxgEE.png';
- const BOTTOM_LEFT2 = 'https://i.imgur.com/wiIzKG2.png';
- const BOTTOM_LEFT3 = 'https://i.imgur.com/SSVz2zQ.png';
- const POSTBIT_BACK2 = 'https://i.imgur.com/a1SutZV.png';
- const POSTBIT_BACKDARK = 'https://i.imgur.com/vd7mwIp.png';
- const NAV_ENDLEFT = 'https://i.imgur.com/7YBxofd.png';
- const NAV_ENDRIGHT = 'https://i.imgur.com/CmNLk9O.png';
- const REPLYBTN = 'https://i.imgur.com/BiO562l.png';
- const NEWTOPIC = 'https://i.imgur.com/WLVQJ3A.png';
- const COLLAPSE_TCAT = 'https://i.imgur.com/nM8CLlT.png';
- const COLLAPSE_THEAD = 'https://i.imgur.com/RpfgdPA.png';
- const THREADCLOSED = 'https://i.imgur.com/f8ISLg2.png';
- function trySwapImages(sourceName, targetUrl, mirrored) {
- const image = document.querySelectorAll(`img[src*="${sourceName}"]`);
- if (!image.length) return false;
- image.forEach(image => {
- image.src = targetUrl;
- // Mirror it horizontally
- if (mirrored) {
- image.style.transformOrigin = '50% 50%'; // center of the element
- image.style.transform = 'scaleX(-1)';
- }
- });
- console.log('[OW Theme] Image ' + sourceName + ' swapped, using original layout size');
- return true;
- }
- function swapBackgroundImgs(sourceName, targetUrl, prefix) {
- const tds = document.querySelectorAll(prefix + `[background*="${sourceName}"]`);
- if (!tds.length) return false;
- tds.forEach(td => {
- // Option 1: replace the HTML attribute directly
- td.setAttribute('background', targetUrl);
- // Option 2: also set inline style to be safe
- td.style.backgroundImage = `url(${targetUrl})`;
- });
- console.log(`[OW Theme] Swapped ${tds.length} background gif(s)`);
- return true;
- }
- function trySwapLogo() {
- return trySwapImages("native_05.jpg", NEW_LOGO_URL);
- }
- function trySwapGradients() {
- swapBackgroundImgs("top-bg.gif", TOP_BG, 'td');
- trySwapImages("top-left1.gif", TOP_LEFT1);
- trySwapImages("top-left2.gif", TOP_LEFT2);
- trySwapImages("top-left3.gif", TOP_LEFT3);
- trySwapImages("top-right1.gif", TOP_LEFT1, true);
- trySwapImages("top-right2.gif", TOP_LEFT2, true);
- trySwapImages("top-right3.gif", TOP_LEFT3, true);
- swapBackgroundImgs("bottom-bg.gif", BOTTOM_BG, 'td');
- trySwapImages("bottom-left1.gif", BOTTOM_LEFT1);
- trySwapImages("bottom-left2.gif", BOTTOM_LEFT2);
- trySwapImages("bottom-left3.gif", BOTTOM_LEFT3);
- trySwapImages("bottom-right1.gif", BOTTOM_LEFT1, true);
- trySwapImages("bottom-right2.gif", BOTTOM_LEFT2, true);
- trySwapImages("bottom-right3.gif", BOTTOM_LEFT3, true);
- const images = document.querySelectorAll(
- `img[src*="${BOTTOM_LEFT3}"], img[src*="${BOTTOM_LEFT2}"]`
- );
- images.forEach(image => {
- if (image.style.transform.includes('scaleX(-1)')) {
- image.style.transform = 'scaleX(-1) translateX(1px)';
- }
- });
- trySwapImages("nav_endleft.gif", NAV_ENDLEFT);
- trySwapImages("nav_endright.gif", NAV_ENDRIGHT);
- swapBackgroundImgs("postbit-back2.gif", POSTBIT_BACK2, 'table');
- trySwapImages("collapse_tcat.gif", COLLAPSE_TCAT);
- trySwapImages("collapse_tcat_collapsed.gif", BOTTOM_LEFT1); //todo fix - doesn't work
- trySwapImages("collapse_thead.gif", COLLAPSE_THEAD);
- trySwapImages("reply.gif", REPLYBTN);
- trySwapImages("threadclosed.gif", THREADCLOSED);
- trySwapImages("newthread.gif", NEWTOPIC);
- return true;
- }
- function removeHeaderRows() {
- const headerLogo = document.querySelector(
- 'img[src*="native_05.jpg"]'
- );
- if (!headerLogo) return false;
- const tbody = headerLogo.closest('tbody');
- if (!tbody) return false;
- const rows = Array.from(tbody.querySelectorAll('tr'));
- if (rows.length < 4) return false;
- const logoRowIndex = rows.findIndex(row =>
- row.contains(headerLogo)
- );
- if (logoRowIndex === -1) return false;
- // Remove the row ABOVE the logo row
- if (rows[logoRowIndex - 1]) {
- rows[logoRowIndex - 1].remove();
- }
- // Remove the row BELOW the logo row
- if (rows[logoRowIndex + 1]) {
- rows[logoRowIndex + 1].remove();
- }
- console.log('[OW Theme] Header rows removed');
- return true;
- }
- // Expand page container width
- function expandPageWidth() {
- const pageDiv = document.querySelector('div.page');
- if (!pageDiv) return false;
- pageDiv.style.width = '870px';
- console.log('[OW Theme] Page width set to 870px');
- return true;
- }
- function expandCenteredTable() {
- // Target the table by width + alignment (unique enough)
- const table = document.querySelector('table[width="796"][align="center"]');
- if (!table) return false;
- // Set new width
- table.style.width = '870px';
- // Optional: adjust left/right border if you want (keep black)
- table.style.borderLeft = '1px solid #000000';
- table.style.borderRight = '1px solid #000000';
- console.log('[OW Theme] Centered table width set to 870px');
- return true;
- }
- function expandPostsTable() {
- // Select all divs with class "page"
- const tables = document.querySelectorAll('div.page');
- tables.forEach(table => {
- // Only target divs with inline width of 794px
- if (table.style.width === '794px') {
- // Set new width
- table.style.width = '870px';
- // Optional: adjust left/right border if you want (keep black)
- table.style.borderLeft = '1px solid #000000';
- table.style.borderRight = '1px solid #000000';
- console.log('[OW Theme] Centered table width set to 870px for one div.page');
- }
- });
- }
- function recolorTables() {
- return true;
- // All tables in the page
- document.querySelectorAll('table, td, tr').forEach(el => {
- if (el.hasAttribute('background') || el.style.background || el.style.backgroundImage) {
- el.style.background = '#6a5931';
- el.style.backgroundImage = 'none';
- }
- });
- console.log('[OW Theme] Table colors updated');
- }
- function addStyle(css) {
- const style = document.createElement('style');
- style.textContent = css;
- document.head.appendChild(style);
- }
- const COLOR_THREAD_LIGHT = "#413920";
- const COLOR_THREAD_DARK = "#3a321b";
- const COLOR_BODY_DARK = "#251e0a";
- function injectThemeCSS() {
- // Only inject if NOT in /archive/
- const hostname = window.location.hostname;
- const path = window.location.pathname;
- if (hostname.includes('owforums.net') && path.startsWith('/rules.php')) {
- addStyle(`
- /* Force page background to be visible */
- body .page,
- page {
- background: `+ COLOR_BODY_DARK + ` !important;;
- }
- body {
- /* Vertical gradient from dark gray at top to black at bottom */
- background: linear-gradient(to bottom, #171309, #000000);
- background-color: #FFFFFF;
- /* Ensure gradient covers full height */
- min-height: 100vh;
- }
- body > div {
- background-color: `+ COLOR_BODY_DARK + `!important; /* replace white with dark background */
- }
- `);
- } else if (!(hostname.includes('owforums.net') && (path.startsWith('/archive/') || path.startsWith('/printthread')))) {
- addStyle(`
- /* Force page background to be visible */
- body .page,
- page {
- background: `+ COLOR_BODY_DARK + `;
- }
- /* alt1 */
- td.alt1,
- td.alt1Active {
- background-color: ` + COLOR_THREAD_DARK + ` !important;
- color: #B7B7B7 !important;
- }
- /* alt2 */
- td.alt2,
- td.alt2Active {
- background-color: ` + COLOR_THREAD_LIGHT + ` !important;
- color: #B7B7B7 !important;
- }
- .tcat
- {
- background: #3E453C url(` + GRADIENT_TCAT + `);
- background-size: auto 100%;
- }
- .vbmenu_control
- {
- background: #2F362D url(` + GRADIENT_TCAT + `);
- background-size: auto 100%;
- }
- .vbmenu_option
- {
- background: #31382F url(` + POSTBIT_BACKDARK + `);
- }
- .vbmenu_hilite
- {
- background: #31382F url(` + COLOR_THREAD_LIGHT + `);
- }
- .thead
- {
- background: none !important; /* removes the image */
- background-color: ` + COLOR_THREAD_DARK + ` !important;
- }
- .tfoot
- {
- background: #31382F url(` + POSTBIT_BACKDARK + `);
- }
- .div.tborder
- {
- background: #31382F url(` + POSTBIT_BACKDARK + `);
- }
- body {
- /* Vertical gradient from dark gray at top to black at bottom */
- background: linear-gradient(to bottom, #171309, #000000);
- /* Ensure gradient covers full height */
- min-height: 100vh;
- }
- .info2 {
- background-color: ` + COLOR_THREAD_DARK + `;
- } /* user info top right of their post */
- .alt1, .alt1Active
- {
- background: ` + COLOR_THREAD_LIGHT + `;
- }
- .alt2, .alt2Active
- {
- background: ` + COLOR_THREAD_DARK + `;
- }
- #content_container {
- background: `+ COLOR_BODY_DARK + `;
- }
- #sidebar_container {
- background: `+ COLOR_BODY_DARK + `;
- }
- #content {
- background: `+ COLOR_BODY_DARK + `;
- }
- .panelsurround {
- background: `+ COLOR_THREAD_DARK + `;
- }
- .panel {
- background: `+ COLOR_THREAD_LIGHT + `;
- }
- .wysiwyg {
- background: `+ COLOR_THREAD_LIGHT + `; !important
- };
- #wysiwyg {
- background: `+ COLOR_THREAD_LIGHT + `; !important
- };
- `);
- }
- }
- function stripInlineBackgrounds() {
- document.querySelectorAll('[style]').forEach(el => {
- if (el.style.backgroundColor || el.style.background) {
- el.style.backgroundColor = '';
- el.style.background = '';
- }
- });
- console.log('[OW Theme] Inline backgrounds stripped');
- }
- function fillMissingInputs() {
- const usernameInput = document.querySelector('#navbar_username');
- if (usernameInput && usernameInput.value.trim() === '') {
- usernameInput.placeholder = 'Login';
- }
- const passwordInput = document.querySelector('#navbar_password');
- if (passwordInput && passwordInput.value.trim() === '') {
- passwordInput.placeholder = 'Password';
- }
- // "Remember Me" checkbox label
- const checkboxLabel = document.querySelector('label[for="cb_cookieuser_navbar"]');
- if (checkboxLabel && !checkboxLabel.querySelector('span')) {
- // Add a span inside the label so checkbox remains intact
- const span = document.createElement('span');
- span.textContent = ' Remember Me?';
- span.style.marginRight = '4px';
- checkboxLabel.appendChild(span);
- }
- // Login button
- const loginButton = document.querySelector('input[type="submit"].button');
- if (loginButton && !loginButton.value.trim()) {
- loginButton.value = 'Login';
- loginButton.title = 'Click to login';
- }
- }
- function fillForumLegendTexts() {
- // Map of image filenames → legend text
- const legendMap = {
- 'forum_new.gif': 'New Posts (Not a big chance you\'ll see this one in the wild anymore)',
- 'forum_old.gif': 'No New Posts',
- 'forum_old_lock.gif': 'Locked Forum'
- };
- // Loop over all relevant table cells
- document.querySelectorAll('tr td img[src*="statusicon/native/forum_"]').forEach(img => {
- const filename = img.src.split('/').pop(); // get last part of src
- const text = legendMap[filename];
- if (text) {
- // The next <td> cell is where the text should go
- const textTd = img.closest('tr').querySelector('td.smallfont:nth-child(2)');
- if (textTd && (!textTd.textContent || textTd.textContent.trim() === '')) {
- textTd.textContent = text;
- textTd.style.paddingLeft = '5px';
- console.log(`[OW Theme] Added legend text: "${text}"`);
- }
- }
- });
- }
- function fillForumHeaders() {
- // The default header texts
- const headers = ['', 'Forum', 'Last Post', 'Threads', 'Posts'];
- // Find all tbody elements whose id starts with collapseobj_forumbit_
- document.querySelectorAll('tbody[id^="collapseobj_forumbit_"]').forEach(tbody => {
- const headerRow = tbody.querySelector('tr:first-child');
- if (headerRow) {
- const tds = headerRow.querySelectorAll('td.thead');
- tds.forEach((td, index) => {
- if (td && (!td.textContent || td.textContent.trim() === '')) {
- td.textContent = headers[index] || '';
- }
- });
- console.log('[OW Theme] Forum header restored for', tbody.id);
- }
- });
- }
- function fillWelcomeText() {
- const tables = document.querySelectorAll('table.tborder');
- for (const table of tables) {
- const rows = table.querySelectorAll('tr');
- if (rows.length < 2) continue;
- const firstTd = rows[0].querySelector('td.tcat');
- const secondTd = rows[1].querySelector('td.alt1');
- if (
- firstTd && secondTd &&
- (!firstTd.textContent.trim() || firstTd.textContent.trim() === '\u00a0') &&
- (!secondTd.textContent.trim() || secondTd.textContent.trim() === '\u00a0')
- ) {
- // Fill tcat header
- firstTd.textContent = 'This is Necrum. The mostly read-only version of Oddworld Forums.';
- console.log('[OW Theme] Added tcat welcome text');
- // Fill alt1 info row
- secondTd.innerHTML = `
- If this is your first visit, be sure to check out the
- <a href="faq.php"><strong>FAQ</strong></a> by clicking the link above.
- You may have to <a href="register.php"><strong>register</strong></a> before you can post.
- To start viewing messages, select the forum that you want to visit from the selection below.
- <div style="height: 1em;"></div> <!-- empty line -->
- If you have any issues with the new theme, or maybe requests for it, reach me at <a href="https://discord.gg/GwK5EQAX"><strong>OWF Discord</strong></a> (Varrok). No promises though.
- `;
- console.log('[OW Theme] Added alt1 welcome info text');
- break; // stop after first match
- }
- }
- }
- function fillWhatsGoingOn() {
- // 1. Fill the main tcat header
- const tcat = document.querySelector('thead tr td.tcat[colspan="2"]');
- if (tcat && (!tcat.textContent.trim() || tcat.textContent.trim() === '\u00a0')) {
- tcat.textContent = "What's Going On?";
- console.log('[OW Theme] Added tcat: What\'s Going On?');
- }
- const td = document.querySelector('tbody tr td.thead[colspan="2"] a[href*="online.php"]');
- if (!td) return;
- // Add label to the <a>
- td.textContent = "Currently Active Users (likely all non human agents)";
- // Find the next text node after the <a>
- const textNode = td.nextSibling;
- if (textNode && textNode.nodeType === Node.TEXT_NODE) {
- // Extract the number from the text (ignore brackets)
- const match = textNode.textContent.match(/[\d,]+/);
- if (match) {
- textNode.textContent = ': ' + match[0]; // keep only the number
- } else {
- textNode.textContent = ''; // fallback
- }
- }
- console.log('[OW Theme] Fixed Currently Active Users label and number');
- // 3. Fill Stats header
- const statsHeader = document.querySelectorAll('tbody tr td.thead[colspan="2"]')[1];
- if (statsHeader && (!statsHeader.textContent.trim() || statsHeader.textContent.trim() === '\u00a0')) {
- statsHeader.textContent = "Oddworld Forums Statistics";
- console.log('[OW Theme] Added Stats header');
- }
- }
- function labelForumStats() {
- const statsDiv = document.querySelector('#collapseobj_forumhome_stats .smallfont div:first-child');
- if (!statsDiv || statsDiv.dataset.labeled) return;
- // Match numbers that may have commas
- const numbers = statsDiv.textContent.match(/[\d,]+/g);
- if (!numbers) return;
- // Build labeled text dynamically
- const labels = ['Threads', 'Posts', 'Members', 'Active Members'];
- const labeledText = numbers.map((num, i) => labels[i] + ': ' + num).join(' ');
- statsDiv.textContent = labeledText;
- statsDiv.dataset.labeled = '1';
- console.log('[OW Theme] Forum stats labeled correctly');
- }
- function labelStatsAndUsers() {
- // 1. Most users ever online
- const onlineDiv = document.querySelector('#collapseobj_forumhome_activeusers .smallfont div:first-child');
- if (onlineDiv && !onlineDiv.dataset.labeled) {
- onlineDiv.textContent = 'Most users ever online was 4,307, 10-18-2015 at 08:35 PM (from archive.org as of 2020)' + onlineDiv.textContent.trim();
- onlineDiv.dataset.labeled = '1'; // mark as done
- console.log('[OW Theme] Labeled Most users ever online');
- }
- // 2. Active users list
- const activeUsersDiv = document.querySelector('#collapseobj_forumhome_activeusers .smallfont div:nth-child(2)');
- if (activeUsersDiv && !activeUsersDiv.dataset.labeled) {
- activeUsersDiv.innerHTML = 'Currently online: ' + activeUsersDiv.innerHTML.trim();
- activeUsersDiv.dataset.labeled = '1';
- console.log('[OW Theme] Labeled currently online members');
- }
- labelForumStats();
- // 4. Newest member
- const newestMemberDiv = document.querySelector('#collapseobj_forumhome_stats .smallfont div:nth-child(2)');
- if (newestMemberDiv && !newestMemberDiv.dataset.labeled) {
- newestMemberDiv.innerHTML = 'Welcome to our newest member: <i>We can\'t have any new members anymore.</i>' + newestMemberDiv.innerHTML.trim();
- newestMemberDiv.dataset.labeled = '1';
- console.log('[OW Theme] Labeled newest member');
- }
- }
- function fillUserCpMenuLabels() {
- const menu = document.getElementById('usercpoptions_menu');
- if (!menu) return;
- const labelMap = {
- 'usercp.php': 'User Control Panel',
- 'private.php': 'Private Messages',
- 'subscription.php': 'Subscriptions',
- 'profile.php?do=editsignature': 'Edit Signature',
- 'profile.php?do=editprofile': 'Edit Profile',
- 'profile.php?do=editoptions': 'Edit Options',
- '#': 'Log Out',
- 'member.php?u=5866': 'View Profile'
- };
- menu.querySelectorAll('a').forEach(a => {
- const href = a.getAttribute('href');
- if (href && labelMap[href]) {
- a.textContent = labelMap[href];
- }
- });
- console.log('[OW Theme] User CP menu labels filled');
- }
- function showPage() {
- // Remove the pre-theme style
- const preStyle = document.getElementById('pre-theme-style');
- if (preStyle) preStyle.remove();
- // Make the page visible
- document.documentElement.style.visibility = 'visible';
- }
- function fillUserCpPanelLabels() {
- // --- 1. Panel Headers ---
- const headerMap = {
- 'pmpanel': 'Private Messages',
- 'subscribepanel': 'Subscriptions',
- 'settingspanel': 'Settings & Options',
- 'miscpanel': 'Miscellaneous'
- };
- Object.entries(headerMap).forEach(([id, text]) => {
- const td = document.querySelector(`#collapseimg_${id}`)?.closest('td');
- if (td && (!td.textContent.trim() || td.textContent.trim() === '\u00a0')) {
- td.appendChild(document.createTextNode(text));
- }
- });
- const labelMap = {
- // Private Messages panel
- 'private.php?folderid=0': 'Inbox',
- 'private.php?folderid=-1': 'Sent Items',
- 'private.php?do=newpm': 'Compose Message',
- 'private.php?do=trackpm': 'Track Messages',
- 'private.php?do=editfolders': 'Edit Folders',
- // Subscriptions panel
- 'subscription.php?do=viewsubscription': 'Your Subscriptions',
- 'subscription.php?do=viewsubscription&daysprune=-1&folderid=all': 'View All',
- 'subscription.php?do=editfolders': 'Edit Subscription Folders',
- // Settings & Options panel
- 'profile.php?do=editsignature': 'Edit Signature',
- 'profile.php?do=editpassword': 'Edit Email & Password',
- 'profile.php?do=editprofile': 'Edit Profile',
- 'profile.php?do=editoptions': 'Edit Options',
- 'profile.php?do=editavatar': 'Edit Avatar',
- 'profile.php?do=editprofilepic': 'Edit Profile Picture',
- // Misc panel
- 'calendar.php?do=viewreminder': 'Calendar Reminders',
- 'profile.php?do=editlist': 'Edit Friend & Ignore List',
- 'profile.php?do=editattachments': 'Edit Attachments'
- };
- document.querySelectorAll('#usercpoptions_menu a, tbody a').forEach(a => {
- const href = a.getAttribute('href');
- if (href && labelMap[href] && !a.textContent.trim()) {
- a.textContent = labelMap[href];
- }
- });
- console.log('[OW Theme] Filled User CP panel labels');
- }
- function fillSubscriptionsPanel() {
- // 1. Fill the header tcat
- const tcat = document.querySelector('#collapseimg_subslistinfo')?.closest('td.tcat');
- if (tcat) {
- // Only fill if text is empty
- if (!tcat.querySelector('span.normal')?.textContent.trim()) {
- let span = tcat.querySelector('span.normal');
- if (!span) {
- span = document.createElement('span');
- span.className = 'normal';
- tcat.appendChild(span);
- }
- span.textContent = "Subscription Info:";
- }
- }
- // 2. Fill legend
- const legend = document.querySelector('#collapseobj_subslistinfo legend');
- if (legend && !legend.textContent.trim()) {
- legend.textContent = "Subscription List";
- }
- // 3. Fill select option text
- document.querySelectorAll('#collapseobj_subslistinfo select option').forEach(option => {
- if (!option.textContent.trim()) {
- // Keep the number but add label
- const num = option.textContent.replace(/[^\d]/g, '') || '0';
- option.textContent = `Inbox (${num})`;
- }
- });
- // 4. Fill submit button
- const submitBtn = document.querySelector('#collapseobj_subslistinfo input[type="submit"]');
- if (submitBtn && !submitBtn.value.trim()) {
- submitBtn.value = "Go";
- }
- console.log('[OW Theme] Filled Subscription List panel labels');
- }
- function fillPrivateMessagesPanel() {
- // 1. Fill legend
- const legend = document.querySelector('#collapseobj_pmlistinfo legend');
- if (legend && !legend.textContent.trim()) {
- legend.textContent = "Folder Info";
- }
- // 2. Fill folder name in <strong>
- const folderStrong = document.querySelector('#collapseobj_pmlistinfo td > strong');
- if (folderStrong && !folderStrong.textContent.trim()) {
- folderStrong.textContent = "Inbox"; // Default folder name
- }
- // 3. Update select options
- document.querySelectorAll('#collapseobj_pmlistinfo select option').forEach(option => {
- if (!option.textContent.trim()) {
- const num = option.textContent.replace(/[^\d]/g, '') || '0';
- const folderName = option.value === "-1" ? "Inbox" : "Other Folder";
- option.textContent = `${folderName} (${num})`;
- }
- });
- // 4. Fill submit button
- const submitBtn = document.querySelector('#collapseobj_pmlistinfo input[type="submit"]');
- if (submitBtn && !submitBtn.value.trim()) {
- submitBtn.value = "Go";
- }
- console.log('[OW Theme] Filled Private Messages panel labels');
- }
- function fillSearchPanel() {
- const table = document.querySelector('table[cellpadding="4"][cellspacing="1"]');
- if (!table) return;
- // 1. Fill empty thead cells with titles
- table.querySelectorAll('td.thead').forEach((thead, idx) => {
- if (!thead.textContent.trim()) {
- thead.textContent = idx === 0 ? "Quick Search" : "Search by Title";
- }
- });
- // 2. Fill search form submit buttons
- const submitButtons = table.querySelectorAll('input[type="submit"]');
- submitButtons.forEach(btn => {
- if (!btn.value.trim()) {
- btn.value = "Search";
- }
- });
- // 3. Fill empty <a> links with text
- table.querySelectorAll('a').forEach(a => {
- if (!a.textContent.trim()) {
- a.textContent = a.href.includes("blog_search.php") ? "Search Blogs" : "Advanced Search";
- }
- });
- // 4. Fill radio label descriptions
- const radios = [
- { id: "rb_nb_sp0", label: "Show All Posts" },
- { id: "rb_nb_sp1", label: "Show Only Your Posts" }
- ];
- radios.forEach(r => {
- const input = document.getElementById(r.id);
- if (input) {
- const label = table.querySelector(`label[for="${r.id}"]`);
- if (label && !label.textContent.trim()) {
- label.insertAdjacentText('beforeend', r.label);
- }
- }
- });
- console.log('[OW Theme] Filled Search Panel labels');
- }
- function cleanSearchPanel() { //not working buttons
- const table = document.querySelector('table[cellpadding="4"][cellspacing="1"]');
- if (!table) return;
- const tbody = table.querySelector('tbody');
- if (!tbody) return;
- // Collect rows with visible links
- const rowsToKeep = Array.from(tbody.querySelectorAll('tr')).filter(tr => {
- const link = tr.querySelector('a');
- return link && link.textContent.trim() !== '';
- });
- // Clear the tbody
- tbody.innerHTML = '';
- // Re-add only the rows we want
- rowsToKeep.forEach(tr => tbody.appendChild(tr));
- // Optional: add small padding to links
- tbody.querySelectorAll('a').forEach(a => {
- a.style.display = 'block'; // ensure vertical stacking
- a.style.paddingLeft = '5px'; // subtle left padding
- a.style.paddingRight = '5px'; // subtle left padding
- });
- console.log('[OW Theme] Search panel cleaned and fixed');
- }
- function fixFooterLinks() {
- // Find all links in the footer area (smallfont strong inside a td)
- const links = document.querySelectorAll('td div.smallfont strong a');
- links.forEach(link => {
- if (!link.textContent.trim()) {
- const href = link.getAttribute('href') || '';
- if (href.includes('sendmessage.php')) { //todo button does nothing
- link.textContent = 'Send Message';
- } else if (href.includes('archive/index.php')) {
- link.textContent = 'Archive';
- } else if (href === '#top') {
- link.textContent = 'Top';
- }
- }
- });
- console.log('[OW Theme] Footer links fixed');
- }
- function replaceOldDomainLinks() {
- const links = document.querySelectorAll('a[href*="oddworldforums.net"]');
- links.forEach(link => {
- const oldHref = link.getAttribute('href');
- const newHref = oldHref.replace('oddworldforums.net', 'owforums.net');
- link.setAttribute('href', newHref);
- });
- console.log('[OW Theme] All oddworldforums.net links replaced with owforums.net');
- }
- function fixPostUserInfoLabels() {
- // Select all post user info blocks
- const infoBlocks = document.querySelectorAll('td.alt2 div.info2');
- infoBlocks.forEach(block => {
- const divs = block.querySelectorAll('div');
- // Safety check: divs.length should be at least 3 for Joined, Location, Posts
- if (divs.length >= 3) {
- // Add labels before existing ": ..."
- if (divs[0].textContent.trim().startsWith(':')) {
- divs[0].textContent = 'Joined: ' + divs[0].textContent.replace(':', '').trim();
- }
- if (divs[1].textContent.trim().startsWith(':')) {
- divs[1].textContent = 'Location: ' + divs[1].textContent.replace(':', '').trim();
- }
- if (divs[2].textContent.trim().startsWith(':')) {
- divs[2].textContent = 'Posts: ' + divs[2].textContent.replace(':', '').trim();
- }
- }
- });
- console.log('[OW Theme] Added labels to post user info');
- }
- async function labelQuotedPostsWithAuthor() {
- const quoteSections = Array.from(
- document.querySelectorAll('div.smallfont + table')
- ).filter(table => {
- const label = table.previousElementSibling;
- return (
- label &&
- label.classList.contains('smallfont') &&
- label.textContent.trim() === ':' &&
- table.querySelector('td.alt2') &&
- table.querySelector('a[href*="showthread.php?p="]')
- );
- });
- // Cache to avoid fetching the same post multiple times
- const fetchedPosts = {};
- const promises = quoteSections.map(async (section) => {
- const link = section.querySelector('a[href*="showthread.php?p="]');
- let username = '';
- if (link) {
- const postUrl = link.href;
- // Skip fetch if we already fetched this post
- if (fetchedPosts[postUrl]) {
- username = fetchedPosts[postUrl];
- } else {
- try {
- const response = await fetch(postUrl, { credentials: 'include' });
- if (response.ok) {
- const htmlText = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(htmlText, 'text/html');
- const postIdMatch = postUrl.match(/p=(\d+)/);
- if (postIdMatch) {
- const postId = postIdMatch[1];
- const postDiv = doc.querySelector(`#post${postId}`);
- if (postDiv) {
- const usernameLink = postDiv.querySelector('.bigusername, .normalusername');
- username = usernameLink ? usernameLink.textContent.trim() : '';
- }
- }
- }
- fetchedPosts[postUrl] = username; // Cache the result
- } catch (err) {
- console.error('Error fetching quote post:', err);
- }
- }
- }
- const labelText = username ? `Quote (from ${username}):` : 'Quote:';
- const labelDiv = section.previousElementSibling;
- if (labelDiv && labelDiv.classList.contains('smallfont')) {
- labelDiv.textContent = labelText;
- } else {
- const newLabelDiv = document.createElement('div');
- newLabelDiv.className = 'smallfont';
- newLabelDiv.style.marginBottom = '2px';
- newLabelDiv.textContent = labelText;
- section.parentNode.insertBefore(newLabelDiv, section);
- }
- });
- await Promise.allSettled(promises); // ensures all promises run without stopping on error
- console.log('[OW Theme] Labeled all quote sections in parallel.');
- }
- function disableSendMessage() {
- const links = Array.from(
- document.querySelectorAll('a[href="sendmessage.php"]')
- );
- if (links.length === 0) return;
- // Last occurrence
- const last = links[links.length - 1];
- last.removeAttribute('href');
- last.removeAttribute('rel');
- last.removeAttribute('accesskey');
- last.textContent = '🧸';
- last.style.fontSize = '20px';
- last.title = "I'm watching you...";
- // Previous occurrence (if it exists)
- if (links.length > 1) {
- const prev = links[links.length - 2];
- prev.removeAttribute('href');
- prev.removeAttribute('rel');
- prev.removeAttribute('accesskey');
- prev.textContent = 'inform the administrator (disabled)'; //doesn't work
- prev.title = 'Messaging disabled';
- prev.style.whiteSpace = 'pre-line'; // Only applies to this link
- }
- console.log('[OW Theme] Modified last and previous Send Message links');
- }
- function replaceThemeOptions() {
- // Find the select containing the optgroup
- const select = document.querySelector('select[name="styleid"]');
- if (!select) return;
- // Define the custom captions you want
- const newCaptions = {
- "18": "Necrum",
- "13": "---- Industrial (Disabled)",
- "1": "---- vBulletin 3 (Disabled)",
- "11": "---- Munch's Oddysee (Disabled)",
- "12": "---- HoneyBee Theme (Disabled)"
- };
- // Move all options out of any optgroup
- select.querySelectorAll('optgroup').forEach(group => {
- const parent = group.parentNode;
- group.querySelectorAll('option').forEach(option => parent.appendChild(option));
- group.remove(); // remove the empty optgroup
- });
- // Replace text for each option
- select.querySelectorAll('option').forEach(option => {
- if (newCaptions[option.value]) {
- option.textContent = newCaptions[option.value];
- }
- // Enable only Native (value 18), disable the rest
- if (option.value !== "18") {
- option.disabled = true;
- } else {
- option.selected = true; // make sure Native is selected
- }
- });
- console.log('[OW Theme] Updated select option captions');
- }
- function labelSidebarButtons() {
- const buttonMap = {
- 'search.php?do=getdaily': 'Daily Posts',
- 'forumdisplay.php?do=markread': 'Mark Forums As Read',
- 'online.php': "Who's Online"
- };
- const links = document.querySelectorAll('.vbmenu_option a[href]');
- links.forEach(link => {
- const href = link.getAttribute('href');
- for (const key in buttonMap) {
- if (href.includes(key)) {
- link.textContent = buttonMap[key];
- // remove cursor: default on parent to make it look clickable
- if (link.parentElement) link.parentElement.style.cursor = 'pointer';
- }
- }
- });
- console.log('[OW Theme] Sidebar buttons labeled.');
- }
- function removeMenuLinks() {
- // Remove "New Posts"
- const newPostsTd = document.querySelector('td.vbmenu_control2 a[href*="search.php?do=getnew"]');
- if (newPostsTd) {
- newPostsTd.closest('td.vbmenu_control2').remove();
- }
- // Remove "Quick Links" (usercptools)
- const quickLinksTd = document.querySelector('td#usercptools');
- if (quickLinksTd) {
- quickLinksTd.remove();
- }
- // Remove "Today's posts" (getdaily)
- const getDailyTd = document.querySelector('td.vbmenu_control2 a[href*="search.php?do=getdaily"]');
- if (getDailyTd) {
- getDailyTd.closest('td.vbmenu_control2').remove();
- }
- console.log('[OW Theme] Removed sections.');
- }
- function fixForumNavigationLabels() {
- const links = [
- { hrefContains: 'do=markread', text: 'Mark Forums As Read' },
- { hrefContains: 'showgroups.php', text: 'View Forum Leaders' },
- { hrefContains: 'memberlist.php', text: 'Members List' },
- ];
- links.forEach(linkInfo => {
- const a = document.querySelector(`div.smallfont a[href*="${linkInfo.hrefContains}"]`);
- if (a) {
- if (!a.textContent.trim()) {
- a.textContent = linkInfo.text;
- }
- }
- });
- console.log('[OW Theme] Fixed missing forum nav labels.');
- }
- function fixDropdownLabels() {
- const labelMap = {
- cp: 'Control Panel',
- pm: 'Private Messages',
- subs: 'Subscriptions',
- wol: "Who's Online",
- search: 'Search',
- home: 'Forum Home'
- };
- const select = document.querySelector('select[name="f"]');
- if (!select) return;
- const groups = select.querySelectorAll('optgroup');
- if (groups[0] && (!groups[0].label || !groups[0].label.trim())) {
- groups[0].label = 'User Tools';
- }
- if (groups[1] && (!groups[1].label || !groups[1].label.trim())) {
- groups[1].label = 'Forums';
- }
- // 1. Fill empty option labels
- select.querySelectorAll('option').forEach(option => {
- const value = option.value?.trim();
- const text = option.textContent.trim();
- if (!text && labelMap[value]) {
- option.textContent = labelMap[value];
- }
- });
- // 2. Remove empty optgroup labels (but keep grouping behavior)
- select.querySelectorAll('optgroup').forEach(group => {
- if (!group.label || !group.label.trim()) {
- group.removeAttribute('label');
- }
- });
- console.log('[OW Theme] Fixed forum jump missing labels');
- }
- function fixSelectedMessagesLabels() {
- const select = document.querySelector('select[name="dowhat"]');
- if (!select) return;
- const labelMap = {
- move: 'Move to Folder',
- delete: 'Delete'
- };
- Array.from(select.options).forEach(option => {
- const text = option.textContent.trim();
- // Replace empty OR placeholder labels
- if (
- labelMap[option.value] &&
- (text === '' || text === '...')
- ) {
- option.textContent = labelMap[option.value];
- }
- });
- console.log('[OW Theme] Fixed Selected Messages action labels');
- }
- function fixBlogQuickSearchLabels() {
- const checkboxLabel = document.querySelector('label[for="cb_titleonly"]');
- if (checkboxLabel && checkboxLabel.textContent.trim() === '') {
- checkboxLabel.appendChild(
- document.createTextNode(' Search titles only')
- );
- }
- const button = document.querySelector(
- 'input.button[tabindex="53"]'
- );
- if (!button) return;
- // Fix missing label
- if (!button.value.trim()) {
- button.value = 'Search Blogs';
- }
- // Fix broken float layout
- button.style.float = 'none';
- button.style.display = 'block';
- button.style.marginLeft = 'auto';
- console.log('[OW Theme] Fixed blog quicksearch checkbox label');
- }
- function fixMemberlistHeaderLabels() {
- const headerCells = document.querySelectorAll('tr[align="center"] td.thead');
- headerCells.forEach(td => {
- const link = td.querySelector('a');
- // Only if the <a> exists but has no text
- if (link && !link.textContent.trim()) {
- if (td.innerHTML.includes('sortasc')) {
- link.textContent = 'Username ↑';
- } else if (td.innerHTML.includes('joindate')) {
- link.textContent = 'Join Date';
- } else if (td.innerHTML.includes('posts')) {
- link.textContent = 'Posts';
- } else if (td.innerHTML.includes('reputation')) {
- link.textContent = 'Reputation';
- } else {
- // Fallback for other empty links
- link.textContent = 'Sort';
- }
- }
- });
- console.log('[OW Theme] Fixed missing memberlist header labels.');
- }
- function fixProfilePanelLabels() {
- // Private Message link
- const pmLink = document.querySelector('td.panelsurround a[href*="private.php?do=newpm"]');
- if (pmLink && !pmLink.textContent.trim()) {
- pmLink.textContent = 'Send PM';
- }
- // Forum Info fieldset legend
- document.querySelectorAll('fieldset.fieldset > legend').forEach(legend => {
- if (!legend.textContent.trim()) {
- legend.textContent = 'Statistics';
- }
- });
- // Additional empty user links
- document.querySelectorAll('td.panelsurround a[href*="search.php?do=finduser"]').forEach(link => {
- if (!link.textContent.trim()) {
- // Look for the username in the next table row
- const currentTr = link.closest('tr');
- let username = null;
- if (currentTr && currentTr.nextElementSibling) {
- const nextTrText = currentTr.nextElementSibling.textContent;
- const match = nextTrText.match(/by\s+(.+)/i);
- if (match) username = match[1].trim();
- }
- if (username) {
- link.textContent = `Find all posts made by ${username}`;
- } else {
- link.textContent = 'Find all posts made by the user';
- }
- }
- });
- console.log('[OW Theme] Filled missing profile panel labels.');
- }
- function removeForumRulesSection() {
- const forumRules = document.getElementById('collapseobj_forumrules');
- if (forumRules) {
- const table = forumRules.closest('table'); // get the parent table
- if (table) {
- table.remove();
- console.log('[OW Theme] Removed forum rules section bottom left (empty).');
- }
- }
- }
- function fixNavigationButton() {
- const form = document.querySelector('form[action="forumdisplay.php"]');
- if (!form) return;
- const select = form.querySelector('select[name="f"]');
- const button = form.querySelector('input[type="submit"]');
- if (!select || !button) return;
- // Wrap the select and button in a flex container
- const wrapper = document.createElement('div');
- wrapper.style.display = 'flex';
- wrapper.style.alignItems = 'center';
- wrapper.style.gap = '4px';
- select.parentNode.insertBefore(wrapper, select);
- wrapper.appendChild(select);
- wrapper.appendChild(button);
- // Set button label and style
- button.value = 'Go';
- button.style.padding = '1px 10px';
- button.style.fontSize = '1em';
- }
- function removeEmptyForumViewers() {
- document.querySelectorAll('td.alt1Active').forEach(td => {
- // Look for the forum link
- const forumLink = td.querySelector('a[href^="forumdisplay.php?f="]');
- if (!forumLink) return;
- // Check the next sibling span for empty brackets
- const nextSpan = forumLink.parentElement.querySelector('span.smallfont');
- if (nextSpan && nextSpan.textContent.trim() === '()') {
- nextSpan.remove(); // remove only the empty brackets
- }
- });
- }
- function fixCalendarForms() {
- // Map for month numbers → names
- const monthNames = [
- '', 'January', 'February', 'March', 'April', 'May', 'June',
- 'July', 'August', 'September', 'October', 'November', 'December'
- ];
- // Fix first form: Jump to month
- const jumpForm = document.querySelector('form[action="calendar.php"]:first-of-type');
- if (jumpForm) {
- // Add month names
- jumpForm.querySelectorAll('select[name="month"] option').forEach((opt, idx) => {
- if (monthNames[idx]) opt.textContent = monthNames[idx];
- });
- // Add label for year dropdown
- jumpForm.querySelector('strong').textContent = 'Jump to Month';
- // Add submit button label
- const submitBtn = jumpForm.querySelector('input[type="submit"]');
- if (submitBtn) submitBtn.value = 'Go';
- // Align button with dropdown
- submitBtn.style.marginLeft = '6px';
- submitBtn.style.verticalAlign = 'top';
- }
- // Fix second form: Calendar Jump
- const calForm = document.querySelectorAll('form[action="calendar.php"]')[1];
- if (calForm) {
- const optGroup = calForm.querySelector('optgroup');
- if (optGroup) optGroup.label = 'Calendar Type';
- // Add submit button label
- const submitBtn = calForm.querySelector('input[type="submit"]');
- if (submitBtn) submitBtn.value = 'Jump';
- // Align button
- submitBtn.style.marginLeft = '6px';
- submitBtn.style.verticalAlign = 'top';
- }
- }
- function fixShowPostsLabels() {
- const labels = [
- { id: 'rb_showposts_0', text: 'Show Threads' },
- { id: 'rb_showposts_1', text: 'Show Posts' }
- ];
- labels.forEach(l => {
- const label = document.querySelector(`label[for="${l.id}"]`);
- if (label) {
- // remove any existing text nodes
- Array.from(label.childNodes)
- .filter(n => n.nodeType === Node.TEXT_NODE)
- .forEach(n => n.remove());
- // append a new text node so input stays intact
- label.appendChild(document.createTextNode(' ' + l.text));
- }
- });
- }
- function fixResetSearchBtn() {
- // Select the reset button
- const resetBtn = document.querySelector('input[type="reset"][onclick*="search.php?type=advanced"]');
- if (resetBtn) {
- resetBtn.value = "Reset"; // Add a caption
- resetBtn.style.position = "relative"; // Allow offset
- resetBtn.style.top = "0px"; // Nudge it 1px up
- }
- }
- function fixEmptyOption() {
- const selects = document.querySelectorAll('select');
- selects.forEach(select => {
- const options = select.options;
- if (options.length >= 2) {
- const secondOption = options[1];
- if (secondOption.value === "1" && secondOption.text.trim() === "Find Threads Started by User") {
- const firstOption = options[0];
- if (firstOption.value === "0" && firstOption.text.trim() === "") {
- firstOption.textContent = "Find Posts Made By User";
- console.log("First option label fixed!");
- }
- }
- }
- });
- const divs = document.querySelectorAll('div');
- divs.forEach(div => {
- if (div.textContent.trim() === ":") {
- div.textContent = "Username:";
- console.log("Username label fixed!");
- }
- });
- }
- function fixProfileDropdownList() {
- // Loop through all menu links
- const menuLinks = document.querySelectorAll('td.vbmenu_option a');
- menuLinks.forEach(a => {
- if (!a.textContent.trim()) {
- const href = a.getAttribute('href');
- let label = "No Label";
- if (href.includes('do=newpm')) label = "Send Private Message";
- else if (href.includes('do=finduser')) label = "Find User Posts";
- else if (href.includes('addlist&userlist=buddy')) label = "Add to Buddy List";
- a.textContent = label;
- console.log(`Added label "${label}" to ${href}`);
- }
- });
- }
- function fixAddBuddyBtnLabels() {
- // Confirm button
- const confirmBtn = document.querySelector('input[type="submit"][name="confirm"]');
- if (confirmBtn && !confirmBtn.value.trim()) {
- confirmBtn.value = "Confirm";
- console.log("Set Confirm button label");
- }
- // Deny button
- const denyBtn = document.querySelector('input[type="submit"][name="deny"]');
- if (denyBtn && !denyBtn.value.trim()) {
- denyBtn.value = "Deny";
- console.log("Set Deny button label");
- }
- }
- function fixBlogSideBarProfile() {
- const blogUserMenu = document.getElementById('blogusermenu');
- if (!blogUserMenu) return;
- // Find the info block directly under the same tborder
- const infoList = blogUserMenu
- .closest('.tborder')
- .querySelector('.alt1 .smallfont ul.nobullets');
- if (!infoList) return;
- const labels = [
- 'Date Joined',
- 'Location',
- 'Posts',
- 'Blog Entries'
- ];
- const items = infoList.querySelectorAll('li');
- items.forEach((li, index) => {
- if (!labels[index]) return;
- const shade = li.querySelector('.shade');
- if (!shade) return;
- let text = shade.textContent.trim();
- // Insert missing label
- if (!text) {
- text = labels[index];
- }
- // Ensure colon
- if (!text.endsWith(':')) {
- text += ':';
- }
- shade.textContent = text;
- });
- console.log('[OW Theme] Blog sidebar labels fixed');
- }
- function fixBlogNavigationBtn() {
- const filterForm = document.querySelector('form[action*="blog.php"][action*="do=list"]');
- if (!filterForm) return;
- const submitBtn = filterForm.querySelector('input[type="submit"]');
- if (!submitBtn) return;
- // Get rid of a pointless button
- if (submitBtn) {
- submitBtn.remove();
- console.log('[OW Theme] Blog filter submit button removed');
- }
- }
- function timeAgo(fromDate) {
- const seconds = Math.floor((Date.now() - fromDate.getTime()) / 1000);
- const units = [
- ['year', 31536000],
- ['month', 2592000],
- ['day', 86400],
- ];
- for (const [unit, value] of units) {
- const amount = Math.floor(seconds / value);
- if (amount >= 1) {
- return `${amount} ${unit}${amount !== 1 ? 's' : ''} ago`;
- }
- }
- return 'today';
- }
- function fixUserProfileLabels() {
- // Locate the Forum Info panel (left column)
- const forumInfoPanel = document.querySelector(
- 'td.panelsurround .panel div > .fieldset'
- );
- if (!forumInfoPanel) return;
- /* ---- Date Joined ---- */
- const joinedDiv = [...document.querySelectorAll('div')]
- .find(div =>
- div.firstChild?.nodeType === Node.TEXT_NODE &&
- div.firstChild.textContent.trim().startsWith(':') &&
- div.querySelector('strong')
- );
- if (!joinedDiv) return;
- const strong = joinedDiv.querySelector('strong');
- const dateText = strong.textContent.trim();
- const parsedDate = new Date(dateText.replace(/-/g, '/'));
- if (isNaN(parsedDate)) return;
- const ageText = timeAgo(parsedDate);
- /* ---- Fix leading colon ---- */
- // Find the text node that contains the leading colon
- for (const node of joinedDiv.childNodes) {
- if (node.nodeType === Node.TEXT_NODE && node.textContent.includes(':')) {
- // Remove the colon and any leading spaces
- node.textContent = node.textContent.replace(/^\s*:\s*/, '');
- break; // only remove the first colon
- }
- }
- /* ---- Insert label before the date ---- */
- const label = document.createElement('strong');
- label.textContent = 'Date Joined: ';
- joinedDiv.insertBefore(label, strong);
- /* ---- Replace (0 ) only ---- */
- joinedDiv.childNodes.forEach(node => {
- if (
- node.nodeType === Node.TEXT_NODE &&
- /\(\s*0\s*\)/.test(node.textContent)
- ) {
- node.textContent = ` (${ageText})`;
- }
- });
- /* ---- Last Post ---- */
- // Find the Statistics fieldset
- const statsFieldset = Array.from(document.querySelectorAll('fieldset'))
- .find(fs => fs.querySelector('legend')?.textContent.trim() === 'Statistics');
- if (!statsFieldset) return;
- // Find the row that contains the last post link
- const lastPostTd = Array.from(statsFieldset.querySelectorAll('td'))
- .find(td =>
- td.querySelector('a[href*="showthread.php"]') &&
- td.textContent.trim().startsWith(':')
- );
- if (!lastPostTd) return;
- for (const node of lastPostTd.childNodes) {
- if (node.nodeType === Node.TEXT_NODE && node.textContent.includes(':')) {
- // Remove colon and leading/trailing spaces
- node.textContent = node.textContent.replace(/^\s*:\s*/, '');
- break; // Only fix the first colon
- }
- }
- // Add the label
- lastPostTd.insertAdjacentText('afterbegin', 'Last Post: ');
- }
- function fixBlogCalendarHeader() {
- // Locate the blog calendar container
- const calendar = document.getElementById('vb_blogcalendar');
- if (!calendar) return;
- // Find the thead header cell
- const headerTh = calendar.querySelector('thead th.thead');
- if (!headerTh) return;
- // Avoid duplicating the label
- if (headerTh.textContent.trim()) return;
- // Create label text node
- const label = document.createElement('span');
- label.textContent = 'Blog Calendar';
- // Insert label before the collapse button (first child)
- headerTh.insertBefore(label, headerTh.firstChild);
- console.log('[OW Theme] Blog Calendar header fixed');
- }
- function removeAdditionalInformationPanelProfile() {
- // Remove Additional Information header
- const addInfoHeader = [...document.querySelectorAll('td.tcat')]
- .find(td => td.textContent.trim() === "Additional Information");
- if (addInfoHeader) addInfoHeader.remove();
- // Remove the Additional Information panel
- const addInfoPanel = document.querySelector('#additionalinfo_list')?.closest('td.panelsurround');
- if (addInfoPanel) addInfoPanel.remove();
- // Stretch Group Memberships header
- const groupHeader = [...document.querySelectorAll('td.tcat')]
- .find(td => td.textContent.trim() === "Group Memberships");
- if (groupHeader) groupHeader.colSpan = 2;
- // Stretch Group Memberships panel
- const groupPanel = document.querySelector('.panelsurround .smallfont')?.closest('td.panelsurround');
- if (groupPanel) groupPanel.colSpan = 2;
- }
- function fixMissingProfileAvatar() {
- const urlParams = new URLSearchParams(window.location.search);
- const userId = urlParams.get('u');
- if (!userId) return;
- const lastPostTd = [...document.querySelectorAll('td')].find(td =>
- td.textContent.trim().startsWith('Last Post:')
- );
- if (!lastPostTd) return;
- const lastPostLink = lastPostTd.querySelector('a');
- if (!lastPostLink) return;
- // Replace showthread.php with showpost.php to get the single post
- const postUrl = lastPostLink.href.replace('showthread.php', 'showpost.php');
- fetch(postUrl)
- .then(response => response.text())
- .then(htmlText => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(htmlText, 'text/html');
- // Try customavatars first
- let avatarImg = doc.querySelector('img[src*="customavatars/avatar"]');
- // Fallback to avatars/ pattern if no customavatars found
- if (!avatarImg) {
- avatarImg = doc.querySelector('img[src*="avatars/"]');
- }
- if (!avatarImg) return; // no avatar found
- const avatarUrl = avatarImg.src;
- // Panel containing username and stars
- const panel = document.querySelector('.bigusername')?.parentElement;
- if (!panel) return;
- const img = document.createElement('img');
- img.src = avatarUrl;
- img.style.cssText = 'float: left; margin: 10px; margin-top: 4px';
- panel.insertBefore(img, panel.firstChild);
- })
- .catch(err => console.error('Failed to fetch last post:', err));
- }
- function cleanContactInfoPanel() {
- // Find the Contact Info header
- const contactHeader = [...document.querySelectorAll('td.tcat')]
- .find(td => td.textContent.trim() === 'Contact Info');
- if (!contactHeader) return;
- // The Contact Info panel is the next row's second <td>
- const contactPanelTd = contactHeader.closest('tr')?.nextElementSibling?.children[1];
- if (!contactPanelTd) return;
- // Remove any <td> inside the Contact Info panel that contains only a colon
- [...contactPanelTd.querySelectorAll('td')].forEach(td => {
- if (/^\s*:\s*$/.test(td.textContent)) {
- td.remove();
- }
- });
- }
- function fixContactInfoSubforumsLabel() {
- // Select all elements in the document
- const allNodes = [...document.querySelectorAll('div, td, span, p')];
- allNodes.forEach(node => {
- if (!node.innerHTML) return;
- // Match empty <strong></strong> followed by colon and subforum links
- if (/^<strong><\/strong>:\s*(<img[^>]+>\s*<a href="forumdisplay\.php\?f=\d+">[^<]+<\/a>)/i.test(node.innerHTML)) {
- node.innerHTML = node.innerHTML.replace(
- /^<strong><\/strong>:/,
- '<strong>Subforums:</strong>'
- );
- }
- });
- }
- function removeEmptyContactStatistics() {
- // Find all fieldsets with legend "Statistics"
- document.querySelectorAll('fieldset.fieldset').forEach(fieldset => {
- const legend = fieldset.querySelector('legend');
- if (legend && legend.textContent.trim() === "Statistics") {
- // Check if the fieldset is inside a Contact Info panel
- const contactPanel = fieldset.closest('td.panelsurround');
- const headerTd = contactPanel?.closest('tr')?.previousElementSibling?.querySelector('td.tcat:nth-child(2)');
- if (headerTd && headerTd.textContent.trim() === "Contact Info") {
- // Check if all <tr> are empty
- const rows = fieldset.querySelectorAll('tr');
- const hasContent = [...rows].some(tr => tr.textContent.trim() !== '');
- if (!hasContent) {
- fieldset.remove(); // remove the entire empty fieldset
- }
- }
- }
- });
- }
- function replaceLegacyTopPanelOnce() {
- const td = document.querySelector(
- 'td[colspan="3"][background*="gradient_tcat.gif"] img[src*="native_01.gif"]'
- )?.closest('td');
- if (!td) return false;
- // Clear old content
- td.innerHTML = '';
- // Preserve gradient background
- td.style.backgroundImage =
- 'url(' + TOP_GRADIENT + ')';
- td.style.backgroundRepeat = 'repeat-x';
- td.style.padding = '0';
- // Create panel container
- const panel = document.createElement('div');
- panel.style.display = 'flex';
- panel.style.justifyContent = 'space-evenly';
- panel.style.alignItems = 'center';
- panel.style.height = '34px';
- panel.style.width = '100%';
- // Embedded button data
- const buttons = [
- {
- href: 'https://discord.gg/GwK5EQAX',
- img: 'https://i.imgur.com/8bunCWy.png',
- alt: 'OWF Discord',
- hoverImg: 'https://i.imgur.com/c37oT49.png'
- },
- {
- href: 'https://magogonthemarch.wordpress.com/',
- img: 'https://i.imgur.com/hmGyBm4.png',
- alt: 'Magog on the March'
- },
- {
- href: 'http://oddworldlibrary.net/',
- img: 'https://i.imgur.com/NU48IiC.png',
- alt: 'Oddworld Library'
- },
- {
- href: 'https://oddwords.hu/',
- img: 'https://i.imgur.com/Sz035L9.png',
- alt: 'Oddwords'
- },
- {
- href: 'http://www.oddworld.com/',
- img: 'https://i.imgur.com/oR7iGLx.png',
- alt: 'Oddworld'
- }
- ];
- // Build buttons
- buttons.forEach(b => {
- const a = document.createElement('a');
- a.href = b.href;
- const img = document.createElement('img');
- img.src = b.img;
- img.alt = b.alt;
- img.style.display = 'block';
- img.style.position = 'relative';
- img.style.top = '1px';
- // Swap image on hover
- if (b.hoverImg) {
- img.addEventListener('mouseenter', () => {
- img.src = b.hoverImg;
- });
- img.addEventListener('mouseleave', () => {
- img.src = b.img;
- });
- }
- a.appendChild(img);
- panel.appendChild(a);
- });
- td.appendChild(panel);
- return true;
- }
- function replaceLegacyNavbarOnce() {
- // Remove all top/bottom divs around navbar
- document.querySelectorAll('.div-top, .div-bot').forEach(div => div.remove());
- const navImg = document.querySelector('img[src*="nav_calendar.gif"]');
- if (!navImg) return false;
- const navTable = navImg.closest('table');
- if (!navTable) return false;
- // Remove div-top / div-bot that are siblings or in wrapping container
- const container = navTable.parentElement;
- if (container) {
- container.querySelectorAll('.div-top, .div-bot').forEach(div => div.remove());
- }
- [navTable.previousElementSibling, navTable.nextElementSibling].forEach(sib => {
- if (!sib) return;
- if (sib.classList.contains('div-top') || sib.classList.contains('div-bot')) {
- sib.remove();
- }
- });
- const buttonImages = {
- usercp: 'https://i.imgur.com/PJhQLxK.png',
- blogs: 'https://i.imgur.com/DQBrc5e.png',
- faq: 'https://i.imgur.com/xOwVVCe.png',
- rules: 'https://i.imgur.com/uKurJ06.png',
- calendar: 'https://i.imgur.com/6DnnYXM.png',
- search: 'https://i.imgur.com/4YwdFOb.png',
- logout: 'https://i.imgur.com/EFd3JmG.png'
- };
- // Apply gradient to the full table so it stretches full width
- navTable.style.background = `url(${TOP_GRADIENT}) repeat-x`;
- navTable.style.width = '100%';
- // Normalize all td cells and unify class
- navTable.querySelectorAll('td.vbmenu_control, td.vbmenu_control2').forEach(td => {
- td.className = 'vbmenu_control2';
- td.style.background = 'transparent';
- td.style.height = '34px';
- td.querySelectorAll('img[src*="nav_div"]').forEach(i => i.remove());
- });
- // Replace / insert button images & handle flex alignment
- navTable.querySelectorAll('td').forEach(td => {
- const a = td.querySelector('a');
- if (!a) return;
- const href = a.getAttribute('href') || '';
- let key = null;
- if (href.includes('usercp')) key = 'usercp';
- else if (href.includes('blog')) key = 'blogs';
- else if (href.includes('faq')) key = 'faq';
- else if (href.includes('rules')) key = 'rules';
- else if (href.includes('calendar')) key = 'calendar';
- else if (href.includes('search')) key = 'search';
- else if (href.includes('logout')) key = 'logout';
- if (!key || !buttonImages[key]) return;
- // Remove old button images (except arrow)
- a.querySelectorAll('img:not([src*="menu_open"])').forEach(img => img.remove());
- // Remove all text nodes safely
- Array.from(a.childNodes).forEach(node => {
- if (node.nodeType === Node.TEXT_NODE) node.remove();
- });
- // Insert main button image
- const img = document.createElement('img');
- img.src = buttonImages[key];
- img.alt = key;
- img.style.display = 'inline-block';
- img.style.verticalAlign = 'middle';
- img.style.marginTop = '1px';
- a.prepend(img);
- // Move arrow inside <a> without flex
- const arrow = td.querySelector('img[src*="menu_open"]');
- if (arrow) {
- arrow.remove();
- arrow.style.display = 'inline-block';
- arrow.style.verticalAlign = 'middle';
- arrow.style.marginLeft = '-5px';
- a.appendChild(arrow);
- }
- });
- return true;
- }
- function labelThreadSortSubmitButton() {
- const tables = Array.from(document.querySelectorAll('table'));
- const targetTable = tables.find(table =>
- table.querySelector('select[name="sort"]') &&
- table.querySelector('select[name="order"]') &&
- table.querySelector('select[name="daysprune"]')
- );
- if (!targetTable) {
- console.warn('[OW Theme] Sort table not found.');
- return;
- }
- const submitButton = targetTable.querySelector('input[type="submit"].button');
- if (!submitButton) {
- console.warn('[OW Theme] Submit button not found.');
- return;
- }
- // Only label if it's currently blank
- if (!submitButton.value.trim()) {
- submitButton.value = 'Apply';
- }
- console.log('[OW Theme] Labeled thread sort submit button.');
- }
- function fixForumJumpButton() {
- const form = document.querySelector(
- 'form[action="forumdisplay.php"] select[name="f"]'
- )?.closest('form');
- if (!form) {
- console.warn('[OW Theme] Forum jump form not found.');
- return;
- }
- const submitButton = form.querySelector('input[type="submit"].button');
- if (!submitButton) {
- console.warn('[OW Theme] Forum jump submit button not found.');
- return;
- }
- submitButton.value = 'Go';
- submitButton.style.verticalAlign = 'top';
- submitButton.style.marginLeft = '6px';
- // Optional: normalize height to match the select
- const select = form.querySelector('select[name="f"]');
- if (select) {
- submitButton.style.height = `${select.offsetHeight}px`;
- }
- console.log('[OW Theme] Forum jump button fixed.');
- }
- function fixForumToolsTextAlignment() {
- const forumTools = document.getElementById('forumtools');
- const forumSearch = document.getElementById('forumsearch');
- if (!forumTools || !forumSearch) {
- console.warn('[OW Theme] Forum header cells not found.');
- return;
- }
- // Align the link text itself
- const link = forumTools.querySelector('a');
- if (link) {
- link.style.verticalAlign = 'middle';
- link.style.lineHeight = '1';
- }
- // Prevent the icon from dragging the baseline down
- const icon = forumTools.querySelector('img');
- if (icon) {
- icon.style.verticalAlign = 'middle';
- icon.style.marginLeft = '4px';
- }
- console.log('[OW Theme] Forum Tools text alignment nudged up.');
- }
- function fixPageNavPopupLabels() {
- const form = document.getElementById('pagenav_form');
- if (!form) {
- console.warn('[OW Theme] Page navigation form not found.');
- return;
- }
- const input = form.querySelector('#pagenav_itxt');
- const button = form.querySelector('#pagenav_ibtn');
- if (!input || !button) {
- console.warn('[OW Theme] Page navigation controls not found.');
- return;
- }
- // Label the page number input
- if (!input.placeholder) {
- input.placeholder = 'Page';
- }
- input.setAttribute('aria-label', 'Page number');
- // Label the button
- if (!button.value.trim()) {
- button.value = 'Go';
- }
- button.setAttribute('aria-label', 'Go to page');
- console.log('[OW Theme] Page navigation popup labels fixed.');
- }
- function getReputationTier(rep) {
- //Todo to be filled later when I actually obtain the rep messages
- if (rep < 0) return 'Uhhhh this guy is not liked at all!';
- if (rep < 50) return 'Wallflower';
- if (rep < 200) return 'Well Known';
- if (rep < 500) return 'Respected';
- if (rep < 2000) return 'Imma need me an ass the size of a truck to fit this rep';
- if (rep < 10000) return 'is a sacred manifestation of the hippy-dippy spirit of Great Mother OWF';
- return 'is Chuck Norris';
- }
- function fixReputationTooltips() {
- const repImages = document.querySelectorAll(
- 'img.inlineimg[src*="images/reputation/"]'
- );
- repImages.forEach(img => {
- const text = img.title || img.alt;
- if (!text) return;
- // Expected format: "Username (1234)"
- const match = text.match(/^(.*?)\s*\((\-?\d+)\)$/);
- if (!match) return;
- const username = match[1].trim();
- const rep = parseInt(match[2], 10);
- if (Number.isNaN(rep)) return;
- const tier = getReputationTier(rep);
- const newText = `${username} ${tier} (${rep})`;
- img.title = newText;
- img.alt = newText;
- });
- console.log('[OW Theme] Reputation tooltips enhanced.');
- }
- function fixSearchTitleOnlyLabel() {
- const select = document.querySelector(
- 'select[name="titleonly"], select[name="titlesonly"]'
- );
- if (!select) {
- console.warn('[OW Theme] titleonly select not found.');
- return;
- }
- const option = Array.from(select.options).find(opt => opt.value === '1');
- if (!option) {
- console.warn('[OW Theme] titleonly=1 option not found.');
- return;
- }
- // Only fix if the label is missing or blank
- if (!option.textContent.trim()) {
- option.textContent = 'Search Titles Only';
- }
- console.log('[OW Theme] Search Titles Only option label fixed.');
- }
- function fixThreadSearchPopup() {
- // Locate the text input
- const input = document.querySelector('td.vbmenu_option input[name="query"]');
- if (!input) {
- console.warn('[OW Theme] Thread search input not found.');
- return;
- }
- // Find the submit button in the same td
- const td = input.closest('td.vbmenu_option');
- if (!td) return;
- const submitButton = td.querySelector('input[type="submit"].button');
- if (!submitButton) {
- console.warn('[OW Theme] Thread search submit button not found.');
- return;
- }
- if (!submitButton.value.trim()) {
- submitButton.value = 'Go';
- }
- submitButton.style.verticalAlign = 'top';
- submitButton.style.marginLeft = '4px';
- submitButton.style.height = `24px`;
- console.log('[OW Theme] Thread search popup fixed.');
- }
- function fixFAQResetButton() {
- // Find the reset button
- const resetBtn = document.querySelector('input[type="reset"].button');
- if (!resetBtn) {
- console.warn('[OW Theme] FAQ reset button not found.');
- return;
- }
- // Only set value if blank
- if (!resetBtn.value.trim()) {
- resetBtn.value = 'Reset';
- }
- // Align with other button
- resetBtn.style.verticalAlign = 'top';
- resetBtn.style.marginLeft = '4px';
- // Match height of the first submit button if available
- const submitBtn = document.querySelector('input[type="submit"].button');
- if (submitBtn) {
- resetBtn.style.height = `${submitBtn.offsetHeight}px`;
- }
- console.log('[OW Theme] FAQ reset button fixed.');
- }
- function addThreadLabels() {
- const labelMap = {
- 'thread_new.gif': 'Thread (New Messages)',
- 'thread.gif': 'Thread (No New Messages)',
- 'thread_lock.gif': 'Thread is closed'
- };
- // Loop through all table rows
- document.querySelectorAll('tr').forEach(tr => {
- // Find the first <td> with a thread icon
- const tdWithIcon = Array.from(tr.children).find(td => td.querySelector('img[src*="thread"]'));
- if (!tdWithIcon) return; // skip rows without a thread icon
- const img = tdWithIcon.querySelector('img[src*="thread"]');
- if (!img) return;
- // Determine the label
- const label = Object.entries(labelMap).find(([key]) => img.src.includes(key))?.[1];
- if (!label) return;
- // Only consider **direct child <td>s after the icon <td>**
- const tdsAfterIcon = Array.from(tr.children).slice(Array.from(tr.children).indexOf(tdWithIcon) + 1);
- // Find the first empty td (no images, only whitespace or )
- for (const td of tdsAfterIcon) {
- if (td.querySelector('img')) continue;
- const text = td.textContent.replace(/\u00A0/g, '').trim();
- if (text === '') {
- td.textContent = label;
- break; // fill only one td per row
- }
- }
- });
- }
- function labelModerators() {
- // Find the thread header row (contains "Showing threads")
- const headerRow = Array.from(document.querySelectorAll('tr')).find(tr => {
- return Array.from(tr.children).some(td =>
- td.classList.contains('thead') && td.textContent.includes('Showing threads')
- );
- });
- if (!headerRow) return; // exit if not found
- // Look at sibling <td> in the same row
- Array.from(headerRow.children).forEach(td => {
- if (!td.classList.contains('thead')) return;
- const text = td.textContent.trim();
- const match = text.match(/^:\s*(\d+)$/); // ": 4"
- if (match) {
- td.textContent = `Moderators: ${match[1]}`;
- }
- });
- }
- function runOnceDOMReady() {
- removeHeaderRows();
- trySwapLogo();
- trySwapGradients();
- expandPageWidth();
- recolorTables();
- expandCenteredTable();
- injectThemeCSS();
- stripInlineBackgrounds();
- showPage();
- fillMissingInputs();
- fillForumLegendTexts();
- fillForumHeaders();
- fillWelcomeText();
- fillWhatsGoingOn();
- labelStatsAndUsers();
- fillUserCpMenuLabels();
- fillUserCpPanelLabels();
- fillSubscriptionsPanel();
- fillPrivateMessagesPanel();
- fillSearchPanel();
- cleanSearchPanel();
- fixFooterLinks();
- replaceOldDomainLinks();
- expandPostsTable();
- fixPostUserInfoLabels();
- labelQuotedPostsWithAuthor();
- disableSendMessage();
- replaceThemeOptions();
- labelSidebarButtons();
- removeMenuLinks();
- fixForumNavigationLabels();
- fixDropdownLabels();
- fixSelectedMessagesLabels();
- fixBlogQuickSearchLabels();
- fixMemberlistHeaderLabels();
- fixProfilePanelLabels();
- removeForumRulesSection();
- fixNavigationButton();
- removeEmptyForumViewers();
- fixCalendarForms();
- fixShowPostsLabels();
- fixResetSearchBtn();
- fixEmptyOption();
- fixProfileDropdownList();
- fixAddBuddyBtnLabels();
- fixBlogSideBarProfile();
- fixBlogNavigationBtn();
- fixBlogCalendarHeader();
- fixUserProfileLabels();
- fixMissingProfileAvatar();
- removeAdditionalInformationPanelProfile();
- cleanContactInfoPanel();
- fixContactInfoSubforumsLabel();
- removeEmptyContactStatistics();
- replaceLegacyTopPanelOnce();
- replaceLegacyNavbarOnce();
- labelThreadSortSubmitButton();
- fixForumJumpButton();
- fixForumToolsTextAlignment();
- fixPageNavPopupLabels();
- fixReputationTooltips();
- fixSearchTitleOnlyLabel();
- fixThreadSearchPopup();
- fixFAQResetButton();
- addThreadLabels();
- labelModerators();
- // Show the page after theme applied
- document.documentElement.style.display = '';
- }
- if (document.body) {
- // DOM exists, run immediately
- runOnceDOMReady();
- } else {
- // Wait for DOMContentLoaded
- document.addEventListener('DOMContentLoaded', runOnceDOMReady);
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment