MSPFA extras

Adds custom quality of life features to MSPFA.

当前为 2020-08-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MSPFA extras
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.6.2
  5. // @description Adds custom quality of life features to MSPFA.
  6. // @author seymour schlong
  7. // @icon https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
  8. // @icon64 https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
  9. // @match https://mspfa.com/
  10. // @match https://mspfa.com/*/
  11. // @match https://mspfa.com/*/?*
  12. // @match https://mspfa.com/?s=*
  13. // @match https://mspfa.com/my/*
  14. // @match https://mspfa.com/random/
  15. // @grant none
  16. // ==/UserScript==
  17.  
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. const currentVersion = "1.6.2";
  23. console.log(`MSPFA extras script v${currentVersion} by seymour schlong`);
  24.  
  25. /**
  26. * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true
  27. * Github to-do completion list
  28. *
  29. * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu - February 23rd, 2020
  30. * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes - February 23rd, 2020
  31. * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates - February 23rd, 2020
  32. * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates - February 23rd, 2020
  33. * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - July 21st, 2020
  34. * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count - July 21st, 2020
  35. * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values - August 7th, 2020
  36. * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers - August 7th, 2020
  37. * https://github.com/GrantGryczan/MSPFA/issues/52 - Hash URLs - August 8th, 2020
  38. * - Page drafts - August 8th, 2020
  39. * - Edit pages button - August 8th, 2020
  40. *
  41. * Extension to-do... maybe...
  42. *
  43. * 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.
  44. */
  45.  
  46. // A general function that allows for waiting until a certain element appears on the page.
  47. const pageLoad = (fn, length) => {
  48. const interval = setInterval(() => {
  49. if (fn()) clearInterval(interval);
  50. }, length ? length*1000 : 500);
  51. };
  52.  
  53. // Saves the options data for the script.
  54. const saveData = (data) => {
  55. localStorage.mspfaextra = JSON.stringify(data);
  56. };
  57.  
  58. // Encases an element within a link
  59. const addLink = (elm, url) => {
  60. const link = document.createElement('a');
  61. link.href = url;
  62. elm.parentNode.insertBefore(link, elm);
  63. link.appendChild(elm);
  64. };
  65.  
  66. // Returns true if version 2 is newer
  67. const compareVer = (ver1, ver2) => {
  68. ver1 = ver1.split(/\./); // current version
  69. ver2 = ver2.split(/\./); // new version
  70. ver1.push(0);
  71. ver1.push(0);
  72. ver2.push(0);
  73. ver2.push(0);
  74. if (parseInt(ver2[0]) > parseInt(ver1[0])) { // 1.x.x.x
  75. return true;
  76. } else if (parseInt(ver2[1]) > parseInt(ver1[1])) { // x.1.x.x
  77. return true;
  78. } else if (parseInt(ver2[2]) > parseInt(ver1[2])) { // x.x.1.x
  79. return true;
  80. } else if (parseInt(ver2[3]) > parseInt(ver1[3])) { // x.x.x.1
  81. return true;
  82. }
  83. return false;
  84. }
  85.  
  86. // Easy br element
  87. const newBr = () => {
  88. return document.createElement('br');
  89. }
  90.  
  91. let settings = {};
  92. const defaultSettings = {
  93. autospoiler: false,
  94. style: 0,
  95. styleURL: "",
  96. night: false,
  97. auto502: true,
  98. textFix: false,
  99. pixelFix: false,
  100. intro: false,
  101. autoUpdate: false,
  102. version: currentVersion,
  103. spoilerValues: {},
  104. drafts: {}
  105. }
  106.  
  107. // Load any previous settings from localStorage
  108. if (localStorage.mspfaextra) {
  109. Object.assign(settings, JSON.parse(localStorage.mspfaextra));
  110. }
  111.  
  112. // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
  113. const checkSettings = () => {
  114. const defaultSettingsKeys = Object.keys(defaultSettings);
  115. for (let i = 0; i < defaultSettingsKeys.length; i++) {
  116. if (typeof settings[defaultSettingsKeys[i]] === "undefined") {
  117. settings[defaultSettingsKeys[i]] = defaultSettings[defaultSettingsKeys[i]];
  118. }
  119. }
  120. saveData(settings);
  121. }
  122.  
  123. checkSettings();
  124.  
  125. // Update saved version to the version used in the script to prevent unnecessary notifications
  126. if (compareVer(settings.version, currentVersion)) {
  127. settings.version = currentVersion;
  128. saveData(settings);
  129. }
  130.  
  131. // Functions to get/change data from the console
  132. window.MSPFAe = {
  133. getSettings: () => {
  134. return settings;
  135. },
  136. getSettingsString: (formatted) => {
  137. if (formatted) {
  138. console.log(JSON.stringify(settings, null, 4));
  139. } else {
  140. console.log(JSON.stringify(settings));
  141. }
  142. },
  143. changeSettings: (newSettings) => {
  144. console.log('Settings updated');
  145. console.log(settings);
  146. Object.assign(settings, newSettings);
  147. saveData(settings);
  148. },
  149. changeSettingsString: (fullString) => {
  150. try {
  151. JSON.parse(fullString);
  152. } catch (err) {
  153. console.error(err);
  154. return;
  155. }
  156. settings = JSON.parse(fullString);
  157. checkSettings();
  158. console.log(settings);
  159. }
  160. }
  161.  
  162. // Delete any unchanged spoiler values or empty drafts
  163. if (location.pathname !== "/my/stories/pages/") {
  164. // Go through spoiler values and remove any that aren't unique
  165. Object.keys(settings.spoilerValues).forEach(adventure => {
  166. if (settings.spoilerValues[adventure].open === "Show" && settings.spoilerValues[adventure].close === "Hide") {
  167. delete settings.spoilerValues[adventure];
  168. } else if (settings.spoilerValues[adventure].open === '' && settings.spoilerValues[adventure].close === '') {
  169. delete settings.spoilerValues[adventure];
  170. }
  171. });
  172. // Go through and remove adventures with empty drafts
  173. Object.keys(settings.drafts).forEach(adventure => {
  174. if (Object.keys(settings.drafts[adventure]).length === 0) {
  175. delete settings.drafts[adventure];
  176. }
  177. });
  178. }
  179.  
  180. const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
  181. const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', '/css/?s=36237', '/css/theme4.css', '/css/theme5.css'];
  182.  
  183. // Dropdown menu
  184. const myLink = document.querySelector('nav a[href="/my/"]');
  185. if (myLink) {
  186. const dropDiv = document.createElement('div');
  187. dropDiv.className = 'dropdown';
  188. Object.assign(dropDiv.style, {
  189. position: 'relative',
  190. display: 'inline-block',
  191. backgroundColor: 'inherit'
  192. });
  193.  
  194. const dropContent = document.createElement('div');
  195. dropContent.className = 'dropdown-content';
  196. Object.assign(dropContent.style, {
  197. display: 'none',
  198. backgroundColor: 'inherit',
  199. position: 'absolute',
  200. textAlign: 'left',
  201. minWidth: '100px',
  202. marginLeft: '-5px',
  203. padding: '2px',
  204. zIndex: '1',
  205. borderRadius: '0 0 5px 5px'
  206. });
  207.  
  208. dropDiv.addEventListener('mouseenter', evt => {
  209. dropContent.style.display = 'block';
  210. dropContent.style.color = getComputedStyle(myLink).color;
  211. });
  212. dropDiv.addEventListener('mouseleave', evt => {
  213. dropContent.style.display = 'none';
  214. });
  215.  
  216. myLink.parentNode.insertBefore(dropDiv, myLink);
  217. dropDiv.appendChild(myLink);
  218. dropDiv.appendChild(dropContent);
  219.  
  220. const dLinks = [];
  221. dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ];
  222. dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ];
  223.  
  224. for (let i = 0; i < dLinks[0].length; i++) {
  225. const newLink = document.createElement('a');
  226. newLink.textContent = dLinks[0][i];
  227. newLink.href = dLinks[1][i];
  228. dropContent.appendChild(newLink);
  229. }
  230.  
  231. // Append "My Profile" to the dropdown list if you're signed in, or "Login" if you're not
  232. pageLoad(() => {
  233. if (window.MSPFA) {
  234. if (window.MSPFA.me.n) {
  235. const newLink = document.createElement('a');
  236. newLink.textContent = "My Profile";
  237. newLink.href = `/user/?u=${window.MSPFA.me.i}`;
  238. dropContent.appendChild(newLink);
  239. return true;
  240. }
  241. }
  242. });
  243. }
  244.  
  245. const hashSearch = location.href.replace(location.origin + location.pathname, '').replace(location.search, '');
  246. if (hashSearch !== '') {
  247. pageLoad(() => {
  248. const idElement = document.querySelector(hashSearch);
  249. if (idElement) {
  250. document.querySelector(hashSearch).scrollIntoView();
  251. return true;
  252. }
  253. }, 1);
  254. }
  255.  
  256. // Error reloading
  257. window.addEventListener("load", () => {
  258. // Reload the page if 502 CloudFlare error page appears
  259. if (settings.auto502 && document.querySelector('.cf-error-overview')) {
  260. window.location.reload();
  261. }
  262.  
  263. // Wait five seconds, then refresh the page
  264. if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") {
  265. setTimeout(() => {
  266. window.location.reload();
  267. }, 5000);
  268. }
  269. });
  270.  
  271. // Message that shows when you first get the script
  272. const showIntroDialog = () => {
  273. 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]');
  274. window.MSPFA.dialog("MSPFA extras message", msg, ["Okay"]);
  275. }
  276.  
  277. // Check for updates by comparing currentVersion to text data from an adventure that has update text and info
  278. const checkForUpdates = (evt) => {
  279. window.MSPFA.request(0, {
  280. do: "story",
  281. s: "36596"
  282. }, story => {
  283. if (typeof story !== "undefined") {
  284. const ver = settings.version.split(/\./);
  285. const newVer = story.p[1].c.split(/\./);
  286. // compare versions
  287. if (compareVer(settings.version, story.p[1].c) || (evt && evt.type === 'click')) {
  288. const msg = window.MSPFA.parseBBCode(story.p[1].b);
  289. settings.version = story.p[1].c;
  290. saveData(settings);
  291. window.MSPFA.dialog(`MSPFA extras update! (${story.p[1].c})`, msg, ["Opt-out", "Dismiss", "Update"], (output, form) => {
  292. if (output === "Update") {
  293. window.open('https://greasyfork.org/en/scripts/396798-mspfa-extras', '_blank').focus();
  294. } else if (output === "Opt-out") {
  295. settings.autoUpdate = false;
  296. saveData(settings);
  297. }
  298. });
  299. }
  300. }
  301. });
  302. };
  303.  
  304.  
  305. // Check for updates and show intro dialog if needed
  306. pageLoad(() => {
  307. if (window.MSPFA) {
  308. if (settings.autoUpdate) {
  309. checkForUpdates();
  310. }
  311.  
  312. if (!settings.intro) {
  313. showIntroDialog();
  314. settings.intro = true;
  315. saveData(settings);
  316. }
  317. return true;
  318. }
  319. });
  320.  
  321. const linkColour = document.createElement('a');
  322. linkColour.href = "/";
  323. linkColour.id = "linkColour";
  324. document.body.appendChild(linkColour);
  325. const details = document.querySelector('#details');
  326. // Add 'link' at the bottom to show the intro dialog again
  327. const introLink = document.createElement('a');
  328. introLink.textContent = 'View Script Message';
  329. introLink.style = 'cursor: pointer; text-decoration: underline;';
  330. introLink.style.transition = '.2s';
  331. introLink.style.color = getComputedStyle(linkColour).color;
  332. introLink.className = 'intro-link';
  333. introLink.addEventListener('click', showIntroDialog);
  334. details.appendChild(introLink);
  335.  
  336. // vbar!!!!
  337. const vbar = document.createElement('span');
  338. Object.assign(vbar, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
  339. details.appendChild(vbar);
  340.  
  341. // Add 'link' at the bottom to show the update dialog again
  342. const updateLink = document.createElement('a');
  343. updateLink.textContent = 'View Update';
  344. updateLink.style = 'cursor: pointer; text-decoration: underline;';
  345. updateLink.style.transition = '.2s';
  346. updateLink.style.color = getComputedStyle(linkColour).color;
  347. updateLink.className = 'intro-link';
  348. updateLink.addEventListener('click', checkForUpdates);
  349. details.appendChild(updateLink);
  350.  
  351. // vbar 2!!!!
  352. const vbar2 = document.createElement('span');
  353. Object.assign(vbar2, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
  354. details.appendChild(vbar2);
  355.  
  356. // if you really enjoy the script and has some extra moneys 🥺
  357. const donateLink = document.createElement('a');
  358. donateLink.textContent = 'Donate';
  359. donateLink.href = 'https://ko-fi.com/ironbean';
  360. donateLink.target = "blank";
  361. details.appendChild(donateLink);
  362.  
  363. // Theme stuff
  364. const theme = document.createElement('link');
  365. Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' });
  366. const updateTheme = (src) => {
  367. theme.href = src;
  368. setTimeout(() => {
  369. introLink.style.color = getComputedStyle(linkColour).color;
  370. updateLink.style.color = getComputedStyle(linkColour).color;
  371. }, 1500);
  372. }
  373. if (!document.querySelector('#theme') && !/^\/css\/|^\/js\//.test(location.pathname)) {
  374. document.querySelector('head').appendChild(theme);
  375. if (settings.night) {
  376. updateTheme('/css/?s=36237');
  377. } else {
  378. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  379. }
  380. }
  381.  
  382. // Dropdown menu and pixelated scaling
  383. const dropStyle = document.createElement('style');
  384. const pixelFixText = 'img, .mspfalogo, .major, .arrow, #flashytitle, .heart, .fav, .notify, .edit, .rss, input, #loading { image-rendering: pixelated !important; }';
  385. const dropStyleText = `#notification { z-index: 2; } .dropdown-content a { color: inherit; padding: 2px; text-decoration: underline; display: block;}`;
  386. if (!document.querySelector('#dropdown-style')) {
  387. dropStyle.id = 'dropdown-style';
  388. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  389. //dropdownStyle.textContent = '#notification { z-index: 2;}.dropdown:hover .dropdown-content { display: block;}.dropdown { position: relative; display: inline-block; background-color: inherit;}.dropdown-content { display: none; position: absolute; text-align: left; background-color: inherit; min-width: 100px; margin-left: -5px; padding: 2px; z-index: 1; border-radius: 0 0 5px 5px;}.dropdown-content a { color: #fffa36; padding: 2px 2px; text-decoration: underline; display: block;}';
  390.  
  391. document.querySelector('head').appendChild(dropStyle);
  392. }
  393.  
  394. // Remove the current theme if the adventure has CSS (to prevent conflicts);
  395. pageLoad(() => {
  396. if (window.MSPFA) {
  397. if (window.MSPFA.story && window.MSPFA.story.y && (window.MSPFA.story.y.toLowerCase().includes('import') || window.MSPFA.story.y.includes('{'))) {
  398. updateTheme('');
  399. return true;
  400. }
  401. }
  402. });
  403.  
  404. // Enabling night mode.
  405. document.querySelector('footer .mspfalogo').addEventListener('click', evt => {
  406. settings.night = !settings.night;
  407. saveData(settings);
  408.  
  409. // Transition to make it feel nicer on the eyes
  410. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + '';
  411. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
  412.  
  413. if (settings.night) {
  414. updateTheme('/css/?s=36237');
  415. } else {
  416. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  417. }
  418.  
  419. setTimeout(() => {
  420. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  421. }, 1000);
  422.  
  423. console.log(`Night mode turned ${settings.night ? 'on' : 'off'}.`);
  424. });
  425.  
  426. if (location.pathname === "/" || location.pathname === "/preview/") {
  427. if (location.search) {
  428. // Automatic spoiler opening
  429. if (settings.autospoiler) {
  430. window.MSPFA.slide.push((p) => {
  431. document.querySelectorAll('#slide .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click());
  432. });
  433. }
  434.  
  435. // Show creation date
  436. pageLoad(() => {
  437. if (document.querySelector('#infobox tr td:nth-child(2)')) {
  438. 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(' ')));
  439. return true;
  440. }
  441. });
  442.  
  443. // Hash scrolling and opening infobox or commmentbox
  444. if (['#infobox', '#commentbox', '#latestpages'].indexOf(hashSearch) !== -1) {
  445. pageLoad(() => {
  446. if (document.querySelector(hashSearch)) {
  447. if (hashSearch === '#infobox') {
  448. document.querySelector('input[data-open="Show Adventure Info"]').click();
  449. } else if (hashSearch === '#commentbox') {
  450. document.querySelector('input[data-open="Show Comments"]').click();
  451. } else if (hashSearch === '#latestpages') {
  452. document.querySelector('input[data-open="Show Adventure Info"]').click();
  453. document.querySelector('input[data-open="Show Latest Pages"]').click();
  454. }
  455. return true;
  456. }
  457. });
  458. }
  459.  
  460. // Attempt to fix text errors
  461. if (settings.textFix) {
  462. pageLoad(() => {
  463. if (window.MSPFA.story && window.MSPFA.story.p) {
  464. // russian/bulgarian is not possible =(
  465. const currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]);
  466. const library = [
  467. ["&acirc;��", "'"],
  468. ["&Atilde;�", "Ñ"],
  469. ["&Atilde;&plusmn;", "ñ"],
  470. ["&Atilde;&sup3;", "ó"],
  471. ["&Atilde;&iexcl;", "á"],
  472. ["&Atilde;&shy;", "í"],
  473. ["&Atilde;&ordm;", "ú"],
  474. ["&Atilde;&copy;", "é"],
  475. ["&Acirc;&iexcl;", "¡"],
  476. ["&Acirc;&iquest;", "¿"],
  477. ["N&Acirc;&ordm;", "#"]
  478. ];
  479. // https://mspfa.com/?s=5280&p=51 -- unknown error
  480.  
  481. const replaceTerms = (p) => {
  482. library.forEach(term => {
  483. if (window.MSPFA.story.p[p]) {
  484. window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]);
  485. window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]);
  486. }
  487. });
  488. };
  489.  
  490. replaceTerms(currentPage-1);
  491.  
  492. window.MSPFA.slide.push(p => {
  493. replaceTerms(p);
  494. replaceTerms(p-2);
  495. });
  496. window.MSPFA.page(currentPage);
  497. return true;
  498. }
  499. });
  500. }
  501.  
  502. // Turn buttons into links
  503. const pageButton = document.createElement('button');
  504. const pageLink = document.createElement('a');
  505. const searchContent = location.search.split('&p=');
  506. pageLink.href = `/my/stories/pages/${searchContent[0]}#p${searchContent[1].split('#')[0]}`;
  507. pageButton.className = 'pages edit major';
  508. pageButton.type = 'button';
  509. pageButton.title = 'Edit Pages';
  510. pageLink.style.marginRight = '9.5px';
  511. pageButton.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAL1JREFUOI1jYKAQMCJzugoL/+NSePPhQ4a569YxoouzoAuU9vUx3AoLY1BbtYqBgYGB4d21a8jS/9ENwTBg/ezZDNpQGh2oy8szJAcFoRiCYUBgairDrd27Gdz9/Bh+vH2LInfn+nUMQ3G6YOemTRiKsQEMA9z9/Bie7N7NYG9tjaGYKBfs3LSJQZuBgeHg0aPkucDe2prhDSUuOHj06Ih3wdw5cxgYZGQYGObMId0FNx8+JKhBQlGRKIOJBgBpTFVYzRG//QAAAABJRU5ErkJggg==")';
  512. pageLink.appendChild(pageButton);
  513.  
  514. pageLoad(() => {
  515. const infoButton = document.querySelector('.edit.major');
  516. if (infoButton) {
  517. pageLoad(() => {
  518. if (window.MSPFA.me.i) {
  519. infoButton.title = "Edit Info";
  520. infoButton.parentNode.insertBefore(pageLink, infoButton);
  521. addLink(infoButton, `/my/stories/info/${searchContent[0]}`);
  522. pageButton.style.display = document.querySelector('.edit.major:not(.pages)').style.display;
  523. return true;
  524. }
  525. });
  526. addLink(document.querySelector('.rss.major'), `/rss/${searchContent[0]}`);
  527. return true;
  528. }
  529. });
  530. window.addEventListener('load', evt => {
  531. const favButton = document.querySelector('.fav');
  532. window.MSPFA.slide.push(p => {
  533. const newSearch = location.search.split('&p=');
  534. pageLink.href = `/my/stories/pages/${newSearch[0]}#p${newSearch[1].split('#')[0]}`;
  535. });
  536. });
  537.  
  538. // Add "Reply" button to comment gear
  539. document.body.addEventListener('click', evt => {
  540. if (evt.toElement.classList.contains('gear')) {
  541. const userID = evt.path[2].classList[2].replace('u', '');
  542. const reportButton = document.querySelector('#dialog button[data-value="Report"]');
  543. const replyButton = document.createElement('button');
  544. replyButton.classList.add('major');
  545. replyButton.type = 'submit';
  546. replyButton.setAttribute('data-value', 'Reply');
  547. replyButton.textContent = 'Reply';
  548. replyButton.style = 'margin-right: 9.5px';
  549. reportButton.parentNode.insertBefore(replyButton, reportButton);
  550.  
  551. replyButton.addEventListener('click', evt => {
  552. document.querySelector('#dialog button[data-value="Cancel"]').click();
  553. const commentBox = document.querySelector('#commentbox textarea');
  554. commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
  555. commentBox.focus();
  556. // Weird bug where if you have JS console open it opens debugger?
  557. });
  558. } else return;
  559. });/**/
  560. }
  561. }
  562. else if (location.pathname === "/my/settings/") { // Custom settings
  563. const saveBtn = document.querySelector('#savesettings');
  564.  
  565. const table = document.querySelector("#editsettings tbody");
  566. let saveTr = table.querySelectorAll("tr");
  567. saveTr = saveTr[saveTr.length - 1];
  568.  
  569. const headerTr = document.createElement('tr');
  570. const header = document.createElement('th');
  571. Object.assign(header, { id: 'extraSettings', textContent: 'Extra Settings' });
  572. headerTr.appendChild(header);
  573.  
  574. const moreTr = document.createElement('tr');
  575. const more = document.createElement('td');
  576. 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.";
  577. moreTr.appendChild(more);
  578.  
  579. const settingsTr = document.createElement('tr');
  580. const localMsg = document.createElement('span');
  581. const settingsTd = document.createElement('td');
  582. 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!";
  583. const plusTable = document.createElement('table');
  584. const plusTbody = document.createElement('tbody');
  585. plusTable.appendChild(plusTbody);
  586. settingsTd.appendChild(localMsg);
  587. settingsTd.appendChild(newBr());
  588. settingsTd.appendChild(newBr());
  589. settingsTd.appendChild(plusTable);
  590. settingsTr.appendChild(settingsTd);
  591.  
  592. plusTable.style = "text-align: center;";
  593.  
  594. // Create checkbox (soooo much better)
  595. const createCheckbox = (text, checked) => {
  596. const optionTr = plusTbody.insertRow(plusTbody.childNodes.length);
  597. const optionTextTd = optionTr.insertCell(0);
  598. const optionInputTd = optionTr.insertCell(1);
  599. const optionInput = document.createElement('input');
  600. optionInputTd.appendChild(optionInput);
  601.  
  602. optionTextTd.textContent = text;
  603. optionInput.type = "checkbox";
  604. optionInput.checked = checked;
  605.  
  606. return optionInput;
  607. }
  608.  
  609. const spoilerInput = createCheckbox("Automatically open spoilers:", settings.autospoiler);
  610. const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", settings.auto502);
  611. const updateInput = createCheckbox("Automatically check for updates:", settings.autoUpdate);
  612. const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", settings.pixelFix);
  613. const textFixInput = createCheckbox("Attempt to fix text errors (experimental)*:", settings.textFix);
  614.  
  615. const cssTr = plusTbody.insertRow(plusTbody.childNodes.length);
  616. const cssTextTd = cssTr.insertCell(0);
  617. const cssSelectTd = cssTr.insertCell(1);
  618. const cssSelect = document.createElement('select');
  619. cssSelectTd.appendChild(cssSelect);
  620.  
  621. cssTextTd.textContent = "Change style:";
  622.  
  623. const customTr = plusTbody.insertRow(plusTbody.childNodes.length);
  624. const customTextTd = customTr.insertCell(0);
  625. const customCssTd = customTr.insertCell(1);
  626. const customCssInput = document.createElement('input');
  627. customCssTd.appendChild(customCssInput);
  628.  
  629. customTextTd.textContent = "Custom CSS URL:";
  630. customCssInput.style.width = "99px";
  631. customCssInput.value = settings.styleURL;
  632.  
  633. styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o)));
  634.  
  635. saveTr.parentNode.insertBefore(headerTr, saveTr);
  636. saveTr.parentNode.insertBefore(settingsTr, saveTr);
  637. saveTr.parentNode.insertBefore(moreTr, saveTr);
  638. cssSelect.selectedIndex = settings.style;
  639.  
  640. const buttonSpan = document.createElement('span');
  641. const draftButton = document.createElement('input');
  642. const spoilerButton = document.createElement('input');
  643. draftButton.style = 'margin: 0 9.5px;';
  644. draftButton.value = 'Clear Drafts';
  645. draftButton.className = 'major';
  646. draftButton.type = 'button';
  647. spoilerButton.value = 'Clear Spoiler Values';
  648. spoilerButton.className = 'major';
  649. spoilerButton.type = 'button';
  650. buttonSpan.appendChild(draftButton);
  651. buttonSpan.appendChild(spoilerButton);
  652. settingsTd.appendChild(buttonSpan);
  653.  
  654. draftButton.addEventListener('click', () => {
  655. window.MSPFA.dialog('Delete all Drafts?', window.MSPFA.parseBBCode(`Doing this will delete all drafts saved for [b]${Object.keys(settings.drafts).length}[/b] adventure(s).\nAre you sure? This action is irreversible.`), ["Yes", "No"], (output, form) => {
  656. if (output === "Yes") {
  657. setTimeout(() => {
  658. window.MSPFA.dialog('Delete all Drafts?', document.createTextNode('Are you really sure?'), ["No", "Yes"], (output, form) => {
  659. if (output === "Yes") {
  660. settings.drafts = {};
  661. saveData(settings);
  662. }
  663. });
  664. }, 1);
  665. }
  666. });
  667. });
  668.  
  669. spoilerButton.addEventListener('click', () => {
  670. window.MSPFA.dialog('Delete all Spoiler Values?', window.MSPFA.parseBBCode(`Doing this will delete all spoiler values saved for [b]${Object.keys(settings.spoilerValues).length}[/b] adventure(s).\nAre you sure? This action is irreversible.`), ["Yes", "No"], (output, form) => {
  671. if (output === "Yes") {
  672. settings.spoilerValues = {};
  673. saveData(settings);
  674. }
  675. });
  676. });
  677.  
  678. // Add event listeners
  679. plusTbody.querySelectorAll('input, select').forEach(elm => {
  680. elm.addEventListener("change", () => {
  681. saveBtn.disabled = false;
  682. });
  683. });
  684.  
  685. saveBtn.addEventListener('mouseup', () => {
  686. settings.autospoiler = spoilerInput.checked;
  687. settings.style = cssSelect.selectedIndex;
  688. settings.styleURL = customCssInput.value;
  689. settings.auto502 = errorInput.checked;
  690. settings.textFix = textFixInput.checked;
  691. settings.pixelFix = pixelFixInput.checked;
  692. settings.autoUpdate = updateInput.checked;
  693. settings.night = false;
  694. console.log(settings);
  695. saveData(settings);
  696.  
  697. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  698.  
  699. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  700.  
  701. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
  702. setTimeout(() => {
  703. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  704. }, 1000);
  705. });
  706. }
  707. else if (location.pathname === "/my/messages/") { // New buttons
  708. const btnStyle = "margin: 10px 5px;";
  709.  
  710. // Select all read messages button.
  711. const selRead = document.createElement('input');
  712. selRead.style = btnStyle;
  713. selRead.value = "Select Read";
  714. selRead.id = "selectread";
  715. selRead.classList.add("major");
  716. selRead.type = "button";
  717.  
  718. // On click, select all messages with the style attribute indicating it as read.
  719. selRead.addEventListener('mouseup', () => {
  720. document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click());
  721. });
  722.  
  723. // Select duplicate message (multiple update notifications).
  724. const selDupe = document.createElement('input');
  725. selDupe.style = btnStyle;
  726. selDupe.value = "Select Same";
  727. selDupe.id = "selectdupe";
  728. selDupe.classList.add("major");
  729. selDupe.type = "button";
  730.  
  731. selDupe.addEventListener('mouseup', evt => {
  732. const temp = document.querySelectorAll('#messages > tr');
  733. const msgs = [];
  734. for (let i = temp.length - 1; i >= 0; i--) {
  735. msgs.push(temp[i]);
  736. }
  737. const titles = [];
  738. msgs.forEach((msg) => {
  739. let title = msg.querySelector('a.major').textContent;
  740. if (/^New update: /.test(title)) { // Select only adventure updates
  741. if (titles.indexOf(title) === -1) {
  742. if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") {
  743. titles.push(title);
  744. }
  745. } else {
  746. msg.querySelector('input').click();
  747. }
  748. }
  749. });
  750. });
  751.  
  752. // Add buttons to the page.
  753. const del = document.querySelector('#deletemsgs');
  754. del.parentNode.appendChild(newBr());
  755. del.parentNode.appendChild(selRead);
  756. del.parentNode.appendChild(selDupe);
  757. }
  758. else if (location.pathname === "/my/messages/new/" && location.search) { // Auto-fill user when linked from a user page
  759. const recipientInput = document.querySelector('#addrecipient');
  760. recipientInput.value = location.search.replace('?u=', '');
  761. pageLoad(() => {
  762. const recipientButton = document.querySelector('#addrecipientbtn');
  763. if (recipientButton) {
  764. recipientButton.click();
  765. if (recipientInput.value === "") { // If the button press doesn't work
  766. return true;
  767. }
  768. }
  769. });
  770. }
  771. else if (location.pathname === "/my/stories/") {
  772. // Add links to buttons
  773. pageLoad(() => {
  774. const adventures = document.querySelectorAll('#stories tr');
  775. if (adventures.length > 0) {
  776. adventures.forEach(story => {
  777. const buttons = story.querySelectorAll('input.major');
  778. const id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', '');
  779. if (id) {
  780. addLink(buttons[0], `/my/stories/info/${id}`);
  781. addLink(buttons[1], `/my/stories/pages/${id}`);
  782. }
  783. });
  784. return true;
  785. }
  786. });
  787.  
  788. // Add user guides
  789. const guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ];
  790. const links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"];
  791. const authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"];
  792.  
  793. const parentTd = document.querySelector('.container > tbody > tr:last-child > td');
  794. const unofficial = parentTd.querySelector('span');
  795. unofficial.textContent = "Unofficial Guides";
  796. const guideTable = document.createElement('table');
  797. const guideTbody = document.createElement('tbody');
  798. guideTable.style.width = "100%";
  799. guideTable.style.textAlign = "center";
  800.  
  801. guideTable.appendChild(guideTbody);
  802. parentTd.appendChild(guideTable);
  803.  
  804. for (let i = 0; i < guides.length; i++) {
  805. const guideTr = guideTbody.insertRow(i);
  806. const guideTd = guideTr.insertCell(0);
  807. const guideLink = document.createElement('a');
  808. guideLink.href = links[i];
  809. guideLink.textContent = guides[i];
  810. guideLink.className = "major";
  811. guideTd.appendChild(guideLink);
  812. guideTd.appendChild(newBr());
  813. guideTd.appendChild(document.createTextNode('by '+authors[i]));
  814. guideTd.appendChild(newBr());
  815. guideTd.appendChild(newBr());
  816. }
  817. }
  818. else if (location.pathname === "/my/stories/info/" && location.search) {
  819. // Button links
  820. addLink(document.querySelector('#userfavs'), `/readers/${location.search}`);
  821. addLink(document.querySelector('#editpages'), `/my/stories/pages/${location.search}`);
  822. }
  823. else if (location.pathname === "/my/stories/pages/" && location.search) {
  824. const adventureID = /\?s=(\d{5})/.exec(location.search)[1];
  825.  
  826. if (!settings.drafts[adventureID]) {
  827. settings.drafts[adventureID] = {}
  828. }
  829.  
  830. // Button links
  831. addLink(document.querySelector('#editinfo'), `/my/stories/info/?s=${adventureID}`);
  832.  
  833. // Default spoiler values
  834. const replaceButton = document.querySelector('#replaceall');
  835. const spoilerButton = document.createElement('input');
  836. spoilerButton.classList.add('major');
  837. spoilerButton.value = 'Default Spoiler Values';
  838. spoilerButton.type = 'button';
  839. replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
  840. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  841. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  842.  
  843. const spoilerSpan = document.createElement('span');
  844. const spoilerOpen = document.createElement('input');
  845. const spoilerClose = document.createElement('input');
  846. spoilerSpan.appendChild(document.createTextNode('Open button text:'));
  847. spoilerSpan.appendChild(newBr());
  848. spoilerSpan.appendChild(spoilerOpen);
  849. spoilerSpan.appendChild(newBr());
  850. spoilerSpan.appendChild(newBr());
  851. spoilerSpan.appendChild(document.createTextNode('Close button text:'));
  852. spoilerSpan.appendChild(newBr());
  853. spoilerSpan.appendChild(spoilerClose);
  854.  
  855. if (!settings.spoilerValues[adventureID]) {
  856. settings.spoilerValues[adventureID] = {
  857. open: 'Show',
  858. close: 'Hide'
  859. }
  860. }
  861.  
  862. spoilerOpen.value = settings.spoilerValues[adventureID].open;
  863. spoilerClose.value = settings.spoilerValues[adventureID].close;
  864.  
  865. spoilerButton.addEventListener('click', evt => {
  866. window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => {
  867. if (output === 'Save') {
  868. settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value;
  869. settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value;
  870. if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') {
  871. delete settings.spoilerValues[adventureID];
  872. }
  873. saveData(settings);
  874. }
  875. });
  876. });
  877.  
  878. document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => {
  879. document.querySelector('#dialog input[name="open"]').value = settings.spoilerValues[adventureID].open;
  880. document.querySelector('#dialog input[name="close"]').value = settings.spoilerValues[adventureID].close;
  881. document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open;
  882. document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close;
  883. });
  884.  
  885. // Buttonless spoilers
  886. const flashButton = document.querySelector('input[title="Flash');
  887. const newSpoilerButton = document.createElement('input');
  888. newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
  889. newSpoilerButton.title = 'Buttonless Spoiler';
  890. newSpoilerButton.type = 'button';
  891. 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");';
  892.  
  893. newSpoilerButton.addEventListener('click', evt => {
  894. const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
  895. if (bbe) {
  896. bbe.focus();
  897. const start = bbe.selectionStart;
  898. const end = bbe.selectionEnd;
  899. bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end);
  900. bbe.selectionStart = start + 26;
  901. bbe.selectionEnd = end + 26;
  902. }
  903. });
  904.  
  905. flashButton.parentNode.insertBefore(newSpoilerButton, flashButton);
  906.  
  907. // Open preview in new tab with middle mouse
  908. document.body.addEventListener('mouseup', evt => {
  909. if (evt.toElement.value === "Preview" && evt.button === 1) {
  910. evt.toElement.click(); // TODO: Find a way to prevent the middle mouse scroll after clicking there.
  911. evt.preventDefault();
  912. return false;
  913. }
  914. });
  915.  
  916. // -- Drafts --
  917. // Accessing draft text
  918. const accessDraftsButton = document.createElement('input');
  919. accessDraftsButton.classList.add('major');
  920. accessDraftsButton.value = 'Saved Drafts';
  921. accessDraftsButton.type = 'button';
  922. replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton);
  923. accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  924. accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  925.  
  926. accessDraftsButton.addEventListener('click', () => {
  927. 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.');
  928. const draftInputTextarea = document.createElement('textarea');
  929. draftInputTextarea.placeholder = 'Paste your draft data here';
  930. draftInputTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  931. draftInputTextarea.rows = 8;
  932. draftDialog.appendChild(newBr());
  933. draftDialog.appendChild(newBr());
  934. draftDialog.appendChild(draftInputTextarea);
  935. setTimeout(() => {
  936. draftInputTextarea.focus();
  937. draftInputTextarea.selectionStart = 0;
  938. draftInputTextarea.selectionEnd = 0;
  939. draftInputTextarea.scrollTop = 0;
  940. }, 1);
  941.  
  942. draftInputTextarea.value = JSON.stringify(settings.drafts[adventureID], null, 4);
  943.  
  944. window.MSPFA.dialog('Saved Drafts', draftDialog, ['Load Draft', 'Cancel'], (output, form) => {
  945. if (output === "Load Draft") {
  946. if (draftInputTextarea.value === '') {
  947. setTimeout(() => {
  948. 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) => {
  949. if (output === "Delete") {
  950. settings.drafts[adventureID] = {};
  951. saveData(settings);
  952. }
  953. });
  954. }, 1);
  955. } else if (draftInputTextarea.value !== JSON.stringify(settings.drafts[adventureID], null, 4)) {
  956. setTimeout(() => {
  957. 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) => {
  958. if (output === "Load") {
  959. let newData = {};
  960. try { // Just in case the data given is invalid.
  961. newData = JSON.parse(draftInputTextarea.value);
  962. } catch (err) {
  963. console.error(err);
  964. setTimeout(() => {
  965. window.MSPFA.dialog('Error', window.MSPFA.parseBBCode('The entered data is invalid.'), ["Okay"]);
  966. }, 1);
  967. return;
  968. }
  969.  
  970. settings.drafts[adventureID] = newData;
  971. saveData(settings);
  972. }
  973. });
  974. }, 1);
  975. }
  976. }
  977. });
  978. });
  979.  
  980. // Draft stuff
  981. const msg = document.createElement('span');
  982. msg.appendChild(document.createTextNode('Command:'));
  983. msg.appendChild(document.createElement('br'));
  984.  
  985. const commandInput = document.createElement('input');
  986. commandInput.style = 'width: 100%; box-sizing: border-box;';
  987. commandInput.readOnly = true;
  988. commandInput.value = 'yes';
  989.  
  990. msg.appendChild(commandInput);
  991. msg.appendChild(document.createElement('br'));
  992. msg.appendChild(document.createElement('br'));
  993.  
  994. msg.appendChild(document.createTextNode('Body:'));
  995.  
  996. const bodyInput = document.createElement('textarea');
  997. bodyInput.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  998. bodyInput.readOnly = true;
  999. bodyInput.rows = 8;
  1000. bodyInput.textContent = '';
  1001.  
  1002. msg.appendChild(bodyInput);
  1003.  
  1004. const showDraftDialog = (pageNum) => {
  1005. const pageElement = document.querySelector(`#p${pageNum}`);
  1006.  
  1007. let shownMessage = msg;
  1008. let optionButtons = [];
  1009.  
  1010. const commandElement = pageElement.querySelector('input[name="cmd"]');
  1011. const pageContentElement = pageElement.querySelector('textarea[name="body"]');
  1012.  
  1013. if (typeof settings.drafts[adventureID][pageNum] === "undefined") {
  1014. shownMessage = document.createTextNode('There is no draft saved for this page.');
  1015. optionButtons = ["Save New", "Close"];
  1016. } else {
  1017. commandInput.value = settings.drafts[adventureID][pageNum].command;
  1018. bodyInput.textContent = settings.drafts[adventureID][pageNum].pageContent;
  1019. optionButtons = ["Save New", "Load", "Delete", "Close"];
  1020. }
  1021.  
  1022. window.MSPFA.dialog(`Page ${pageNum} Draft`, shownMessage, optionButtons, (output, form) => {
  1023. if (output === "Save New") {
  1024. if (typeof settings.drafts[adventureID][pageNum] === "undefined") {
  1025. settings.drafts[adventureID][pageNum] = {
  1026. command: commandElement.value,
  1027. pageContent: pageContentElement.value
  1028. }
  1029. saveData(settings);
  1030. } else {
  1031. setTimeout(() => {
  1032. 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) => {
  1033. if (output === "Yes") {
  1034. settings.drafts[adventureID][pageNum] = {
  1035. command: commandElement.value,
  1036. pageContent: pageContentElement.value
  1037. }
  1038. saveData(settings);
  1039. }
  1040. });
  1041. }, 1);
  1042. }
  1043. } else if (output === "Load") {
  1044. if (pageContentElement.value === '' && (commandElement.value === '' || commandElement.value === document.querySelector('#defaultcmd').value)) {
  1045. commandElement.value = settings.drafts[adventureID][pageNum].command;
  1046. pageContentElement.value = settings.drafts[adventureID][pageNum].pageContent;
  1047. pageElement.querySelector('input[value="Save"]').disabled = false;
  1048. } else {
  1049. setTimeout(() => {
  1050. 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) => {
  1051. if (output === "Yes") {
  1052. commandElement.value = settings.drafts[adventureID][pageNum].command;
  1053. pageContentElement.value = settings.drafts[adventureID][pageNum].pageContent;
  1054. pageElement.querySelector('input[value="Save"]').disabled = false;
  1055. }
  1056. });
  1057. }, 1);
  1058. }
  1059. } else if (output === "Delete") {
  1060. setTimeout(() => {
  1061. window.MSPFA.dialog('Delete this draft?', document.createTextNode('This action is unreversable! Are you sure?'), ["Yes", "No"], (output, form) => {
  1062. if (output === "Yes") {
  1063. delete settings.drafts[adventureID][pageNum];
  1064. saveData(settings);
  1065. }
  1066. });
  1067. }, 1);
  1068. }
  1069. });
  1070. }
  1071.  
  1072. const createDraftButton = (form) => {
  1073. const draftButton = document.createElement('input');
  1074. draftButton.className = 'major draft';
  1075. draftButton.type = 'button';
  1076. draftButton.value = 'Draft';
  1077. draftButton.style = 'margin-right: 9.5px;';
  1078. draftButton.addEventListener('click', () => {
  1079. showDraftDialog(form.id.replace('p', ''));
  1080. });
  1081. return draftButton;
  1082. }
  1083.  
  1084. pageLoad(() => {
  1085. let allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1086. if (allPages.length !== 0) {
  1087. allPages.forEach(form => {
  1088. const prevButton = form.querySelector('input[name="preview"]');
  1089. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1090. });
  1091. document.querySelector('input[value="Add"]').addEventListener('click', () => {
  1092. allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1093. const form = document.querySelector(`#p${allPages.length}`);
  1094. const prevButton = form.querySelector('input[name="preview"]');
  1095. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1096. });
  1097. return true;
  1098. }
  1099. });
  1100.  
  1101. /* // Removed because apparently MSPFA already does this fine!
  1102. if (hashSearch) {
  1103. pageLoad(() => {
  1104. const element = document.querySelector(hashSearch);
  1105. if (element) {
  1106. if (element.style.display === "none") {
  1107. element.style = '';
  1108. }
  1109. return true;
  1110. }
  1111. });
  1112. }/**/
  1113. }
  1114. else if (location.pathname === "/user/") {
  1115. const id = location.search.slice(3);
  1116. const statAdd = [];
  1117. // Button links
  1118. pageLoad(() => {
  1119. const msgButton = document.querySelector('#sendmsg');
  1120. if (msgButton) {
  1121. addLink(msgButton, `/my/messages/new/${location.search}`);
  1122. addLink(document.querySelector('#favstories'), `/favs/${location.search}`);
  1123. return true;
  1124. }
  1125. });
  1126.  
  1127. // Add extra user stats
  1128. pageLoad(() => {
  1129. if (window.MSPFA) {
  1130. const stats = document.querySelector('#userinfo table');
  1131.  
  1132. const joinTr = stats.insertRow(1);
  1133. const joinTextTd = joinTr.insertCell(0);
  1134. joinTextTd.appendChild(document.createTextNode("Account created:"));
  1135. const joinDate = joinTr.insertCell(1);
  1136. const joinTime = document.createElement('b');
  1137. joinTime.textContent = "Loading...";
  1138. joinDate.appendChild(joinTime);
  1139.  
  1140. const advCountTr = stats.insertRow(2);
  1141. const advTextTd = advCountTr.insertCell(0);
  1142. advTextTd.appendChild(document.createTextNode("Adventures created:"));
  1143. const advCount = advCountTr.insertCell(1);
  1144. const advCountText = document.createElement('b');
  1145. advCountText.textContent = "Loading...";
  1146. advCount.appendChild(advCountText);
  1147.  
  1148. if (statAdd.indexOf('date') === -1) {
  1149. window.MSPFA.request(0, {
  1150. do: "user",
  1151. u: id
  1152. }, user => {
  1153. if (typeof user !== "undefined") {
  1154. statAdd.push('date');
  1155. joinTime.textContent = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
  1156. }
  1157. });
  1158. }
  1159.  
  1160. if (statAdd.indexOf('made') === -1) {
  1161. window.MSPFA.request(0, {
  1162. do: "editor",
  1163. u: id
  1164. }, s => {
  1165. if (typeof s !== "undefined") {
  1166. statAdd.push('made');
  1167. advCountText.textContent = s.length;
  1168. }
  1169. });
  1170. }
  1171.  
  1172. if (document.querySelector('#favstories').style.display !== 'none' && statAdd.indexOf('fav') === -1) {
  1173. statAdd.push('fav');
  1174. const favCountTr = stats.insertRow(3);
  1175. const favTextTd = favCountTr.insertCell(0);
  1176. favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
  1177. const favCount = favCountTr.insertCell(1);
  1178. const favCountText = document.createElement('b');
  1179. favCountText.textContent = "Loading...";
  1180. window.MSPFA.request(0, {
  1181. do: "favs",
  1182. u: id
  1183. }, s => {
  1184. if (typeof s !== "undefined") {
  1185. favCountText.textContent = s.length;
  1186. }
  1187. });
  1188. favCount.appendChild(favCountText);
  1189. }
  1190.  
  1191. return true;
  1192. }
  1193. });
  1194. }
  1195. else if (location.pathname === "/favs/" && location.search) {
  1196. // Button links
  1197. pageLoad(() => {
  1198. const stories = document.querySelectorAll('#stories tr');
  1199. let favCount = 0;
  1200.  
  1201. if (stories.length > 0) {
  1202. stories.forEach(story => {
  1203. favCount++;
  1204. const id = story.querySelector('a').href.replace('https://mspfa.com/', '');
  1205. pageLoad(() => {
  1206. if (window.MSPFA.me.i) {
  1207. addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`);
  1208. return true;
  1209. }
  1210. });
  1211. addLink(story.querySelector('.rss.major'), `/rss/${id}`);
  1212. });
  1213.  
  1214. // Fav count
  1215. const username = document.querySelector('#username');
  1216. username.parentNode.appendChild(newBr());
  1217. username.parentNode.appendChild(newBr());
  1218. username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`));
  1219.  
  1220. return true;
  1221. }
  1222. });
  1223. }
  1224. else if (location.pathname === "/search/" && location.search) {
  1225. // Character and word statistics
  1226. const statTable = document.createElement('table');
  1227. const statTbody = document.createElement('tbody');
  1228. const statTr = statTbody.insertRow(0);
  1229. const charCount = statTr.insertCell(0);
  1230. const wordCount = statTr.insertCell(0);
  1231. const statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2);
  1232. const statParentTd = statParentTr.insertCell(0);
  1233.  
  1234. const statHeaderTr = statTbody.insertRow(0);
  1235. const statHeader = document.createElement('th');
  1236. statHeader.colSpan = '2';
  1237.  
  1238. statHeaderTr.appendChild(statHeader);
  1239. statHeader.textContent = 'Statistics may not be entirely accurate.';
  1240.  
  1241. statTable.style.width = "100%";
  1242.  
  1243. charCount.textContent = "Character count: loading...";
  1244. wordCount.textContent = "Word count: loading...";
  1245.  
  1246. statTable.appendChild(statTbody);
  1247. statParentTd.appendChild(statTable);
  1248.  
  1249. pageLoad(() => {
  1250. if (document.querySelector('#pages br')) {
  1251. const bbc = window.MSPFA.BBC.slice();
  1252. bbc.splice(0, 3);
  1253.  
  1254. window.MSPFA.request(0, {
  1255. do: "story",
  1256. s: location.search.replace('?s=', '')
  1257. }, story => {
  1258. if (typeof story !== "undefined") {
  1259. const pageContent = [];
  1260. story.p.forEach(p => {
  1261. pageContent.push(p.c);
  1262. pageContent.push(p.b);
  1263. });
  1264.  
  1265. const storyText = pageContent.join(' ')
  1266. .replace(/\n/g, ' ')
  1267. .replace(bbc[0][0], '$1')
  1268. .replace(bbc[1][0], '$1')
  1269. .replace(bbc[2][0], '$1')
  1270. .replace(bbc[3][0], '$1')
  1271. .replace(bbc[4][0], '$2')
  1272. .replace(bbc[5][0], '$3')
  1273. .replace(bbc[6][0], '$3')
  1274. .replace(bbc[7][0], '$3')
  1275. .replace(bbc[8][0], '$3')
  1276. .replace(bbc[9][0], '$3')
  1277. .replace(bbc[10][0], '$2')
  1278. .replace(bbc[11][0], '$1')
  1279. .replace(bbc[12][0], '$3')
  1280. .replace(bbc[13][0], '$3')
  1281. .replace(bbc[14][0], '')
  1282. .replace(bbc[16][0], '$1')
  1283. .replace(bbc[17][0], '$2 $4 $5')
  1284. .replace(bbc[18][0], '$2 $4 $5')
  1285. .replace(bbc[19][0], '')
  1286. .replace(bbc[20][0], '')
  1287. .replace(/<(.*?)>/g, '');
  1288.  
  1289. wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`;
  1290. charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`;
  1291. }
  1292. });
  1293. return true;
  1294. }
  1295. });
  1296. }
  1297. else if (location.pathname === "/stories/" && location.search) {
  1298. // Ripped shamelessly right from mspfa lol
  1299. let rawParams;
  1300. if (location.href.indexOf("#") != -1) {
  1301. rawParams = location.href.slice(0, location.href.indexOf("#"));
  1302. } else {
  1303. rawParams = location.href;
  1304. }
  1305. if (rawParams.indexOf("?") != -1) {
  1306. rawParams = rawParams.slice(rawParams.indexOf("?") + 1).split("&");
  1307. } else {
  1308. rawParams = [];
  1309. }
  1310. const params = {};
  1311. for (let i = 0; i < rawParams.length; i++) {
  1312. try {
  1313. const p = rawParams[i].split("=");
  1314. params[p[0]] = decodeURIComponent(p[1]);
  1315. } catch (err) {}
  1316. }
  1317.  
  1318. const adventureList = document.querySelector('#doit');
  1319. const resultAmount = document.createElement('span');
  1320. adventureList.parentNode.appendChild(resultAmount);
  1321.  
  1322. pageLoad(() => {
  1323. if (window.MSPFA) {
  1324. window.MSPFA.request(0, {
  1325. do: "stories",
  1326. n: params.n,
  1327. t: params.t,
  1328. h: params.h,
  1329. o: params.o,
  1330. p: params.p,
  1331. m: 20000
  1332. }, (s) => {
  1333. resultAmount.textContent = `Number of results: ${s.length}`;
  1334. return true;
  1335. });
  1336. return true;
  1337. }
  1338. },1);
  1339. }
  1340. })();