// ==UserScript==
// @name MSPFA extras
// @namespace http://tampermonkey.net/
// @version 1.6.5.1
// @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/?s=*
// @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.6.5.1";
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
*
* 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.
*/
// 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) => {
const link = document.createElement('a');
link.href = url;
elm.parentNode.insertBefore(link, elm);
link.appendChild(elm);
};
// Returns true if version 2 is newer
const compareVer = (ver1, ver2) => {
ver1 = ver1.split(/\./); // current version
ver2 = ver2.split(/\./); // new version
ver1.push(0);
ver1.push(0);
ver2.push(0);
ver2.push(0);
if (parseInt(ver2[0]) > parseInt(ver1[0])) { // 1.x.x.x
return true;
} else if (parseInt(ver2[1]) > parseInt(ver1[1])) { // x.1.x.x
return true;
} else if (parseInt(ver2[2]) > parseInt(ver1[2])) { // x.x.1.x
return true;
} else if (parseInt(ver2[3]) > parseInt(ver1[3]) && parseInt(ver2[2]) === parseInt(ver1[2])) { // x.x.x.1
return true;
}
return false;
}
// Easy br element
const newBr = () => {
return document.createElement('br');
}
let settings = {};
let drafts = {};
const defaultSettings = {
autospoiler: false,
style: 0,
styleURL: "",
night: false,
auto502: true,
textFix: false,
pixelFix: false,
intro: false,
autoUpdate: true,
commandScroll: false,
preload: true,
version: currentVersion,
spoilerValues: {}
}
let pageLoaded = false;
// Load any previous settings from localStorage
if (localStorage.mspfaextra) {
Object.assign(settings, JSON.parse(localStorage.mspfaextra));
Object.assign(drafts, JSON.parse(localStorage.mspfadrafts));
// 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();
// Update saved version to the version used in the script to prevent unnecessary notifications
if (compareVer(settings.version, currentVersion)) {
settings.version = currentVersion;
saveData(settings);
}
// 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: () => {
return drafts;
},
getDraftsString: (formatted) => {
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);
}
}
// 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.boxShadow = '1px 1px 5px red, -1px -1px 5px red, -1px 1px 5px red, -1px 1px 5px red';
selected.style.transition = '0.5s';
pageLoad(() => {
if (pageLoaded) {
selected.style.boxShadow = '';
}
});
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);
}
// Delete any unchanged spoiler values or empty drafts
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];
}
});
// Go through and remove adventures with empty drafts
Object.keys(drafts).forEach(adventure => {
if (Object.keys(drafts[adventure]).length === 0) {
delete drafts[adventure];
}
});
}
const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', '/css/?s=36237', '/css/theme4.css', '/css/theme5.css'];
// Dropdown menu
const myLink = document.querySelector('nav a[href="/my/"]');
if (myLink) {
const dropDiv = document.createElement('div');
dropDiv.className = 'dropdown';
Object.assign(dropDiv.style, {
position: 'relative',
display: 'inline-block',
backgroundColor: 'inherit'
});
const dropContent = document.createElement('div');
dropContent.className = 'dropdown-content';
Object.assign(dropContent.style, {
display: 'none',
backgroundColor: 'inherit',
position: 'absolute',
textAlign: 'left',
minWidth: '100px',
marginLeft: '-5px',
padding: '2px',
zIndex: '1',
borderRadius: '0 0 5px 5px'
});
dropDiv.addEventListener('mouseenter', evt => {
dropContent.style.display = 'block';
dropContent.style.color = getComputedStyle(myLink).color;
});
dropDiv.addEventListener('mouseleave', evt => {
dropContent.style.display = 'none';
});
myLink.parentNode.insertBefore(dropDiv, myLink);
dropDiv.appendChild(myLink);
dropDiv.appendChild(dropContent);
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];
newLink.style = 'visibility: visible; word-spacing: normal; letter-spacing: normal; font-size: 10px;';
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 newLink = document.createElement('a');
newLink.textContent = "My Profile";
newLink.href = `/user/?u=${window.MSPFA.me.i}`;
newLink.style = 'visibility: visible; word-spacing: normal; letter-spacing: normal; font-size: 10px;';
dropContent.appendChild(newLink);
return true;
}
}
});
}
// Error reloading
window.addEventListener("load", () => {
pageLoaded = true;
// Reload the page if 502 CloudFlare error page appears
if (settings.auto502 && document.querySelector('.cf-error-overview')) {
window.location.reload();
}
// 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);
}
});
// 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 for updates by comparing currentVersion to text data from an adventure that has update text and info
const checkForUpdates = (evt) => {
fetch(`https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/update.txt`, { cache:'no-store' }).then((req) => {
if (req.ok) {
return req.text().then(r => {
const version = /^{(.*?)}/.exec(r)[1];
const content = r.replace(/^{(.*?)}\r\n/, '');
if (compareVer(settings.version, version) || (evt && evt.type === 'click')) {
const msg = window.MSPFA.parseBBCode(content);
settings.version = version;
saveData(settings);
window.MSPFA.dialog(`MSPFA extras update! (${version})`, msg, ["Opt-out", "Dismiss", "Update"], (output, form) => {
if (output === "Update") {
window.open('https://greasyfork.org/en/scripts/396798-mspfa-extras', '_blank').focus();
} else if (output === "Opt-out") {
settings.autoUpdate = false;
saveData(settings);
}
});
}
});
}
});
};
// Check for updates and show intro dialog if needed
pageLoad(() => {
if (window.MSPFA) {
if (settings.autoUpdate) {
checkForUpdates();
}
if (!settings.intro) {
showIntroDialog();
settings.intro = true;
saveData(settings);
}
return true;
}
});
// Link to get colour from
const linkColour = document.createElement('a');
linkColour.href = "/";
linkColour.id = "linkColour";
document.body.appendChild(linkColour);
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.style = 'cursor: pointer; text-decoration: underline;';
introLink.style.transition = '.2s';
introLink.style.color = getComputedStyle(linkColour).color;
introLink.className = 'intro-link';
introLink.addEventListener('click', showIntroDialog);
details.appendChild(introLink);
// vbar!!!!
const vbar = document.createElement('span');
Object.assign(vbar, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
details.appendChild(vbar);
// Add 'link' at the bottom to show the update dialog again
const updateLink = document.createElement('a');
updateLink.textContent = 'View Update';
updateLink.style = 'cursor: pointer; text-decoration: underline;';
updateLink.style.transition = '.2s';
updateLink.style.color = getComputedStyle(linkColour).color;
updateLink.className = 'intro-link';
updateLink.addEventListener('click', checkForUpdates);
details.appendChild(updateLink);
// vbar 2!!!!
const vbar2 = document.createElement('span');
Object.assign(vbar2, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
details.appendChild(vbar2);
// 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;
setTimeout(() => {
introLink.style.color = getComputedStyle(linkColour).color;
updateLink.style.color = getComputedStyle(linkColour).color;
}, 1500);
}
if (!document.querySelector('#theme')) {
document.querySelector('head').appendChild(theme);
if (settings.night) {
updateTheme('/css/?s=36237');
} else {
updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
}
}
// Dropdown menu and pixelated scaling
const dropStyle = document.createElement('style');
const pixelFixText = 'img, .mspfalogo, .major, .arrow, #flashytitle, .heart, .fav, .notify, .edit, .rss, input, #loading { image-rendering: pixelated !important; }';
const dropStyleText = `#notification { z-index: 2; } .dropdown-content a { color: inherit; padding: 2px; text-decoration: underline; display: block;}`;
if (!document.querySelector('#dropdown-style')) {
dropStyle.id = 'dropdown-style';
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
document.querySelector('head').appendChild(dropStyle);
}
// Enabling night mode.
document.querySelector('footer .mspfalogo').addEventListener('click', evt => {
settings.night = !settings.night;
saveData(settings);
// Transition to make it feel nicer on the eyes
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + '';
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
if (settings.night) {
updateTheme('/css/?s=36237');
} else {
updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
}
setTimeout(() => {
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
}, 1000);
console.log(`Night mode turned ${settings.night ? 'on' : 'off'}.`);
});
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 the next page
if (settings.preload) {
const preloadImages = document.createElement('div');
preloadImages.id = 'preload';
preloadImages.style.display = 'none';
document.querySelector('#container').appendChild(preloadImages);
window.MSPFA.slide.push(p => {
preloadImages.innerHTML = '';
if (window.MSPFA.story.p[p-2]) {
window.MSPFA.parseBBCode(window.MSPFA.story.p[p-2].b).querySelectorAll('img').forEach(image => {
preloadImages.appendChild(image);
});
}
if (window.MSPFA.story.p[p]) {
window.MSPFA.parseBBCode(window.MSPFA.story.p[p].b).querySelectorAll('img').forEach(image => {
preloadImages.appendChild(image);
});
}
});
}
// 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
if (settings.textFix) temp--; // Text Fix adds a page load, so it needs an extra value to not activate
window.MSPFA.slide.push((p) => {
if (temp < 0) {
temp++;
} else {
window.scroll(0, heightTop);
}
});
}
// 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());
});
}
// Show creation date
pageLoad(() => {
if (document.querySelector('#infobox tr td:nth-child(2)')) {
document.querySelector('#infobox tr td:nth-child(2)').appendChild(document.createTextNode('Creation date: ' + new Date(window.MSPFA.story.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) {
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);
});
window.MSPFA.page(currentPage);
return true;
}
});
}
// Turn buttons into links
const pageButton = document.createElement('button');
const pageLink = document.createElement('a');
pageLink.href = `/my/stories/pages/?s=${params.s}#p${params.p}`;
pageButton.className = 'pages edit major';
pageButton.type = 'button';
pageButton.title = 'Edit Pages';
pageLink.style.marginRight = '9.5px';
pageButton.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAL1JREFUOI1jYKAQMCJzugoL/+NSePPhQ4a569YxoouzoAuU9vUx3AoLY1BbtYqBgYGB4d21a8jS/9ENwTBg/ezZDNpQGh2oy8szJAcFoRiCYUBgairDrd27Gdz9/Bh+vH2LInfn+nUMQ3G6YOemTRiKsQEMA9z9/Bie7N7NYG9tjaGYKBfs3LSJQZuBgeHg0aPkucDe2prhDSUuOHj06Ih3wdw5cxgYZGQYGObMId0FNx8+JKhBQlGRKIOJBgBpTFVYzRG//QAAAABJRU5ErkJggg==")';
pageLink.appendChild(pageButton);
// Edit pages button & button link
pageLoad(() => {
const infoButton = document.querySelector('.edit.major');
if (infoButton) {
pageLoad(() => {
if (window.MSPFA.me.i) {
infoButton.title = "Edit Info";
infoButton.parentNode.insertBefore(pageLink, 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;
}
if (pageLoaded) return true;
});
addLink(document.querySelector('.rss.major'), `/rss/?s=${params.s}`);
return true;
}
});
// Add "Reply" button to comment gear
document.body.addEventListener('click', evt => {
if (evt.toElement.classList.contains('gear')) {
const userID = evt.path[2].classList[2].replace('u', '');
const reportButton = document.querySelector('#dialog button[data-value="Report"]');
const replyButton = document.createElement('button');
replyButton.classList.add('major');
replyButton.type = 'submit';
replyButton.setAttribute('data-value', 'Reply');
replyButton.textContent = 'Reply';
replyButton.style = 'margin-right: 9.5px';
reportButton.parentNode.insertBefore(replyButton, reportButton);
replyButton.addEventListener('click', evt => {
document.querySelector('#dialog button[data-value="Cancel"]').click();
const commentBox = document.querySelector('#commentbox textarea');
commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
commentBox.focus();
});
} else return;
});/**/
}
}
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) => {
const optionTr = plusTbody.insertRow(plusTbody.childNodes.length);
const optionTextTd = optionTr.insertCell(0);
const optionInputTd = optionTr.insertCell(1);
const optionInput = document.createElement('input');
optionInputTd.appendChild(optionInput);
optionTextTd.textContent = text;
optionInput.type = "checkbox";
optionInput.checked = checked;
return optionInput;
}
const spoilerInput = createCheckbox("Automatically open spoilers:", settings.autospoiler);
const preloadInput = createCheckbox("Preload images for the pages immediately before and after:", settings.preload);
const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", settings.auto502);
const commandScrollInput = createCheckbox("Scroll back up to the nav bar when switching page:", settings.commandScroll);
const updateInput = createCheckbox("Automatically check for updates:", settings.autoUpdate);
const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", settings.pixelFix);
const textFixInput = createCheckbox("Attempt to fix text errors (experimental)*:", settings.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.style = 'margin: 0 9.5px;';
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(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.style = 'max-height: 250px; overflow-y: scroll; border: 1px solid grey; padding: 2px; width: 100%; text-align: center; vertical-align: middle;';
const listTbody = document.createElement('tbody');
listTable.appendChild(listTbody);
if (Object.keys(drafts).length === 0) {
draftButton.disabled = true;
}
draftButton.addEventListener('click', () => {
draftMsg.appendChild(listTable);
listTbody.innerHTML = '';
Object.keys(drafts).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=d`, 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 draft?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
if (output === "Yes") {
delete drafts[adv];
saveDrafts(drafts);
setTimeout(() => {
draftButton.click();
}, 1);
if (Object.keys(drafts).length === 0) {
draftButton.disabled = true;
}
}
});
}, 1);
});
}
});
});
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") {
drafts = {};
saveDrafts(drafts);
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);
draftButton.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.autoUpdate = updateInput.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]);
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
setTimeout(() => {
dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
}, 1000);
});
}
else if (location.pathname === "/my/messages/") { // New buttons
const btnStyle = "margin: 10px 5px;";
// Select all read messages button.
const selRead = document.createElement('input');
selRead.style = btnStyle;
selRead.value = "Select Read";
selRead.id = "selectread";
selRead.classList.add("major");
selRead.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');
selDupe.style = btnStyle;
selDupe.value = "Select Same";
selDupe.id = "selectdupe";
selDupe.classList.add("major");
selDupe.type = "button";
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) => {
let title = msg.querySelector('a.major').textContent;
if (/^New update: /.test(title)) { // Select only adventure updates
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();
}
}
});
});
// 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(selDupe);
}
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}`);
}
});
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');
guideLink.href = links[i];
guideLink.textContent = guides[i];
guideLink.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}`);
}
else if (location.pathname === "/my/stories/pages/" && location.search) {
const adventureID = params.s;
if (!drafts[adventureID]) {
drafts[adventureID] = {}
}
// Button links
addLink(document.querySelector('#editinfo'), `/my/stories/info/?s=${adventureID}`);
// Default spoiler values
const replaceButton = document.querySelector('#replaceall');
const spoilerButton = document.createElement('input');
spoilerButton.classList.add('major');
spoilerButton.value = 'Default Spoiler Values';
spoilerButton.type = 'button';
spoilerButton.id = 'spoilers';
replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
replaceButton.parentNode.insertBefore(newBr(), replaceButton);
replaceButton.parentNode.insertBefore(newBr(), replaceButton);
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);
if (!settings.spoilerValues[adventureID]) {
settings.spoilerValues[adventureID] = {
open: 'Show',
close: 'Hide'
}
}
spoilerOpen.value = settings.spoilerValues[adventureID].open;
spoilerClose.value = settings.spoilerValues[adventureID].close;
spoilerButton.addEventListener('click', evt => {
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 = settings.spoilerValues[adventureID].open;
document.querySelector('#dialog input[name="close"]').value = settings.spoilerValues[adventureID].close;
document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open;
document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close;
});
// Buttonless spoilers
const flashButton = document.querySelector('input[title="Flash');
const newSpoilerButton = document.createElement('input');
newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
newSpoilerButton.title = 'Buttonless Spoiler';
newSpoilerButton.type = 'button';
newSpoilerButton.style = 'background-position: -66px -88px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAAXNSR0IArs4c6QAAARNJREFUOI3V1LFqhEAQBuDfPaxcLkFwsbhgJ2STWlhQG4twvT7IWecZ8iB3L5In2BfIbaVNglaipMkQPAiBMAnkr2Zg52OZYoD/lg0AKKVMkiQmiqK7russ9cMwvPm+v03T9MHzvKtlWUD1OI5nrXVzOUO9AICyLA9cPzXGfFp1XR+54JWllDJcMFkCAIQQr1wwWQIAiqJouWCyBADM8/zCBa+sMAy3XDBZAgCqqnrkgsmiVVxzwSsrjuNbLpgsAQB5nt9zwWSJj77hgskSAOCcO3GpnNbX+Y0jJL57+NNsAEBKuTPGtFrrxlp7yrLsYIxp+74/B0Fws9/vn6SUu2maPKqdc891XR8vZwDAWvsHe+bOOy65ZXyuZ2jeAAAAAElFTkSuQmCC");';
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);
// Open preview in new tab with middle mouse
document.body.addEventListener('mouseup', evt => {
if (evt.toElement.value === "Preview" && evt.button === 1) {
evt.toElement.click(); // TODO: Find a way to prevent the middle mouse scroll after clicking there.
evt.preventDefault();
return false;
}
});
// -- Drafts --
// Accessing draft text
const accessDraftsButton = document.createElement('input');
accessDraftsButton.classList.add('major');
accessDraftsButton.value = 'Saved Drafts';
accessDraftsButton.type = 'button';
accessDraftsButton.id = 'drafts';
replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton);
accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
accessDraftsButton.addEventListener('click', () => {
const draftDialog = window.MSPFA.parseBBCode('Use the textbox below to copy out the data and save to a file somewhere else.\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;';
draftInputTextarea.rows = 8;
draftDialog.appendChild(newBr());
draftDialog.appendChild(newBr());
draftDialog.appendChild(draftInputTextarea);
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") {
drafts[adventureID] = {};
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;
}
drafts[adventureID] = newData;
saveDrafts(drafts);
}
});
}, 1);
}
}
});
});
// Draft stuff
const msg = document.createElement('span');
msg.appendChild(document.createTextNode('Command:'));
msg.appendChild(document.createElement('br'));
const commandInput = document.createElement('input');
commandInput.style = 'width: 100%; box-sizing: border-box;';
commandInput.readOnly = true;
commandInput.value = 'yes';
msg.appendChild(commandInput);
msg.appendChild(document.createElement('br'));
msg.appendChild(document.createElement('br'));
msg.appendChild(document.createTextNode('Body:'));
const bodyInput = document.createElement('textarea');
bodyInput.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
bodyInput.readOnly = true;
bodyInput.rows = 8;
bodyInput.textContent = '';
msg.appendChild(bodyInput);
const showDraftDialog = (pageNum) => {
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") {
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") {
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 unreversable! Are you sure?'), ["Yes", "No"], (output, form) => {
if (output === "Yes") {
delete drafts[adventureID][pageNum];
saveDrafts(drafts);
}
});
}, 1);
}
});
}
const createDraftButton = (form) => {
const draftButton = document.createElement('input');
draftButton.className = 'major draft';
draftButton.type = 'button';
draftButton.value = 'Draft';
draftButton.style = 'margin-right: 9.5px;';
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);
});
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);
});
return true;
}
});
if (params.click) {
if (params.click === 's') {
spoilerButton.click();
} else if (params.click === 'd') {
accessDraftsButton.click();
}
}
/* // Removed because apparently MSPFA already does this fine!
if (hashSearch) {
pageLoad(() => {
const element = document.querySelector(hashSearch);
if (element) {
if (element.style.display === "none") {
element.style = '';
}
return true;
}
});
}/**/
}
else if (location.pathname === "/my/profile/") {
// Add profile CSS box
/*
pageLoad(() => {
if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) {
// add 15 to char cap for <style></style>
// replace
const bio = document.querySelector('textarea[name="userdesc"]');
const styleRow = document.querySelector('#editprofile tbody').insertRow(7);
styleRow.style = 'text-align: left;';
const styleCell = styleRow.insertCell(0);
styleCell.colSpan = 2;
const styleTextarea = document.createElement('textarea');
styleTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
styleTextarea.rows = 8;
styleCell.appendChild(document.createTextNode('Custom profile style:'));
styleCell.appendChild(newBr());
styleCell.appendChild(styleTextarea);
styleTextarea.value = /<style>(.*?)<\/style>/i.exec(bio.value)[1];
bio.value = bio.value.replace(/<style>(.*?)<\/style>/i, '');
return true;
}
},0.25);/**/
}
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 adventure 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) {
// Button links
pageLoad(() => {
const 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;
});
}
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) {
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) {
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;
});
}
})();