Listography Backup

Adds functionality for plaintext list export for backup purposes

当前为 2023-07-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Listography Backup
  3. // @version 1.0.0
  4. // @author petracoding
  5. // @namespace petracoding
  6. // @grant none
  7. // @include https://listography.com/*
  8. // @include http://listography.com/*
  9. // @description Adds functionality for plaintext list export for backup purposes
  10. // ==/UserScript==
  11.  
  12. /******/ (() => { // webpackBootstrap
  13. /******/ var __webpack_modules__ = ({
  14.  
  15. /***/ 84:
  16. /***/ ((module, exports, __webpack_require__) => {
  17.  
  18. // Imports
  19. var ___CSS_LOADER_API_IMPORT___ = __webpack_require__(645);
  20. exports = ___CSS_LOADER_API_IMPORT___(false);
  21. // Module
  22. exports.push([module.id, ".backup-button{background:none;border:none;color:#777;font-family:helvetica, arial, sans-serif;font-size:12px;cursor:pointer;font-style:italic}.backup-button:hover,.backup-button:focus{text-decoration:underline}#backup-loading,.backup-popup{position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);padding:50px;background:white;z-index:999;border:2px dotted lightgray;border-radius:5px;width:500px;min-height:200px;justify-content:center;align-items:center}.backup-popup h1{margin-top:0;margin-bottom:0}.backup-popup h2{margin-top:30px}.backup-popup textarea{width:100%;min-height:300px;border:1px solid #919191;border-radius:5px;padding:10px;box-sizing:border-box}.backup-popup{width:80%;min-height:300px}.backup-popup-scrollbox{max-height:300px;overflow-y:auto;border:1px solid #919191;border-radius:5px;padding:10px}.backup-popup-overlay{content:\"\";position:fixed;z-index:99;background:rgba(0,0,0,0.7);width:100%;height:100%;top:0;left:0}\n", ""]);
  23. // Exports
  24. module.exports = exports;
  25.  
  26.  
  27. /***/ }),
  28.  
  29. /***/ 645:
  30. /***/ ((module) => {
  31.  
  32. "use strict";
  33.  
  34.  
  35. /*
  36. MIT License http://www.opensource.org/licenses/mit-license.php
  37. Author Tobias Koppers @sokra
  38. */
  39. // css base code, injected by the css-loader
  40. // eslint-disable-next-line func-names
  41. module.exports = function (useSourceMap) {
  42. var list = []; // return the list of modules as css string
  43.  
  44. list.toString = function toString() {
  45. return this.map(function (item) {
  46. var content = cssWithMappingToString(item, useSourceMap);
  47.  
  48. if (item[2]) {
  49. return "@media ".concat(item[2], " {").concat(content, "}");
  50. }
  51.  
  52. return content;
  53. }).join('');
  54. }; // import a list of modules into the list
  55. // eslint-disable-next-line func-names
  56.  
  57.  
  58. list.i = function (modules, mediaQuery, dedupe) {
  59. if (typeof modules === 'string') {
  60. // eslint-disable-next-line no-param-reassign
  61. modules = [[null, modules, '']];
  62. }
  63.  
  64. var alreadyImportedModules = {};
  65.  
  66. if (dedupe) {
  67. for (var i = 0; i < this.length; i++) {
  68. // eslint-disable-next-line prefer-destructuring
  69. var id = this[i][0];
  70.  
  71. if (id != null) {
  72. alreadyImportedModules[id] = true;
  73. }
  74. }
  75. }
  76.  
  77. for (var _i = 0; _i < modules.length; _i++) {
  78. var item = [].concat(modules[_i]);
  79.  
  80. if (dedupe && alreadyImportedModules[item[0]]) {
  81. // eslint-disable-next-line no-continue
  82. continue;
  83. }
  84.  
  85. if (mediaQuery) {
  86. if (!item[2]) {
  87. item[2] = mediaQuery;
  88. } else {
  89. item[2] = "".concat(mediaQuery, " and ").concat(item[2]);
  90. }
  91. }
  92.  
  93. list.push(item);
  94. }
  95. };
  96.  
  97. return list;
  98. };
  99.  
  100. function cssWithMappingToString(item, useSourceMap) {
  101. var content = item[1] || ''; // eslint-disable-next-line prefer-destructuring
  102.  
  103. var cssMapping = item[3];
  104.  
  105. if (!cssMapping) {
  106. return content;
  107. }
  108.  
  109. if (useSourceMap && typeof btoa === 'function') {
  110. var sourceMapping = toComment(cssMapping);
  111. var sourceURLs = cssMapping.sources.map(function (source) {
  112. return "/*# sourceURL=".concat(cssMapping.sourceRoot || '').concat(source, " */");
  113. });
  114. return [content].concat(sourceURLs).concat([sourceMapping]).join('\n');
  115. }
  116.  
  117. return [content].join('\n');
  118. } // Adapted from convert-source-map (MIT)
  119.  
  120.  
  121. function toComment(sourceMap) {
  122. // eslint-disable-next-line no-undef
  123. var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
  124. var data = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(base64);
  125. return "/*# ".concat(data, " */");
  126. }
  127.  
  128. /***/ }),
  129.  
  130. /***/ 121:
  131. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  132.  
  133. var api = __webpack_require__(379);
  134. var content = __webpack_require__(84);
  135.  
  136. content = content.__esModule ? content.default : content;
  137.  
  138. if (typeof content === 'string') {
  139. content = [[module.id, content, '']];
  140. }
  141.  
  142. var options = {};
  143.  
  144. options.insert = "head";
  145. options.singleton = false;
  146.  
  147. var update = api(content, options);
  148.  
  149.  
  150.  
  151. module.exports = content.locals || {};
  152.  
  153. /***/ }),
  154.  
  155. /***/ 379:
  156. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  157.  
  158. "use strict";
  159.  
  160.  
  161. var isOldIE = function isOldIE() {
  162. var memo;
  163. return function memorize() {
  164. if (typeof memo === 'undefined') {
  165. // Test for IE <= 9 as proposed by Browserhacks
  166. // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
  167. // Tests for existence of standard globals is to allow style-loader
  168. // to operate correctly into non-standard environments
  169. // @see https://github.com/webpack-contrib/style-loader/issues/177
  170. memo = Boolean(window && document && document.all && !window.atob);
  171. }
  172.  
  173. return memo;
  174. };
  175. }();
  176.  
  177. var getTarget = function getTarget() {
  178. var memo = {};
  179. return function memorize(target) {
  180. if (typeof memo[target] === 'undefined') {
  181. var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself
  182.  
  183. if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
  184. try {
  185. // This will throw an exception if access to iframe is blocked
  186. // due to cross-origin restrictions
  187. styleTarget = styleTarget.contentDocument.head;
  188. } catch (e) {
  189. // istanbul ignore next
  190. styleTarget = null;
  191. }
  192. }
  193.  
  194. memo[target] = styleTarget;
  195. }
  196.  
  197. return memo[target];
  198. };
  199. }();
  200.  
  201. var stylesInDom = [];
  202.  
  203. function getIndexByIdentifier(identifier) {
  204. var result = -1;
  205.  
  206. for (var i = 0; i < stylesInDom.length; i++) {
  207. if (stylesInDom[i].identifier === identifier) {
  208. result = i;
  209. break;
  210. }
  211. }
  212.  
  213. return result;
  214. }
  215.  
  216. function modulesToDom(list, options) {
  217. var idCountMap = {};
  218. var identifiers = [];
  219.  
  220. for (var i = 0; i < list.length; i++) {
  221. var item = list[i];
  222. var id = options.base ? item[0] + options.base : item[0];
  223. var count = idCountMap[id] || 0;
  224. var identifier = "".concat(id, " ").concat(count);
  225. idCountMap[id] = count + 1;
  226. var index = getIndexByIdentifier(identifier);
  227. var obj = {
  228. css: item[1],
  229. media: item[2],
  230. sourceMap: item[3]
  231. };
  232.  
  233. if (index !== -1) {
  234. stylesInDom[index].references++;
  235. stylesInDom[index].updater(obj);
  236. } else {
  237. stylesInDom.push({
  238. identifier: identifier,
  239. updater: addStyle(obj, options),
  240. references: 1
  241. });
  242. }
  243.  
  244. identifiers.push(identifier);
  245. }
  246.  
  247. return identifiers;
  248. }
  249.  
  250. function insertStyleElement(options) {
  251. var style = document.createElement('style');
  252. var attributes = options.attributes || {};
  253.  
  254. if (typeof attributes.nonce === 'undefined') {
  255. var nonce = true ? __webpack_require__.nc : 0;
  256.  
  257. if (nonce) {
  258. attributes.nonce = nonce;
  259. }
  260. }
  261.  
  262. Object.keys(attributes).forEach(function (key) {
  263. style.setAttribute(key, attributes[key]);
  264. });
  265.  
  266. if (typeof options.insert === 'function') {
  267. options.insert(style);
  268. } else {
  269. var target = getTarget(options.insert || 'head');
  270.  
  271. if (!target) {
  272. throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
  273. }
  274.  
  275. target.appendChild(style);
  276. }
  277.  
  278. return style;
  279. }
  280.  
  281. function removeStyleElement(style) {
  282. // istanbul ignore if
  283. if (style.parentNode === null) {
  284. return false;
  285. }
  286.  
  287. style.parentNode.removeChild(style);
  288. }
  289. /* istanbul ignore next */
  290.  
  291.  
  292. var replaceText = function replaceText() {
  293. var textStore = [];
  294. return function replace(index, replacement) {
  295. textStore[index] = replacement;
  296. return textStore.filter(Boolean).join('\n');
  297. };
  298. }();
  299.  
  300. function applyToSingletonTag(style, index, remove, obj) {
  301. var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE
  302.  
  303. /* istanbul ignore if */
  304.  
  305. if (style.styleSheet) {
  306. style.styleSheet.cssText = replaceText(index, css);
  307. } else {
  308. var cssNode = document.createTextNode(css);
  309. var childNodes = style.childNodes;
  310.  
  311. if (childNodes[index]) {
  312. style.removeChild(childNodes[index]);
  313. }
  314.  
  315. if (childNodes.length) {
  316. style.insertBefore(cssNode, childNodes[index]);
  317. } else {
  318. style.appendChild(cssNode);
  319. }
  320. }
  321. }
  322.  
  323. function applyToTag(style, options, obj) {
  324. var css = obj.css;
  325. var media = obj.media;
  326. var sourceMap = obj.sourceMap;
  327.  
  328. if (media) {
  329. style.setAttribute('media', media);
  330. } else {
  331. style.removeAttribute('media');
  332. }
  333.  
  334. if (sourceMap && typeof btoa !== 'undefined') {
  335. css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");
  336. } // For old IE
  337.  
  338. /* istanbul ignore if */
  339.  
  340.  
  341. if (style.styleSheet) {
  342. style.styleSheet.cssText = css;
  343. } else {
  344. while (style.firstChild) {
  345. style.removeChild(style.firstChild);
  346. }
  347.  
  348. style.appendChild(document.createTextNode(css));
  349. }
  350. }
  351.  
  352. var singleton = null;
  353. var singletonCounter = 0;
  354.  
  355. function addStyle(obj, options) {
  356. var style;
  357. var update;
  358. var remove;
  359.  
  360. if (options.singleton) {
  361. var styleIndex = singletonCounter++;
  362. style = singleton || (singleton = insertStyleElement(options));
  363. update = applyToSingletonTag.bind(null, style, styleIndex, false);
  364. remove = applyToSingletonTag.bind(null, style, styleIndex, true);
  365. } else {
  366. style = insertStyleElement(options);
  367. update = applyToTag.bind(null, style, options);
  368.  
  369. remove = function remove() {
  370. removeStyleElement(style);
  371. };
  372. }
  373.  
  374. update(obj);
  375. return function updateStyle(newObj) {
  376. if (newObj) {
  377. if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {
  378. return;
  379. }
  380.  
  381. update(obj = newObj);
  382. } else {
  383. remove();
  384. }
  385. };
  386. }
  387.  
  388. module.exports = function (list, options) {
  389. options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
  390. // tags it will allow on a page
  391.  
  392. if (!options.singleton && typeof options.singleton !== 'boolean') {
  393. options.singleton = isOldIE();
  394. }
  395.  
  396. list = list || [];
  397. var lastIdentifiers = modulesToDom(list, options);
  398. return function update(newList) {
  399. newList = newList || [];
  400.  
  401. if (Object.prototype.toString.call(newList) !== '[object Array]') {
  402. return;
  403. }
  404.  
  405. for (var i = 0; i < lastIdentifiers.length; i++) {
  406. var identifier = lastIdentifiers[i];
  407. var index = getIndexByIdentifier(identifier);
  408. stylesInDom[index].references--;
  409. }
  410.  
  411. var newLastIdentifiers = modulesToDom(newList, options);
  412.  
  413. for (var _i = 0; _i < lastIdentifiers.length; _i++) {
  414. var _identifier = lastIdentifiers[_i];
  415.  
  416. var _index = getIndexByIdentifier(_identifier);
  417.  
  418. if (stylesInDom[_index].references === 0) {
  419. stylesInDom[_index].updater();
  420.  
  421. stylesInDom.splice(_index, 1);
  422. }
  423. }
  424.  
  425. lastIdentifiers = newLastIdentifiers;
  426. };
  427. };
  428.  
  429. /***/ })
  430.  
  431. /******/ });
  432. /************************************************************************/
  433. /******/ // The module cache
  434. /******/ var __webpack_module_cache__ = {};
  435. /******/
  436. /******/ // The require function
  437. /******/ function __webpack_require__(moduleId) {
  438. /******/ // Check if module is in cache
  439. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  440. /******/ if (cachedModule !== undefined) {
  441. /******/ return cachedModule.exports;
  442. /******/ }
  443. /******/ // Create a new module (and put it into the cache)
  444. /******/ var module = __webpack_module_cache__[moduleId] = {
  445. /******/ id: moduleId,
  446. /******/ // no module.loaded needed
  447. /******/ exports: {}
  448. /******/ };
  449. /******/
  450. /******/ // Execute the module function
  451. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  452. /******/
  453. /******/ // Return the exports of the module
  454. /******/ return module.exports;
  455. /******/ }
  456. /******/
  457. /************************************************************************/
  458. /******/ /* webpack/runtime/compat get default export */
  459. /******/ (() => {
  460. /******/ // getDefaultExport function for compatibility with non-harmony modules
  461. /******/ __webpack_require__.n = (module) => {
  462. /******/ var getter = module && module.__esModule ?
  463. /******/ () => (module['default']) :
  464. /******/ () => (module);
  465. /******/ __webpack_require__.d(getter, { a: getter });
  466. /******/ return getter;
  467. /******/ };
  468. /******/ })();
  469. /******/
  470. /******/ /* webpack/runtime/define property getters */
  471. /******/ (() => {
  472. /******/ // define getter functions for harmony exports
  473. /******/ __webpack_require__.d = (exports, definition) => {
  474. /******/ for(var key in definition) {
  475. /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  476. /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
  477. /******/ }
  478. /******/ }
  479. /******/ };
  480. /******/ })();
  481. /******/
  482. /******/ /* webpack/runtime/hasOwnProperty shorthand */
  483. /******/ (() => {
  484. /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  485. /******/ })();
  486. /******/
  487. /******/ /* webpack/runtime/nonce */
  488. /******/ (() => {
  489. /******/ __webpack_require__.nc = undefined;
  490. /******/ })();
  491. /******/
  492. /************************************************************************/
  493. var __webpack_exports__ = {};
  494. // This entry need to be wrapped in an IIFE because it need to be in strict mode.
  495. (() => {
  496. "use strict";
  497. /* harmony import */ var _css_main_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(121);
  498. /* harmony import */ var _css_main_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_main_scss__WEBPACK_IMPORTED_MODULE_0__);
  499.  
  500. // import "./folder/file";
  501.  
  502. const pathname = getPathOfUrl();
  503. const userName = getPathOfUrl(document.querySelector(".user-box .title a").href).substring(1);
  504. const userId = document.querySelector(".about img").getAttribute("src").replace("/action/user-image?uid=", "");
  505. const indexPath = "/" + userName + "/index";
  506.  
  507. let popup;
  508. let popupoverlay;
  509. let listsToBackup = (/* unused pure expression or super */ null && ([]));
  510. let output = "";
  511. let listOutputHtml = "";
  512. let listOutputListography = "";
  513. let asyncForEach;
  514.  
  515. init();
  516.  
  517. function init() {
  518. // only if the user is on their profile
  519. if (document.querySelector(".global-menu .create-list")) {
  520. console.log("Listography Export script by petracoding loaded. => https://greasyfork.org/en/scripts/405872-listography-backup");
  521. HTML();
  522.  
  523. // "forEach" is not async. here is our own async version of it.
  524. // usage: await asyncForEach(myArray, async () => { ... })
  525. asyncForEach = async (array, callback) => {
  526. for (let index = 0; index < array.length; index++) {
  527. await callback(array[index], index, array);
  528. }
  529. };
  530.  
  531. // start backup if user clicked the backup all link and was redirected to the archive
  532. if (pathname == indexPath + "?backup=true") {
  533. startBackupAll();
  534. }
  535. }
  536. }
  537.  
  538. //////////////////////////////////// BACKUP
  539.  
  540. async function startBackupAll() {
  541. const listLinksToOpen = document.querySelectorAll(".body_folder .list a");
  542. if (!listLinksToOpen) {
  543. showPopup("No lists found.", true);
  544. return;
  545. }
  546.  
  547. startOutput();
  548.  
  549. await asyncForEach([...listLinksToOpen], async (link) => {
  550. let list = await openListInArchive(link);
  551. await editListAndAddToOutput(list);
  552. readListAndAddToOutput(list);
  553. });
  554.  
  555. finishOutput();
  556. }
  557.  
  558. async function startBackupVisible() {
  559. const listSelector = ".list-container";
  560. const listSelectorInArchive = "#list_container .slot";
  561.  
  562. const listNodes = document.querySelectorAll(listSelector + ", " + listSelectorInArchive);
  563.  
  564. if (!listNodes || listNodes.length < 1) {
  565. showPopup("No visible lists found.", true);
  566. return;
  567. }
  568.  
  569. startOutput();
  570.  
  571. await asyncForEach([...listNodes], async (list) => {
  572. await editListAndAddToOutput(list);
  573. readListAndAddToOutput(list);
  574. });
  575.  
  576. finishOutput();
  577. }
  578.  
  579. function openListInArchive(link) {
  580. let listOpenPromise = new Promise(function (resolve, reject) {
  581. link.click();
  582. let listId = link.getAttribute("id").replace("list_" + userId + "_", "");
  583.  
  584. let attempt = 1;
  585. let checkIfIsInEditMode = setInterval(function () {
  586. if (document.querySelector("#listbox-" + listId + " .menu")) {
  587. resolve(document.querySelector("#listbox-" + listId));
  588. clearInterval(checkIfIsInEditMode);
  589. } else {
  590. if (attempt > 200) {
  591. reject("List could not be backed up.");
  592. clearInterval(checkIfIsInEditMode);
  593. }
  594. attempt = attempt + 1;
  595. }
  596. }, 100);
  597. });
  598.  
  599. listOpenPromise.then(
  600. function (list) {
  601. return list;
  602. },
  603. function (errorMsg) {
  604. alert(errorMsg);
  605. }
  606. );
  607.  
  608. return listOpenPromise;
  609. }
  610.  
  611. function readListAndAddToOutput(listElement) {
  612. const listContent = listElement.querySelector(".text_content").innerHTML;
  613. listOutputHtml += "<br/><br/><br/>";
  614. listOutputHtml += getListOutput(listElement, listContent, true);
  615. }
  616.  
  617. function editListAndAddToOutput(listElement) {
  618. let listEditPromise = new Promise(function (resolve, reject) {
  619. const editButton = listElement.querySelector(".menu .item a[href*=edit-list]");
  620. if (!editButton) {
  621. reject("List could not be edited.");
  622. }
  623. editButton.click();
  624.  
  625. let attempt = 1;
  626. let checkIfIsInEditMode = setInterval(function () {
  627. if (listElement.querySelector(".category_editor")) {
  628. let listContent = listElement.querySelector("textarea").innerHTML;
  629. listElement.querySelector(".cancel.button_1_of_3").click();
  630. resolve(listContent);
  631. clearInterval(checkIfIsInEditMode);
  632. } else {
  633. if (attempt > 200) {
  634. reject("List could not be backed up.");
  635. clearInterval(checkIfIsInEditMode);
  636. }
  637. attempt = attempt + 1;
  638. }
  639. }, 100);
  640. });
  641.  
  642. listEditPromise.then(
  643. function (listContent) {
  644. listOutputListography += "\n\n\n--------------------------------------------------------\n\n\n";
  645. listOutputListography += getListOutput(listElement, listContent, false);
  646. },
  647. function (errorMsg) {
  648. alert(errorMsg);
  649. }
  650. );
  651.  
  652. return listEditPromise;
  653. }
  654.  
  655. //////////////////////////////////// HELPERS
  656.  
  657. function getPathOfUrl(url, tld) {
  658. let href;
  659. if (!url) {
  660. href = window.location.href;
  661. } else {
  662. href = url;
  663. }
  664. let ending;
  665. if (!tld) {
  666. ending = ".com/";
  667. } else {
  668. ending = "." + tld + "/";
  669. }
  670. return href.substring(href.indexOf(ending) + ending.length - 1);
  671. }
  672.  
  673. function startOutput() {
  674. document.querySelector("#backup-loading").style.display = "block";
  675. popupoverlay.style.display = "block";
  676. const url = location.href.replace("?backup=true", "");
  677. output = "<h1>Here are your lists:</h1><h2>Listography Markup</h2><textarea id='backup-output'>Backup of " + url;
  678. }
  679.  
  680. function finishOutput() {
  681. document.querySelector("#backup-loading").style.display = "none";
  682. popupoverlay.style.display = "none";
  683. showPopup(output + listOutputListography + "</textarea><h2>HTML</h2><div class='backup-popup-scrollbox'>" + listOutputHtml + "</div>", true);
  684. }
  685.  
  686. function getListOutput(listEl, listContent, htmlMode) {
  687. if (!listEl || !listContent) return;
  688.  
  689. let listId;
  690. if (listEl.querySelector(".listbox")) {
  691. listId = listEl.querySelector(".listbox").getAttribute("id").replace("listbox-", "");
  692. } else {
  693. listId = listEl.querySelector("[id*=listbox-content-slot]").getAttribute("id").replace("listbox-content-slot-", "");
  694. }
  695.  
  696. let listLink = "Link: " + listEl.querySelector(".box-title a").getAttribute("href");
  697.  
  698. let listTitle = listEl.querySelector(".box-title a").innerHTML.replace('<span class="box-subtitle">', "").replace("</span>", "").replace(/\s\s+/g, " ").trim();
  699.  
  700. let listDates = "created on " + listEl.querySelector(".dates").innerHTML.replace("∞", "").replace("+", "").replace(" <br>", ", last updated on ").replace(/\s\s+/g, " ").trim();
  701.  
  702. let listImage = listEl.querySelector(".icon");
  703. if (listImage) {
  704. listImage = "\nIcon: " + listImage.getAttribute("src").replace("&small=1", "");
  705. } else {
  706. listImage = "";
  707. }
  708.  
  709. if (htmlMode) {
  710. return `<div class="box-title"><a href="${listLink}">${listTitle}</a></div><br />(${listDates})<br />${listImage}<br />` + adjustListContent(listContent, listId);
  711. }
  712.  
  713. return listTitle + "\n" + listLink + "\n(" + listDates + ")" + listImage + "\n\n" + adjustListContent(listContent, listId);
  714. }
  715.  
  716. function adjustListContent(listContent, listId) {
  717. // Add image urls
  718. const attachmentUrl = "https://listography.com/user/" + userId + "/list/" + listId + "/attachment/";
  719. listContent = listContent.replace(/\[([a-z]+)\]/g, "[$1: " + attachmentUrl + "$1]");
  720.  
  721. return listContent;
  722. }
  723.  
  724. //////////////////////////////////// HTML
  725.  
  726. function HTML() {
  727. createPopup();
  728. createLoading();
  729.  
  730. createBackupAllButton();
  731. createBackupVisibleButton();
  732. }
  733.  
  734. function createBackupAllButton() {
  735. const menu = document.querySelector(".global-menu tbody");
  736. const tr = document.createElement("tr");
  737. const td = document.createElement("td");
  738.  
  739. const button = document.createElement("input");
  740. button.type = "button";
  741. button.value = "backup all";
  742. button.className = "backup-button";
  743.  
  744. if (onIndexPage()) {
  745. button.onclick = startBackupAll;
  746. } else {
  747. button.onclick = goToIndex;
  748. }
  749.  
  750. td.appendChild(button);
  751. tr.appendChild(td);
  752. menu.appendChild(tr);
  753. }
  754.  
  755. function createBackupVisibleButton() {
  756. const menu = document.querySelector(".global-menu tbody");
  757. const tr = document.createElement("tr");
  758. const td = document.createElement("td");
  759.  
  760. const button = document.createElement("input");
  761. button.type = "button";
  762. button.value = "backup visible";
  763. button.className = "backup-button";
  764. button.onclick = startBackupVisible;
  765.  
  766. td.appendChild(button);
  767. tr.appendChild(td);
  768. menu.appendChild(tr);
  769. }
  770.  
  771. function onIndexPage() {
  772. return pathname.startsWith(indexPath) && pathname.indexOf("?v") < 0;
  773. }
  774.  
  775. function goToIndex() {
  776. location.href = indexPath + "?backup=true";
  777. }
  778.  
  779. function createPopup() {
  780. popupoverlay = document.createElement("div");
  781. popupoverlay.className = "backup-popup-overlay";
  782. popup = document.createElement("div");
  783. popup.className = "backup-popup";
  784.  
  785. document.body.appendChild(popup);
  786. document.body.appendChild(popupoverlay);
  787.  
  788. hidePopup();
  789. }
  790.  
  791. function createLoading() {
  792. let loading = document.createElement("div");
  793. loading.setAttribute("id", "backup-loading");
  794. loading.style.display = "none";
  795. loading.innerHTML =
  796. "<h1>Loading...</h1><h2>Please wait while the list contents are being copied.</h2>This may take a while if you have more than 100 lists. Don't worry, your lists are not being edited, and the date of the last edit will also not change.";
  797.  
  798. document.body.appendChild(loading);
  799. }
  800.  
  801. function showPopup(text, allowClosing) {
  802. if (text) popup.innerHTML = text;
  803. popupoverlay.style.display = "block";
  804. popup.style.display = "block";
  805. document.body.style.overflow = "hidden";
  806.  
  807. if (allowClosing) {
  808. popupoverlay.onclick = hidePopup;
  809. }
  810. }
  811.  
  812. function hidePopup() {
  813. popup.style.display = "none";
  814. popupoverlay.style.display = "none";
  815. document.body.style.overflow = "auto";
  816. }
  817.  
  818. })();
  819.  
  820. /******/ })()
  821. ;