MSPFA extras

Adds custom quality of life features to MSPFA.

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

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