- // ==UserScript==
- // @name Mangago Backup Lists
- // @namespace http://tampermonkey.net/
- // @version 1.2.4
- // @description Backup your reading lists
- // @author You
- // @match https://www.mangago.me/*
- // @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/3.0.6/isotope.pkgd.min.js
- // @run-at document-start
- // @grant none
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // plugins used:
- // Isotope - for sortable list view: https://isotope.metafizzy.co/
- // lz-string - for string compression: https://pieroxy.net/blog/pages/lz-string/index.html
- console.log('mggBackup v.1.2.4');
-
- // Current limit for sortable view. Any more than this results in degraded browser performance.
- // Sadly, this is probably the limit of what I can do with this userscript on mgg's site + given its current structure and capability.
- // I'm working on getting a separate website up and running so that we can have a more.
- // For actual backups (.csv(excel)/.json) though, I've tested it working okay for up to 10,000 stories. Beyond that, I'm afraid I can't guarantee success :'<
- const idealLimit = 1000
-
- // function that detects how many current pages a specific list has
- function setTotalPages() {
- const $paginagtion = $('.pagination').first();
- let totalPages = undefined
-
- if ($paginagtion[0]) {
- totalPages = $paginagtion.attr('total');
- } else {
- totalPages = 1;
- }
-
- localStorage.setItem('totalPages', totalPages);
- }
-
- function detectList() {
- // detects list by current URL
- if (getUrlWithoutParams().match(/(manga\/1\/)/gm)) {
- return 1 // manga/1/ = Want To Read
- } else if ((getUrlWithoutParams().match(/(manga\/2\/)/gm))) {
- return 2 //manga/2/ = Currently Reading
- } else if (getUrlWithoutParams().match(/(manga\/3\/)/gm)) {
- return 3 //manga/3/ = Already Read
- }
-
- return 0
- }
-
- // function that gathers details about the stories to save
- function saveList(custom = false) {
- console.log('saving...');
-
- if (!custom) {
- const arr = [];
- const $toSave = $('#collection-nav').next().find('.manga');
-
- $toSave.each(function() {
- const $this = $(this);
-
- // gets details for each story
- const $titleLink = $this.find('.title').find('a');
- const title = $titleLink.text();
-
- const author = $this.find('.title').next().find('div').text().split("|")[1].trim();
-
- const link = $titleLink.attr('href');
- const cover = $this.find('.cover').find('img').data('src');
- let timestamp = '';
- let tags = [];
-
- $this.find('.status-rate').find('div').each(function() {
- if ($(this).text().match(/(marked)/gm)) {
- timestamp = $(this).text().replace(/(marked)/gm, '').trim();
- }
-
- if ($(this).text().match(/(Tags)/gm)) {
- $(this).find('.tag')?.each(function() {
- tags.push($(this).text().trim());
- });
- }
- })
-
- const $note = $this.find('.short-note');
-
- const note = {
- text: $note.text().trim(),
- html: $note.text().trim() ? $note[0].outerHTML : ``
- }
-
- let rating = undefined
-
- const $stars = $this.find('.status-rate').find('.stars9').first().find('.stars9');
-
- let ratingWidth = 0;
-
- if ($stars.css('width')) {
- ratingWidth = parseInt($stars.css('width').replace(/(px)/gm, ''));
- }
-
- // star ratings are weird because they are not explicitly exposed as 1,2,3,4,5
- // so I'm only getting the width of the yellow fill of the stars and assigning each into 1-5 LMAO
- switch (ratingWidth) {
- case (11):
- rating = 1;
- break;
- case (22):
- rating = 2;
- break;
- case (33):
- rating = 3;
- break;
- case (44):
- rating = 4;
- break;
- case (55):
- rating = 5;
- break;
- default:
- rating = undefined;
- break;
- }
-
- arr.push({
- title,
- author,
- link,
- cover,
- timestamp,
- note,
- rating,
- tags
- });
- })
-
- // I turned these into single letter keys for space saving.
- const listId = detectList() === 1
- ? 'w' // want to read
- : detectList() === 2
- ? 'c' // currently reading
- : detectList() === 3
- ? 'd' // done reading
- : undefined;
-
- if (listId) {
- // this part is the reason why if you check your localStorage on dev tools, there are weird signs
- // I am using lz-string to compress strings to be able to save more stories
- const compressed = compressString(JSON.stringify(arr))
-
- const { page } = getUrlParams()
-
- localStorage.setItem(`${listId + page}`, compressed);
- }
- } else {
- const params = getUrlParams();
-
- const { page } = params;
-
- const listDetails = {
- title: {
- text: '',
- html: ''
- },
- listId: '',
- curator: {
- username: '',
- id: '',
- },
- created: '',
- updated: '',
- description: {
- text: '',
- html: ''
- },
- tags: []
- }
-
- if (page) {
- if (parseInt(page, 10) === 1) {
- const $h1 = $('.w-title').find('h1');
- const titleText = $h1.text().trim();
- listDetails.title.text = titleText;
- listDetails.title.html = titleText ? $h1[0].outerHTML : '';
- listDetails.listId = getListId();
-
- const $userProfile = $('.user-profile')
- const $info = $userProfile.find('.info');
- const curatorLinkParts = $userProfile.find('.pic').find('a').attr('href').split('/').filter(url => url !== '');
- const curatorId = curatorLinkParts[curatorLinkParts.length - 1];
- listDetails.curator.username = $info.find('h2').text();
- listDetails.curator.id = curatorId;
-
- const dates = $info.contents().filter(function(){
- return this.nodeType == 3;
- })[1].nodeValue.trim().split(': ');
-
- listDetails.updated = dates[2];
- listDetails.created = dates[1].split('Last')[0].trim();
-
- const $description = $('.article').find('.description')
-
- const descText = $description.text().trim()
-
- listDetails.description.text = descText;
- listDetails.description.html = descText ? $description[0].outerHTML : '';
-
- const tagsArr = []
- const $tags = $('.content').find('.tag');
-
- $tags.each((i, el) => {
- const $el = $(el);
- tagsArr.push($el.text());
- })
-
- listDetails.tags = tagsArr;
-
- const listId = getListId();
- const type = getTypeIndexFromCustomList(listId).type;
-
- localStorage.setItem(`${type}${listId}-d`, compressString(JSON.stringify(listDetails)));
- }
- }
-
- const arr = [];
- const $toSave = $('.manga.note-and-order');
-
- const $h1 = $('.w-title').find('h1');
- const titleText = $h1.text().trim();
- const listTitleDetails = {
- text: titleText,
- html: titleText ? $h1[0].outerHTML : ''
- }
-
- $toSave.each(function(i, el) {
- const $this = $(el);
-
- // gets details for each story
- const $titleLink = $this.find('.title').find('a');
- const title = $titleLink.text();
-
- const author = $this.find('.info').filter((i, el) => {
- const $el = $(el);
-
- return $el.text().match(/(Author\(s\))/gm);
- }).find('span').text();
-
-
- const link = $titleLink.attr('href');
- const cover = $this.find('.cover').find('img').data('src') || $('.album-photos').find('img').first().attr('src') || 'none';
-
- const index = $this.attr('_index');
-
- const tags = $this.find('.info').filter((i, el) => {
- const $el = $(el);
-
- return $el.text().match(/(Genre\(s\))/gm);
- }).find('span').text().split('/').map(item => item.trim()).filter(item => item !== "");
-
-
- const $note = $this.find('.info.summary');
-
- const note = {
- text: $note.text().trim(),
- html: $note.text().trim() ? $note[0].outerHTML : ``
- }
-
- const rating = $this.find('.title').next().find('.info').filter((i, el) => {
- const $el = $(el);
-
- let returnFlag = false;
- if ($el.find('#stars0')[0]) {
- returnFlag = true;
- }
-
- return returnFlag;
- }).find('span').text().trim();
-
- const timestamp = $this.find('.info.summary').next().find('.left').text();
-
- arr.push({
- title,
- author,
- link,
- cover,
- timestamp,
- note,
- rating,
- tags,
- index,
- listId: getListId(),
- listTitleDetails,
- });
- });
-
- const listId = getListId();
- const type = getTypeIndexFromCustomList(listId).type;
-
- if (listId && type) {
- const existing = decompressString(localStorage.getItem(`${type}${listId}`));
-
- if (existing) {
- const parsedExisting = JSON.parse(existing);
- const newArr = [...parsedExisting, ...arr];
- const compressedNewArr = compressString(JSON.stringify(newArr));
- localStorage.setItem(`${type}${listId}`, compressedNewArr);
- } else {
- const compressedNewArr = compressString(JSON.stringify(arr));
- localStorage.setItem(`${type}${listId}`, compressedNewArr);
- }
- }
- }
- }
-
- // clear localStorage keys where keys begin with w/c/d (for previously saved stories) from probably previous backups
- // x is letter for custom lists, y is for followed lists
- function clearListRelatedStorageItems (customList = false) {
- const keys = Object.keys(localStorage);
-
- let targetArr = [];
-
- if (!customList) {
- targetArr = ['w', 'c', 'd']
- } else {
- targetArr = ['x', 'y']
- }
-
- for ( var i = 0, len = localStorage.length; i < len; ++i ) {
- const match = targetArr.indexOf(keys[i].charAt(0)) !== -1;
-
- if (match) {
- localStorage.removeItem(keys[i]);
- }
- }
- }
-
- // other related items saved
- function clearLocalStorageItems (specific = []) {
- const items = [
- 'backupMode', // trigger check for doing page by page backup
- 'totalPages', // key for total number of pagination per list type
- 'backupTime', // latest available backup time
- 'generate', // generate boolean for custom list
- ]
-
- const finalArrayToTarget = specific.length > 0 ? specific : items
-
- finalArrayToTarget.forEach(item => {
- localStorage.removeItem(item)
- })
- }
-
- // sortable list will be seen as a tab next to [Done Reading] list and can be triggered when number of stories < idealLimit
- function appendSortableList() {
- const count = getAllBackup().finalCount;
-
- if (!$('#navCustom')[0]) {
- $('#collection-nav').append(`
- <div id="navCustom" class="nav sub nav-custom">
- Sortable List
- </div>
- `)
-
- $('body').append(`<style>
- #collection-nav .nav-custom {
- background-color: #0069ed;
- color: #ffffff;
- cursor: pointer;
- transition: .2s;
- }
-
- #collection-nav .nav-custom:hover {
- background-color: #ffffff;
- color: #0069ed;
- cursor: pointer;
- }
- </style>`)
-
- // Clicking the tab redirects to `/manga/4/` an unuse\/4d url, so I just decided to dump backed up stories there
- $('#navCustom').on('click', () => {
- if (getUserId() !== undefined) {
- if (count >= idealLimit) {
- const proceed = confirm('Your have more than 1000 stories. The sortable list might be slow, or might not work properly at all. Proceed anyway?')
-
- if (proceed) {
- window.location.replace(`https://www.mangago.me/home/people/${getUserId()}/manga/4/`);
- }
- } else {
- window.location.replace(`https://www.mangago.me/home/people/${getUserId()}/manga/4/`);
- }
- } else {
- alert('Error: your userId cannot be obtained. Be sure you are on a url where people/1234567/manga... is visible')
- }
- })
- }
-
- }
-
- // string compression to save space. read more about it over at: https://pieroxy.net/blog/pages/lz-string/index.html
- function compressString(string) {
- if (LZString) {
- return LZString.compressToUTF16(string)
- } else {
- throw new Error ('lz-string plugin missing')
- }
- }
-
- function decompressString(string) {
- if (LZString) {
- return LZString.decompressFromUTF16(string);
- } else {
- throw new Error ('lz-string plugin missing')
- }
- }
-
- // Tags are usually filled with emoticons and special characters. cleaning function for tag identifier when filtering sort view
- function cleanAndHyphenateTag(tag) {
- return tag.replace(/\W/g, '').replace(/ +/g, '-').toLowerCase();
- }
-
- // Params are what we see on URLs after the main link. http://sample.com/?paramSample=1
- // this can be extracted on the page and this will become a variable named paramSample with a value of 1
- function getUrlWithoutParams() {
- return window.location.href.split(/[?#]/)[0]
- }
-
- // mgg currently has use url formatted like this: https://www.mangago.me/home/people/1234567/
- // I'm getting the id part with this function
- function getUserId() {
- const url = window.location.href.split('/')
- const isAturlWithUserId = url.some(urlPart => {
- return urlPart === "people"
- })
-
- if (isAturlWithUserId) {
- const targetIndex = url.indexOf("people") + 1
- return url[targetIndex]
- } else {
- return undefined
- }
- }
-
- // get Id for custom lists. hereby defining custom lists as any lists that are not want to read/reading/done
- function getListId() {
- const url = window.location.href.split('/')
- const isAturlWithUserId = url.some(urlPart => {
- return urlPart === "mangalist"
- })
-
- if (isAturlWithUserId) {
- const targetIndex = url.indexOf("mangalist") + 1
- return url[targetIndex]
- } else {
- return undefined
- }
- }
-
- // detect if part of url has pattern pertaining to custom lists
- function isForCustomLists() {
- const url = window.location.href.split('/');
- const isCustom = url.some(urlPart => urlPart === 'list' || urlPart === 'mangalist');
- return isCustom;
- }
-
- // used when retrieving the stored items into localStorage.
- // targets localStorage items that start with w/c/d as set by previous backup
- function getListFromStorage(letterId, custom = false, fetchMainDetails = false) {
- const keys = Object.keys(localStorage);
-
- let finalKeys = keys.filter((key) => {
- if (custom) {
- if (fetchMainDetails) {
- return key.charAt(0) === letterId && key.match(/(-d)/gm)
- } else {
- return key.charAt(0) === letterId && !key.match(/(-d)/gm)
- }
- }
-
- return key.charAt(0) === letterId
- })
-
- const finalArr = []
-
- finalKeys.forEach(key => {
- finalArr.push(JSON.parse(decompressString(localStorage.getItem(key))))
- })
-
- return finalArr.flat()
- }
-
- // compiles all lists from storage
- function getAllBackup() {
- const wantToRead = getListFromStorage('w');
- const currentlyReading = getListFromStorage('c');
- const alreadyRead = getListFromStorage('d');
-
- const finalObj = {
- wantToRead,
- currentlyReading,
- alreadyRead
- }
-
- const allData = [
- {
- arr: wantToRead,
- id: 'wantToRead'
- },
- {
- arr: currentlyReading,
- id: 'currentlyReading'
- },
- {
- arr: alreadyRead,
- id: 'alreadyRead'
- },
- ]
-
- return {
- allData,
- finalObj,
- finalCount: wantToRead.length + currentlyReading.length + alreadyRead.length
- }
- }
-
- function getAllBackupCustom() {
- const created = getListFromStorage('x', true);
- const followed = getListFromStorage('y', true);
- const createdDetails = getListFromStorage('x', true, true);
- const followedDetails = getListFromStorage('y', true, true);
-
- const finalObj = {
- created,
- followed,
- mainDetails: [...createdDetails, ...followedDetails]
- }
-
- const allData = [
- {
- arr: created,
- id: 'created'
- },
- {
- arr: followed,
- id: 'followed'
- },
- ]
-
- return {
- allData,
- finalObj,
- }
- }
-
- // function for escaping double quotes (") and commas (,) on tricky CSV formats
- // see rules over at https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules
- function escape(value) {
- if(!['"','\r','\n',','].some(e => value.indexOf(e) !== -1)) {
- return value;
- }
-
- return '"' + value.replace(/"/g, '""') + '"';
- }
-
- // attaches all the fetched data into a clickable link that is shaped like a button
- // attached near the header on the user profile page when a previous successful backup is available
- function generateDownloadLinksToBackup() {
- const rows = []
-
- const allBackup = getAllBackup()
-
- allBackup.allData.forEach(item => {
- item.arr.forEach(subitem => {
- const { title, author, link, cover, timestamp, note, rating, tags } = subitem
- let finalRating = -1
-
- if (rating) {
- finalRating = rating
- }
-
- rows.push([
- escape(title ? title.toString(): ""),
- escape(author ? author.toString(): ""),
- escape(link ? link.toString(): ""),
- escape(cover ? cover.toString(): ""),
- escape(timestamp ? timestamp.toString(): ""),
- escape(note.text ? note.text.toString() : ""),
- escape(finalRating ? finalRating.toString(): ""),
- escape(tags ? tags.join(',').toString(): ""),
- escape(item.id ? item.id.toString() : ""),
- ])
- })
- })
-
- const latestBackupTime = localStorage.getItem('backupTime');
- const fileSafeBackupTime = latestBackupTime.replace(/[^a-z0-9]/gi, '_').toLowerCase();
-
- // CSV handling
- let csvString = rows.map(e => e.join(",")).join("\n")
- let universalBOM = "\uFEFF";
- let csvContent = 'data:text/csv; charset=utf-8,' + encodeURIComponent(universalBOM+csvString);
-
- let downloadLinkCsv = document.createElement("a");
- downloadLinkCsv.href = csvContent;
- downloadLinkCsv.download = `list-backup-user${getUserId()}-${fileSafeBackupTime}.csv`;
- downloadLinkCsv.classList.add('c-btn');
- downloadLinkCsv.classList.add('download-link');
- downloadLinkCsv.id = 'downloadCsv';
- downloadLinkCsv.text = 'Download CSV';
-
- $('.info').find("h1").after(downloadLinkCsv);
-
- // JSON data handling
- let jsonContent = "data:text/json;charset=utf-8," + "\ufeff" + encodeURIComponent(JSON.stringify(allBackup.finalObj));
-
- let downloadLinkJson = document.createElement("a");
- downloadLinkJson.href = jsonContent;
- downloadLinkJson.download = `list-backup-user${getUserId()}-${fileSafeBackupTime}.json`;
- downloadLinkJson.classList.add('c-btn');
- downloadLinkJson.classList.add('download-link');
- downloadLinkJson.id = 'downloadJson';
- downloadLinkJson.text = 'Download JSON';
-
- $('.info').find("h1").after(downloadLinkJson);
-
- $('.info').find("h1").after(`<span style="display: block; margin-right: 12px; color: #06E8F6; font-size: 16px;">Latest Backup (${latestBackupTime}): </span>`);
-
- $('body').append(`<style>
- #downloadJson.c-btn {
- top: 70px;
- }
-
- .download-link {
- margin-right: 15px;
- margin-top: 12px;
- }
- </style>`)
- }
-
- // same as above but for custom lists
- function generateDownloadLinksToBackupCustom() {
- const rows = []
-
- const allBackup = getAllBackupCustom()
- const finalObj = allBackup.finalObj
-
- allBackup.allData.forEach(item => {
- item.arr.forEach(subitem => {
- const { title, author, link, cover, timestamp, note, rating, tags, index, listId } = subitem;
- let finalRating = -1;
-
- if (rating) {
- finalRating = rating;
- }
-
- let targetList = finalObj.mainDetails.filter(item => item.listId === listId)[0];
-
- rows.push([
- escape(title ? title.toString(): ""),
- escape(author ? author.toString(): ""),
- escape(link ? link.toString(): ""),
- escape(cover ? cover.toString(): ""),
- escape(timestamp ? timestamp.toString(): ""),
- escape(note.text ? note.text.toString() : ""),
- escape(finalRating ? finalRating.toString(): ""),
- escape(tags ? tags.join(',').toString(): ""),
- escape(index ? index.toString(): ""),
- escape(listId ? listId.toString(): ""),
- escape(targetList.title ? targetList.title.text.toString() : ""),
- escape(item.id ? item.id.toString() : ""),
- ])
- })
- })
-
- const latestBackupTime = localStorage.getItem('backupTimeCustom');
- const fileSafeBackupTime = latestBackupTime.replace(/[^a-z0-9]/gi, '_').toLowerCase();
-
- // CSV handling
- let csvString = rows.map(e => e.join(",")).join("\n")
- let universalBOM = "\uFEFF";
- let csvContent = 'data:text/csv; charset=utf-8,' + encodeURIComponent(universalBOM+csvString);
-
- let downloadLinkCsv = document.createElement("a");
- downloadLinkCsv.href = csvContent;
- downloadLinkCsv.download = `list-backup-user${getUserId()}-${fileSafeBackupTime}.csv`;
- downloadLinkCsv.classList.add('c-btn');
- downloadLinkCsv.classList.add('download-link');
- downloadLinkCsv.id = 'downloadCsv';
- downloadLinkCsv.text = 'Download Custom List CSV';
-
- $('.info').find("h1").after(downloadLinkCsv);
-
- // JSON data handling
- let jsonContent = "data:text/json;charset=utf-8," + "\ufeff" + encodeURIComponent(JSON.stringify(allBackup.finalObj));
-
- let downloadLinkJson = document.createElement("a");
- downloadLinkJson.href = jsonContent;
- downloadLinkJson.download = `list-backup-user${getUserId()}-${fileSafeBackupTime}.json`;
- downloadLinkJson.classList.add('c-btn');
- downloadLinkJson.classList.add('download-link');
- downloadLinkJson.id = 'downloadJson';
- downloadLinkJson.text = 'Download Custom List JSON';
-
- $('.info').find("h1").after(downloadLinkJson);
-
- $('.info').find("h1").after(`<span style="display: block; margin-right: 12px; color: #06E8F6; font-size: 16px;">Latest Custom List Backup (${latestBackupTime}): </span>`);
-
- $('body').append(`<style>
- #downloadJson.c-btn {
- top: 70px;
- }
-
- .download-link {
- margin-right: 15px;
- margin-top: 12px;
- }
- </style>`)
- }
-
- // on sortable list view, when a story card is not visible on the screen, do not load image yet
- // load only when the user scrolls onto the said card
- function createObserver(targetEl) {
- let observer;
-
- let options = {
- root: null,
- rootMargin: "0px",
- };
-
- // IntersectionObserver is an API that detects elements' visual visibility on screen
- // read more about it at https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
- observer = new IntersectionObserver((entries, observer) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- const $el = $(targetEl);
- const $wrapper = $el.find('.art-wrapper');
- const $img = $wrapper.find('img');
- const newSrc = $img.data('src');
- $img.attr("src", newSrc);
- $wrapper.addClass("img-loaded");
- observer.unobserve(targetEl);
- }
- })
- }, options);
- observer.observe(targetEl);
- }
-
- // gets URL parameters in any given link
- function getUrlParams() {
- const urlSearchParams = new URLSearchParams(window.location.search);
- const params = Object.fromEntries(urlSearchParams.entries());
-
- return params
- }
-
- // for fetching existing custom lists' data from local storage
- function getCustomLists() {
- const listsCreated = decompressString(localStorage.getItem('listsCreated')) || undefined; // FIXME prolly
- const listsFollowed = decompressString(localStorage.getItem('listsFollowed')) || undefined; // FIXME prolly
-
- const stringified = {
- created: listsCreated,
- followed: listsFollowed
- }
-
- const parsed = {
- created: listsCreated ? JSON.parse(listsCreated) : undefined,
- followed: listsFollowed ? JSON.parse(listsFollowed) : undefined
- }
-
- return {
- stringified,
- parsed
- };
- }
-
- // detects if custom list is 'created' or 'followed' + returns index
- function getTypeIndexFromCustomList(id) {
- if (!id) { return undefined };
- const customLists = getCustomLists();
-
- const indexCreated = customLists.parsed.created.indexOf(id);
-
- if (indexCreated !== -1) {
- return {
- type: 'x',
- index: indexCreated
- }
- }
-
- const indexFollowed = customLists.parsed.followed.indexOf(id);
-
- if (indexFollowed !== -1) {
- return {
- type: 'y',
- index: indexFollowed
- }
- }
-
- return {
- type: undefined,
- index: -1
- };
- }
-
- // gets contents of a list (details and content) given its id
- function getListIdContents(id) {
- if (!id) {return undefined};
-
- const keys = Object.keys(localStorage);
-
- const r = new RegExp(`${id}`)
- let isContentAvailable = keys.some((key) => {
- return key.match(r)
- })
-
- if (isContentAvailable) {
- const check = `${getTypeIndexFromCustomList(id).type}${id}`
- const d = `${check}-d`;
-
- const mainDetailsKey = keys.filter(key => key === d);
- const contentKey = keys.filter(key => key === check);
-
- return {
- mainDetails: JSON.parse(decompressString(localStorage.getItem(mainDetailsKey))),
- content: JSON.parse(decompressString(localStorage.getItem(contentKey))),
- }
- }
-
- return undefined;
- }
-
- // checks if sortable view caters to a specific custom list
- function checkIfCustomSortableMode() {
- const urlWithoutParams = getUrlWithoutParams();
- if (urlWithoutParams.match(/(manga\/4\/)/gm)) {
- const splitUrl = urlWithoutParams.split('/').filter(item => item !== '');
-
- let flag = false;
-
- if (splitUrl[splitUrl.length - 1] != 4) {
- flag = true
- }
-
- return flag;
- } else {
- return false
- }
- }
-
- // only start checking for backups/backup-ing when window has finished loading
- window.onload = function () {
- if (typeof jQuery === "undefined") {
- // copied from below, function-ify
- // added to catch lists where list is added to custom lists but goes to 404
- let typeIndex = getTypeIndexFromCustomList(getListId());
-
- let nextTarget = typeIndex.index + 1;
-
- const customLists = getCustomLists();
- const { parsed } = customLists;
- const { created, followed } = parsed;
-
- // x = created custom lists
- if (typeIndex.type === 'x') {
- if (created[nextTarget]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${created[nextTarget]}/?filter=&page=1`;
- window.location.replace(targetUrl);
- } else {
- if (followed[0]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${followed[0]}/?filter=&page=1`;
- window.location.replace(targetUrl);
- }
- }
- }
-
- // y = followed created lists
- if (typeIndex.type === 'y') {
- if (followed[nextTarget]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${followed[nextTarget]}/?filter=&page=1`
- window.location.replace(targetUrl);
- } else {
- //finalizing storage keys for ending backup process
- localStorage.setItem('backupTimeCustom', new Date().toLocaleString());
- localStorage.setItem('backupModeCustom', 'off');
- localStorage.removeItem('totalPages');
-
- alert('backup done! Redirect to list page after clicking okay');
- const userId = localStorage.getItem('backupUser');
- localStorage.setItem('generate', 'yes');
- window.location.replace(`https://www.mangago.me/home/people/${userId}/list/`)
- }
- }
- } else {
- // add custom button styles
- $('body').append(`<style>
- .c-btn {
- display: inline-block;
- border: none;
- padding: 8px 16px;
- text-decoration: none;
- background: #0069ed;
- color: #ffffff;
- font-family: sans-serif;
- font-size: 1rem;
- cursor: pointer;
- text-align: center;
- transition: background 250ms ease-in-out,
- transform 150ms ease;
- -webkit-appearance: none;
- -moz-appearance: none;
- }
-
- .c-btn:hover,
- .c-btn:focus {
- background: #0053ba;
- }
-
- .c-btn:focus {
- outline: 1px solid #fff;
- outline-offset: -4px;
- }
-
- .c-btn:active {
- transform: scale(0.99);
- }
-
- .c-btn-backup {
- padding: 0.5rem 1rem;
-
- background-color: green;
- }
-
- .c-btn-backup:hover {
- background-color: #09ab09;
- }
-
- .c-btn-backup:focus {
- background-color: #09ab09;
- }
-
- .c-btn-reset {
- padding: 0.5rem 1rem;
-
- background-color: #c74242;
- }
-
- .c-btn-reset:hover {
- background-color: #fb5757;
- }
-
- .c-btn-reset:focus {
- background-color: #fb5757;
- }
-
- .user-profile h1 {
- margin-bottom: 15px;
- }
-
- .user-profile h1 button {
- margin-left: 10px;
- }
- </style>`)
-
- // add check for custom lists
- const custom = isForCustomLists() || checkIfCustomSortableMode();
-
- const latestBackup = (localStorage.getItem(custom ? 'backupTimeCustom' : 'backupTime'));
- // const backupUser = (localStorage.getItem('backupUser'));
-
- if (latestBackup && !isForCustomLists() && !checkIfCustomSortableMode()) {
- generateDownloadLinksToBackup();
-
- appendSortableList();
- }
-
- // detects if user is on a custom list page and checks if there is current available data for sort view
- if (
- isForCustomLists()
- && getListIdContents(getListId())
- && localStorage.getItem('backupModeCustom') === 'off'
- && localStorage.getItem('backupUser')
- ) {
- $('.w-title').find('h1').append(`<button id="customListSortable" class="c-btn" style="margin-left: 12px;">Sortable List</button>`)
-
- $('#customListSortable').on('click', () => {
- let targetUrl = `https://www.mangago.me/home/people/${localStorage.getItem('backupUser')}/manga/4/${getListId()}/`;
- window.location.replace(targetUrl);
- })
- }
-
- // code block responsible for sortable list view
- if (window.location.href.match(/(manga\/4\/)/gm)) {
- let customListId = undefined;
-
- const urlWithoutParams = getUrlWithoutParams();
- const splitUrl = urlWithoutParams.split('/').filter(item => item !== '');
-
- if (splitUrl[splitUrl.length - 1] !== 4) {
- customListId = splitUrl[splitUrl.length - 1]
- }
-
- // take into consideration custom mode
- const customListIdContent = checkIfCustomSortableMode() ? getListIdContents(customListId) : undefined;
-
- const typeIndex = getTypeIndexFromCustomList(customListId);
-
- const allBackup = customListIdContent ? {
- allData: [{
- arr: customListIdContent.content
- }],
- finalObj: {
- created: typeIndex.type === 'x' ? customListIdContent.content : [],
- followed: typeIndex.type === 'y' ? customListIdContent.content : [],
- mainDetails: [customListIdContent.mainDetails]
- }
- } : getAllBackup();
-
- if (latestBackup) {
- const allData = allBackup.allData;
-
- const allTags = []
-
- allData.forEach(item => {
- item.arr.forEach(subitem => {
- if (subitem.tags) {
- if (subitem.tags.length > 0) {
- subitem.tags.forEach(tag => {
- if (allTags.indexOf(tag) === -1) {
- allTags.push(tag)
- }
- })
- }
- }
- })
- })
-
- $('body').append(`<div id="floatingNote">
- <div class="note-closer"><span class="emoji emoji274c"></span></div>
- </div>`)
- $('body').append(`<style>
- #floatingNote {
- position: absolute;
- top: 0;
- left: 0;
- width: auto;
- min-width: 250px;
- max-width: 500px;
- height: auto;
- background: #262730;
- }
-
- #floatingNote.is-active {
- padding: 15px;
- }
-
- .note-closer .emoji {
- display: none;
- position: absolute;
- top: 0;
- right: 0;
-
- cursor: pointer;
- }
-
- #floatingNote.is-active .note-closer .emoji{
- display: block;
- }
-
- #floatingNote .tag-wrapper {
- margin-top: 12px;
- }
-
- #floatingNote .tag {
- display: inline-block;
- background-color: #28F;
- border-radius: 2px;
- font-size: 14px;
- color: white;
- padding: 2px;
- margin-right: 2px;
- margin-bottom: 5px;
- }
- </style>`)
-
- $('.note-closer').on('click', (i, el) => {
- $('#floatingNote').find('.info.summary').remove();
- $('#floatingNote').hide()
- $('#floatingNote').removeClass('is-active')
- })
-
- // repeats over every single saved story card and creates the HTML for the story card
- const allContentHtml = allData.map(item => {
- return item.arr.map(subitem => {
- const { title, link, cover, timestamp, author, rating, tags, note } = subitem
-
- let ratingWidth = 0;
-
- switch (rating) {
- case 1:
- ratingWidth = 11;
- break;
- case 2:
- ratingWidth = 22;
- break;
- case 3:
- ratingWidth = 33;
- break;
- case 4:
- ratingWidth = 44;
- break;
- case 5:
- ratingWidth = 55;
- break;
- default:
- break;
- }
-
- const pixelPlaceholder = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAA1BMVEVHcEyC+tLSAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=`;
-
- return `<div class="${`element-item-outer ${item.id} ${rating}-star ${
- tags.map(tag => {
- return (cleanAndHyphenateTag(tag)) + " "
- }).join('')
- }`}" data-category="${item.id}">
- ${note.text
- ? `<div class="note-trigger" data-note-html="${
- note.html.replace(/(")/gm, '"')}" data-tags="${tags.join(',').replace(/(")/gm, '"')
- }"></div>`
- : ``
- }
- <div class="element-item-inner">
- <div class="element-item">
- <div class="art-wrapper">
- <a href="${link}">
- <img id="${title.split(/\s/g).join('').replace(/[^a-zA-Z ]/g, '')}" src="${pixelPlaceholder}" data-src="${cover}" alt="${title + ' ' + cover}" />
- </a>
- </div>
- <div class="details">
- <h3 class="title">
- <a href="${link}" title="${title}">
- ${title.substring(0, 40).trim()}${title.length >= 40 ? '...' : ''}
- </a>
- </h3>
- <p class="artist" title="${author}">by ${author.substring(0, 20).trim()}${author.length >= 20 ? '...' : ''}</p>
- <p class="rating" style="display: none;">${rating !== undefined ? rating : -1}</p>
-
- <div style="display: none;">${note.text}</div>
-
- ${
- !customListIdContent
- ? rating !== undefined
- ? rating !== -1
- ? `<div class="stars9" id="stars0"><div class="stars9" id="stars5" style="width:${ratingWidth}px;background-position:0 -9px;margin-bottom: 14px;"></div></div>
- <div style="padding: 4px;"></div>`
- : ``
- : ``
- : rating !== undefined
- ? `<div class="non-star-rating">${rating}/10.0</div>`
- : ``
- }
-
- <div className="tag-wrapper">${
- tags.map(tag => {
- return `<span class="tag">${tag}</span>`
- }).join('')
- }</div>
-
- <!-- add notes and tags flippable card -->
- <p class="date">${timestamp}</p>
- </div>
- </div>
- </div>
- </div>`
- }).join('')
- }).join('')
-
-
- $('body').append(`<style>
- .rightside {
- display: none !important;
- }
-
- #back_top {
- display: none !important;
- }
-
- .article {
- width: 100% !important;
- }
-
- #page.widepage {
- width: calc(100% - 60px);
- }
-
- .non-star-rating {
- margin-top: -8px;
- color: #FBFA7C;
- margin-bottom: 4px;
- }
- </style>`)
-
- if (allBackup.finalCount >= idealLimit) {
- $('.article').find('.content').append(`<h1 style="color: #ff7979; margin-bottom: 20px;">Warning: You currently have more than 1000 stories. Sortable view is not optimized for too many stories, hence the degraded performance.</h1>`);
- }
-
- let mainDetails = undefined;
-
- if (customListIdContent) {
- mainDetails = customListIdContent.mainDetails;
- }
-
- // creates the HTML elements needed for the sortable list view filter/sort/search UI
- $('.article').find('.content').append(`
- <div>
- ${
- customListIdContent
- ? `
- <div style="margin-bottom: 20px;">
- <a class="c-btn" href="${`https://www.mangago.me/home/mangalist/${mainDetails.listId}/`}">Return to Current List</a>
- <a class="c-btn" href="${`https://www.mangago.me/home/people/${getUserId()}/list/`}">Return to All Custom Lists</a>
- </div>
-
- ${mainDetails.title.html}
-
- <div class="info" style="margin-top: 12px">
- <h2 style="margin-bottom:0">
- <a href="${`https://www.mangago.me/home/people/${mainDetails.curator.id}/home/`}">
- <span style="color: #ececec; font-size: 18px;">curated by</span>
- <span style="text-decoration: underline; color: #06E8F6; font-size: 18px;">${mainDetails.curator.username}</span>
- </a>
- </h2>
- <span>Create: ${mainDetails.created} Last update: ${mainDetails.updated}</span>
- </div>
- <div style="max-width: 600px; margin-top: 20px;">
- ${mainDetails.description.html}
- </div>
- `
- : ``
- }
- </div>
-
- <div class="filters">
- <h2>List + Tags</h2>
- <div class="button-group" data-filter-group="default"> <button class="button is-checked" data-filter="">show all</button>
- ${
- !customListIdContent
- ? `<button class="button" data-filter=".wantToRead">Want To Read</button>
- <button class="button" data-filter=".currentlyReading">Currently Reading</button>
- <button class="button" data-filter=".alreadyRead">Already Read</button>`
- : ``
- }
- ${
- allTags.map((tag) => {
- return `<button class="button" data-filter=".${cleanAndHyphenateTag(tag)}">${tag}</button>`
- }).join('')
- }
- </div>
-
- ${
- !customListIdContent
- ?`<h2>Rating</h2>
- <div class="button-group" data-filter-group="stars"> <button class="button is-checked" data-filter="">any</button>
- <button class="button" data-filter="notUnrated">rated</button>
- <button class="button" data-filter=".5-star">5 ★</button>
- <button class="button" data-filter=".4-star">4 ★</button>
- <button class="button" data-filter=".3-star">3 ★</button>
- <button class="button" data-filter=".2-star">2 ★</button>
- <button class="button" data-filter=".1-star">1 ★</button>
- </div>`
- : ``
- }
- </div>
-
- <h2>Sort</h2>
- <div id="sorts" class="button-group"> <button class="button is-checked" data-sort-by="original-order">original order</button>
- <button class="button" data-sort-by="title" data-sort-direction="asc">
- <span>title</span>
- <span class="chevron bottom"></span>
- </button>
- <button class="button" data-sort-by="date" data-sort-direction="desc">
- <span>date added</span>
- <span class="chevron"></span>
- </button>
- <button class="button" data-sort-by="artist" data-sort-direction="asc">
- <span>author/artist</span>
- <span class="chevron bottom"></span>
- </button>
- <button class="button" data-sort-by="rating" data-sort-direction="desc">
- <span>rating</span>
- <span class="chevron"></span>
- </button>
- </div>
-
- <h2>Search</h2>
- <p><input type="text" class="quicksearch" placeholder="Search" /></p>
-
- <p class="filter-count"></p>
-
- <div class="grid">
- ${allContentHtml}
- </div>
- `)
-
- $('.note-trigger').each(function( i, el ) {
- var $el = $( el );
-
- $el.on('click', (e) => {
- const $this = $(this);
-
- if ($this.is(':visible')) {
- $('#floatingNote').find('.info.summary').remove();
- $('#floatingNote').hide();
- $('#floatingNote').removeClass('is-active');
- }
-
- const note = $this.data('note-html');
-
- const tags = $this.data('tags');
- const $floatingNote = $('#floatingNote');
-
- if ($floatingNote.find('.short-note')[0]) {
- $floatingNote.find('.short-note').remove();
- }
-
- if ($floatingNote.find('.tag-wrapper')[0]) {
- $floatingNote.find('.tag-wrapper').remove();
- }
- $floatingNote.append(note);
- $floatingNote.append(`<div class="tag-wrapper">
- ${
- tags.length > 0 ? tags.split(',').map(tag => {
- return `<span class="tag">${tag}</span>`
- }).join('') : ''
- }
- </div>`)
- $floatingNote.addClass('is-active');
- $floatingNote.show();
-
- if (e.pageX > window.innerWidth - 250) { // FIXME: make 250 a variable synced to min-width of floatingNote
- $floatingNote.css('left', e.pageX - 250 + 'px');
- } else {
- $floatingNote.css('left', e.pageX + 'px');
- }
-
- if (e.clientY > window.innerHeight - $floatingNote.height()) {
- $floatingNote.css('top', e.pageY - $floatingNote.height() + 'px');
- } else {
- $floatingNote.css('top', e.pageY + 'px');
- }
- })
- })
-
- $('body').append(`<style>
- * { box-sizing: border-box; }
-
- body {
- font-family: sans-serif;
- }
-
- /* ---- button ---- */
- .button .chevron {
- border-style: solid;
- border-width: 0.25em 0.25em 0 0;
- content: '';
- display: inline-block;
- height: 0.45em;
- left: 0.15em;
- position: relative;
- top: 0.35em;
- transform: rotate(-45deg);
- vertical-align: top;
- width: 0.45em;
-
- transition: .2s;
- }
-
- .button .chevron.bottom {
- transform: rotate(135deg);
- }
-
- .button {
- display: inline-block;
- padding: 0.5em 1.0em;
- min-height: 40px;
- background: #EEE;
- border: none;
- border-radius: 7px;
- background-image: linear-gradient( to bottom, hsla(0, 0%, 0%, 0), hsla(0, 0%, 0%, 0.2) );
- color: #222;
- font-family: sans-serif;
- font-size: 16px;
- text-shadow: 0 1px white;
- cursor: pointer;
- }
-
- .button:hover {
- background-color: #8CF;
- text-shadow: 0 1px hsla(0, 0%, 100%, 0.5);
- color: #222;
- }
-
- .button:active,
- .button.is-checked {
- background-color: #28F;
- }
-
- .button.is-checked {
- color: white;
- text-shadow: 0 -1px hsla(0, 0%, 0%, 0.8);
- }
-
- .button:active {
- box-shadow: inset 0 1px 10px hsla(0, 0%, 0%, 0.8);
- }
-
- /* ---- button-group ---- */
-
- .button-group {
- margin-bottom: 20px;
- }
-
- .button-group:after {
- content: '';
- display: block;
- clear: both;
- }
-
- .button-group .button {
- float: left;
- border-radius: 0;
- margin-left: 0;
- margin-right: 1px;
- }
-
- .button-group .button:first-child { border-radius: 0.5em 0 0 0.5em; }
- .button-group .button:last-child { border-radius: 0 0.5em 0.5em 0; }
-
- .content h2 {
- margin-bottom: 10px;
- }
-
- .quicksearch {
- padding: 5px;
- margin-bottom: 25px;
- font-size: 16px;
- width: 300px;
- height: 40px;
- }
-
- /* ---- isotope ---- */
-
- .grid {
- border: 1px solid #333;
- }
-
- /* clear fix */
- .grid:after {
- content: '';
- display: block;
- clear: both;
- }
-
- /* ---- .element-item ---- */
-
- .element-item-outer {
- position: relative;
- width: 250px;
- height: 155px;
-
- margin: 5px;
-
- perspective: 1000px;
- }
-
- .note-trigger {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 2;
- width: 15px;
- height: 15px;
-
- background-color: #f47dbb;
-
- cursor: pointer;
- transition: .2s;
- }
-
- .note-trigger:hover {
- background-color: #ff002f;
- }
-
- .element-item-inner .is-active {
- transform: rotateY(180deg);
- }
-
- .element-item-inner {
- position: relative;
- transition: transform 0.5s;
- transform-style: preserve-3d;
- }
-
- .element-item {
- position: absolute;
- top: 0;
- left: 0;
-
- -webkit-backface-visibility: hidden; /* Safari */
- backface-visibility: hidden;
- }
-
- .element-item {
- display: flex;
- width: 250px;
- height: 155px;
- padding: 10px;
- background: #353743;
- color: #262524;
-
- -webkit-backface-visibility: hidden; /* Safari */
- backface-visibility: hidden;
- }
-
- .element-item > * {
- margin: 0;
- padding: 0;
- }
-
- .element-item .title a {
- font-size: 14px;
- color: #06E8F6;
- }
-
- .element-item .artist {
- margin-bottom: 8px;
- font-size: 12px;
- color: #ddd;
- }
-
- .element-item img {
- max-width: 90px;
- }
-
- .element-item .art-wrapper {
- min-width: 90px;
- height: 135px;
- margin-right: 10px;
- overflow: hidden;
- }
-
- .element-item .details {
- display: flex;
- flex-grow: 1;
- flex-direction: column;
- overflow: hidden;
- }
-
- .element-item .date {
- font-size: 8px;
- margin-left: auto;
- margin-top: auto;
- color: #b7b7b7;
- }
-
- .element-item .tag-wrapper {
- display: flex;
- }
-
- .element-item .tag {
- display: inline-block;
- background-color: #428be7;
- border-radius: 2px;
- font-size: 8px;
- color: white;
- padding: 2px;
- margin-right: 2px;
- margin-bottom: 5px;
- }
-
- /* .element-item .number {
- position: absolute;
- right: 8px;
- top: 5px;
- } */
- </style>`)
-
- // Isotope related js
-
- // store filter for each button group
- var buttonFilters = {};
-
- // quick search regex
- var qsRegex;
-
- $('.filters').on( 'click', '.button', function() {
- var $this = $(this);
- // get group key
- var $buttonGroup = $this.parents('.button-group');
- var filterGroup = $buttonGroup.attr('data-filter-group');
- // set filter for group
- buttonFilters[ filterGroup ] = $this.attr('data-filter');
- // Isotope arrange
- $grid.isotope();
- });
-
- // Initialization of isotope grid. Read more about isotope at: https://isotope.metafizzy.co/
- var $grid = $('.grid').isotope({
- itemSelector: '.element-item-outer',
- layoutMode: 'fitRows',
- getSortData: {
- title: '.title',
- date: '.date',
- artist: '.artist',
- rating: '.rating',
- category: '[data-category]',
- },
- filter: function() {
- var $this = $(this);
- var searchResult = qsRegex ? $this.text().match( qsRegex ) : true;
-
- var isFilterMatched = true;
-
- for ( var prop in buttonFilters ) {
- var filter = buttonFilters[ prop ];
- // use function if it matches
- filter = filterFns[ filter ] || filter;
- // test each filter
- if ( filter ) {
- isFilterMatched = isFilterMatched && $(this).is( filter );
- }
- // break if not matched
- if ( !isFilterMatched ) {
- break;
- }
- }
-
- return searchResult && isFilterMatched;
- }
- });
-
- var iso = $grid.data('isotope');
- var $filterCount = $('.filter-count');
-
- function updateFilterCount() {
- $filterCount.text( iso.filteredItems.length + ' items' );
- }
-
- updateFilterCount();
-
- $('.element-item-outer').each((i, el) => {
- createObserver(el);
- })
-
- // filter functions
- var filterFns = {
- notUnrated: function() {
- var number = $(this).find('.rating').text();
- return parseInt(number, 10) !== -1;
- }
- };
-
- // bind sort button click
- $('#sorts').on( 'click', 'button', function() {
- var $this = $(this);
- var sortByValue = $this.attr('data-sort-by');
- $grid.isotope({ sortBy: sortByValue });
- $grid.isotope();
-
- updateFilterCount();
- });
-
- // change is-checked class on buttons
- $('.button-group').each( function( i, buttonGroup ) {
- var $buttonGroup = $( buttonGroup );
- $buttonGroup.on( 'click', 'button', function() {
- $buttonGroup.find('.is-checked').removeClass('is-checked');
- $( this ).addClass('is-checked');
-
- /* Get the element name to sort */
- var sortValue = $(this).attr('data-sort-by');
-
- /* Get the sorting direction: asc||desc */
- var direction = $(this).attr('data-sort-direction');
-
- /* convert it to a boolean */
- var isAscending = (direction == 'asc');
- var newDirection = (isAscending) ? 'desc' : 'asc';
-
- /* pass it to isotope */
- $grid.isotope({ sortBy: sortValue, sortAscending: isAscending });
-
- $(this).attr('data-sort-direction', newDirection);
-
- $(this).find('.chevron').toggleClass('bottom');
- });
-
- updateFilterCount();
- });
-
- // use value of search field to filter
- var $quicksearch = $('.quicksearch').keyup( debounce( function() {
- qsRegex = new RegExp( $quicksearch.val(), 'gi' );
- $grid.isotope();
- }, 200 ) );
-
- // debounce so filtering doesn't happen every millisecond
- function debounce( fn, threshold ) {
- var timeout;
- threshold = threshold || 100;
- return function debounced() {
- clearTimeout( timeout );
- var args = arguments;
- var _this = this;
- function delayed() {
- fn.apply( _this, args );
- }
- timeout = setTimeout( delayed, threshold );
- };
- }
- // end of isotope related js
- }
-
- } else {
- const userId = getUserId();
- const backupMode = localStorage.getItem(custom ? 'backupModeCustom' : 'backupMode');
-
- if (userId !== undefined) { // Only do action if user is on an mgg link where userId can be inferred.
- if (backupMode !== 'on') {
- const $userH1 = $('.user-profile').find('h1');
- const btnBackup = `<button id="btnBackup" class="c-btn c-btn-backup">${
- custom ? 'Create New Custom Backup' : 'Create New Backup'
- }</button>`;
- const btnReset = `<button id="btnReset" class="c-btn c-btn-reset">${
- custom ? 'Reset All Custom' : 'Reset All'
- }</button>`;
-
- if ($userH1[0] !== undefined) {
- $($userH1).append(btnBackup);
- $($userH1).append(btnReset);
- }
-
- $('#btnBackup').on('click', () => {
- if (custom) {
- localStorage.setItem('backupUser', getUserId());
- }
-
- const initiateBackup = () => {
- clearListRelatedStorageItems(custom);
-
- let targetUrl = `https://www.mangago.me/home/people/${userId}/${custom ? 'list/create' : 'manga/1'}/?backup=on`
- window.location.replace(targetUrl);
- }
-
- if (latestBackup) { // ask for confirmation when a previous successful backup is available
- const proceed = confirm("There is an existing backup. Overwrite?");
-
- if (proceed) {
- clearLocalStorageItems(custom ? ['backupTimeCustom', 'generate'] : ['backupTime']);
- if (custom) {
- localStorage.removeItem('listsCreated');
- localStorage.removeItem('listsFollowed');
- }
-
- initiateBackup();
- }
- } else {
- initiateBackup();
- }
- });
-
- $('#btnReset').on('click', () => {
- if (latestBackup) { // ask for confirmation when a previous successful backup is available
- const proceed = confirm("Confirm reset?");
-
- if (proceed) {
- clearLocalStorageItems(custom ? ['backupTimeCustom', 'generate'] : ['backupTime']);
- clearListRelatedStorageItems(custom);
-
- if (custom) {
- localStorage.removeItem('listsCreated');
- localStorage.removeItem('listsFollowed');
- localStorage.removeItem('backupUser');
- }
- window.location.reload();
- }
- } else {
- clearListRelatedStorageItems(custom);
-
- if (custom) {
- localStorage.removeItem('listsCreated');
- localStorage.removeItem('listsFollowed');
- localStorage.removeItem('backupUser');
- }
- window.location.reload();
- }
- });
- }
-
- // add custom lists into consideration
- const { backup, page } = getUrlParams();
-
- const customLists = getCustomLists();
- const listsCreated = customLists.stringified.created
- const listsFollowed = customLists.stringified.followed
-
- if (backup === 'on') {
- // process for initiating saving content by setting specific storage keys for custom lists
- // get created list ids for backup
- localStorage.setItem(custom ? 'backupModeCustom' : 'backupMode', 'on');
-
- if (custom) {
- if (getUrlWithoutParams().match(/(create)/gm)) {
- if (!listsCreated) {
- if ($('.left.wrap')[0]) {
- let createdArr = []
-
- $('.left.wrap').each((i, el) => {
- const $el = $(el);
- let splitUrl = $el.attr('href').split('/');
- splitUrl = splitUrl.filter(item => item !== "");
- const listId = splitUrl[splitUrl.length -1];
- createdArr.push(listId);
- })
-
- localStorage.setItem('listsCreated', compressString(JSON.stringify(createdArr)));
- window.location.replace(`https://www.mangago.me/home/people/${userId}/list/follow/`);
- } else {
- localStorage.setItem('listsCreated', compressString(JSON.stringify([])));
- window.location.replace(`https://www.mangago.me/home/people/${userId}/list/follow/`);
- }
- }
- }
- } else {
- window.location.replace(`https://www.mangago.me/home/people/${userId}/manga/1/?page=1`);
- }
- }
-
- // do the same for followed custom lists
- if (backupMode === 'on' && custom && listsCreated && !listsFollowed) {
- if (getUrlWithoutParams().match(/(follow)/gm)) {
- // FIXME: functionify above
- if (!listsFollowed) {
- if ($('.left.wrap')[0]) {
- let followedArr = []
-
- $('.left.wrap').each((i, el) => {
- const $el = $(el);
- let splitUrl = $el.attr('href').split('/');
- splitUrl = splitUrl.filter(item => item !== "");
- const listId = splitUrl[splitUrl.length -1];
- followedArr.push(listId);
- })
-
- localStorage.setItem('listsFollowed', compressString(JSON.stringify(followedArr)));
- window.location.reload();
- } else {
- localStorage.setItem('listsFollowed', compressString(JSON.stringify([])));
- window.location.reload();
- }
- }
- }
- }
-
- // checks where the user should be on flow process for custom lists and redirects accordingly
- if (backupMode === 'on' && listsCreated && listsFollowed) {
- if (JSON.parse(listsCreated).length > 0) {
- window.location.replace(`https://www.mangago.me/home/mangalist/${JSON.parse(listsCreated)[0]}/?filter=&page=1`)
- } else if (JSON.parse(listsFollowed).length > 0) {
- window.location.replace(`https://www.mangago.me/home/mangalist/${JSON.parse(listsFollowed)[0]}/?filter=&page=1`)
- } else {
- alert ('nothing to backup, your created/followed lists are empty');
- }
- }
-
- // actual start of backup, copied from above, edited for custom lists
- // if (backupMode === 'on') {
- if (backupMode === 'on' && !custom) {
- // gets total pagination numbers
- const totalPagesFromMemory = localStorage.getItem('totalPages');
-
- // when no total pages are set, get total pages from pagination data first
- if (!totalPagesFromMemory) {
- setTotalPages()
- window.location.reload();
- } else {
- saveList();
-
- if (page) {
- const currentPage = parseInt(page, 10);
-
- // will execute saving page by page until it equals the last number on pagination
- if (currentPage < totalPagesFromMemory) {
- const newPage = currentPage + 1;
- const newUrl = getUrlWithoutParams() + `?page=${newPage}`;
- window.location.replace(newUrl);
- } else {
- // when a list type is done, move on to next list
- if (detectList() < 3) {
- localStorage.removeItem('totalPages');
- window.location.replace(`https://www.mangago.me/home/people/${userId}/manga/${detectList() + 1}/?page=1`);
- } else {
- // if last list type is done, set backupTime, and backupUser, generate download links
- localStorage.setItem('backupTime', new Date().toLocaleString());
- localStorage.setItem('backupUser', getUserId());
-
- generateDownloadLinksToBackup();
-
- localStorage.setItem('backupMode', 'off');
- localStorage.removeItem('totalPages');
- appendSortableList();
- alert('backup done! page will refresh one more time to reflect download links');
- window.location.reload();
- }
- }
- }
- }
- }
-
- const generate = localStorage.getItem('generate');
- if (custom && generate === 'yes') {
- generateDownloadLinksToBackupCustom();
- }
- } else {
- if (backupMode === 'on' && getUrlWithoutParams().match(/(mangalist)/gm)) {
- // gets total pagination numbers
- const totalPagesFromMemory = localStorage.getItem('totalPages');
-
- // when no total pages are set, get total pages from pagination data first
- if (!totalPagesFromMemory) {
- setTotalPages();
- window.location.reload();
- } else {
- let typeIndex = getTypeIndexFromCustomList(getListId());
-
- if (typeIndex.index !== -1) {
- saveList(custom); //custom type
-
- const { page } = getUrlParams()
-
- if (page) {
- const currentPage = parseInt(page, 10);
-
- if (currentPage < totalPagesFromMemory) {
- // TODO: check if not done
- const newPage = currentPage + 1;
- const newUrl = getUrlWithoutParams() + `?filter=&page=${newPage}`;
- window.location.replace(newUrl);
- } else {
- localStorage.removeItem('totalPages');
-
- let nextTarget = typeIndex.index + 1;
-
- const customLists = getCustomLists();
- const { parsed } = customLists;
- const { created, followed } = parsed;
-
- // x = created custom lists
- if (typeIndex.type === 'x') {
- if (created[nextTarget]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${created[nextTarget]}/?filter=&page=1`;
- window.location.replace(targetUrl);
- } else {
- if (followed[0]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${followed[0]}/?filter=&page=1`;
- window.location.replace(targetUrl);
- }
- }
- }
-
- // y = followed created lists
- if (typeIndex.type === 'y') {
- if (followed[nextTarget]) {
- let targetUrl = `https://www.mangago.me/home/mangalist/${followed[nextTarget]}/?filter=&page=1`
- window.location.replace(targetUrl);
- } else {
- //finalizing storage keys for ending backup process
- localStorage.setItem('backupTimeCustom', new Date().toLocaleString());
- localStorage.setItem('backupModeCustom', 'off');
- localStorage.removeItem('totalPages');
-
- alert('backup done! Redirect to list page after clicking okay');
- const userId = localStorage.getItem('backupUser');
- localStorage.setItem('generate', 'yes');
- window.location.replace(`https://www.mangago.me/home/people/${userId}/list/`)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- })();