您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds custom quality of life features to MSPFA.
当前为
// ==UserScript== // @name MSPFA extras // @namespace http://tampermonkey.net/ // @version 1.7.6 // @description Adds custom quality of life features to MSPFA. // @author seymour schlong // @icon https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png // @icon64 https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png // @match https://mspfa.com/ // @match https://mspfa.com/?* // @match https://mspfa.com/*/ // @match https://mspfa.com/*/?* // @match https://mspfa.com/my/* // @match https://mspfa.com/random/ // @exclude https://mspfa.com/js/* // @exclude https://mspfa.com/css/* // @grant none // ==/UserScript== (function() { 'use strict'; const currentVersion = "1.7.6"; console.log(`MSPFA extras script v${currentVersion} by seymour schlong`); const debug = false; /** * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true * Github to-do completion list (and other stuff too) * * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu - February 23rd, 2020 * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes - February 23rd, 2020 * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates - February 23rd, 2020 * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates - February 23rd, 2020 * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - July 21st, 2020 * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count - July 21st, 2020 * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values - August 7th, 2020 * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers - August 7th, 2020 * https://github.com/GrantGryczan/MSPFA/issues/52 - Hash URLs - August 8th, 2020 * - Page drafts - August 8th, 2020 * - Edit pages button - August 8th, 2020 * - Image preloading - August 20th, 2020 * https://github.com/GrantGryczan/MSPFA/issues/19 - Manage game saves - August 22nd, 2020 * * Extension to-do... maybe... * * If trying to save a page and any other save button is not disabled, ask the user if they would rather Save All instead, or prompt to disable update notifications. * When adding a new page, store it in an array and if that array length is > 1 when someone tries to save, prompt them to press Save All? */ // A general function that allows for waiting until a certain element appears on the page. const pageLoad = (fn, length) => { const interval = setInterval(() => { if (fn()) clearInterval(interval); }, length ? length*1000 : 500); }; // Saves the options data for the script. const saveData = (data) => { localStorage.mspfaextra = JSON.stringify(data); if (debug) { console.log('Settings:'); console.log(data); } }; // Saves the data for drafts const saveDrafts = (data) => { localStorage.mspfadrafts = JSON.stringify(data); if (debug) { console.log('Drafts:'); console.log(data); } }; // Encases an element within a link const addLink = (elm, url, target) => { const link = document.createElement('a'); link.href = url; link.draggable = false; if (elm.parentNode) elm.parentNode.insertBefore(link, elm); if (target) link.target = target; link.appendChild(elm); return link; }; // Easy br element const newBr = () => { return document.createElement('br'); } // Make creating label elements easier const createLabel = (text, id) => { const newLabel = document.createElement('label'); newLabel.textContent = text; newLabel.setAttribute('for', id); return newLabel; } let settings = {}; let drafts = {}; const defaultSettings = { autospoiler: false, style: 0, styleURL: "", night: false, auto502: true, textFix: false, pixelFix: false, intro: false, commandScroll: false, preload: true, dialogKeys: true, dialogFocus: false, spoilerValues: {} } let pageLoaded = false; const loadDrafts = () => { if (localStorage.mspfadrafts) { drafts = JSON.parse(localStorage.mspfadrafts); } } loadDrafts(); // Load any previous settings from localStorage if (localStorage.mspfaextra) { Object.assign(settings, JSON.parse(localStorage.mspfaextra)); // Get draft data from settings if (typeof settings.drafts === "object") { if (Object.keys(settings.drafts).length > 0 && Object.keys(drafts).length === 0) { drafts = settings.drafts; } } saveDrafts(drafts); } // If any settings are undefined, re-set to their default state. (For older users when new things get stored) const checkSettings = () => { const defaultSettingsKeys = Object.keys(defaultSettings); for (let i = 0; i < defaultSettingsKeys.length; i++) { if (typeof settings[defaultSettingsKeys[i]] === "undefined") { settings[defaultSettingsKeys[i]] = defaultSettings[defaultSettingsKeys[i]]; } } saveData(settings); } checkSettings(); if (GM_info && GM_info.scriptHandler !== "Tampermonkey" && !settings.warned) { alert(`It appears that you're running the MSPFA extras script with ${GM_info.scriptHandler}.\nUnfortunately, this script cannot run at its full potential because of that.\nTry switching to Tampermonkey if you want to use more of the features!\n(this message will only appear once.)`); settings.warned = true; saveData(settings); } // Scrolls you to where you need to be const hashSearch = location.href.replace(location.origin + location.pathname, '').replace(location.search, ''); if (hashSearch !== '') { pageLoad(() => { const idElement = document.querySelector(hashSearch); if (idElement) { const selected = document.querySelector(hashSearch); selected.scrollIntoView(); selected.style.outline = '3px solid black'; selected.style.transition = '0.5s'; pageLoad(() => { if (pageLoaded) { selected.style.outline = '0px solid black'; } }); return true; } }, 1); } // Ripped shamelessly right from mspfa lol (URL search parameters -- story ID, page num, etc.) let rawParams; if (location.href.indexOf("#") != -1) { rawParams = location.href.slice(0, location.href.indexOf("#")); } else { rawParams = location.href; } if (rawParams.indexOf("?") != -1) { rawParams = rawParams.slice(rawParams.indexOf("?") + 1).split("&"); } else { rawParams = []; } const params = {}; for (let i = 0; i < rawParams.length; i++) { try { const p = rawParams[i].split("="); params[p[0]] = decodeURIComponent(p[1]); } catch (err) {} } if (debug) { console.log('URL parameters:'); console.log(params); } // Functions to get/change data from the console window.MSPFAe = { getSettings: () => { return settings; }, getSettingsString: (formatted) => { if (formatted) { console.log(JSON.stringify(settings, null, 4)); } else { console.log(JSON.stringify(settings)); } }, getDrafts: () => { loadDrafts(); return drafts; }, getDraftsString: (formatted) => { loadDrafts(); if (formatted) { console.log(JSON.stringify(drafts, null, 4)); } else { console.log(JSON.stringify(drafts)); } }, changeSettings: (newSettings) => { console.log('Settings updated'); console.log(settings); Object.assign(settings, newSettings); saveData(settings); }, changeSettingsString: (fullString) => { try { JSON.parse(fullString); } catch (err) { console.error(err); return; } settings = JSON.parse(fullString); checkSettings(); console.log(settings); }, getParams: params } // Error reloading if (settings.auto502 && document.querySelector('#cf-wrapper')) { window.location.reload(); } window.addEventListener("load", () => { // Reload the page if 502 CloudFlare error page appears // Wait five seconds, then refresh the page if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") { setTimeout(() => { window.location.reload(); }, 5000); } pageLoaded = true; }); // Delete any unchanged spoiler values if (location.pathname !== "/my/stories/pages/") { // Go through spoiler values and remove any that aren't unique Object.keys(settings.spoilerValues).forEach(adventure => { if (settings.spoilerValues[adventure].open === "Show" && settings.spoilerValues[adventure].close === "Hide") { delete settings.spoilerValues[adventure]; } else if (settings.spoilerValues[adventure].open === '' && settings.spoilerValues[adventure].close === '') { delete settings.spoilerValues[adventure]; } }); } const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"]; const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', 'https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/themes/dark.css', '/css/theme4.css', '/css/theme5.css']; const createDropdown = (parent, explore) => { const dropDiv = document.createElement('div'); dropDiv.className = 'dropdown'; dropDiv.style.display = 'inline-block'; const dropContent = document.createElement('div'); dropContent.className = 'dropdown-content'; dropContent.style.display = 'none'; dropDiv.addEventListener('mouseenter', evt => { dropContent.style.display = 'block'; dropContent.style.color = getComputedStyle(parent).color; dropContent.querySelectorAll('a').forEach(link => { link.style.color = getComputedStyle(parent).color; }); }); if (!explore) { dropDiv.addEventListener('mouseleave', evt => { dropContent.style.display = 'none'; }); } parent.parentNode.insertBefore(dropDiv, parent); dropDiv.appendChild(parent); dropDiv.appendChild(dropContent); return [dropDiv, dropContent]; } // "MY MSPFA" dropdown const myLink = document.querySelector('nav a[href="/my/"]'); if (myLink) { const dropContent = createDropdown(myLink)[1]; const dLinks = []; dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ]; dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ]; for (let i = 0; i < dLinks[0].length; i++) { const newLink = document.createElement('a'); newLink.textContent = dLinks[0][i]; newLink.href = dLinks[1][i]; dropContent.appendChild(newLink); } // Append "My Profile" to the dropdown list if you're signed in pageLoad(() => { if (window.MSPFA) { if (window.MSPFA.me.n) { const newFavLink = document.createElement('a'); newFavLink.textContent = "My Favourites"; newFavLink.href = `/favs/?u=${window.MSPFA.me.i}`; const newMyLink = document.createElement('a'); newMyLink.textContent = "My Profile"; newMyLink.href = `/user/?u=${window.MSPFA.me.i}`; dropContent.appendChild(newFavLink); dropContent.appendChild(newMyLink); // Move SETTINGS to the bottom dropContent.appendChild(dropContent.querySelectorAll('a')[2]); return true; } } },.1); } // "RANDOM" dropdown const randomLink = document.querySelector('nav a[href="/random/"]'); if (randomLink) { // Thank you @MadCreativity 🙏 const dropContent = createDropdown(randomLink)[1]; (async () => { const dLinks = []; dLinks[0] = [ 'Recent ongoing' ]; dLinks[1] = [ await fetch(`https://mspfa-extras-server.herokuapp.com/api/random`).then(e => e.text()) ]; for (let i = 0; i < dLinks[0].length; i++) { const newLink = document.createElement('a'); newLink.textContent = dLinks[0][i]; newLink.href = dLinks[1][i]; dropContent.appendChild(newLink); } })() } // "EXPLORE" dropdown const exploreLink = document.querySelector('nav a[href="/stories/"'); if (exploreLink) { const dropdown = createDropdown(exploreLink, true); const dropDiv = dropdown[0]; const dropContent = dropdown[1]; const userLink = document.createElement('a'); userLink.textContent = 'User Search'; userLink.href = '/?s=36596&p=7' dropContent.appendChild(userLink); const exploreInput = document.createElement('input'); Object.assign(exploreInput, { type: 'text', placeholder: 'Search...', id: 'dropdown-explore' }); dropContent.appendChild(exploreInput); exploreInput.addEventListener('keydown', ke => { if (ke.code === 'Enter') { const searchLink = `/stories/?go=1&n=${encodeURIComponent(exploreInput.value)}&t=&h=14&o=favs&p=p&m=50&load=true`; if (ke.altKey || ke.ctrlKey) { window.open(searchLink, '_blank').focus(); } else { location.href = searchLink; } return; } }); dropDiv.addEventListener('mouseleave', evt => { // If input is focused if (document.activeElement !== exploreInput) { dropContent.style.display = 'none'; } }); document.body.addEventListener('click', evt => { if (document.activeElement !== exploreInput) { dropContent.style.display = 'none'; } }); } document.querySelector('header .mspfalogo').parentNode.draggable = false; addLink(document.querySelector('footer .mspfalogo'), 'javascript://'); // Message that shows when you first get the script const showIntroDialog = () => { const msg = window.MSPFA.parseBBCode('Hi! Thanks for installing this script!\n\nBe sure to check the [url=https://greasyfork.org/en/scripts/396798-mspfa-extras#additional-info]GreasyFork[/url] page to see a full list of features, and don\'t forget to check out your [url=https://mspfa.com/my/settings/#extraSettings]settings[/url] page to tweak things to how you want.\n\nIf you have any suggestions, or you find a bug, please be sure to let me know on Discord at [url=discord://discordapp.com/users/277928549866799125]@seymour schlong#3669[/url].\n\n[size=12]This dialog will only appear once. To view it again, click "View Script Message" at the bottom of the site.[/size]'); window.MSPFA.dialog("MSPFA extras message", msg, ["Okay"]); } // Check if show intro dialog has displayed if (!settings.intro) { pageLoad(() => { if (window.MSPFA) { showIntroDialog(); settings.intro = true; saveData(settings); return true; } }); } const details = document.querySelector('#details'); // Add 'link' at the bottom to show the intro dialog again const introLink = document.createElement('a'); introLink.textContent = 'View Script Message'; introLink.href = 'javascript://'; introLink.addEventListener('click', showIntroDialog); details.appendChild(introLink); // vbar!!!! const vbar = document.createElement('span'); Object.assign(vbar, {className: 'vbar', textContent: '|'}); details.appendChild(document.createTextNode(' ')); details.appendChild(vbar); details.appendChild(document.createTextNode(' ')); // if you really enjoy the script and has some extra moneys 🥺 const donateLink = document.createElement('a'); donateLink.textContent = 'Donate'; donateLink.href = 'https://ko-fi.com/ironbean'; donateLink.target = "blank"; details.appendChild(donateLink); // Theme stuff const theme = document.createElement('link'); Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' }); const updateTheme = (src) => { theme.href = src; } if (!document.querySelector('#theme')) { document.querySelector('head').appendChild(theme); if (settings.night) { updateTheme(styleUrls[3]); } else { updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); } } const pixelText = () => { return settings.pixelFix ? 'body { image-rendering: pixelated; image-rendering: -moz-crisp-edges; } .cellicon { image-rendering: -webkit-optimize-contrast !important; }' : ''; } // Dropdown menu and pixelated scaling const mspfaeCSS = document.createElement('link'); Object.assign(mspfaeCSS, { id: 'script-css', type: 'text/css', rel: 'stylesheet', href: 'https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/mspfae.css' }); document.querySelector('head').appendChild(mspfaeCSS); const extraStyle = document.createElement('style'); if (!document.querySelector('#extra-style')) { extraStyle.id = 'extra-style'; extraStyle.textContent = pixelText(); document.querySelector('head').appendChild(extraStyle); } let nightSwitch = []; // Enabling night mode. document.querySelector('footer .mspfalogo').addEventListener('click', evt => { settings.night = !settings.night; saveData(settings); for (let i = 0; i < nightSwitch.length; i++) { clearTimeout(nightSwitch[i]); } nightSwitch = []; // Transition to make it feel nicer on the eyes extraStyle.textContent = pixelText(); extraStyle.textContent = pixelText() + ' *{transition:1.5s;}'; if (settings.night) { updateTheme(styleUrls[3]); } else { updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); } nightSwitch.push(setTimeout(() => { extraStyle.textContent = pixelText(); }, 1500)); }); // Enable keyboard controls for some dialog boxes (enter/esc to accept/close) const dialog = document.querySelector('#dialog'); document.addEventListener('keydown', evt => { if (settings.dialogKeys && !dialog.textContent.includes('BBCode')) { if (dialog.style.display === '' && (evt.code === 'Enter' || evt.code === "Escape") && (document.activeElement === document.body || settings.dialogFocus)) { let buttons = dialog.querySelectorAll('button'); if (buttons.length === 1) { buttons[0].click(); } else if (buttons.length === 2) { if (["Okay", "Yes"].indexOf(buttons[0].textContent) !== -1 && evt.code === "Enter") { buttons[0].click(); } } if (["Cancel", "Close", "No"].indexOf(buttons[buttons.length - 1].textContent) !== -1 && evt.code === "Escape") { buttons[buttons.length - 1].click(); } } } }); if (location.pathname.includes('//')) { location.href = location.pathname.replace(/\/\//g, '/') + location.search; } if (location.pathname === "/" || location.pathname === "/preview/") { if (location.search) { // Remove the current theme if the adventure has CSS (to prevent conflicts); if (settings.style > 0) { pageLoad(() => { if (window.MSPFA) { if (window.MSPFA.story && window.MSPFA.story.y && (window.MSPFA.story.y.toLowerCase().includes('import') || window.MSPFA.story.y.includes('{'))) { if (!settings.night) updateTheme(''); return true; } } if (pageLoaded) return true; }); } // Preload adjacent pages if (settings.preload) { const preloadImages = document.createElement('div'); preloadImages.id = 'preload'; document.querySelector('#container').appendChild(preloadImages); window.MSPFA.slide.push(p => { preloadImages.innerHTML = ''; if (window.MSPFA.story.p[p-2]) { let page = window.MSPFA.parseBBCode(window.MSPFA.story.p[p-2].b); page.querySelectorAll('img').forEach(image => { preloadImages.appendChild(image); }); page.innerHTML = ''; } if (window.MSPFA.story.p[p]) { let page = window.MSPFA.parseBBCode(window.MSPFA.story.p[p].b); page.querySelectorAll('img').forEach(image => { preloadImages.appendChild(image); }); page.innerHTML = ''; } }); } // Automatic spoiler opening if (settings.autospoiler) { window.MSPFA.slide.push((p) => { document.querySelectorAll('#slide .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click()); }); } // Scroll up to the nav bar when changing page so you don't have to scroll down as much =) if (settings.commandScroll) { const heightTop = document.querySelector('nav').getBoundingClientRect().top - document.body.getBoundingClientRect().top; let temp = -2; // To prevent moving the page down when loading it for the first time window.MSPFA.slide.push((p) => { if (temp < 0) { temp++; } else { window.scroll(0, heightTop); } }); } // Show creation date pageLoad(() => { let infoTd = document.querySelector('#infobox tr td:nth-child(2)'); if (infoTd) { infoTd.appendChild(document.createTextNode('Creation date: ' + new Date(window.MSPFA.story.d).toString().split(' ').splice(1, 3).join(' '))); infoTd.appendChild(newBr()); infoTd.appendChild(document.createTextNode('Last update: ' + new Date(window.MSPFA.story.p[window.MSPFA.story.p.length-1].d).toString().split(' ').splice(1, 3).join(' '))); return true; } }); // Hash scrolling and opening infobox or commmentbox if (['#infobox', '#commentbox', '#newcomment', '#latestpages'].indexOf(hashSearch) !== -1) { pageLoad(() => { if (document.querySelector(hashSearch)) { if (hashSearch === '#infobox') { document.querySelector('input[data-open="Show Adventure Info"]').click(); } else if (hashSearch === '#commentbox' || hashSearch === '#newcomment') { document.querySelector('input[data-open="Show Comments"]').click(); } else if (hashSearch === '#latestpages') { document.querySelector('input[data-open="Show Adventure Info"]').click(); document.querySelector('input[data-open="Show Latest Pages"]').click(); } return true; } }); } // Attempt to fix text errors if (settings.textFix && location.pathname !== "/preview/") { pageLoad(() => { if (window.MSPFA.story && window.MSPFA.story.p) { // russian/bulgarian is not possible =( const currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]); const library = [ ["â��", "'"], ["Ã�", "Ñ"], ["ñ", "ñ"], ["ó", "ó"], ["á", "á"], ["Ä�", "ą"], ["í", "í"], ["ú", "ú"], ["é", "é"], ["Å�", "ł"], ["ż", "ż"], ["¡", "¡"], ["¿", "¿"], ["Nº", "#"] ]; // https://mspfa.com/?s=5280&p=51 -- unknown error const replaceTerms = (p) => { library.forEach(term => { if (window.MSPFA.story.p[p]) { window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]); window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]); } }); }; replaceTerms(currentPage-1); window.MSPFA.slide.push(p => { replaceTerms(p); replaceTerms(p-2); }); return true; } }); } // Turn buttons into links const pageButton = document.createElement('button'); const pageLink = addLink(pageButton, `/my/stories/pages/?s=${params.s}#p${params.p}`); pageButton.className = 'pages edit major'; pageButton.type = 'button'; pageButton.title = 'Edit Pages'; // Edit pages button & button link pageLoad(() => { const infoButton = document.querySelector('.edit.major'); if (infoButton) { pageLoad(() => { if (window.MSPFA.me.i) { // Change notify and favourite titles to make sense document.querySelector('.notify.major').title = 'Toggle Notifications'; const favButton = document.querySelector('.fav.major'); let fav = favButton.className.includes(' lit'); const changeTitle = () => { if (fav) { favButton.title = 'Unfavorite'; } else { favButton.title = 'Favorite'; } } changeTitle(); favButton.addEventListener('click', () => { fav = !fav; changeTitle(); }); infoButton.title = "Edit Info"; infoButton.parentNode.insertBefore(pageLink, infoButton); infoButton.parentNode.insertBefore(document.createTextNode(' '), infoButton); addLink(infoButton, `/my/stories/info/?s=${params.s}`); pageButton.style.display = document.querySelector('.edit.major:not(.pages)').style.display; // Change change page link when switching pages window.MSPFA.slide.push(p => { const newSearch = location.search.split('&p='); pageLink.href = `/my/stories/pages/?s=${params.s}#p${newSearch[1].split('#')[0]}`; }); return true; } }); addLink(document.querySelector('.rss.major'), `/rss/?s=${params.s}`); return true; } }); // Add "Reply" button next to comment gear setInterval(() => { if (document.querySelector('#commentbox > .spoiler.open')) { document.querySelectorAll('.gear').forEach(gear => { if (!gear.parentNode.querySelector('.reply')) { const replyDiv = document.createElement('div'); replyDiv.className = 'reply'; gear.insertAdjacentElement('afterEnd', replyDiv); gear.insertAdjacentHTML('afterEnd', '<span style="float: right"> </span>'); const userID = gear.parentNode.parentNode.classList[2].replace('u', ''); replyDiv.addEventListener('click', () => { const commentBox = document.querySelector('#commentbox textarea'); commentBox.value = `[user]${userID}[/user], ${commentBox.value}`; commentBox.focus(); commentBox.parentNode.scrollIntoView(); }); } }); } }, 500); } } else if (location.pathname === "/my/") { const editStories = document.querySelector('#editstories'); editStories.classList.remove('alt'); const parent = editStories.parentNode; const viewSaves = document.createElement('a'); Object.assign(viewSaves, { id: 'viewsaves', className: 'major', textContent: 'View Adventure Saves' }); parent.appendChild(viewSaves); parent.appendChild(newBr()); parent.appendChild(newBr()); pageLoad(() => { if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) { viewSaves.href = `/?s=36596&p=6`; return true; } },.1); const messages = document.querySelector('#messages'); pageLoad(() => { if (messages.textContent.includes('(')) { document.title = document.title + messages.textContent.toLowerCase().replace('messages', ''); return true; } }); } else if (location.pathname === "/my/settings/") { // Custom settings const saveBtn = document.querySelector('#savesettings'); const table = document.querySelector("#editsettings tbody"); let saveTr = table.querySelectorAll("tr"); saveTr = saveTr[saveTr.length - 1]; const headerTr = document.createElement('tr'); const header = document.createElement('th'); Object.assign(header, { id: 'extraSettings', textContent: 'Extra Settings' }); headerTr.appendChild(header); const moreTr = document.createElement('tr'); const more = document.createElement('td'); more.textContent = "* This only applies to a select few older adventures that have had their text corrupted. Some punctuation is fixed, as well as regular characters with accents. Currently only some spanish/french is fixable. Russian/Bulgarian is not possible."; moreTr.appendChild(more); const settingsTr = document.createElement('tr'); const localMsg = document.createElement('span'); const settingsTd = document.createElement('td'); localMsg.innerHTML = "Because this is an extension, any data saved is only <b>locally</b> on this device.<br>Don't forget to <b>save</b> when you've finished making changes!"; const plusTable = document.createElement('table'); const plusTbody = document.createElement('tbody'); plusTable.appendChild(plusTbody); settingsTd.appendChild(localMsg); settingsTd.appendChild(newBr()); settingsTd.appendChild(newBr()); settingsTd.appendChild(plusTable); settingsTr.appendChild(settingsTd); plusTable.style = "text-align: center;"; // Create checkbox (soooo much better) const createCheckbox = (text, checked, id) => { const optionTr = plusTbody.insertRow(plusTbody.childNodes.length); const optionTextTd = optionTr.insertCell(0); const optionLabel = createLabel(text, id); const optionInputTd = optionTr.insertCell(1); const optionInput = document.createElement('input'); optionInputTd.appendChild(optionInput); optionTextTd.appendChild(optionLabel); optionInput.type = "checkbox"; optionInput.checked = checked; optionInput.id = id; return optionInput; } const spoilerInput = createCheckbox("Automatically open spoilers:", settings.autospoiler, 'autospoiler'); const preloadInput = createCheckbox("Preload images for the pages immediately before and after:", settings.preload, 'preload'); const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", settings.auto502, 'auto502'); const commandScrollInput = createCheckbox("Scroll back up to the nav bar when switching page:", settings.commandScroll, 'commandScroll'); const dialogKeysInput = createCheckbox("Use enter/escape keys to accept/exit control some dialogs:", settings.dialogKeys, 'dialogKeys'); const dialogFocusInput = createCheckbox("Let keys work while dialog isn't focused (above required):", settings.dialogFocus, 'dialogFocus'); const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", settings.pixelFix, 'pixelFix'); const textFixInput = createCheckbox("Attempt to fix text errors (experimental)*:", settings.textFix, 'textFix'); const cssTr = plusTbody.insertRow(plusTbody.childNodes.length); const cssTextTd = cssTr.insertCell(0); const cssSelectTd = cssTr.insertCell(1); const cssSelect = document.createElement('select'); cssSelectTd.appendChild(cssSelect); cssTextTd.textContent = "Change style:"; const customTr = plusTbody.insertRow(plusTbody.childNodes.length); const customTextTd = customTr.insertCell(0); const customCssTd = customTr.insertCell(1); const customCssInput = document.createElement('input'); customCssTd.appendChild(customCssInput); customTextTd.textContent = "Custom CSS URL:"; customCssInput.style.width = "99px"; customCssInput.value = settings.styleURL; styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o))); saveTr.parentNode.insertBefore(headerTr, saveTr); saveTr.parentNode.insertBefore(settingsTr, saveTr); saveTr.parentNode.insertBefore(moreTr, saveTr); cssSelect.selectedIndex = settings.style; const buttonSpan = document.createElement('span'); const draftButton = document.createElement('input'); const spoilerButton = document.createElement('input'); draftButton.value = 'Manage Drafts'; draftButton.className = 'major'; draftButton.type = 'button'; spoilerButton.value = 'Manage Spoiler Values'; spoilerButton.className = 'major'; spoilerButton.type = 'button'; buttonSpan.appendChild(draftButton); buttonSpan.appendChild(document.createTextNode(' ')); buttonSpan.appendChild(spoilerButton); settingsTd.appendChild(buttonSpan); const draftMsg = window.MSPFA.parseBBCode('Here you can manage the drafts that you have saved for your adventure(s).\n'); const listTable = document.createElement('table'); listTable.id = 'draft-table'; const listTbody = document.createElement('tbody'); listTable.appendChild(listTbody); const draftsEmpty = () => { loadDrafts(); let empty = true; Object.keys(drafts).forEach(adv => { if (empty) { const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1; if (Object.keys(drafts[adv]).length > length) { empty = false; } } }); return empty; } setInterval(() => { draftButton.disabled = draftsEmpty(); }, 1000); draftButton.addEventListener('click', () => { draftMsg.appendChild(listTable); listTbody.innerHTML = ''; loadDrafts(); const addAdv = (story, name) => { const storyTr = listTbody.insertRow(listTable.rows); const titleLink = document.createElement('a'); Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${story}&click=d`, textContent: name, target: '_blank' }); storyTr.insertCell(0).appendChild(titleLink); const deleteButton = document.createElement('input'); Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' }); storyTr.insertCell(1).appendChild(deleteButton); deleteButton.addEventListener('click', () => { setTimeout(() => { window.MSPFA.dialog('Delete adventure draft?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => { if (output === "Yes") { loadDrafts(); drafts[story] = {}; if (settings.drafts && settings.drafts[story]) { delete settings.drafts[story]; saveData(settings); } saveDrafts(drafts); setTimeout(() => { draftButton.click(); }, 1); if (draftsEmpty) { draftButton.disabled = true; } } }); }, 1); }); } Object.keys(drafts).forEach(adv => { const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1; if (Object.keys(drafts[adv]).length > length) { if (!!length) { addAdv(adv, drafts[adv].cachedTitle); } else { window.MSPFA.request(0, { do: "story", s: adv }, story => { if (typeof story !== "undefined") { console.log(story); addAdv(adv, story.n); } }); } } }); window.MSPFA.dialog('Manage Drafts', draftMsg, ["Delete All", "Close"], (output, form) => { if (output === "Delete All") { setTimeout(() => { window.MSPFA.dialog('Delete all Drafts?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => { if (output === "Yes") { Object.keys(drafts).forEach(adv => { drafts[adv] = {}; }); saveDrafts(drafts); if (typeof settings.drafts !== "undefined") { delete settings.drafts; saveData(settings); } draftButton.disabled = true; } }); }, 1); } }); }); if (Object.keys(settings.spoilerValues).length === 0) { spoilerButton.disabled = true; } const spoilerMsg = window.MSPFA.parseBBCode('Here you can manage the spoiler values that you have set for your adventure(s).\nClick on an adventure\'s title to see the values.\n'); spoilerButton.addEventListener('click', () => { spoilerMsg.appendChild(listTable); listTbody.innerHTML = ''; Object.keys(settings.spoilerValues).forEach(adv => { window.MSPFA.request(0, { do: "story", s: adv }, story => { if (typeof story !== "undefined") { const storyTr = listTbody.insertRow(listTable.rows); const titleLink = document.createElement('a'); Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${adv}&click=s`, textContent: story.n, target: '_blank' }); storyTr.insertCell(0).appendChild(titleLink); const deleteButton = document.createElement('input'); Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' }); storyTr.insertCell(1).appendChild(deleteButton); deleteButton.addEventListener('click', () => { setTimeout(() => { window.MSPFA.dialog('Delete adventure spoilers?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => { if (output === "Yes") { delete settings.spoilerValues[adv]; saveData(settings); setTimeout(() => { spoilerButton.click(); }, 1); if (Object.keys(settings.spoilerValues).length === 0) { spoilerButton.disabled = true; } } }); }, 1); }); } }); }); window.MSPFA.dialog('Manage Spoiler Values', spoilerMsg, ["Delete All", "Close"], (output, form) => { if (output === "Delete All") { setTimeout(() => { window.MSPFA.dialog('Delete all Spoiler Values?', 'Are you sure you want to delete all spoiler values?\nThis action cannot be undone!', ["Yes", "No"], (output, form) => { if (output === "Yes") { settings.spoilerValues = {}; saveData(settings); spoilerButton.disabled = true; } }); }, 1); } }); }); // Add event listeners plusTbody.querySelectorAll('input, select').forEach(elm => { elm.addEventListener("change", () => { saveBtn.disabled = false; }); }); saveBtn.addEventListener('mouseup', () => { settings.autospoiler = spoilerInput.checked; settings.style = cssSelect.selectedIndex; settings.styleURL = customCssInput.value; settings.auto502 = errorInput.checked; settings.textFix = textFixInput.checked; settings.pixelFix = pixelFixInput.checked; settings.dialogKeys = dialogKeysInput.checked; settings.dialogFocus = dialogFocusInput.checked; settings.commandScroll = commandScrollInput.checked; settings.preload = preloadInput.checked; settings.night = false; console.log(settings); saveData(settings); updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); extraStyle.textContent = pixelText() + ' *{transition:1s}'; extraStyle.textContent = pixelText(); setTimeout(() => { extraStyle.textContent = pixelText(); }, 1000); }); } else if (location.pathname === "/my/messages/") { // New buttons // Select all read messages button. const selRead = document.createElement('input'); Object.assign(selRead, { value: 'Select Read', className: 'major', type: 'button' }); // On click, select all messages with the style attribute indicating it as read. selRead.addEventListener('mouseup', () => { document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click()); }); // Select duplicate message (multiple update notifications). const selDupe = document.createElement('input'); Object.assign(selDupe, { value: 'Select Same', className: 'major', type: 'button', style: 'margin-top: 6px' }); selDupe.addEventListener('mouseup', evt => { const temp = document.querySelectorAll('#messages > tr'); const msgs = []; for (let i = temp.length - 1; i >= 0; i--) { msgs.push(temp[i]); } const titles = []; msgs.forEach((msg) => { const title = msg.querySelector('a.major').textContent; // Select only adventure updates if (/^New update: /.test(title)) { if (titles.indexOf(title) === -1) { if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") { titles.push(title); } } else { msg.querySelector('input').click(); } } }); }); // Prune button const pruneButton = document.createElement('input'); Object.assign(pruneButton, { type: 'button', value: 'Prune', className: 'major' }); pruneButton.addEventListener('click', () => { const ageInput = document.createElement('input'); Object.assign(ageInput, { type: 'number', min: 1, max: 10, value: 1 }); const msgState = document.createElement('select'); ['all', 'all unread', 'all read'].forEach(option => { const op = document.createElement('option'); op.textContent = option; msgState.appendChild(op); }); const timeUnit = document.createElement('select'); ['month(s)', 'week(s)', 'day(s)'].forEach(option => { const op = document.createElement('option'); op.textContent = option; timeUnit.appendChild(op); }); timeUnit.childNodes[1].setAttribute('selected', 'selected'); const msg = document.createElement('span'); msg.appendChild(document.createTextNode('Prune ')); msg.appendChild(msgState); msg.appendChild(document.createTextNode(' messages older than ')); msg.appendChild(ageInput); msg.appendChild(timeUnit); window.MSPFA.dialog('Prune messages', msg, ['Prune', 'Cancel'], (output, form) => { if (output === 'Prune') { document.querySelector('#messages').childNodes.forEach(node => { if (node.firstChild.firstChild.checked) { node.firstChild.firstChild.click(); } const selectedState = msgState.selectedOptions[0].textContent; const selectedUnit = timeUnit.selectedOptions[0].textContent; if (selectedState === 'all unread') { if (node.firstChild.style.borderLeftColor === 'rgb(221, 221, 221)') { return; } } else if (selectedState === 'all read') { if (node.firstChild.style.borderLeftColor === 'rgb(92, 174, 223)') { return; } } const dateText = node.childNodes[2].childNodes[2].textContent.split(' - '); const messageDate = new Date(dateText[dateText.length-1]); const currentDate = Date.now(); const diff = Math.floor(Math.round((currentDate-messageDate)/(1000*60*60))/24); // Difference in days if (selectedUnit === 'month(s)') diff = Math.floor(diff / 30); else if (selectedUnit === 'week(s)') diff = Math.floor(diff / 7); if (diff >= ageInput.value) { node.firstChild.firstChild.click(); } }); setTimeout(() => { document.querySelector('input[value=Delete]').click(); }, 1); } }); }); // Maybe add a "Merge Updates" button? // [Merge Updates] would create a list of updates, similar to [Select Same] // Add buttons to the page. const del = document.querySelector('#deletemsgs'); del.parentNode.appendChild(newBr()); del.parentNode.appendChild(selRead); del.parentNode.appendChild(document.createTextNode(' ')); del.parentNode.appendChild(selDupe); del.parentNode.appendChild(document.createTextNode(' ')); del.parentNode.appendChild(pruneButton); // Click the green cube to open the update/comment in a new tab, and mark notification as read. pageLoad(() => { if (document.querySelector('#messages').childNodes.length > 0) { if (document.querySelector('#messages').textContent === 'No new messages were found.') { // Disable some buttons if there are no messages. pruneButton.disabled = true; selDupe.disabled = true; return true; } else { document.querySelector('#messages').childNodes.forEach(node => { if (node.textContent.includes('New update:') && node.textContent.includes('MS Paint Fan Adventures')) { const link = addLink(node.querySelector('.cellicon'), node.querySelector('.spoiler a').href); link.addEventListener('mouseup', () => { const spoiler = node.querySelector('.spoiler'); const button = spoiler.querySelector('input'); spoiler.className = 'spoiler closed'; button.click(); button.click(); }); } else if ((node.textContent.includes('New comment on ') || node.textContent.includes('You were tagged on ')) && node.textContent.includes('MS Paint Fan Adventures')) { const link = addLink(node.querySelector('.cellicon'), node.querySelectorAll('.spoiler a')[1].href + '#commentbox'); link.addEventListener('mouseup', () => { const spoiler = node.querySelector('.spoiler'); const button = spoiler.querySelector('input'); spoiler.className = 'spoiler closed'; button.click(); button.click(); }); } }); return true; } } }); } else if (location.pathname === "/my/messages/new/" && location.search) { // Auto-fill user when linked from a user page const recipientInput = document.querySelector('#addrecipient'); recipientInput.value = params.u; pageLoad(() => { const recipientButton = document.querySelector('#addrecipientbtn'); if (recipientButton) { recipientButton.click(); if (recipientInput.value === "") { // If the button press doesn't work return true; } } }); } else if (location.pathname === "/my/stories/") { // Add links to buttons pageLoad(() => { const adventures = document.querySelectorAll('#stories tr'); if (adventures.length > 0) { adventures.forEach(story => { const buttons = story.querySelectorAll('input.major'); const id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', ''); if (id) { addLink(buttons[0], `/my/stories/info/${id}`); addLink(buttons[1], `/my/stories/pages/${id}`); addLink(story.querySelector('img'), `/${id}&p=1`); } }); return true; } if (pageLoaded) return true; }); // Add user guides const guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ]; const links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"]; const authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"]; const parentTd = document.querySelector('.container > tbody > tr:last-child > td'); const unofficial = parentTd.querySelector('span'); unofficial.textContent = "Unofficial Guides"; const guideTable = document.createElement('table'); const guideTbody = document.createElement('tbody'); guideTable.style.width = "100%"; guideTable.style.textAlign = "center"; guideTable.appendChild(guideTbody); parentTd.appendChild(guideTable); for (let i = 0; i < guides.length; i++) { const guideTr = guideTbody.insertRow(i); const guideTd = guideTr.insertCell(0); const guideLink = document.createElement('a'); Object.assign(guideLink, { href: links[i], textContent: guides[i], className: 'major' }); guideTd.appendChild(guideLink); guideTd.appendChild(newBr()); guideTd.appendChild(document.createTextNode('by '+authors[i])); guideTd.appendChild(newBr()); guideTd.appendChild(newBr()); } } else if (location.pathname === "/my/stories/info/" && location.search) { // Button links addLink(document.querySelector('#userfavs'), `/readers/?s=${params.s}`); addLink(document.querySelector('#editpages'), `/my/stories/pages/?s=${params.s}`); // Reorder some elements under Icons to be more sensible const bannerInput = document.querySelector('input[name="storybanner"]'); const featureInput = document.querySelector('input[name="storythumb"]'); bannerInput.parentNode.style.paddingLeft = '130px'; const hint = document.createElement('input'); Object.assign(hint, { className: 'major', value: '?', style: 'padding: 0', type: 'button' }); hint.addEventListener('click', () => { window.MSPFA.dialog('Info', document.createTextNode('Featuring is no longer available.'), ['Okay']); }); const bannerPrev = document.querySelector(`a[href="/?b=${params.s}`); const featurePrev = document.querySelector(`a[href="/?f=${params.s}`); featurePrev.parentNode.insertBefore(hint, featurePrev); featurePrev.parentNode.insertBefore(featurePrev, featureInput.nextSibling); bannerPrev.parentNode.insertBefore(bannerPrev, bannerInput.nextSibling); if (params.s !== 'new') { // Download adventure data const downloadButton = document.createElement('input'); Object.assign(downloadButton, { className: 'major', value: 'Export Data', type: 'button', style: 'margin-top: 6px' }); const downloadLink = document.createElement('a'); window.MSPFA.request(0, { do: "story", s: params.s }, (s) => { if (s) { downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(s, null, 4))); // Display week of anniversary banner const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; const ann = new Date(s.d); const end = new Date(s.d + 7*24*60*60*1000); bannerInput.parentNode.insertBefore(document.createTextNode(months[ann.getMonth()] + ' ' + ann.getDate() + ' - ' + months[end.getMonth()] + ' ' + end.getDate()), bannerInput.nextSibling.nextSibling); bannerInput.parentNode.insertBefore(newBr(), bannerInput.nextSibling.nextSibling); } }); downloadLink.setAttribute('download', `${params.s}.json`); downloadLink.appendChild(downloadButton); document.querySelector('#savestory').parentNode.appendChild(newBr()); document.querySelector('#savestory').parentNode.appendChild(downloadLink); } } else if (location.pathname === "/my/stories/pages/" && location.search) { const adventureID = params.s; const notifyLabel = createLabel('Notify readers of new pages during this editing session: ', 'notifyreaders'); const notifyButton = document.querySelector('#notifyreaders'); notifyButton.previousSibling.textContent = ''; notifyButton.parentNode.insertBefore(notifyLabel, notifyButton);/**/ if (!drafts[adventureID]) { drafts[adventureID] = {}; saveDrafts(drafts); } pageLoad(() => { if (document.querySelector('#storyname').textContent !== '-') { drafts[adventureID].cachedTitle = document.querySelector('#storyname').textContent; saveDrafts(drafts); return true; } }); // Button links addLink(document.querySelector('#editinfo'), `/my/stories/info/?s=${adventureID}`); // Default spoiler values const replaceButton = document.querySelector('#replaceall'); const spoilerButton = document.createElement('input'); Object.assign(spoilerButton, { className: 'major', value: 'Default Spoiler Values', type: 'button'}); replaceButton.parentNode.insertBefore(spoilerButton, replaceButton); replaceButton.parentNode.insertBefore(newBr(), replaceButton); replaceButton.parentNode.insertBefore(newBr(), replaceButton); if (!settings.spoilerValues[adventureID]) { settings.spoilerValues[adventureID] = { open: 'Show', close: 'Hide' } } spoilerButton.addEventListener('click', evt => { const spoilerSpan = document.createElement('span'); const spoilerOpen = document.createElement('input'); const spoilerClose = document.createElement('input'); spoilerSpan.appendChild(document.createTextNode('Open button text:')); spoilerSpan.appendChild(newBr()); spoilerSpan.appendChild(spoilerOpen); spoilerSpan.appendChild(newBr()); spoilerSpan.appendChild(newBr()); spoilerSpan.appendChild(document.createTextNode('Close button text:')); spoilerSpan.appendChild(newBr()); spoilerSpan.appendChild(spoilerClose); spoilerOpen.value = settings.spoilerValues[adventureID].open; spoilerClose.value = settings.spoilerValues[adventureID].close; window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => { if (output === 'Save') { settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value; settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value; if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') { delete settings.spoilerValues[adventureID]; } saveData(settings); } }); }); document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => { document.querySelector('#dialog input[name="open"]').value = document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open; document.querySelector('#dialog input[name="close"]').value = document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close; }); // --- Custom BBToolbar buttons // Buttonless spoilers const flashButton = document.querySelector('input[title=Flash]'); const newSpoilerButton = document.createElement('input'); newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler'); Object.assign(newSpoilerButton, { title: 'Buttonless Spoiler', type: 'button', style: 'background-position: -66px -88px;' }); newSpoilerButton.addEventListener('click', evt => { const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea'); if (bbe) { bbe.focus(); const start = bbe.selectionStart; const end = bbe.selectionEnd; bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end); bbe.selectionStart = start + 26; bbe.selectionEnd = end + 26; } }); flashButton.parentNode.insertBefore(newSpoilerButton, flashButton); // Audio button const audioButton = document.createElement('input'); Object.assign(audioButton, { title: 'Audio Player', type: 'button', style: 'background-position: -22px -110px' }); audioButton.addEventListener('click', evt => { const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea'); if (bbe) { const msg = window.MSPFA.parseBBCode('Audio URL:<br>'); const audioInput = document.createElement('input'); Object.assign(audioInput, { type: 'url', name: 'audio-url', required: true }); const autoplayButton = document.createElement('input'); autoplayButton.type = 'checkbox'; autoplayButton.id = 'autoplay'; autoplayButton.checked = true; const loopButton = document.createElement('input'); loopButton.type = 'checkbox'; loopButton.id = 'loop'; loopButton.checked = true; const controlsButton = document.createElement('input'); controlsButton.type = 'checkbox'; controlsButton.id = 'controls'; msg.appendChild(audioInput); msg.appendChild(newBr()); msg.appendChild(createLabel('Autoplay: ', 'autoplay')); msg.appendChild(autoplayButton); msg.appendChild(newBr()); msg.appendChild(createLabel('Loop: ', 'loop')); msg.appendChild(loopButton); msg.appendChild(newBr()); msg.appendChild(createLabel('Show controls: ', 'controls')); msg.appendChild(controlsButton); msg.appendChild(newBr()); window.MSPFA.dialog("Audio Player", msg, ["Okay", "Cancel"], (output, form) => { if (output == "Okay") { bbe.focus(); const start = bbe.selectionStart; const end = bbe.selectionEnd; const properties = `"${autoplayButton.checked ? ' autoplay' : ''}${loopButton.checked ? ' loop' : ''}${controlsButton.checked ? ' controls' : ''}`; bbe.value = bbe.value.slice(0, start) + '<audio src="' + audioInput.value + properties +'>' + bbe.value.slice(start); bbe.selectionStart = start + properties.length + audioInput.value.length + 13; bbe.selectionEnd = end + properties.length + audioInput.value.length + 13; } }); audioInput.select(); } }); flashButton.insertAdjacentElement('afterEnd', audioButton); // YouTube button const youtubeButton = document.createElement('input'); Object.assign(youtubeButton, { title: 'YouTube Video', type: 'button', style: 'background-position: 0px -110px' }); youtubeButton.addEventListener('click', evt => { const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea'); if (bbe) { const msg = window.MSPFA.parseBBCode('Video URL:<br>'); const videoUrl = document.createElement('input'); videoUrl.type = 'url'; videoUrl.name = 'youtube'; videoUrl.required = true; const autoplayButton = document.createElement('input'); autoplayButton.type = 'checkbox'; autoplayButton.checked = true; autoplayButton.id = 'autoplay'; const controlsButton = document.createElement('input'); controlsButton.type = 'checkbox'; controlsButton.checked = true; controlsButton.id = 'controls'; const fullscreenButton = document.createElement('input'); fullscreenButton.type = 'checkbox'; fullscreenButton.checked = true; fullscreenButton.id = 'fullscreen'; const widthInput = document.createElement('input'); Object.assign(widthInput, { type: 'number', required: true, value: 650, style: 'width: 5em' }); const heightInput = document.createElement('input'); Object.assign(heightInput, { type: 'number', required: true, value: 450, style: 'width: 5em' }); msg.appendChild(videoUrl); msg.appendChild(newBr()); msg.appendChild(createLabel('Autoplay: ', 'autoplay')); msg.appendChild(autoplayButton); msg.appendChild(newBr()); msg.appendChild(createLabel('Show controls: ', 'controls')); msg.appendChild(controlsButton); msg.appendChild(newBr()); msg.appendChild(createLabel('Allow fullscreen: ', 'fullscreen')); msg.appendChild(fullscreenButton); msg.appendChild(newBr()); msg.appendChild(document.createTextNode('Embed size: ')); msg.appendChild(widthInput); msg.appendChild(document.createTextNode('x')); msg.appendChild(heightInput); window.MSPFA.dialog("YouTube Embed", msg, ["Okay", "Cancel"], (output, form) => { if (output == "Okay") { let videoID = videoUrl.value.split('/'); videoID = videoID[videoID.length-1].replace('watch?v=', '').split('&')[0]; bbe.focus(); const start = bbe.selectionStart; const end = bbe.selectionEnd; const iframeContent = `<iframe width="${widthInput.value}" height="${heightInput.value}" src="https://www.youtube.com/embed/${videoID}?autoplay=${+autoplayButton.checked}&controls=${+controlsButton.checked}" frameborder="0" allow="accelerometer; ${autoplayButton.checked ? 'autoplay; ' : ''}encrypted-media;"${fullscreenButton.checked ? ' allowfullscreen' : ''}></iframe>`; bbe.value = bbe.value.slice(0, start) + iframeContent + bbe.value.slice(start); bbe.selectionStart = start + iframeContent + 13; bbe.selectionEnd = end + iframeContent + 13; } }); videoUrl.select(); } }); flashButton.insertAdjacentElement('afterEnd', youtubeButton); flashButton.insertAdjacentText('afterEnd', ' '); // Get preview link const getPreviewLink = (form) => { const page = parseInt(form.querySelector('a.major').textContent.replace('Page ', '')); return "/preview/?s=" + params.s + "&p=" + page + "&d=" + encodeURIComponent(JSON.stringify({ p: page, c: form.querySelector('input[name=cmd]').value, b: form.querySelector('textarea[name=body]').value, n: form.querySelector('input[name=next]').value, k: !form.querySelector('input[name=usekeys]').checked })); } // -- Drafts -- // Accessing draft text const accessDraftsButton = document.createElement('input'); Object.assign(accessDraftsButton, { className: 'major', value: 'Saved Drafts', type: 'button' }); replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton); accessDraftsButton.parentNode.insertBefore(document.createTextNode(' '), replaceButton); //accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton); //accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton); accessDraftsButton.addEventListener('click', () => { loadDrafts(); const draftDialog = window.MSPFA.parseBBCode('Use the textbox below to copy out the data and save to a file somewhere else, or click the download button below.\nYou can also paste in data to replace the current drafts to ones stored there.'); const draftInputTextarea = document.createElement('textarea'); draftInputTextarea.placeholder = 'Paste your draft data here'; draftInputTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;'; const downloadLink = document.createElement('a'); downloadLink.textContent = 'Download drafts'; downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(drafts[adventureID], null, 4))); downloadLink.setAttribute('download', `${adventureID}.json`); draftInputTextarea.rows = 8; draftDialog.appendChild(newBr()); draftDialog.appendChild(newBr()); draftDialog.appendChild(draftInputTextarea); draftDialog.appendChild(newBr()); draftDialog.appendChild(newBr()); draftDialog.appendChild(downloadLink); setTimeout(() => { draftInputTextarea.focus(); draftInputTextarea.selectionStart = 0; draftInputTextarea.selectionEnd = 0; draftInputTextarea.scrollTop = 0; }, 1); draftInputTextarea.value = JSON.stringify(drafts[adventureID], null, 4); window.MSPFA.dialog('Saved Drafts', draftDialog, ["Load Draft", "Cancel"], (output, form) => { if (output === "Load Draft") { if (draftInputTextarea.value === '') { setTimeout(() => { window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to delete this adventure\'s draft data?\nMake sure you have it saved somewhere!'), ["Delete", "Cancel"], (output, form) => { if (output === "Delete") { loadDrafts(); drafts[adventureID] = {}; if (settings.drafts && settings.drafts[adventureID]) { delete settings.drafts[adventureID]; saveData(settings); } saveDrafts(drafts); } }); }, 1); } else if (draftInputTextarea.value !== JSON.stringify(drafts[adventureID], null, 4)) { setTimeout(() => { window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to load this draft data?\nAll previous draft data for this adventure will be lost!'), ["Load", "Cancel"], (output, form) => { if (output === "Load") { let newData = {}; try { // Just in case the data given is invalid. newData = JSON.parse(draftInputTextarea.value); } catch (err) { console.error(err); setTimeout(() => { window.MSPFA.dialog('Error', window.MSPFA.parseBBCode('The entered data is invalid.'), ["Okay"]); }, 1); return; } loadDrafts(); drafts[adventureID] = newData; saveDrafts(drafts); } }); }, 1); } } }); }); const addDraftPagesButton = document.createElement('input'); replaceButton.parentNode.insertBefore(addDraftPagesButton, replaceButton); addDraftPagesButton.parentNode.insertBefore(newBr(), replaceButton); addDraftPagesButton.parentNode.insertBefore(newBr(), replaceButton); Object.assign(addDraftPagesButton, { className: 'major', value: 'Add Draft Pages', type: 'button' }); addDraftPagesButton.addEventListener('click', () => { const newPageForm = document.querySelector('#newpage'); let currentPage = 1; let pageAmount = 0; if (newPageForm.nextSibling.nodeName !== '#text') { currentPage = parseInt(newPageForm.nextSibling.id.replace('p', ''))+1; } for (let i = currentPage; drafts[adventureID][i] && pageAmount < 10; i++) { pageAmount++; } if (pageAmount > 0) { window.MSPFA.dialog("Drafts: Add New Pages", document.createTextNode(`This will add the pages with draft data from pages ${currentPage}-${currentPage+pageAmount-1} (you are only allowed 10 at once)`), ["Okay", "Cancel"], (output, form) => { if (output === "Okay") { for (let i = currentPage; drafts[adventureID][i] && i < currentPage+10; i++) { newPageForm.querySelector('input[name=cmd]').value = drafts[adventureID][i].command; newPageForm.querySelector('textarea[name=body]').value = drafts[adventureID][i].pageContent; if (drafts[adventureID][i].next) newPageForm.querySelector('input[name=next]').value = drafts[adventureID][i].next; newPageForm.querySelector('input[name=save]').click(); } } }); } }); // Draft stuff const showDraftDialog = (pageNum) => { loadDrafts(); const msg = document.createElement('span'); msg.appendChild(document.createTextNode('Command:')); msg.appendChild(document.createElement('br')); const commandInput = document.createElement('input'); Object.assign(commandInput, { style: 'width: 100%; box-sizing: border-box;', readOnly: true, }); msg.appendChild(commandInput); msg.appendChild(document.createElement('br')); msg.appendChild(document.createElement('br')); msg.appendChild(document.createTextNode('Body:')); const bodyInput = document.createElement('textarea'); Object.assign(bodyInput, { style: 'width: 100%; box-sizing: border-box; resize: vertical;', readOnly: true, rows: 8 }); msg.appendChild(bodyInput); const pageElement = document.querySelector(`#p${pageNum}`); let shownMessage = msg; let optionButtons = []; const commandElement = pageElement.querySelector('input[name="cmd"]'); const pageContentElement = pageElement.querySelector('textarea[name="body"]'); if (typeof drafts[adventureID][pageNum] === "undefined") { shownMessage = document.createTextNode('There is no draft saved for this page.'); optionButtons = ["Save New", "Close"]; } else { commandInput.value = drafts[adventureID][pageNum].command; bodyInput.textContent = drafts[adventureID][pageNum].pageContent; optionButtons = ["Save New", "Load", "Delete", "Close"]; } window.MSPFA.dialog(`Page ${pageNum} Draft`, shownMessage, optionButtons, (output, form) => { if (output === "Save New") { if (typeof drafts[adventureID][pageNum] === "undefined") { loadDrafts(); drafts[adventureID][pageNum] = { command: commandElement.value, pageContent: pageContentElement.value } saveDrafts(drafts); } else { setTimeout(() => { window.MSPFA.dialog('Overwrite current draft?', document.createTextNode('Doing this will overwrite your current draft with what is currently written in the page box. Are you sure?'), ["Yes", "No"], (output, form) => { if (output === "Yes") { loadDrafts(); drafts[adventureID][pageNum] = { command: commandElement.value, pageContent: pageContentElement.value } saveDrafts(drafts); } }); }, 1); } } else if (output === "Load") { if (pageContentElement.value === '' && (commandElement.value === '' || commandElement.value === document.querySelector('#defaultcmd').value)) { commandElement.value = drafts[adventureID][pageNum].command; pageContentElement.value = drafts[adventureID][pageNum].pageContent; pageElement.querySelector('input[value="Save"]').disabled = false; } else { setTimeout(() => { window.MSPFA.dialog('Overwrite current page?', document.createTextNode('Doing this will overwrite the page\'s content with what is currently written in the draft. Are you sure?'), ["Yes", "No"], (output, form) => { if (output === "Yes") { commandElement.value = drafts[adventureID][pageNum].command; pageContentElement.value = drafts[adventureID][pageNum].pageContent; pageElement.querySelector('input[value="Save"]').disabled = false; } }); }, 1); } } else if (output === "Delete") { setTimeout(() => { window.MSPFA.dialog('Delete this draft?', document.createTextNode('This action is irreversable! Are you sure?'), ["Yes", "No"], (output, form) => { if (output === "Yes") { loadDrafts(); delete drafts[adventureID][pageNum]; if (settings.drafts && settings.drafts[adventureID] && settings.drafts[adventureID][pageNum]) { delete settings.drafts[adventureID][pageNum]; saveData(settings); } saveDrafts(drafts); } }); }, 1); } }); } const createDraftButton = (form) => { const draftButton = document.createElement('input'); Object.assign(draftButton, { className: 'major draft', type: 'button', value: 'Draft' }); draftButton.addEventListener('click', () => { showDraftDialog(form.id.replace('p', '')); }); return draftButton; } pageLoad(() => { let allPages = document.querySelectorAll('#storypages form:not(#newpage)'); if (allPages.length !== 0) { allPages.forEach(form => { const prevButton = form.querySelector('input[name="preview"]'); prevButton.parentNode.insertBefore(createDraftButton(form), prevButton); prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton); // Preview const previewButton = form.querySelector('input[value=Preview]'); const previewLink = addLink(previewButton, getPreviewLink(form), '_blank'); previewButton.addEventListener('mousedown', () => { previewLink.href = getPreviewLink(form); }); // "Enable keyboard shortcuts" label const shortcutCheck = form.querySelector('input[type="checkbox"]'); shortcutCheck.previousSibling.textContent = ''; shortcutCheck.id = `key-${form.id}`; shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck); }); document.querySelector('input[value="Add"]').addEventListener('click', () => { allPages = document.querySelectorAll('#storypages form:not(#newpage)'); const form = document.querySelector(`#p${allPages.length}`); const prevButton = form.querySelector('input[name="preview"]'); prevButton.parentNode.insertBefore(createDraftButton(form), prevButton); prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton); // Preview link const previewButton = form.querySelector('input[value=Preview]'); const previewLink = addLink(previewButton, getPreviewLink(form), '_blank'); previewButton.addEventListener('mousedown', () => { previewLink.href = getPreviewLink(form); }); // "Enable keyboard shortcuts" label const shortcutCheck = form.querySelector('input[type="checkbox"]'); shortcutCheck.previousSibling.textContent = ''; shortcutCheck.id = `key-${form.id}`; shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck); }); const newForm = document.querySelector('#newpage'); { // "Enable keyboard shortcuts" label const shortcutCheck = newForm.querySelector('input[type="checkbox"]'); shortcutCheck.previousSibling.textContent = ''; shortcutCheck.id = `key-${newForm.id}`; shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck); } const newPreviewButton = newForm.querySelector('input[value=Preview]'); const newPreviewLink = addLink(newPreviewButton, getPreviewLink(newForm), '_blank'); newPreviewButton.addEventListener('mousedown', () => { newPreviewLink.href = getPreviewLink(newForm); }); return true; } }); if (params.click) { if (params.click === 's') { spoilerButton.click(); } else if (params.click === 'd') { accessDraftsButton.click(); } } // Don't scroll after pressing a BBToolbar button (awesome) let lastScroll = window.scrollY; pageLoad(() => { if (document.querySelectorAll('#storypages textarea').length > 1) { document.querySelectorAll('#storypages textarea').forEach(textarea => { textarea.addEventListener('focus', () => { window.scrollTo(window.scrollX, lastScroll); }); }); document.addEventListener('scroll', evt => { lastScroll = window.scrollY; }); return true; } }); // Focus on the text input when clicking on the Color or Background Color BBToolbar buttons const colourButtons = [document.querySelector('#bbtoolbar input[data-tag=color]'), document.querySelector('#bbtoolbar input[data-tag=background]')]; let prevColour; colourButtons[0].addEventListener('click', () => { const inputs = document.querySelectorAll('#dialog input'); document.querySelector('button[data-value="Okay"]').addEventListener('click', () => { prevColour = inputs[0].value; }); if (prevColour) { inputs[0].value = prevColour; inputs[1].value = prevColour; } }); colourButtons.forEach(button => { button.addEventListener('click', () => { document.querySelector('#dialog input[type=text]').select(); }); }); } else if (location.pathname === "/my/profile/") { // Nothing } else if (location.pathname === "/user/") { // Button links pageLoad(() => { const msgButton = document.querySelector('#sendmsg'); if (msgButton) { addLink(msgButton, `/my/messages/new/?u=${params.u}`); addLink(document.querySelector('#favstories'), `/favs/?u=${params.u}`); return true; } }); // Add extra user stats pageLoad(() => { if (window.MSPFA) { const stats = document.querySelector('#userinfo table'); const joinTr = stats.insertRow(1); const joinTextTd = joinTr.insertCell(0); joinTextTd.appendChild(document.createTextNode("Account created:")); const joinDate = joinTr.insertCell(1); const joinTime = document.createElement('b'); joinTime.textContent = "Loading..."; joinDate.appendChild(joinTime); const advCountTr = stats.insertRow(2); const advTextTd = advCountTr.insertCell(0); advTextTd.appendChild(document.createTextNode("Adventures created:")); const advCount = advCountTr.insertCell(1); const advCountText = document.createElement('b'); advCountText.textContent = "Loading..."; advCount.appendChild(advCountText); // Show user creation date window.MSPFA.request(0, { do: "user", u: params.u }, user => { if (typeof user !== "undefined") { joinTime.textContent = new Date(user.d).toString().split(' ').splice(1, 4).join(' '); } // Show created adventures window.MSPFA.request(0, { do: "editor", u: params.u }, s => { if (typeof s !== "undefined") { advCountText.textContent = s.length; } // Show favourites if (document.querySelector('#favstories').style.display !== 'none') { const favCountTr = stats.insertRow(3); const favTextTd = favCountTr.insertCell(0); favTextTd.appendChild(document.createTextNode("Adventures favorited:")); const favCount = favCountTr.insertCell(1); const favCountText = document.createElement('b'); favCountText.textContent = "Loading..."; window.MSPFA.request(0, { do: "favs", u: params.u }, s => { if (typeof s !== "undefined") { favCountText.textContent = s.length; } }); favCount.appendChild(favCountText); } }); }); return true; } }); } else if (location.pathname === "/favs/" && location.search) { const toggleButton = document.createElement('input'); Object.assign(toggleButton, { className: "major", type: "button", value: "Hide Muted Adventures", title: 'Cycle through muted/unmuted adventures' }); const buttonRow = document.querySelector('table.container.alt').insertRow(2); const actionSpan = document.createElement('span'); let stories = []; // Button links pageLoad(() => { stories = document.querySelectorAll('#stories tr'); let favCount = 0; if (stories.length > 0) { stories.forEach(story => { favCount++; const id = story.querySelector('a').href.replace('https://mspfa.com/', ''); pageLoad(() => { if (window.MSPFA.me.i) { addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`); return true; } if (pageLoaded) return true; }); addLink(story.querySelector('.rss.major'), `/rss/${id}`); }); // Fav count const username = document.querySelector('#username'); username.parentNode.appendChild(newBr()); username.parentNode.appendChild(newBr()); username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`)); return true; } if (pageLoaded) return true; }); pageLoad(() => { if (window.MSPFA && window.MSPFA.me) { if (window.MSPFA.me.i === params.u) { const cell = buttonRow.insertCell(0); cell.appendChild(toggleButton); cell.appendChild(newBr()); cell.appendChild(actionSpan); return true; } } if (pageLoaded) return true; }); let type = 0; toggleButton.addEventListener('click', () => { type++; if (type > 2) type = 0; stories.forEach(story => { const unmuted = story.querySelector('.notify').className.includes(' lit'); story.style.display = ''; if (type === 2 && unmuted || type === 1 && !unmuted) { story.style.display = 'none'; } }); if (type === 0) { // show all actionSpan.textContent = ''; } else if (type === 1) { // hide muted toggleButton.value = 'Hide Unmuted Adventures'; } else { // only muted toggleButton.value = 'Show All Adventures'; } }); } else if (location.pathname === "/search/" && location.search) { // Character and word statistics const statTable = document.createElement('table'); const statTbody = document.createElement('tbody'); const statTr = statTbody.insertRow(0); const charCount = statTr.insertCell(0); const wordCount = statTr.insertCell(0); const statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2); const statParentTd = statParentTr.insertCell(0); const statHeaderTr = statTbody.insertRow(0); const statHeader = document.createElement('th'); statHeader.colSpan = '2'; statHeaderTr.appendChild(statHeader); statHeader.textContent = 'Statistics may not be entirely accurate.'; statTable.style.width = "100%"; charCount.textContent = "Character count: loading..."; wordCount.textContent = "Word count: loading..."; statTable.appendChild(statTbody); statParentTd.appendChild(statTable); pageLoad(() => { if (document.querySelector('#pages br')) { const bbc = window.MSPFA.BBC.slice(); bbc.splice(0, 3); window.MSPFA.request(0, { do: "story", s: params.s }, story => { if (typeof story !== "undefined") { const pageContent = []; story.p.forEach(p => { pageContent.push(p.c); pageContent.push(p.b); }); const storyText = pageContent.join(' ') .replace(/\n/g, ' ') .replace(bbc[0][0], '$1') .replace(bbc[1][0], '$1') .replace(bbc[2][0], '$1') .replace(bbc[3][0], '$1') .replace(bbc[4][0], '$2') .replace(bbc[5][0], '$3') .replace(bbc[6][0], '$3') .replace(bbc[7][0], '$3') .replace(bbc[8][0], '$3') .replace(bbc[9][0], '$3') .replace(bbc[10][0], '$2') .replace(bbc[11][0], '$1') .replace(bbc[12][0], '$3') .replace(bbc[13][0], '$3') .replace(bbc[14][0], '') .replace(bbc[16][0], '$1') .replace(bbc[17][0], '$2 $4 $5') .replace(bbc[18][0], '$2 $4 $5') .replace(bbc[19][0], '') .replace(bbc[20][0], '') .replace(/<(.*?)>/g, ''); wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`; charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`; } }); return true; } }); } else if (location.pathname === "/stories/" && location.search) { // Add a button to hide adventures with a tag const tagHide = document.createElement('input'); Object.assign(tagHide, { id: 'taghide', value: '-', className: 'major', type: 'button', style: 'padding: 0;', title: 'Hide Tag' }); const tagAdd = document.querySelector('#tagadd'); tagAdd.parentNode.insertBefore(tagHide, tagAdd.nextSibling); tagAdd.parentNode.insertBefore(document.createTextNode(' '), tagAdd.nextSibling); const tagselect = document.querySelector('#tagselect'); const taglist = document.querySelector('textarea[name=tags]'); tagHide.addEventListener('click', () => { let tags = []; if(taglist.value) { tags = taglist.value.split(","); } if(tagselect.value && tags.indexOf('-'+tagselect.value) == -1) { tags.push('-'+tagselect.value); } taglist.value = tags.join(","); tagselect.options[0].selected = true; }); // Add titles on hover to the [?] and [+] buttons document.querySelector('#taghelp').title = 'Tip'; tagAdd.title = 'Add Tag'; // Click text to check/uncheck boxes ['Ongoing', 'Complete', 'Inactive'].forEach(t => { const check = document.querySelector(`input[name="${t.toLowerCase()}"]`); check.id = `check_${t.toLowerCase()}`; const label = createLabel(' ' + t + ' ', check.id); check.nextSibling.textContent = ''; check.parentNode.insertBefore(label, check.nextSibling); }); const adventureList = document.querySelector('#doit'); const resultAmount = document.createElement('span'); adventureList.parentNode.appendChild(resultAmount); pageLoad(() => { if (window.MSPFA) { window.MSPFA.request(0, { do: "stories", n: params.n, t: params.t, h: params.h, o: params.o, p: params.p, m: 20000 }, (s) => { resultAmount.textContent = `Number of results: ${s.length}`; return true; }); return true; } },1); pageLoad(() => { const stories = document.querySelector('#stories'); if (stories.childNodes.length > 0) { if (params.load && stories.childNodes.length === 1) { stories.querySelector('a').click(); } stories.querySelectorAll('tr').forEach(story => { const storyID = story.querySelector('a.major').href.split('&')[0].replace(/\D/g, ''); addLink(story.querySelector('.rss'), `/rss/?s=${storyID}`); pageLoad(() => { if (window.MSPFA.me.i) { addLink(story.querySelector('.edit.major'), `/my/stories/info/?s=${storyID}`); return true; } if (pageLoaded) return true; }); }); return true; } if (pageLoaded) return true; }); } })();