MSPFA extras

Adds custom quality of life features to MSPFA.

当前为 2021-05-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MSPFA extras
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8.3
  5. // @description Adds custom quality of life features to MSPFA.
  6. // @author seymour schlong
  7. // @icon https://mspfae.miro.gg/icon
  8. // @icon64 https://mspfae.miro.gg/icon
  9. // @match https://mspfa.com/
  10. // @match https://mspfa.com/?*
  11. // @match https://mspfa.com/*/
  12. // @match https://mspfa.com/*/?*
  13. // @match https://mspfa.com/my/*
  14. // @match https://mspfa.com/random/
  15. // @exclude https://mspfa.com/js/*
  16. // @exclude https://mspfa.com/css/*
  17. // @exclude https://mspfa.com/rss/*
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. const currentVersion = "1.8.3";
  25. console.log(`MSPFA extras script v${currentVersion} by seymour schlong`);
  26.  
  27. const debug = false;
  28.  
  29. /**
  30. * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true
  31. * Github to-do completion list (and other stuff too) - mm/dd/yy
  32. *
  33. * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu - 02/23/2020
  34. * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes - 02/23/2020
  35. * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates - 02/23/2020
  36. * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates - 02/23/2020
  37. * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - 07/21/2020
  38. * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count - 07/21/2020
  39. * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values - 08/07/2020
  40. * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers - 08/07/2020
  41. * https://github.com/GrantGryczan/MSPFA/issues/52 - Hash URLs - 08/08/2020
  42. * - Page drafts - 08/08/2020
  43. * - Edit pages button - 08/08/2020
  44. * - Image preloading - 08/20/2020
  45. * https://github.com/GrantGryczan/MSPFA/issues/19 - Manage game saves - 08/22/2020
  46. * https://github.com/GrantGryczan/MSPFA/issues/38 - User search page - 11/02/2020
  47. *
  48. * Extension to-do... maybe...
  49. *
  50. * 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.
  51. * When adding a new page, store it in an array and if that array length is > 1 when someone tries to save, prompt them to press Save All?
  52. */
  53.  
  54. // A general function that allows for waiting until a certain element appears on the page.
  55. const pageLoad = (fn, length) => {
  56. const interval = setInterval(() => {
  57. if (fn()) clearInterval(interval);
  58. }, length ? length*1000 : 500);
  59. };
  60.  
  61. // Saves the options data for the script.
  62. const saveData = (data) => {
  63. localStorage.mspfaextra = JSON.stringify(data);
  64. if (debug) {
  65. console.log('Settings:');
  66. console.log(data);
  67. }
  68. };
  69.  
  70. // Saves the data for drafts
  71. const saveDrafts = (data) => {
  72. localStorage.mspfadrafts = JSON.stringify(data);
  73. if (debug) {
  74. console.log('Drafts:');
  75. console.log(data);
  76. }
  77. };
  78.  
  79. // Encases an element within a link
  80. const addLink = (elm, url, target) => {
  81. const link = document.createElement('a');
  82. link.href = url;
  83. link.draggable = false;
  84. if (elm.parentNode) elm.parentNode.insertBefore(link, elm);
  85. if (target) link.target = target;
  86. link.appendChild(elm);
  87. return link;
  88. };
  89.  
  90. // Easy br element
  91. const newBr = () => {
  92. return document.createElement('br');
  93. }
  94.  
  95. const clearListeners = (elm) => {
  96. let clone = elm.cloneNode(1);
  97. elm.parentNode.replaceChild(clone, elm);
  98. return clone;
  99. }
  100.  
  101. // Make creating label elements easier
  102. const createLabel = (text, id) => {
  103. const newLabel = document.createElement('label');
  104. newLabel.textContent = text;
  105. newLabel.setAttribute('for', id);
  106. return newLabel;
  107. }
  108.  
  109. let settings = {};
  110. let drafts = {};
  111.  
  112. const defaultSettings = {
  113. autospoiler: false,
  114. style: 0,
  115. styleURL: "",
  116. night: false,
  117. auto502: true,
  118. textFix: false,
  119. pixelFix: false,
  120. intro: false,
  121. commandScroll: false,
  122. preload: true,
  123. dialogKeys: true,
  124. dialogFocus: false,
  125. navStick: false,
  126. tabTitles: true,
  127. spoilerValues: {}
  128. }
  129.  
  130. let pageLoaded = false;
  131.  
  132. const loadDrafts = () => {
  133. if (localStorage.mspfadrafts) {
  134. drafts = JSON.parse(localStorage.mspfadrafts);
  135. }
  136. }
  137. loadDrafts();
  138.  
  139. // Load any previous settings from localStorage
  140. if (localStorage.mspfaextra) {
  141. Object.assign(settings, JSON.parse(localStorage.mspfaextra));
  142.  
  143. // Get draft data from settings
  144. if (typeof settings.drafts === "object") {
  145. if (Object.keys(settings.drafts).length > 0 && Object.keys(drafts).length === 0) {
  146. drafts = settings.drafts;
  147. }
  148. }
  149. saveDrafts(drafts);
  150. }
  151.  
  152. // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
  153. const checkSettings = () => {
  154. const defaultSettingsKeys = Object.keys(defaultSettings);
  155. for (let i = 0; i < defaultSettingsKeys.length; i++) {
  156. if (typeof settings[defaultSettingsKeys[i]] === "undefined") {
  157. settings[defaultSettingsKeys[i]] = defaultSettings[defaultSettingsKeys[i]];
  158. }
  159. }
  160. saveData(settings);
  161. }
  162.  
  163. checkSettings();
  164.  
  165. if (GM_info && GM_info.scriptHandler !== "Tampermonkey" && !settings.warned) {
  166. alert(`It appears that you're running the MSPFA extras script with ${GM_info.scriptHandler}.\nUnfortunately, this script cannot run at its full potential because of that.\nTry switching to Tampermonkey if you want to use more of the features!\n(this message will only appear once.)`);
  167. settings.warned = true;
  168. saveData(settings);
  169. }
  170.  
  171. // Enable the sticky nav bar (scrolls with you)
  172. if (settings.navStick) {
  173. pageLoad(() => {
  174. let formIDs = {
  175. '/stories/': '#explore',
  176. '/my/profile/': '#editprofile',
  177. '/my/settings/': '#editsettings',
  178. '/my/stories/info/': '#editstory',
  179. '/my/messages/new/': '#newmessage'
  180. }
  181.  
  182. let nav = document.querySelector('nav');
  183. if (nav) {
  184. nav.className = 'nav-sticky';
  185. if (location.pathname === '/' && location.search && params.s || location.pathname === '/preview/') {
  186. let slide = document.querySelector('#slide');
  187. slide.parentNode.insertBefore(nav, slide);
  188. } else {
  189. let parentDiv = document.createElement('div');
  190. parentDiv.appendChild(nav);
  191. document.querySelectorAll('.alert').forEach(banner => {
  192. parentDiv.appendChild(banner);
  193. });
  194.  
  195. if (formIDs[location.pathname]) {
  196. let container = document.querySelector(formIDs[location.pathname]);
  197. container.parentNode.insertBefore(parentDiv, container);
  198. parentDiv.appendChild(container);
  199. } else {
  200. let containers = document.querySelectorAll('#main > table.container');
  201.  
  202. containers[0].parentNode.insertBefore(parentDiv, containers[0]);
  203. document.querySelectorAll('.banner').forEach(b => {
  204. parentDiv.appendChild(b);
  205. });
  206. containers.forEach(c => {
  207. parentDiv.appendChild(c);
  208. });
  209.  
  210. let groupshot = document.querySelector('#groupShotContainer');
  211. if (groupshot) {
  212. console.log(containers[0]);
  213. containers[0].parentNode.insertBefore(groupshot, containers[0]);
  214. }
  215. }
  216. }
  217. return true;
  218. }
  219. });
  220. }
  221.  
  222. // Scrolls you to where you need to be
  223. const hashSearch = location.href.replace(location.origin + location.pathname, '').replace(location.search, '');
  224. if (hashSearch !== '') {
  225. pageLoad(() => {
  226. const idElement = document.querySelector(hashSearch);
  227. if (idElement) {
  228. const selected = document.querySelector(hashSearch);
  229. selected.scrollIntoView();
  230. selected.style.outline = '3px solid black';
  231. selected.style.transition = '0.5s';
  232. pageLoad(() => {
  233. if (pageLoaded) {
  234. selected.style.outline = '0px solid black';
  235. }
  236. });
  237.  
  238. return true;
  239. }
  240. }, 1);
  241. }
  242.  
  243. // Ripped shamelessly right from mspfa lol (URL search parameters -- story ID, page num, etc.)
  244. let rawParams;
  245. if (location.href.indexOf("#") != -1) {
  246. rawParams = location.href.slice(0, location.href.indexOf("#"));
  247. } else {
  248. rawParams = location.href;
  249. }
  250. if (rawParams.indexOf("?") != -1) {
  251. rawParams = rawParams.slice(rawParams.indexOf("?") + 1).split("&");
  252. } else {
  253. rawParams = [];
  254. }
  255. const params = {};
  256. for (let i = 0; i < rawParams.length; i++) {
  257. try {
  258. const p = rawParams[i].split("=");
  259. params[p[0]] = decodeURIComponent(p[1]);
  260. } catch (err) {}
  261. }
  262.  
  263. // Show the URL params if in debug more
  264. if (debug) {
  265. console.log('URL parameters:');
  266. console.log(params);
  267. }
  268.  
  269. // Functions to get/change data from the console
  270. window.MSPFAe = {
  271. getSettings: () => {
  272. return settings;
  273. },
  274. getSettingsString: (formatted) => {
  275. if (formatted) {
  276. console.log(JSON.stringify(settings, null, 4));
  277. } else {
  278. console.log(JSON.stringify(settings));
  279. }
  280. },
  281. getDrafts: () => {
  282. loadDrafts();
  283. return drafts;
  284. },
  285. getDraftsString: (formatted) => {
  286. loadDrafts();
  287. if (formatted) {
  288. console.log(JSON.stringify(drafts, null, 4));
  289. } else {
  290. console.log(JSON.stringify(drafts));
  291. }
  292. },
  293. changeSettings: (newSettings) => {
  294. console.log('Settings updated');
  295. console.log(settings);
  296. Object.assign(settings, newSettings);
  297. saveData(settings);
  298. },
  299. changeSettingsString: (fullString) => {
  300. try {
  301. JSON.parse(fullString);
  302. } catch (err) {
  303. console.error(err);
  304. return;
  305. }
  306. settings = JSON.parse(fullString);
  307. checkSettings();
  308. console.log(settings);
  309. },
  310. getParams: params
  311. }
  312.  
  313. // Reload the page if 502 CloudFlare error page appears
  314. if (settings.auto502 && document.querySelector('#cf-wrapper')) {
  315. window.location.reload();
  316. }
  317. window.addEventListener("load", () => {
  318. // Wait five seconds, then refresh the page
  319. if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") {
  320. setTimeout(() => {
  321. window.location.reload();
  322. }, 5000);
  323. }
  324.  
  325. pageLoaded = true;
  326. });
  327.  
  328. // Delete any unchanged spoiler values
  329. if (location.pathname !== "/my/stories/pages/") {
  330. // Go through spoiler values and remove any that aren't unique
  331. Object.keys(settings.spoilerValues).forEach(adventure => {
  332. if (settings.spoilerValues[adventure].open === "Show" && settings.spoilerValues[adventure].close === "Hide") {
  333. delete settings.spoilerValues[adventure];
  334. } else if (settings.spoilerValues[adventure].open === '' && settings.spoilerValues[adventure].close === '') {
  335. delete settings.spoilerValues[adventure];
  336. }
  337. });
  338. }
  339.  
  340. const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
  341. const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', 'https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/themes/dark.css', '/css/theme4.css', '/css/theme5.css'];
  342.  
  343. const createDropdown = (parent, explore) => {
  344. const dropDiv = document.createElement('div');
  345. dropDiv.className = 'dropdown';
  346. dropDiv.style.display = 'inline-block';
  347.  
  348. const dropContent = document.createElement('div');
  349. dropContent.className = 'dropdown-content';
  350. dropContent.style.display = 'none';
  351.  
  352. dropDiv.addEventListener('mouseenter', evt => {
  353. dropContent.style.display = 'block';
  354. dropContent.style.color = getComputedStyle(parent).color;
  355. dropContent.style.backgroundImage = getComputedStyle(parent.parentNode.parentNode).backgroundImage;
  356.  
  357. dropContent.querySelectorAll('a').forEach(link => {
  358. link.style.color = getComputedStyle(parent).color;
  359. link.style.fontSize = getComputedStyle(parent, 'after').fontSize;
  360. });
  361. });
  362.  
  363. if (!explore) {
  364. dropDiv.addEventListener('mouseleave', evt => {
  365. dropContent.style.display = 'none';
  366. });
  367. }
  368.  
  369. parent.parentNode.insertBefore(dropDiv, parent);
  370. dropDiv.appendChild(parent);
  371. dropDiv.appendChild(dropContent);
  372. return [dropDiv, dropContent];
  373. }
  374.  
  375. // "MY MSPFA" dropdown
  376. const myLink = document.querySelector('nav a[href="/my/"]');
  377. if (myLink) {
  378. const dropContent = createDropdown(myLink)[1];
  379.  
  380. const dLinks = [];
  381. dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ];
  382. dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ];
  383.  
  384. for (let i = 0; i < dLinks[0].length; i++) {
  385. const newLink = document.createElement('a');
  386. newLink.textContent = dLinks[0][i];
  387. newLink.href = dLinks[1][i];
  388. dropContent.appendChild(newLink);
  389. }
  390.  
  391. // Append "My Profile" to the dropdown list if you're signed in
  392. pageLoad(() => {
  393. if (window.MSPFA) {
  394. if (window.MSPFA.me.n) {
  395. const newFavLink = document.createElement('a');
  396. newFavLink.textContent = "My Favourites";
  397. newFavLink.href = `/favs/?u=${window.MSPFA.me.i}`;
  398.  
  399. const newMyLink = document.createElement('a');
  400. newMyLink.textContent = "My Profile";
  401. newMyLink.href = `/user/?u=${window.MSPFA.me.i}`;
  402.  
  403. dropContent.appendChild(newFavLink);
  404. dropContent.appendChild(newMyLink);
  405.  
  406. // Move SETTINGS to the bottom
  407. dropContent.appendChild(dropContent.querySelectorAll('a')[2]);
  408. return true;
  409. }
  410. }
  411. },.1);
  412. }
  413.  
  414. // "RANDOM" dropdown
  415. const randomLink = document.querySelector('nav a[href="/random/"]');
  416. if (randomLink) {
  417. // Thank you @MadCreativity 🙏
  418. const dropContent = createDropdown(randomLink)[1];
  419.  
  420. (async () => {
  421. const dLinks = [];
  422. dLinks[0] = [ 'Recent ongoing' ];
  423. dLinks[1] = [ await fetch(`https://mspfa-extras-server.herokuapp.com/api/random`).then(e => e.text()) ];
  424.  
  425. for (let i = 0; i < dLinks[0].length; i++) {
  426. const newLink = document.createElement('a');
  427. newLink.textContent = dLinks[0][i];
  428. newLink.href = dLinks[1][i];
  429. dropContent.appendChild(newLink);
  430. }
  431. })()
  432. }
  433.  
  434. // "EXPLORE" dropdown
  435. const exploreLink = document.querySelector('nav a[href="/stories/"');
  436. if (exploreLink) {
  437. const dropdown = createDropdown(exploreLink, true);
  438. const dropDiv = dropdown[0];
  439. const dropContent = dropdown[1];
  440.  
  441. const userLink = document.createElement('a');
  442. userLink.textContent = 'User Search';
  443. userLink.href = '/?s=36596&p=7'
  444. dropContent.appendChild(userLink);
  445.  
  446. const exploreInput = document.createElement('input');
  447. Object.assign(exploreInput, { type: 'text', placeholder: 'Search...', id: 'dropdown-explore' });
  448. dropContent.appendChild(exploreInput);
  449. exploreInput.addEventListener('keydown', ke => {
  450. if (ke.code === 'Enter') {
  451. const searchLink = `/stories/?go=1&n=${encodeURIComponent(exploreInput.value)}&t=&h=14&o=favs&p=p&m=50&load=true`;
  452. if (ke.altKey || ke.ctrlKey) {
  453. window.open(searchLink, '_blank').focus();
  454. } else {
  455. location.href = searchLink;
  456. }
  457. return;
  458. }
  459. });
  460. dropDiv.addEventListener('mouseleave', evt => {
  461. // If input is focused
  462. if (document.activeElement !== exploreInput) {
  463. dropContent.style.display = 'none';
  464. }
  465. });
  466. document.body.addEventListener('click', evt => {
  467. if (document.activeElement !== exploreInput) {
  468. dropContent.style.display = 'none';
  469. }
  470. });
  471. }
  472.  
  473. document.querySelector('header .mspfalogo').parentNode.draggable = false;
  474. addLink(document.querySelector('footer .mspfalogo'), 'javascript://');
  475.  
  476. // Message that shows when you first get the script
  477. const showIntroDialog = () => {
  478. 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]');
  479. window.MSPFA.dialog("MSPFA extras v" + currentVersion, msg, ["Okay"]);
  480. }
  481.  
  482. // Check if show intro dialog has displayed
  483. if (!settings.intro) {
  484. pageLoad(() => {
  485. if (window.MSPFA) {
  486. showIntroDialog();
  487. settings.intro = true;
  488. saveData(settings);
  489. return true;
  490. }
  491. });
  492. }
  493.  
  494. const docTitle = document.head.querySelector('title');
  495. const setTitle = (t, s) => {
  496. if (settings.tabTitles) {
  497. docTitle.textContent = t + (s ? " - MS Paint Fan Adventures" : "");
  498. }
  499. }
  500.  
  501. const details = document.querySelector('#details');
  502.  
  503. // Add 'link' at the bottom to show the intro dialog again
  504. const introLink = document.createElement('a');
  505. introLink.textContent = 'View Script Message';
  506. introLink.href = 'javascript://';
  507. introLink.addEventListener('click', showIntroDialog);
  508. details.appendChild(introLink);
  509.  
  510. // Theme stuff
  511. const theme = document.createElement('link');
  512. Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' });
  513. const updateTheme = (src) => {
  514. theme.href = src;
  515. }
  516. if (!document.querySelector('#theme')) {
  517. document.querySelector('head').appendChild(theme);
  518. if (settings.night) {
  519. updateTheme(styleUrls[3]);
  520. } else {
  521. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  522. }
  523. }
  524.  
  525. const pixelText = () => {
  526. return settings.pixelFix ? 'body { image-rendering: pixelated; image-rendering: -moz-crisp-edges; } .cellicon { image-rendering: -webkit-optimize-contrast !important; }' : '';
  527. }
  528.  
  529. // Dropdown menu and pixelated scaling
  530. const mspfaeCSS = document.createElement('link');
  531. Object.assign(mspfaeCSS, { id: 'script-css', type: 'text/css', rel: 'stylesheet', href: 'https://mspfae.miro.gg/style' });
  532. document.querySelector('head').appendChild(mspfaeCSS);
  533.  
  534. const extraStyle = document.createElement('style');
  535. if (!document.querySelector('#extra-style')) {
  536. extraStyle.id = 'extra-style';
  537. extraStyle.textContent = pixelText();
  538. document.querySelector('head').appendChild(extraStyle);
  539. }
  540.  
  541. let nightSwitch = [];
  542.  
  543. // Enabling night mode.
  544. document.querySelector('footer .mspfalogo').addEventListener('click', evt => {
  545. settings.night = !settings.night;
  546. saveData(settings);
  547.  
  548. for (let i = 0; i < nightSwitch.length; i++) {
  549. clearTimeout(nightSwitch[i]);
  550. }
  551. nightSwitch = [];
  552.  
  553. // Transition to make it feel nicer on the eyes
  554. extraStyle.textContent = pixelText();
  555. extraStyle.textContent = pixelText() + ' *{transition:1.5s;}';
  556.  
  557. if (settings.night) {
  558. updateTheme(styleUrls[3]);
  559. } else {
  560. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  561. }
  562.  
  563. nightSwitch.push(setTimeout(() => {
  564. extraStyle.textContent = pixelText();
  565. }, 1500));
  566. });
  567.  
  568. // Enable keyboard controls for some dialog boxes (enter/esc to accept/close)
  569. const dialog = document.querySelector('#dialog');
  570. document.addEventListener('keydown', evt => {
  571. if (settings.dialogKeys && !dialog.textContent.includes('BBCode')) {
  572. if (dialog.style.display === '' && (evt.code === 'Enter' || evt.code === "Escape") && (document.activeElement === document.body || settings.dialogFocus)) {
  573. let buttons = dialog.querySelectorAll('button');
  574. if (buttons.length === 1) {
  575. buttons[0].click();
  576. } else if (buttons.length === 2) {
  577. if (["Okay", "Yes"].indexOf(buttons[0].textContent) !== -1 && evt.code === "Enter") {
  578. buttons[0].click();
  579. }
  580. }
  581. if (["Cancel", "Close", "No"].indexOf(buttons[buttons.length - 1].textContent) !== -1 && evt.code === "Escape") {
  582. buttons[buttons.length - 1].click();
  583. }
  584. }
  585. }
  586. });
  587.  
  588. if (location.pathname.includes('//')) {
  589. location.href = location.pathname.replace(/\/\//g, '/') + location.search;
  590. }
  591.  
  592. if (location.pathname === "/" || location.pathname === "/preview/") {
  593. if (location.search) {
  594. // Remove the current theme if the adventure has CSS (to prevent conflicts);
  595. if (settings.style > 0) {
  596. pageLoad(() => {
  597. if (window.MSPFA) {
  598. if (window.MSPFA.story && window.MSPFA.story.y && (window.MSPFA.story.y.toLowerCase().includes('import') || window.MSPFA.story.y.includes('{'))) {
  599. if (!settings.night) updateTheme('');
  600. return true;
  601. }
  602. }
  603. if (pageLoaded) return true;
  604. });
  605. }
  606.  
  607. const excludedElements = ['audio', 'video', 'iframe'];
  608. const getPreloadImages = (code) => {
  609. let e = document.createElement("span");
  610. e.innerHTML = code.replace(window.MSPFA.BBC[17][0], window.MSPFA.BBC[17][1]).replace(window.MSPFA.BBC[18][0], window.MSPFA.BBC[18][1]);
  611. let images = e.querySelectorAll("img");
  612. for (let t of excludedElements) {
  613. e.querySelectorAll(t).forEach(elm => elm.parentNode.removeChild(elm));
  614. }
  615. e = '';
  616. return images;
  617. }
  618.  
  619. // Preload adjacent pages
  620. if (settings.preload) {
  621. const preloadImages = document.createElement('div');
  622. preloadImages.id = 'preload';
  623. preloadImages.style.display = 'none';
  624. document.querySelector('#container').appendChild(preloadImages);
  625. window.MSPFA.slide.push(p => {
  626. preloadImages.innerHTML = '';
  627. if (window.MSPFA.story.p[p-2]) {
  628. getPreloadImages(window.MSPFA.story.p[p-2].b).forEach(image => {
  629. preloadImages.appendChild(image);
  630. });
  631. }
  632. if (window.MSPFA.story.p[p]) {
  633. getPreloadImages(window.MSPFA.story.p[p].b).forEach(image => {
  634. preloadImages.appendChild(image);
  635. });
  636. }
  637. });
  638. }
  639.  
  640. // Automatic spoiler opening
  641. if (settings.autospoiler) {
  642. window.MSPFA.slide.push((p) => {
  643. document.querySelectorAll('#content .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click());
  644. });
  645. }
  646.  
  647. // Scroll up to the nav bar when changing page so you don't have to scroll down as much =)
  648. if (settings.commandScroll) {
  649. let heightTop = document.querySelector('header').getBoundingClientRect().height;
  650. let temp = -2; // To prevent moving the page down when loading it for the first time
  651. window.MSPFA.slide.push((p) => {
  652. if (temp < 0) {
  653. temp++;
  654. } else {
  655. window.scroll(0, heightTop);
  656. heightTop = document.querySelector('header').getBoundingClientRect().height;
  657. }
  658. });
  659. }
  660.  
  661. // Show creation date
  662. pageLoad(() => {
  663. let infoTd = document.querySelector('#infobox tr td:nth-child(2)');
  664. let dateSpan = document.createElement('span');
  665. dateSpan.id = 'escript-dates';
  666. dateSpan.class = 'escript-elm';
  667. if (infoTd) {
  668. dateSpan.appendChild(document.createTextNode('Creation date: ' + new Date(window.MSPFA.story.d).toString().split(' ').splice(1, 3).join(' ')));
  669. dateSpan.appendChild(newBr());
  670. dateSpan.appendChild(document.createTextNode('Last update: ' + new Date(window.MSPFA.story.p[window.MSPFA.story.p.length-1].d).toString().split(' ').splice(1, 3).join(' ')));
  671. infoTd.appendChild(dateSpan);
  672. return true;
  673. }
  674. });
  675.  
  676. // Hash scrolling and opening infobox or commmentbox
  677. if (['#infobox', '#commentbox', '#newcomment', '#latestpages'].indexOf(hashSearch) !== -1) {
  678. pageLoad(() => {
  679. if (document.querySelector(hashSearch)) {
  680. if (hashSearch === '#infobox') {
  681. document.querySelector('input[data-open="Show Adventure Info"]').click();
  682. } else if (hashSearch === '#commentbox' || hashSearch === '#newcomment') {
  683. document.querySelector('input[data-open="Show Comments"]').click();
  684. } else if (hashSearch === '#latestpages') {
  685. document.querySelector('input[data-open="Show Adventure Info"]').click();
  686. document.querySelector('input[data-open="Show Latest Pages"]').click();
  687. }
  688. return true;
  689. }
  690. });
  691. }
  692.  
  693. // Attempt to fix text errors
  694. if (settings.textFix && location.pathname !== "/preview/") {
  695. pageLoad(() => {
  696. if (window.MSPFA.story && window.MSPFA.story.p) {
  697. // russian/bulgarian is not possible =(
  698. const currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]);
  699. const library = [
  700. ["&acirc;��", "'"],
  701. ["&Atilde;�", "Ñ"],
  702. ["&Atilde;&plusmn;", "ñ"],
  703. ["&Atilde;&sup3;", "ó"],
  704. ["&Atilde;&iexcl;", "á"],
  705. ["&Auml;�", "ą"],
  706. ["&Atilde;&shy;", "í"],
  707. ["&Atilde;&ordm;", "ú"],
  708. ["&Atilde;&copy;", "é"],
  709. ["&Aring;�", "ł"],
  710. ["&Aring;&frac14;", "ż"],
  711. ["&Acirc;&iexcl;", "¡"],
  712. ["&Acirc;&iquest;", "¿"],
  713. ["N&Acirc;&ordm;", "#"]
  714. ];
  715. // https://mspfa.com/?s=5280&p=51 -- unknown error
  716.  
  717. const replaceTerms = (p) => {
  718. library.forEach(term => {
  719. if (window.MSPFA.story.p[p]) {
  720. window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]);
  721. window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]);
  722. }
  723. });
  724. };
  725.  
  726. replaceTerms(currentPage-1);
  727.  
  728. window.MSPFA.slide.push(p => {
  729. replaceTerms(p);
  730. replaceTerms(p-2);
  731. });
  732. return true;
  733. }
  734. });
  735. }
  736.  
  737. // Turn buttons into links
  738. const pageButton = document.createElement('button');
  739. const pageLink = addLink(pageButton, `/my/stories/pages/?s=${params.s}#p${params.p}`);
  740. pageButton.className = 'pages edit major';
  741. pageButton.type = 'button';
  742. pageButton.title = 'Edit Pages';
  743.  
  744. // Edit pages button & button link
  745. pageLoad(() => {
  746. const infoButton = document.querySelector('.edit.major');
  747. if (infoButton) {
  748. pageLoad(() => {
  749. if (window.MSPFA.me.i) {
  750. // Change notify and favourite titles to make sense
  751. document.querySelector('.notify.major').title = 'Toggle Notifications';
  752. const favButton = document.querySelector('.fav.major');
  753. let fav = favButton.className.includes(' lit');
  754. const changeTitle = () => {
  755. if (fav) {
  756. favButton.title = 'Unfavorite';
  757. } else {
  758. favButton.title = 'Favorite';
  759. }
  760. }
  761. changeTitle();
  762. favButton.addEventListener('click', () => {
  763. fav = !fav;
  764. changeTitle();
  765. });
  766.  
  767. infoButton.title = "Edit Info";
  768. infoButton.parentNode.insertBefore(pageLink, infoButton);
  769. infoButton.parentNode.insertBefore(document.createTextNode(' '), infoButton);
  770. addLink(infoButton, `/my/stories/info/?s=${params.s}`);
  771. pageButton.style.display = document.querySelector('.edit.major:not(.pages)').style.display;
  772.  
  773. // Change change page link when switching pages
  774. window.MSPFA.slide.push(p => {
  775. const newSearch = location.search.split('&p=');
  776. pageLink.href = `/my/stories/pages/?s=${params.s}#p${newSearch[1].split('#')[0]}`;
  777. });
  778. return true;
  779. }
  780. });
  781. window.addEventListener('load', () => {
  782. addLink(clearListeners(document.querySelector('.rss.major')), `/rss/?s=${params.s}`);
  783. });
  784. return true;
  785. }
  786. });
  787.  
  788. // Add "Reply" button next to comment gear
  789. setInterval(() => {
  790. if (document.querySelector('#commentbox > .spoiler.open')) {
  791. document.querySelectorAll('.gear').forEach(gear => {
  792. if (!gear.parentNode.querySelector('.reply')) {
  793. const replyDiv = document.createElement('div');
  794. replyDiv.className = 'reply';
  795. gear.insertAdjacentElement('afterEnd', replyDiv);
  796. gear.insertAdjacentHTML('afterEnd', '<span style="float: right"> </span>');
  797. const userID = gear.parentNode.parentNode.classList[2].replace('u', '');
  798.  
  799. replyDiv.addEventListener('click', () => {
  800. const commentBox = document.querySelector('#commentbox textarea');
  801. commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
  802. commentBox.focus();
  803. commentBox.parentNode.scrollIntoView();
  804. });
  805. }
  806. });
  807. }
  808. }, 500);
  809. }
  810. }
  811. else if (location.pathname === "/my/") {
  812. const editStories = document.querySelector('#editstories');
  813. editStories.classList.remove('alt');
  814. const parent = editStories.parentNode;
  815. const viewSaves = document.createElement('a');
  816. Object.assign(viewSaves, { id: 'viewsaves', className: 'major', textContent: 'View Adventure Saves' });
  817.  
  818. parent.appendChild(viewSaves);
  819. parent.appendChild(newBr());
  820. parent.appendChild(newBr());
  821.  
  822. pageLoad(() => {
  823. if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) {
  824. viewSaves.href = `/?s=36596&p=6`;
  825. return true;
  826. }
  827. },.1);
  828.  
  829. const messages = document.querySelector('#messages');
  830.  
  831. pageLoad(() => {
  832. if (messages.textContent.includes('(')) {
  833. document.title = document.title + messages.textContent.toLowerCase().replace('messages', '');
  834. return true;
  835. }
  836. });
  837. }
  838. else if (location.pathname === "/my/settings/") { // Custom settings
  839. setTitle("My Settings", 1);
  840.  
  841. const saveBtn = document.querySelector('#savesettings');
  842.  
  843. const table = document.querySelector("#editsettings tbody");
  844. let saveTr = table.querySelectorAll("tr");
  845. saveTr = saveTr[saveTr.length - 1];
  846.  
  847. const headerTr = document.createElement('tr');
  848. const header = document.createElement('th');
  849. Object.assign(header, { id: 'extraSettings', textContent: 'Extra Settings' });
  850. headerTr.appendChild(header);
  851.  
  852. const settingsTr = document.createElement('tr');
  853. const localMsg = window.MSPFA.parseBBCode('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!<br>Click on the <input value="?" class="major" type="button" disabled style="padding: 0"> boxes for descriptions.');
  854. const settingsTd = document.createElement('td');
  855. const plusTable = document.createElement('table');
  856. const plusTbody = document.createElement('tbody');
  857. plusTable.appendChild(plusTbody);
  858. settingsTd.appendChild(localMsg);
  859. settingsTd.appendChild(newBr());
  860. settingsTd.appendChild(newBr());
  861. settingsTd.appendChild(plusTable);
  862. settingsTr.appendChild(settingsTd);
  863.  
  864. plusTable.style = "text-align: center;";
  865.  
  866. // Create checkbox (soooo much better)
  867. const createCheckbox = (text, desc, checked, id) => {
  868. const optionTr = plusTbody.insertRow(plusTbody.childNodes.length);
  869. const optionTextTd = optionTr.insertCell(0);
  870. const optionLabel = createLabel(text, id);
  871. const optionInputTd = optionTr.insertCell(1);
  872. const optionInput = document.createElement('input');
  873. optionInputTd.appendChild(optionInput);
  874.  
  875. optionTextTd.appendChild(optionLabel);
  876. optionInput.type = "checkbox";
  877. optionInput.checked = checked;
  878. optionInput.id = id;
  879.  
  880. const tipButton = document.createElement('input');
  881. Object.assign(tipButton, { className: 'major', value: '?', style: 'padding: 0', type: 'button', title: 'What does enabling this do?' });
  882. tipButton.addEventListener('click', () => {
  883. window.MSPFA.dialog('What does enabling this do?', window.MSPFA.parseBBCode(desc), ["Close"]);
  884. });
  885. optionInputTd.appendChild(document.createTextNode(' '));
  886. optionInputTd.appendChild(tipButton);
  887.  
  888. return optionInput;
  889. }
  890.  
  891. const spoilerInput = createCheckbox("Automatically open spoilers:", 'This will automatically open any spoiler when you change pages without any interaction necessary.', settings.autospoiler, 'autospoiler');
  892. const preloadInput = createCheckbox("Preload images for the pages immediately before and after:", 'The pages directly before and after the one you\'re on will be loaded, saving time loading images and making the experience more seamless.', settings.preload, 'preload');
  893. const tabInput = createCheckbox("Change some tab titles to describe the pages better:", 'On some pages, the tab\'s title will appropriately label the content to help with tab managament.<br><br>As of right now, only applies to the editing pages, My settings, messages, and adventures, but if requested, can be extended to apply to many more.', settings.tabTitles, 'tabTitles');
  894. const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", 'Automatically reloads the webpage when an error occurs from Cloudflare, such as the 520 and 502 errors, as well as refreshing when the "Too Much Data" page shows.', settings.auto502, 'auto502');
  895. const commandScrollInput = createCheckbox("Scroll back up to the nav bar when switching page:", 'When going back and forth pages, scroll back up to the nav bar\'s position so you don\'t have to scroll up each time.', settings.commandScroll, 'commandScroll');
  896. const navStickInput = createCheckbox("Makes the nav bar sticky, and scrolls with you:", 'Scrolling down the page will always show the nav bar.<br>[i]Note: This feature may end up being a bit buggy at times. If any errors occur, please let me know so I can fix them ASAP.[/i]', settings.navStick, 'navStick');
  897. const dialogKeysInput = createCheckbox("Use enter/escape keys to accept/exit control some dialogs:", 'Some dialogs can now be closed out of or confirmed by pressing the Escape or Enter keys respectively. The Escape key can close most dialogs.', settings.dialogKeys, 'dialogKeys');
  898. const dialogFocusInput = createCheckbox("Let keys work while dialog isn't focused (above required):", 'While the Dialog Keys option is enabled, you can press keys to press the buttons regardless of whether or not it\'s not focused.', settings.dialogFocus, 'dialogFocus');
  899. const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", 'Makes images scale up or down using nearest neighbour to prevent making the images fuzzy when zooming in, or on monitors with upscaling. This is disabled for images with the .cellicon class.', settings.pixelFix, 'pixelFix');
  900. const textFixInput = createCheckbox("Attempt to fix text errors (experimental):", 'When changing pages, attempts to fix unicode errors and broken text.<br>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. Some languages like Russian, Bulgarian, and Japanese is just not possible.', settings.textFix, 'textFix');
  901.  
  902. const cssTr = plusTbody.insertRow(plusTbody.childNodes.length);
  903. const cssTextTd = cssTr.insertCell(0);
  904. const cssSelectTd = cssTr.insertCell(1);
  905. const cssSelect = document.createElement('select');
  906. cssSelectTd.appendChild(cssSelect);
  907.  
  908. cssTextTd.textContent = "Change style:";
  909.  
  910. const customTr = plusTbody.insertRow(plusTbody.childNodes.length);
  911. const customTextTd = customTr.insertCell(0);
  912. const customCssTd = customTr.insertCell(1);
  913. const customCssInput = document.createElement('input');
  914. customCssTd.appendChild(customCssInput);
  915.  
  916. customTextTd.textContent = "Custom CSS URL:";
  917. customCssInput.style.width = "99px";
  918. customCssInput.value = settings.styleURL;
  919.  
  920. styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o)));
  921.  
  922. saveTr.parentNode.insertBefore(headerTr, saveTr);
  923. saveTr.parentNode.insertBefore(settingsTr, saveTr);
  924. cssSelect.selectedIndex = settings.style;
  925.  
  926. const buttonSpan = document.createElement('span');
  927. const draftButton = document.createElement('input');
  928. const spoilerButton = document.createElement('input');
  929. draftButton.value = 'Manage Drafts';
  930. draftButton.className = 'major';
  931. draftButton.type = 'button';
  932. spoilerButton.value = 'Manage Spoiler Values';
  933. spoilerButton.className = 'major';
  934. spoilerButton.type = 'button';
  935. buttonSpan.appendChild(draftButton);
  936. buttonSpan.appendChild(document.createTextNode(' '));
  937. buttonSpan.appendChild(spoilerButton);
  938. settingsTd.appendChild(buttonSpan);
  939.  
  940. const draftMsg = window.MSPFA.parseBBCode('Here you can manage the drafts that you have saved for your adventure(s).\n');
  941. const listTable = document.createElement('table');
  942. listTable.id = 'draft-table';
  943. const listTbody = document.createElement('tbody');
  944. listTable.appendChild(listTbody);
  945.  
  946. const draftsEmpty = () => {
  947. loadDrafts();
  948. let empty = true;
  949. Object.keys(drafts).forEach(adv => {
  950. if (empty) {
  951. const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1;
  952. if (Object.keys(drafts[adv]).length > length) {
  953. empty = false;
  954. }
  955. }
  956. });
  957. return empty;
  958. }
  959.  
  960. setInterval(() => {
  961. draftButton.disabled = draftsEmpty();
  962. }, 1000);
  963.  
  964. draftButton.addEventListener('click', () => {
  965. draftMsg.appendChild(listTable);
  966. listTbody.innerHTML = '';
  967. loadDrafts();
  968.  
  969. const addAdv = (story, name) => {
  970. const storyTr = listTbody.insertRow(listTable.rows);
  971. const titleLink = document.createElement('a');
  972. Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${story}&click=d`, textContent: name, target: '_blank' });
  973. storyTr.insertCell(0).appendChild(titleLink);
  974. const deleteButton = document.createElement('input');
  975. Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
  976. storyTr.insertCell(1).appendChild(deleteButton);
  977.  
  978. deleteButton.addEventListener('click', () => {
  979. setTimeout(() => {
  980. window.MSPFA.dialog('Delete adventure draft?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  981. if (output === "Yes") {
  982. loadDrafts();
  983. drafts[story] = {};
  984.  
  985. if (settings.drafts && settings.drafts[story]) {
  986. delete settings.drafts[story];
  987. saveData(settings);
  988. }
  989.  
  990. saveDrafts(drafts);
  991.  
  992. setTimeout(() => {
  993. draftButton.click();
  994. }, 1);
  995.  
  996. if (draftsEmpty) {
  997. draftButton.disabled = true;
  998. }
  999. }
  1000. });
  1001. }, 1);
  1002. });
  1003. }
  1004.  
  1005. Object.keys(drafts).forEach(adv => {
  1006. const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1;
  1007. if (Object.keys(drafts[adv]).length > length) {
  1008. if (!!length) {
  1009. addAdv(adv, drafts[adv].cachedTitle);
  1010. }
  1011. else {
  1012. window.MSPFA.request(0, {
  1013. do: "story",
  1014. s: adv
  1015. }, story => {
  1016. if (typeof story !== "undefined") {
  1017. console.log(story);
  1018. addAdv(adv, story.n);
  1019. }
  1020. });
  1021. }
  1022. }
  1023. });
  1024.  
  1025. window.MSPFA.dialog('Manage Drafts', draftMsg, ["Delete All", "Close"], (output, form) => {
  1026. if (output === "Delete All") {
  1027. setTimeout(() => {
  1028. window.MSPFA.dialog('Delete all Drafts?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  1029. if (output === "Yes") {
  1030. Object.keys(drafts).forEach(adv => {
  1031. drafts[adv] = {};
  1032. });
  1033. saveDrafts(drafts);
  1034.  
  1035. if (typeof settings.drafts !== "undefined") {
  1036. delete settings.drafts;
  1037. saveData(settings);
  1038. }
  1039.  
  1040. draftButton.disabled = true;
  1041. }
  1042. });
  1043. }, 1);
  1044. }
  1045. });
  1046. });
  1047.  
  1048. if (Object.keys(settings.spoilerValues).length === 0) {
  1049. spoilerButton.disabled = true;
  1050. }
  1051.  
  1052. 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');
  1053.  
  1054. spoilerButton.addEventListener('click', () => {
  1055. spoilerMsg.appendChild(listTable);
  1056. listTbody.innerHTML = '';
  1057. Object.keys(settings.spoilerValues).forEach(adv => {
  1058. window.MSPFA.request(0, {
  1059. do: "story",
  1060. s: adv
  1061. }, story => {
  1062. if (typeof story !== "undefined") {
  1063. const storyTr = listTbody.insertRow(listTable.rows);
  1064. const titleLink = document.createElement('a');
  1065. Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${adv}&click=s`, textContent: story.n, target: '_blank' });
  1066. storyTr.insertCell(0).appendChild(titleLink);
  1067. const deleteButton = document.createElement('input');
  1068. Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
  1069. storyTr.insertCell(1).appendChild(deleteButton);
  1070.  
  1071. deleteButton.addEventListener('click', () => {
  1072. setTimeout(() => {
  1073. window.MSPFA.dialog('Delete adventure spoilers?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  1074. if (output === "Yes") {
  1075. delete settings.spoilerValues[adv];
  1076. saveData(settings);
  1077.  
  1078. setTimeout(() => {
  1079. spoilerButton.click();
  1080. }, 1);
  1081.  
  1082. if (Object.keys(settings.spoilerValues).length === 0) {
  1083. spoilerButton.disabled = true;
  1084. }
  1085. }
  1086. });
  1087. }, 1);
  1088. });
  1089. }
  1090. });
  1091. });
  1092. window.MSPFA.dialog('Manage Spoiler Values', spoilerMsg, ["Delete All", "Close"], (output, form) => {
  1093. if (output === "Delete All") {
  1094. setTimeout(() => {
  1095. 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) => {
  1096. if (output === "Yes") {
  1097. settings.spoilerValues = {};
  1098. saveData(settings);
  1099. spoilerButton.disabled = true;
  1100. }
  1101. });
  1102. }, 1);
  1103. }
  1104. });
  1105. });
  1106.  
  1107. // Add event listeners
  1108. plusTbody.querySelectorAll('input, select').forEach(elm => {
  1109. elm.addEventListener("change", () => {
  1110. saveBtn.disabled = false;
  1111. });
  1112. });
  1113.  
  1114. saveBtn.addEventListener('mouseup', () => {
  1115. settings.autospoiler = spoilerInput.checked;
  1116. settings.style = cssSelect.selectedIndex;
  1117. settings.styleURL = customCssInput.value;
  1118. settings.tabTitles = tabInput.checked;
  1119. settings.auto502 = errorInput.checked;
  1120. settings.textFix = textFixInput.checked;
  1121. settings.pixelFix = pixelFixInput.checked;
  1122. settings.dialogKeys = dialogKeysInput.checked;
  1123. settings.dialogFocus = dialogFocusInput.checked;
  1124. settings.commandScroll = commandScrollInput.checked;
  1125. settings.preload = preloadInput.checked;
  1126. settings.navStick = navStickInput.checked;
  1127. settings.night = false;
  1128. console.log(settings);
  1129. saveData(settings);
  1130.  
  1131. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  1132.  
  1133. extraStyle.textContent = pixelText() + ' *{transition:1s}';
  1134.  
  1135. extraStyle.textContent = pixelText();
  1136. setTimeout(() => {
  1137. extraStyle.textContent = pixelText();
  1138. }, 1000);
  1139. });
  1140. }
  1141. else if (location.pathname === "/my/messages/") { // New buttons
  1142. setTitle("My Messages", 1);
  1143.  
  1144. // Select all read messages button.
  1145. const selRead = document.createElement('input');
  1146. Object.assign(selRead, { value: 'Select Read', className: 'major', type: 'button' });
  1147.  
  1148. // On click, select all messages with the style attribute indicating it as read.
  1149. selRead.addEventListener('mouseup', () => {
  1150. document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click());
  1151. });
  1152.  
  1153. // Select duplicate message (multiple update notifications).
  1154. const selDupe = document.createElement('input');
  1155. Object.assign(selDupe, { value: 'Select Same', className: 'major', type: 'button', style: 'margin-top: 6px' });
  1156.  
  1157. selDupe.addEventListener('mouseup', evt => {
  1158. const temp = document.querySelectorAll('#messages > tr');
  1159. const msgs = [];
  1160. for (let i = temp.length - 1; i >= 0; i--) {
  1161. msgs.push(temp[i]);
  1162. }
  1163. const titles = [];
  1164. msgs.forEach((msg) => {
  1165. const title = msg.querySelector('a.major').textContent;
  1166. // Select only adventure updates
  1167. if (/^New update: /.test(title)) {
  1168. if (titles.indexOf(title) === -1) {
  1169. if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") {
  1170. titles.push(title);
  1171. }
  1172. } else {
  1173. msg.querySelector('input').click();
  1174. }
  1175. }
  1176. });
  1177. });
  1178.  
  1179. // Prune button
  1180. const pruneButton = document.createElement('input');
  1181. Object.assign(pruneButton, { type: 'button', value: 'Prune', className: 'major' });
  1182.  
  1183. pruneButton.addEventListener('click', () => {
  1184. const ageInput = document.createElement('input');
  1185. Object.assign(ageInput, { type: 'number', min: 1, max: 10, value: 1 });
  1186.  
  1187. const msgState = document.createElement('select');
  1188. ['all', 'all unread', 'all read'].forEach(option => {
  1189. const op = document.createElement('option');
  1190. op.textContent = option;
  1191. msgState.appendChild(op);
  1192. });
  1193.  
  1194. const timeUnit = document.createElement('select');
  1195. ['month(s)', 'week(s)', 'day(s)'].forEach(option => {
  1196. const op = document.createElement('option');
  1197. op.textContent = option;
  1198. timeUnit.appendChild(op);
  1199. });
  1200. timeUnit.childNodes[1].setAttribute('selected', 'selected');
  1201.  
  1202. const msg = document.createElement('span');
  1203. msg.appendChild(document.createTextNode('Prune '));
  1204. msg.appendChild(msgState);
  1205. msg.appendChild(document.createTextNode(' messages older than '));
  1206. msg.appendChild(ageInput);
  1207. msg.appendChild(timeUnit);
  1208.  
  1209. window.MSPFA.dialog('Prune messages', msg, ['Prune', 'Cancel'], (output, form) => {
  1210. if (output === 'Prune') {
  1211. document.querySelector('#messages').childNodes.forEach(node => {
  1212. if (node.firstChild.firstChild.checked) {
  1213. node.firstChild.firstChild.click();
  1214. }
  1215.  
  1216. const selectedState = msgState.selectedOptions[0].textContent;
  1217. const selectedUnit = timeUnit.selectedOptions[0].textContent;
  1218.  
  1219. if (selectedState === 'all unread') {
  1220. if (node.firstChild.style.borderLeftColor === 'rgb(221, 221, 221)') {
  1221. return;
  1222. }
  1223. }
  1224. else if (selectedState === 'all read') {
  1225. if (node.firstChild.style.borderLeftColor === 'rgb(92, 174, 223)') {
  1226. return;
  1227. }
  1228. }
  1229. const dateText = node.childNodes[2].childNodes[2].textContent.split(' - ');
  1230. const messageDate = new Date(dateText[dateText.length-1]);
  1231. const currentDate = Date.now();
  1232. const diff = Math.floor(Math.round((currentDate-messageDate)/(1000*60*60))/24); // Difference in days
  1233.  
  1234. if (selectedUnit === 'month(s)') diff = Math.floor(diff / 30);
  1235. else if (selectedUnit === 'week(s)') diff = Math.floor(diff / 7);
  1236.  
  1237. if (diff >= ageInput.value) {
  1238. node.firstChild.firstChild.click();
  1239. }
  1240. });
  1241.  
  1242. setTimeout(() => {
  1243. document.querySelector('input[value=Delete]').click();
  1244. }, 1);
  1245. }
  1246. });
  1247. });
  1248.  
  1249. // Maybe add a "Merge Updates" button?
  1250. // [Merge Updates] would create a list of updates, similar to [Select Same]
  1251.  
  1252. // Add buttons to the page.
  1253. const del = document.querySelector('#deletemsgs');
  1254. del.parentNode.appendChild(newBr());
  1255. del.parentNode.appendChild(selRead);
  1256. del.parentNode.appendChild(document.createTextNode(' '));
  1257. del.parentNode.appendChild(selDupe);
  1258. del.parentNode.appendChild(document.createTextNode(' '));
  1259. del.parentNode.appendChild(pruneButton);
  1260.  
  1261. // Click the green cube to open the update/comment in a new tab, and mark notification as read.
  1262. pageLoad(() => {
  1263. if (document.querySelector('#messages').childNodes.length > 0) {
  1264. if (document.querySelector('#messages').textContent === 'No new messages were found.') {
  1265. // Disable some buttons if there are no messages.
  1266. pruneButton.disabled = true;
  1267. selDupe.disabled = true;
  1268. return true;
  1269. } else {
  1270. document.querySelector('#messages').childNodes.forEach(node => {
  1271. if (node.textContent.includes('New update:') && node.textContent.includes('MS Paint Fan Adventures')) {
  1272. const link = addLink(node.querySelector('.cellicon'), node.querySelector('.spoiler a').href);
  1273. link.addEventListener('mouseup', () => {
  1274. const spoiler = node.querySelector('.spoiler');
  1275. const button = spoiler.querySelector('input');
  1276. spoiler.className = 'spoiler closed';
  1277. button.click();
  1278. button.click();
  1279. });
  1280. }
  1281. else if ((node.textContent.includes('New comment on ') || node.textContent.includes('You were tagged on ')) && node.textContent.includes('MS Paint Fan Adventures')) {
  1282. const link = addLink(node.querySelector('.cellicon'), node.querySelectorAll('.spoiler a')[1].href + '#commentbox');
  1283. link.addEventListener('mouseup', () => {
  1284. const spoiler = node.querySelector('.spoiler');
  1285. const button = spoiler.querySelector('input');
  1286. spoiler.className = 'spoiler closed';
  1287. button.click();
  1288. button.click();
  1289. });
  1290. }
  1291. });
  1292. return true;
  1293. }
  1294. }
  1295. });
  1296. }
  1297. else if (location.pathname === "/my/messages/new/" && location.search) { // Auto-fill user when linked from a user page
  1298. const recipientInput = document.querySelector('#addrecipient');
  1299. recipientInput.value = params.u;
  1300. pageLoad(() => {
  1301. const recipientButton = document.querySelector('#addrecipientbtn');
  1302. if (recipientButton) {
  1303. recipientButton.click();
  1304. if (recipientInput.value === "") { // If the button press doesn't work
  1305. return true;
  1306. }
  1307. }
  1308. });
  1309. }
  1310. else if (location.pathname === "/my/stories/") {
  1311. setTitle("My Stories", 1);
  1312.  
  1313. // Add links to buttons
  1314. pageLoad(() => {
  1315. const adventures = document.querySelectorAll('#stories tr');
  1316. if (adventures.length > 0) {
  1317. adventures.forEach(story => {
  1318. const buttons = story.querySelectorAll('input.major');
  1319. const id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', '');
  1320. if (id) {
  1321. addLink(buttons[0], `/my/stories/info/${id}`);
  1322. addLink(buttons[1], `/my/stories/pages/${id}`);
  1323. addLink(story.querySelector('img'), `/${id}&p=1`);
  1324. }
  1325. });
  1326. return true;
  1327. }
  1328. if (pageLoaded) return true;
  1329. });
  1330.  
  1331. // Add user guides
  1332. const guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ];
  1333. const links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"];
  1334. const authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"];
  1335.  
  1336. const parentTd = document.querySelector('.container > tbody > tr:last-child > td');
  1337. const unofficial = parentTd.querySelector('span');
  1338. unofficial.textContent = "Unofficial Guides";
  1339. const guideTable = document.createElement('table');
  1340. const guideTbody = document.createElement('tbody');
  1341. guideTable.style.width = "100%";
  1342. guideTable.style.textAlign = "center";
  1343.  
  1344. guideTable.appendChild(guideTbody);
  1345. parentTd.appendChild(guideTable);
  1346.  
  1347. for (let i = 0; i < guides.length; i++) {
  1348. const guideTr = guideTbody.insertRow(i);
  1349. const guideTd = guideTr.insertCell(0);
  1350. const guideLink = document.createElement('a');
  1351. Object.assign(guideLink, { href: links[i], textContent: guides[i], className: 'major' });
  1352. guideTd.appendChild(guideLink);
  1353. guideTd.appendChild(newBr());
  1354. guideTd.appendChild(document.createTextNode('by '+authors[i]));
  1355. guideTd.appendChild(newBr());
  1356. guideTd.appendChild(newBr());
  1357. }
  1358. }
  1359. else if (location.pathname === "/my/stories/info/" && location.search) {
  1360. // Button links
  1361. addLink(document.querySelector('#userfavs'), `/readers/?s=${params.s}`);
  1362. addLink(document.querySelector('#editpages'), `/my/stories/pages/?s=${params.s}`);
  1363.  
  1364. // Reorder some elements under Icons to be more sensible
  1365. const bannerInput = document.querySelector('input[name="storybanner"]');
  1366. bannerInput.parentNode.style.paddingLeft = '130px';
  1367. const bannerPrev = document.querySelector(`a[href="/?b=${params.s}`);
  1368. bannerPrev.parentNode.insertBefore(bannerPrev, bannerInput.nextSibling);
  1369.  
  1370. bannerInput.parentNode.removeChild(document.querySelector('#storyicon').nextSibling);
  1371.  
  1372. pageLoad(() => {
  1373. const title = document.querySelector('#storyname').textContent;
  1374.  
  1375. if (title !== 'Add a new adventure!') {
  1376. setTitle('Info - ' + title);
  1377. return true;
  1378. }
  1379. });
  1380.  
  1381. if (params.s !== 'new') {
  1382. // Download adventure data
  1383. const downloadButton = document.createElement('input');
  1384. Object.assign(downloadButton, { className: 'major', value: 'Export Data', type: 'button', style: 'margin-top: 6px' });
  1385. const downloadLink = document.createElement('a');
  1386. window.MSPFA.request(0, {
  1387. do: "story",
  1388. s: params.s
  1389. }, (s) => {
  1390. if (s) {
  1391. downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(s, null, 4)));
  1392.  
  1393. // Display week of anniversary banner
  1394. const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
  1395. const ann = new Date(s.d);
  1396. const end = new Date(s.d + 7*24*60*60*1000);
  1397. bannerInput.parentNode.insertBefore(document.createTextNode(months[ann.getMonth()] + ' ' + ann.getDate() + ' - ' + months[end.getMonth()] + ' ' + end.getDate()), bannerInput.nextSibling.nextSibling);
  1398. bannerInput.parentNode.insertBefore(newBr(), bannerInput.nextSibling.nextSibling);
  1399. }
  1400. });
  1401. downloadLink.setAttribute('download', `${params.s}.json`);
  1402. downloadLink.appendChild(downloadButton);
  1403. document.querySelector('#savestory').parentNode.appendChild(newBr());
  1404. document.querySelector('#savestory').parentNode.appendChild(downloadLink);
  1405. }
  1406. }
  1407. else if (location.pathname === "/my/stories/pages/" && location.search) {
  1408. const adventureID = params.s;
  1409.  
  1410. const bbtoolbar = document.querySelector('#bbtoolbar');
  1411.  
  1412. const notifyLabel = createLabel('Notify readers of new pages during this editing session: ', 'notifyreaders');
  1413. const notifyButton = document.querySelector('#notifyreaders');
  1414. notifyButton.previousSibling.textContent = '';
  1415. notifyButton.parentNode.insertBefore(notifyLabel, notifyButton);
  1416.  
  1417. if (!drafts[adventureID]) {
  1418. drafts[adventureID] = {};
  1419. saveDrafts(drafts);
  1420. }
  1421.  
  1422. // Add key combos for bolding, underlining, italicizing, quick colours, and drafts. More can be added in the future.
  1423. document.body.addEventListener('keydown', ke => {
  1424. if (document.activeElement.nodeName === "TEXTAREA") {
  1425. let form = document.activeElement.parentNode.parentNode.parentNode.parentNode.parentNode;
  1426. if (form.nodeName === "FORM") {
  1427. if (ke.key === "b" && ke.ctrlKey && !ke.shiftKey && !ke.altKey) {
  1428. ke.preventDefault();
  1429. bbtoolbar.querySelector("input[data-tag='b']").click();
  1430. } else if (ke.key === "i" && ke.ctrlKey && !ke.shiftKey && !ke.altKey) {
  1431. ke.preventDefault();
  1432. bbtoolbar.querySelector("input[data-tag='i']").click();
  1433. } else if (ke.key === "u" && ke.ctrlKey && !ke.shiftKey && !ke.altKey) {
  1434. ke.preventDefault();
  1435. bbtoolbar.querySelector("input[data-tag='u']").click();
  1436. } else if (ke.key === "c" && ke.ctrlKey && !ke.shiftKey && ke.altKey) {
  1437. ke.preventDefault();
  1438. bbtoolbar.querySelector("input[data-tag='color']").click();
  1439. document.querySelector('#dialog button').click();
  1440. } else if (ke.key === "d" && ke.ctrlKey && !ke.shiftKey && !ke.altKey) {
  1441. if (form.id !== "newpage") {
  1442. ke.preventDefault();
  1443. form.querySelector('.draft').click();
  1444. }
  1445. }
  1446. }
  1447. }
  1448. });
  1449.  
  1450. pageLoad(() => {
  1451. const title = document.querySelector('#storyname').textContent;
  1452.  
  1453. if (title !== '-') {
  1454. drafts[adventureID].cachedTitle = title;
  1455. saveDrafts(drafts);
  1456.  
  1457. setTitle('Pages - ' + title);
  1458. return true;
  1459. }
  1460. });
  1461.  
  1462. // Button links
  1463. addLink(document.querySelector('#editinfo'), `/my/stories/info/?s=${adventureID}`);
  1464.  
  1465. // Default spoiler values
  1466. const replaceButton = document.querySelector('#replaceall');
  1467. const spoilerButton = document.createElement('input');
  1468. Object.assign(spoilerButton, { className: 'major', value: 'Default Spoiler Values', type: 'button'});
  1469. replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
  1470. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  1471. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  1472.  
  1473. if (!settings.spoilerValues[adventureID]) {
  1474. settings.spoilerValues[adventureID] = {
  1475. open: 'Show',
  1476. close: 'Hide'
  1477. }
  1478. }
  1479.  
  1480. spoilerButton.addEventListener('click', evt => {
  1481. const spoilerSpan = document.createElement('span');
  1482. const spoilerOpen = document.createElement('input');
  1483. const spoilerClose = document.createElement('input');
  1484. spoilerSpan.appendChild(document.createTextNode('Open button text:'));
  1485. spoilerSpan.appendChild(newBr());
  1486. spoilerSpan.appendChild(spoilerOpen);
  1487. spoilerSpan.appendChild(newBr());
  1488. spoilerSpan.appendChild(newBr());
  1489. spoilerSpan.appendChild(document.createTextNode('Close button text:'));
  1490. spoilerSpan.appendChild(newBr());
  1491. spoilerSpan.appendChild(spoilerClose);
  1492.  
  1493. spoilerOpen.value = settings.spoilerValues[adventureID].open;
  1494. spoilerClose.value = settings.spoilerValues[adventureID].close;
  1495.  
  1496. window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => {
  1497. if (output === 'Save') {
  1498. settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value;
  1499. settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value;
  1500. if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') {
  1501. delete settings.spoilerValues[adventureID];
  1502. }
  1503. saveData(settings);
  1504. }
  1505. });
  1506. });
  1507.  
  1508. document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => {
  1509. document.querySelector('#dialog input[name="open"]').value = document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open;
  1510. document.querySelector('#dialog input[name="close"]').value = document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close;
  1511. });
  1512.  
  1513. // --- Custom BBToolbar buttons
  1514. // Buttonless spoilers
  1515. const flashButton = document.querySelector('input[title=Flash]');
  1516. const newSpoilerButton = document.createElement('input');
  1517. newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
  1518. Object.assign(newSpoilerButton, { title: 'Buttonless Spoiler', type: 'button', style: 'background-position: -66px -88px;' });
  1519.  
  1520. newSpoilerButton.addEventListener('click', evt => {
  1521. const bbe = bbtoolbar.parentNode.querySelector('textarea');
  1522. if (bbe) {
  1523. bbe.focus();
  1524. const start = bbe.selectionStart;
  1525. const end = bbe.selectionEnd;
  1526. bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end);
  1527. bbe.selectionStart = start + 26;
  1528. bbe.selectionEnd = end + 26;
  1529. }
  1530. });
  1531.  
  1532. flashButton.parentNode.insertBefore(newSpoilerButton, flashButton);
  1533.  
  1534. // Audio button
  1535. const audioButton = document.createElement('input');
  1536. Object.assign(audioButton, { title: 'Audio Player', type: 'button', style: 'background-position: -22px -110px' });
  1537.  
  1538. audioButton.addEventListener('click', evt => {
  1539. const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
  1540. if (bbe) {
  1541. const msg = window.MSPFA.parseBBCode('Audio URL:<br>');
  1542. const audioInput = document.createElement('input');
  1543. Object.assign(audioInput, { type: 'url', name: 'audio-url', required: true });
  1544.  
  1545. const autoplayButton = document.createElement('input');
  1546. autoplayButton.type = 'checkbox';
  1547. autoplayButton.id = 'autoplay';
  1548. autoplayButton.checked = true;
  1549.  
  1550. const loopButton = document.createElement('input');
  1551. loopButton.type = 'checkbox';
  1552. loopButton.id = 'loop';
  1553. loopButton.checked = true;
  1554.  
  1555. const controlsButton = document.createElement('input');
  1556. controlsButton.type = 'checkbox';
  1557. controlsButton.id = 'controls';
  1558.  
  1559. msg.appendChild(audioInput);
  1560. msg.appendChild(newBr());
  1561. msg.appendChild(createLabel('Autoplay: ', 'autoplay'));
  1562. msg.appendChild(autoplayButton);
  1563. msg.appendChild(newBr());
  1564. msg.appendChild(createLabel('Loop: ', 'loop'));
  1565. msg.appendChild(loopButton);
  1566. msg.appendChild(newBr());
  1567. msg.appendChild(createLabel('Show controls: ', 'controls'));
  1568. msg.appendChild(controlsButton);
  1569. msg.appendChild(newBr());
  1570.  
  1571. window.MSPFA.dialog("Audio Player", msg, ["Okay", "Cancel"], (output, form) => {
  1572. if (output == "Okay") {
  1573. const start = bbe.selectionStart;
  1574. const end = bbe.selectionEnd;
  1575. const properties = `"${autoplayButton.checked ? ' autoplay' : ''}${loopButton.checked ? ' loop' : ''}${controlsButton.checked ? ' controls' : ''}`;
  1576. bbe.value = bbe.value.slice(0, start) + '<audio src="' + audioInput.value + properties +'>' + bbe.value.slice(start);
  1577. bbe.selectionStart = start + properties.length + audioInput.value.length + 13;
  1578. bbe.selectionEnd = end + properties.length + audioInput.value.length + 13;
  1579. }
  1580.  
  1581. });
  1582.  
  1583. audioInput.select();
  1584. }
  1585. });
  1586.  
  1587. flashButton.insertAdjacentElement('afterEnd', audioButton);
  1588.  
  1589. // YouTube button
  1590. const youtubeButton = document.createElement('input');
  1591. Object.assign(youtubeButton, { title: 'YouTube Video', type: 'button', style: 'background-position: 0px -110px' });
  1592.  
  1593. youtubeButton.addEventListener('click', evt => {
  1594. const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
  1595. if (bbe) {
  1596. const msg = window.MSPFA.parseBBCode('Video URL:<br>');
  1597. const videoUrl = document.createElement('input');
  1598. videoUrl.type = 'url';
  1599. videoUrl.name = 'youtube';
  1600. videoUrl.required = true;
  1601.  
  1602. const autoplayButton = document.createElement('input');
  1603. autoplayButton.type = 'checkbox';
  1604. autoplayButton.checked = true;
  1605. autoplayButton.id = 'autoplay';
  1606.  
  1607. const controlsButton = document.createElement('input');
  1608. controlsButton.type = 'checkbox';
  1609. controlsButton.checked = true;
  1610. controlsButton.id = 'controls';
  1611.  
  1612. const fullscreenButton = document.createElement('input');
  1613. fullscreenButton.type = 'checkbox';
  1614. fullscreenButton.checked = true;
  1615. fullscreenButton.id = 'fullscreen';
  1616.  
  1617. const widthInput = document.createElement('input');
  1618. Object.assign(widthInput, { type: 'number', required: true, value: 650, style: 'width: 5em' });
  1619.  
  1620. const heightInput = document.createElement('input');
  1621. Object.assign(heightInput, { type: 'number', required: true, value: 450, style: 'width: 5em' });
  1622.  
  1623. msg.appendChild(videoUrl);
  1624. msg.appendChild(newBr());
  1625. msg.appendChild(createLabel('Autoplay: ', 'autoplay'));
  1626. msg.appendChild(autoplayButton);
  1627. msg.appendChild(newBr());
  1628. msg.appendChild(createLabel('Show controls: ', 'controls'));
  1629. msg.appendChild(controlsButton);
  1630. msg.appendChild(newBr());
  1631. msg.appendChild(createLabel('Allow fullscreen: ', 'fullscreen'));
  1632. msg.appendChild(fullscreenButton);
  1633. msg.appendChild(newBr());
  1634. msg.appendChild(document.createTextNode('Embed size: '));
  1635. msg.appendChild(widthInput);
  1636. msg.appendChild(document.createTextNode('x'));
  1637. msg.appendChild(heightInput);
  1638.  
  1639. window.MSPFA.dialog("YouTube Embed", msg, ["Okay", "Cancel"], (output, form) => {
  1640. if (output == "Okay") {
  1641. let videoID = videoUrl.value.split('/');
  1642. videoID = videoID[videoID.length-1].replace('watch?v=', '').split('&')[0];
  1643.  
  1644. const start = bbe.selectionStart;
  1645. const end = bbe.selectionEnd;
  1646. const iframeContent = `<iframe width="${widthInput.value}" height="${heightInput.value}" src="https://www.youtube.com/embed/${videoID}?autoplay=${+autoplayButton.checked}&controls=${+controlsButton.checked}" frameborder="0" allow="accelerometer; ${autoplayButton.checked ? 'autoplay; ' : ''}encrypted-media;"${fullscreenButton.checked ? ' allowfullscreen' : ''}></iframe>`;
  1647. bbe.value = bbe.value.slice(0, start) + iframeContent + bbe.value.slice(start);
  1648. bbe.selectionStart = start + iframeContent + 13;
  1649. bbe.selectionEnd = end + iframeContent + 13;
  1650. }
  1651.  
  1652. });
  1653.  
  1654. videoUrl.select();
  1655. }
  1656. });
  1657.  
  1658. flashButton.insertAdjacentElement('afterEnd', youtubeButton);
  1659. flashButton.insertAdjacentText('afterEnd', ' ');
  1660.  
  1661. // Get preview link
  1662. const getPreviewLink = (form) => {
  1663. const page = parseInt(form.querySelector('a.major').textContent.replace('Page ', ''));
  1664. return "/preview/?s=" + params.s + "&p=" + page + "&d=" + encodeURIComponent(JSON.stringify({
  1665. p: page,
  1666. c: form.querySelector('input[name=cmd]').value,
  1667. b: form.querySelector('textarea[name=body]').value,
  1668. n: form.querySelector('input[name=next]').value,
  1669. k: !form.querySelector('input[name=usekeys]').checked
  1670. }));
  1671. }
  1672.  
  1673. // -- Drafts --
  1674. // Accessing draft text
  1675. const accessDraftsButton = document.createElement('input');
  1676. Object.assign(accessDraftsButton, { className: 'major', value: 'Saved Drafts', type: 'button' });
  1677. replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton);
  1678. accessDraftsButton.parentNode.insertBefore(document.createTextNode(' '), replaceButton);
  1679. //accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  1680. //accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  1681.  
  1682. accessDraftsButton.addEventListener('click', () => {
  1683. loadDrafts();
  1684.  
  1685. const draftDialog = window.MSPFA.parseBBCode('Use the textbox below to copy out the data and save to a file somewhere else, or click the download button below.\nYou can also paste in data to replace the current drafts to ones stored there.');
  1686. const draftInputTextarea = document.createElement('textarea');
  1687. draftInputTextarea.placeholder = 'Paste your draft data here';
  1688. draftInputTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  1689.  
  1690. const downloadLink = document.createElement('a');
  1691. downloadLink.textContent = 'Download drafts';
  1692. downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(drafts[adventureID], null, 4)));
  1693. downloadLink.setAttribute('download', `${adventureID}.json`);
  1694.  
  1695. draftInputTextarea.rows = 8;
  1696. draftDialog.appendChild(newBr());
  1697. draftDialog.appendChild(newBr());
  1698. draftDialog.appendChild(draftInputTextarea);
  1699. draftDialog.appendChild(newBr());
  1700. draftDialog.appendChild(newBr());
  1701. draftDialog.appendChild(downloadLink);
  1702. setTimeout(() => {
  1703. draftInputTextarea.focus();
  1704. draftInputTextarea.selectionStart = 0;
  1705. draftInputTextarea.selectionEnd = 0;
  1706. draftInputTextarea.scrollTop = 0;
  1707. }, 1);
  1708.  
  1709. draftInputTextarea.value = JSON.stringify(drafts[adventureID], null, 4);
  1710.  
  1711. window.MSPFA.dialog('Saved Drafts', draftDialog, ["Load Draft", "Cancel"], (output, form) => {
  1712. if (output === "Load Draft") {
  1713. if (draftInputTextarea.value === '') {
  1714. setTimeout(() => {
  1715. 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) => {
  1716. if (output === "Delete") {
  1717. loadDrafts();
  1718. drafts[adventureID] = {};
  1719.  
  1720. if (settings.drafts && settings.drafts[adventureID]) {
  1721. delete settings.drafts[adventureID];
  1722. saveData(settings);
  1723. }
  1724.  
  1725. saveDrafts(drafts);
  1726. }
  1727. });
  1728. }, 1);
  1729. } else if (draftInputTextarea.value !== JSON.stringify(drafts[adventureID], null, 4)) {
  1730. setTimeout(() => {
  1731. 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) => {
  1732. if (output === "Load") {
  1733. let newData = {};
  1734. try { // Just in case the data given is invalid.
  1735. newData = JSON.parse(draftInputTextarea.value);
  1736. } catch (err) {
  1737. console.error(err);
  1738. setTimeout(() => {
  1739. window.MSPFA.dialog('Error', window.MSPFA.parseBBCode('The entered data is invalid.'), ["Okay"]);
  1740. }, 1);
  1741. return;
  1742. }
  1743.  
  1744. loadDrafts();
  1745. drafts[adventureID] = newData;
  1746. saveDrafts(drafts);
  1747. }
  1748. });
  1749. }, 1);
  1750. }
  1751. }
  1752. });
  1753. });
  1754.  
  1755. const addDraftPagesButton = document.createElement('input');
  1756. replaceButton.parentNode.insertBefore(addDraftPagesButton, replaceButton);
  1757. addDraftPagesButton.parentNode.insertBefore(newBr(), replaceButton);
  1758. addDraftPagesButton.parentNode.insertBefore(newBr(), replaceButton);
  1759.  
  1760. Object.assign(addDraftPagesButton, { className: 'major', value: 'Add Draft Pages', type: 'button' });
  1761. addDraftPagesButton.addEventListener('click', () => {
  1762. const newPageForm = document.querySelector('#newpage');
  1763. let currentPage = 1;
  1764. let pageAmount = 0;
  1765. if (newPageForm.nextSibling.nodeName !== '#text') {
  1766. currentPage = parseInt(newPageForm.nextSibling.id.replace('p', ''))+1;
  1767. }
  1768. for (let i = currentPage; drafts[adventureID][i] && pageAmount < 10; i++) {
  1769. pageAmount++;
  1770. }
  1771.  
  1772. let rangeStr = `s ${currentPage}-${currentPage+pageAmount-1}`;
  1773. if (currentPage === currentPage+pageAmount-1) rangeStr = ` ${currentPage}`;
  1774.  
  1775. if (pageAmount > 0) {
  1776. window.MSPFA.dialog("Drafts: Add New Pages", window.MSPFA.parseBBCode(`This will add the pages with draft data from page${rangeStr}.<br>[i][center](you are only allowed 10 at once)[/center][/i]`), ["Okay", "Cancel"], (output, form) => {
  1777. if (output === "Okay") {
  1778. for (let i = currentPage; drafts[adventureID][i] && i < currentPage+10; i++) {
  1779. newPageForm.querySelector('input[name=cmd]').value = drafts[adventureID][i].command;
  1780. newPageForm.querySelector('textarea[name=body]').value = drafts[adventureID][i].pageContent;
  1781. if (drafts[adventureID][i].next) newPageForm.querySelector('input[name=next]').value = drafts[adventureID][i].next;
  1782.  
  1783. newPageForm.querySelector('input[name=save]').click();
  1784. }
  1785. }
  1786. });
  1787. }
  1788. });
  1789.  
  1790. // Draft stuff
  1791. const showDraftDialog = (pageNum) => {
  1792. loadDrafts();
  1793.  
  1794. const msg = document.createElement('span');
  1795. msg.appendChild(document.createTextNode('Command:'));
  1796. msg.appendChild(document.createElement('br'));
  1797.  
  1798. const commandInput = document.createElement('input');
  1799. Object.assign(commandInput, { style: 'width: 100%; box-sizing: border-box;', readOnly: true, });
  1800.  
  1801. msg.appendChild(commandInput);
  1802. msg.appendChild(document.createElement('br'));
  1803. msg.appendChild(document.createElement('br'));
  1804.  
  1805. msg.appendChild(document.createTextNode('Body:'));
  1806.  
  1807. const bodyInput = document.createElement('textarea');
  1808. Object.assign(bodyInput, { style: 'width: 100%; box-sizing: border-box; resize: vertical;', readOnly: true, rows: 8 });
  1809.  
  1810. msg.appendChild(bodyInput);
  1811.  
  1812. msg.appendChild(document.createElement('br'));
  1813. msg.appendChild(document.createElement('br'));
  1814. msg.appendChild(document.createTextNode('Next page(s):'));
  1815. msg.appendChild(document.createElement('br'));
  1816. const nextPageInput = document.createElement('input');
  1817. Object.assign(nextPageInput, { style: 'width: 100%; box-sizing: border-box;', readOnly: true, });
  1818. msg.appendChild(nextPageInput);
  1819.  
  1820. const pageElement = document.querySelector(`#p${pageNum}`);
  1821.  
  1822. let shownMessage = msg;
  1823. let optionButtons = [];
  1824.  
  1825. const commandElement = pageElement.querySelector('input[name="cmd"]');
  1826. const pageContentElement = pageElement.querySelector('textarea[name="body"]');
  1827. const nextPagesElement = pageElement.querySelector('input[name="next"]');
  1828.  
  1829. if (typeof drafts[adventureID][pageNum] === "undefined") {
  1830. shownMessage = document.createTextNode('There is no draft saved for this page.');
  1831. optionButtons = ["Save New", "Close"];
  1832. } else {
  1833. commandInput.value = drafts[adventureID][pageNum].command;
  1834. bodyInput.textContent = drafts[adventureID][pageNum].pageContent;
  1835. nextPageInput.value = drafts[adventureID][pageNum].next ? drafts[adventureID][pageNum].next : parseInt(pageNum)+1;
  1836. optionButtons = ["Save New", "Load", "Delete", "Close"];
  1837. }
  1838.  
  1839. window.MSPFA.dialog(`Page ${pageNum} Draft`, shownMessage, optionButtons, (output, form) => {
  1840. if (output === "Save New") {
  1841. if (typeof drafts[adventureID][pageNum] === "undefined") {
  1842. loadDrafts();
  1843. drafts[adventureID][pageNum] = {
  1844. command: commandElement.value,
  1845. pageContent: pageContentElement.value,
  1846. next: nextPagesElement.value
  1847. }
  1848. saveDrafts(drafts);
  1849. } else {
  1850. setTimeout(() => {
  1851. 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) => {
  1852. if (output === "Yes") {
  1853. loadDrafts();
  1854. drafts[adventureID][pageNum] = {
  1855. command: commandElement.value,
  1856. pageContent: pageContentElement.value,
  1857. next: nextPagesElement.value
  1858. }
  1859. saveDrafts(drafts);
  1860. }
  1861. });
  1862. }, 1);
  1863. }
  1864. } else if (output === "Load") {
  1865. if (pageContentElement.value === '' && (commandElement.value === '' || commandElement.value === document.querySelector('#defaultcmd').value)) {
  1866. commandElement.value = drafts[adventureID][pageNum].command;
  1867. pageContentElement.value = drafts[adventureID][pageNum].pageContent;
  1868. if (drafts[adventureID][pageNum].next) nextPagesElement.value = drafts[adventureID][pageNum].next;
  1869. pageElement.querySelector('input[value="Save"]').disabled = false;
  1870. } else {
  1871. setTimeout(() => {
  1872. 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) => {
  1873. if (output === "Yes") {
  1874. commandElement.value = drafts[adventureID][pageNum].command;
  1875. pageContentElement.value = drafts[adventureID][pageNum].pageContent;
  1876. if (drafts[adventureID][pageNum].next) nextPagesElement.value = drafts[adventureID][pageNum].next;
  1877. pageElement.querySelector('input[value="Save"]').disabled = false;
  1878. }
  1879. });
  1880. }, 1);
  1881. }
  1882. } else if (output === "Delete") {
  1883. setTimeout(() => {
  1884. window.MSPFA.dialog('Delete this draft?', document.createTextNode('This action is irreversable! Are you sure?'), ["Yes", "No"], (output, form) => {
  1885. if (output === "Yes") {
  1886. loadDrafts();
  1887. delete drafts[adventureID][pageNum];
  1888.  
  1889. if (settings.drafts && settings.drafts[adventureID] && settings.drafts[adventureID][pageNum]) {
  1890. delete settings.drafts[adventureID][pageNum];
  1891. saveData(settings);
  1892. }
  1893.  
  1894. saveDrafts(drafts);
  1895. }
  1896. });
  1897. }, 1);
  1898. }
  1899. });
  1900. }
  1901.  
  1902. const createDraftButton = (form) => {
  1903. const draftButton = document.createElement('input');
  1904. Object.assign(draftButton, { className: 'major draft', type: 'button', value: 'Draft' });
  1905. draftButton.addEventListener('click', () => {
  1906. showDraftDialog(form.id.replace('p', ''));
  1907. });
  1908. return draftButton;
  1909. }
  1910.  
  1911. pageLoad(() => {
  1912. let allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1913. if (allPages.length !== 0) {
  1914. allPages.forEach(form => {
  1915. const prevButton = form.querySelector('input[name="preview"]');
  1916. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1917. prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton);
  1918.  
  1919. // Preview
  1920. const previewButton = form.querySelector('input[value=Preview]');
  1921. const previewLink = addLink(previewButton, getPreviewLink(form), '_blank');
  1922. previewButton.addEventListener('mousedown', () => {
  1923. previewLink.href = getPreviewLink(form);
  1924. });
  1925.  
  1926. // "Enable keyboard shortcuts" label
  1927. const shortcutCheck = form.querySelector('input[type="checkbox"]');
  1928. shortcutCheck.previousSibling.textContent = '';
  1929. shortcutCheck.id = `key-${form.id}`;
  1930. shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
  1931. });
  1932. document.querySelector('input[value="Add"]').addEventListener('click', () => {
  1933. allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1934. const form = document.querySelector(`#p${allPages.length}`);
  1935. const prevButton = form.querySelector('input[name="preview"]');
  1936. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1937. prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton);
  1938.  
  1939. // Preview link
  1940. const previewButton = form.querySelector('input[value=Preview]');
  1941. const previewLink = addLink(previewButton, getPreviewLink(form), '_blank');
  1942. previewButton.addEventListener('mousedown', () => {
  1943. previewLink.href = getPreviewLink(form);
  1944. });
  1945.  
  1946. // "Enable keyboard shortcuts" label
  1947. const shortcutCheck = form.querySelector('input[type="checkbox"]');
  1948. shortcutCheck.previousSibling.textContent = '';
  1949. shortcutCheck.id = `key-${form.id}`;
  1950. shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
  1951. });
  1952. const newForm = document.querySelector('#newpage');
  1953. {
  1954. // "Enable keyboard shortcuts" label
  1955. const shortcutCheck = newForm.querySelector('input[type="checkbox"]');
  1956. shortcutCheck.previousSibling.textContent = '';
  1957. shortcutCheck.id = `key-${newForm.id}`;
  1958. shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
  1959. }
  1960. const newPreviewButton = newForm.querySelector('input[value=Preview]');
  1961. const newPreviewLink = addLink(newPreviewButton, getPreviewLink(newForm), '_blank');
  1962. newPreviewButton.addEventListener('mousedown', () => {
  1963. newPreviewLink.href = getPreviewLink(newForm);
  1964. });
  1965. return true;
  1966. }
  1967. });
  1968.  
  1969. if (params.click) {
  1970. if (params.click === 's') {
  1971. spoilerButton.click();
  1972. } else if (params.click === 'd') {
  1973. accessDraftsButton.click();
  1974. }
  1975. }
  1976.  
  1977. // Don't scroll after pressing a BBToolbar button (awesome)
  1978. /* Disabled because it sometimes doesn't function properly, and for the most part, may be annoying.
  1979. let lastScroll = window.scrollY;
  1980. pageLoad(() => {
  1981. if (document.querySelectorAll('#storypages textarea').length > 1) {
  1982. document.querySelectorAll('#storypages textarea').forEach(textarea => {
  1983. textarea.addEventListener('focus', () => {
  1984. window.scrollTo(window.scrollX, lastScroll);
  1985. });
  1986. });
  1987.  
  1988. document.addEventListener('scroll', evt => {
  1989. lastScroll = window.scrollY;
  1990. });
  1991. return true;
  1992. }
  1993. });/**/
  1994.  
  1995. // Focus on the text input when clicking on the Color or Background Color BBToolbar buttons
  1996. const colourButtons = [document.querySelector('#bbtoolbar input[data-tag=color]'), document.querySelector('#bbtoolbar input[data-tag=background]')];
  1997.  
  1998. let prevColour;
  1999.  
  2000. colourButtons[0].addEventListener('click', () => {
  2001. const inputs = document.querySelectorAll('#dialog input');
  2002. document.querySelector('button[data-value="Okay"]').addEventListener('click', () => {
  2003. prevColour = inputs[0].value;
  2004. });
  2005. if (prevColour) {
  2006. inputs[0].value = prevColour;
  2007. inputs[1].value = prevColour;
  2008. }
  2009. });
  2010.  
  2011. colourButtons.forEach(button => {
  2012. button.addEventListener('click', () => {
  2013. setTimeout(() => {
  2014. document.querySelector('#dialog input[type=text]').select();
  2015. }, 1);
  2016. });
  2017. });
  2018.  
  2019. // Focus on select dropdown when choosing font
  2020. document.querySelector('#bbtoolbar input[data-tag=font]').addEventListener('click', () => {
  2021. setTimeout(() => {
  2022. document.querySelector('#dialog select').focus();
  2023. }, 1);
  2024. });
  2025. }
  2026. else if (location.pathname === "/my/profile/") {
  2027. // Nothing yet
  2028. }
  2029. else if (location.pathname === "/user/") {
  2030. // Button links
  2031. pageLoad(() => {
  2032. const msgButton = document.querySelector('#sendmsg');
  2033. if (msgButton) {
  2034. addLink(msgButton, `/my/messages/new/?u=${params.u}`);
  2035. addLink(document.querySelector('#favstories'), `/favs/?u=${params.u}`);
  2036. return true;
  2037. }
  2038. });
  2039.  
  2040. // Add extra user stats
  2041. pageLoad(() => {
  2042. if (window.MSPFA) {
  2043. const stats = document.querySelector('#userinfo table');
  2044.  
  2045. const joinTr = stats.insertRow(1);
  2046. const joinTextTd = joinTr.insertCell(0);
  2047. joinTextTd.appendChild(document.createTextNode("Account created:"));
  2048. const joinDate = joinTr.insertCell(1);
  2049. const joinTime = document.createElement('b');
  2050. joinTime.textContent = "Loading...";
  2051. joinDate.appendChild(joinTime);
  2052.  
  2053. const advCountTr = stats.insertRow(2);
  2054. const advTextTd = advCountTr.insertCell(0);
  2055. advTextTd.appendChild(document.createTextNode("Adventures created:"));
  2056. const advCount = advCountTr.insertCell(1);
  2057. const advCountText = document.createElement('b');
  2058. advCountText.textContent = "Loading...";
  2059. advCount.appendChild(advCountText);
  2060.  
  2061. // Show user creation date
  2062. window.MSPFA.request(0, {
  2063. do: "user",
  2064. u: params.u
  2065. }, user => {
  2066. if (typeof user !== "undefined") {
  2067. joinTime.textContent = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
  2068. }
  2069.  
  2070. // Show created adventures
  2071. window.MSPFA.request(0, {
  2072. do: "editor",
  2073. u: params.u
  2074. }, s => {
  2075. if (typeof s !== "undefined") {
  2076. advCountText.textContent = s.length;
  2077. }
  2078.  
  2079. // Show favourites
  2080. if (document.querySelector('#favstories').style.display !== 'none') {
  2081. const favCountTr = stats.insertRow(3);
  2082. const favTextTd = favCountTr.insertCell(0);
  2083. favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
  2084. const favCount = favCountTr.insertCell(1);
  2085. const favCountText = document.createElement('b');
  2086. favCountText.textContent = "Loading...";
  2087. window.MSPFA.request(0, {
  2088. do: "favs",
  2089. u: params.u
  2090. }, s => {
  2091. if (typeof s !== "undefined") {
  2092. favCountText.textContent = s.length;
  2093. }
  2094. });
  2095. favCount.appendChild(favCountText);
  2096. }
  2097. });
  2098. });
  2099.  
  2100. return true;
  2101. }
  2102. });
  2103. }
  2104. else if (location.pathname === "/favs/" && location.search) {
  2105. const toggleButton = document.createElement('input');
  2106. Object.assign(toggleButton, { className: "major", type: "button", value: "Hide Muted Adventures", title: 'Cycle through muted/unmuted adventures' });
  2107. const buttonRow = document.querySelector('table.container.alt').insertRow(2);
  2108.  
  2109. let stories = [];
  2110. // Button links
  2111. pageLoad(() => {
  2112. stories = document.querySelectorAll('#stories tr');
  2113. let favCount = 0;
  2114.  
  2115. if (stories.length > 0) {
  2116. stories.forEach(story => {
  2117. favCount++;
  2118. const id = story.querySelector('a').href.replace('https://mspfa.com/', '');
  2119. pageLoad(() => {
  2120. if (window.MSPFA.me.i) {
  2121. addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`);
  2122. return true;
  2123. }
  2124. if (pageLoaded) return true;
  2125. });
  2126. addLink(story.querySelector('.rss.major'), `/rss/${id}`);
  2127. });
  2128.  
  2129. // Fav count
  2130. const username = document.querySelector('#username');
  2131. username.parentNode.appendChild(newBr());
  2132. username.parentNode.appendChild(newBr());
  2133. username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`));
  2134.  
  2135. return true;
  2136. }
  2137. if (pageLoaded) return true;
  2138. });
  2139.  
  2140. pageLoad(() => {
  2141. if (window.MSPFA && window.MSPFA.me) {
  2142. if (window.MSPFA.me.i === params.u) {
  2143. const cell = buttonRow.insertCell(0);
  2144. cell.appendChild(toggleButton);
  2145. return true;
  2146. }
  2147. }
  2148. if (pageLoaded) return true;
  2149. });
  2150.  
  2151. let type = 0;
  2152.  
  2153. toggleButton.addEventListener('click', () => {
  2154. type++;
  2155. if (type > 2) type = 0;
  2156.  
  2157. stories.forEach(story => {
  2158. const unmuted = story.querySelector('.notify').className.includes(' lit');
  2159. story.style.display = '';
  2160. if (type === 2 && unmuted || type === 1 && !unmuted) {
  2161. story.style.display = 'none';
  2162. }
  2163. });
  2164.  
  2165. if (type === 0) {
  2166. // show all
  2167. toggleButton.value = 'Hide Muted Adventures';
  2168. }
  2169. else if (type === 1) {
  2170. // hide muted
  2171. toggleButton.value = 'Hide Unmuted Adventures';
  2172. }
  2173. else {
  2174. // only muted
  2175. toggleButton.value = 'Show All Adventures';
  2176. }
  2177. });
  2178. }
  2179. else if (location.pathname === "/search/" && location.search) {
  2180. // Character and word statistics
  2181. const statTable = document.createElement('table');
  2182. const statTbody = document.createElement('tbody');
  2183. const statTr = statTbody.insertRow(0);
  2184. const charCount = statTr.insertCell(0);
  2185. const wordCount = statTr.insertCell(0);
  2186. const statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2);
  2187. const statParentTd = statParentTr.insertCell(0);
  2188.  
  2189. const statHeaderTr = statTbody.insertRow(0);
  2190. const statHeader = document.createElement('th');
  2191. statHeader.colSpan = '2';
  2192.  
  2193. statHeaderTr.appendChild(statHeader);
  2194. statHeader.textContent = 'Statistics may not be entirely accurate.';
  2195.  
  2196. statTable.style.width = "100%";
  2197.  
  2198. charCount.textContent = "Character count: loading...";
  2199. wordCount.textContent = "Word count: loading...";
  2200.  
  2201. statTable.appendChild(statTbody);
  2202. statParentTd.appendChild(statTable);
  2203.  
  2204. pageLoad(() => {
  2205. if (document.querySelector('#pages br')) {
  2206. const bbc = window.MSPFA.BBC.slice();
  2207. bbc.splice(0, 3);
  2208.  
  2209. window.MSPFA.request(0, {
  2210. do: "story",
  2211. s: params.s
  2212. }, story => {
  2213. if (typeof story !== "undefined") {
  2214. const pageContent = [];
  2215. story.p.forEach(p => {
  2216. pageContent.push(p.c);
  2217. pageContent.push(p.b);
  2218. });
  2219.  
  2220. const storyText = pageContent.join(' ')
  2221. .replace(/\n/g, ' ')
  2222. .replace(bbc[0][0], '$1')
  2223. .replace(bbc[1][0], '$1')
  2224. .replace(bbc[2][0], '$1')
  2225. .replace(bbc[3][0], '$1')
  2226. .replace(bbc[4][0], '$2')
  2227. .replace(bbc[5][0], '$3')
  2228. .replace(bbc[6][0], '$3')
  2229. .replace(bbc[7][0], '$3')
  2230. .replace(bbc[8][0], '$3')
  2231. .replace(bbc[9][0], '$3')
  2232. .replace(bbc[10][0], '$2')
  2233. .replace(bbc[11][0], '$1')
  2234. .replace(bbc[12][0], '$3')
  2235. .replace(bbc[13][0], '$3')
  2236. .replace(bbc[14][0], '')
  2237. .replace(bbc[16][0], '$1')
  2238. .replace(bbc[17][0], '$2 $4 $5')
  2239. .replace(bbc[18][0], '$2 $4 $5')
  2240. .replace(bbc[19][0], '')
  2241. .replace(bbc[20][0], '')
  2242. .replace(/<(.*?)>/g, '');
  2243.  
  2244. wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`;
  2245. charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`;
  2246. }
  2247. });
  2248. return true;
  2249. }
  2250. });
  2251. }
  2252. else if (location.pathname === "/stories/" && location.search) {
  2253.  
  2254. const searchButton = document.querySelector('#doit');
  2255.  
  2256. // Let you press "Enter" to search while the tags textarea is selected
  2257. document.querySelector('textarea[name=tags]').addEventListener('keydown', evt => {
  2258. if (evt.key === "Enter") {
  2259. evt.preventDefault();
  2260. searchButton.click();
  2261. }
  2262. });
  2263.  
  2264. // Add a button to hide adventures with a tag
  2265. const tagHide = document.createElement('input');
  2266. Object.assign(tagHide, { id: 'taghide', value: '-', className: 'major', type: 'button', style: 'padding: 0;', title: 'Hide Tag' });
  2267.  
  2268. const tagAdd = document.querySelector('#tagadd');
  2269. tagAdd.parentNode.insertBefore(tagHide, tagAdd.nextSibling);
  2270. tagAdd.parentNode.insertBefore(document.createTextNode(' '), tagAdd.nextSibling);
  2271.  
  2272. const tagselect = document.querySelector('#tagselect');
  2273. const taglist = document.querySelector('textarea[name=tags]');
  2274. tagHide.addEventListener('click', () => {
  2275. let tags = [];
  2276. if(taglist.value) {
  2277. tags = taglist.value.split(",");
  2278. }
  2279. if(tagselect.value && tags.indexOf('-'+tagselect.value) == -1) {
  2280. tags.push('-'+tagselect.value);
  2281. }
  2282. taglist.value = tags.join(",");
  2283. tagselect.options[0].selected = true;
  2284. });
  2285.  
  2286. // Add titles on hover to the [?] and [+] buttons
  2287. document.querySelector('#taghelp').title = 'Tip';
  2288. tagAdd.title = 'Add Tag';
  2289.  
  2290. // Click text to check/uncheck boxes
  2291. ['Ongoing', 'Complete', 'Inactive'].forEach(t => {
  2292. const check = document.querySelector(`input[name="${t.toLowerCase()}"]`);
  2293. check.id = `check_${t.toLowerCase()}`;
  2294. const label = createLabel(' ' + t + ' ', check.id);
  2295. check.nextSibling.textContent = '';
  2296. check.parentNode.insertBefore(label, check.nextSibling);
  2297. });
  2298. const resultAmount = document.createElement('span');
  2299. searchButton.parentNode.appendChild(resultAmount);
  2300.  
  2301. pageLoad(() => {
  2302. if (window.MSPFA) {
  2303. window.MSPFA.request(0, {
  2304. do: "stories",
  2305. n: params.n,
  2306. t: params.t,
  2307. h: params.h,
  2308. o: params.o,
  2309. p: params.p,
  2310. m: 50000
  2311. }, (s) => {
  2312. resultAmount.textContent = `Number of results: ${s.length}`;
  2313. return true;
  2314. });
  2315. return true;
  2316. }
  2317. },1);
  2318.  
  2319. pageLoad(() => {
  2320. const stories = document.querySelector('#stories');
  2321. if (stories.childNodes.length > 0) {
  2322. if (params.load && stories.childNodes.length === 1) {
  2323. stories.querySelector('a').click();
  2324. }
  2325.  
  2326. stories.querySelectorAll('tr').forEach(story => {
  2327. const storyID = story.querySelector('a.major').href.split('&')[0].replace(/\D/g, '');
  2328. addLink(story.querySelector('.rss'), `/rss/?s=${storyID}`);
  2329.  
  2330. pageLoad(() => {
  2331. if (window.MSPFA.me.i) {
  2332. addLink(story.querySelector('.edit.major'), `/my/stories/info/?s=${storyID}`);
  2333. return true;
  2334. }
  2335. if (pageLoaded) return true;
  2336. });
  2337. });
  2338. return true;
  2339. }
  2340. if (pageLoaded) return true;
  2341. });
  2342. }
  2343. })();