EntryList

Common functions for working on lists of entries

目前为 2019-10-20 提交的版本,查看 最新版本

此脚本不应直接安装,它是供其他脚本使用的外部库。如果你需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/390248/742489/EntryList.js

  1. // EntryList library
  2. //
  3. // Common functions for modifying/hiding/etc. entries in page, based on
  4. // entry features or presence in one or more lists.
  5. // For instance: hide all YouTube videos that have been watched and highlight
  6. // the ones that have been started but not finished, highlight Netflix movies
  7. // based on IMDb lists, etc.
  8. //
  9. // https://greasyfork.org/scripts/390248-entrylist
  10. // Copyright (C) 2019, Guido Villa
  11. // Original idea and some of the code are taken from IMDb 'My Movies' enhancer:
  12. // Copyright (C) 2008-2018, Ricardo Mendonça Ferreira (ric@mpcnet.com.br)
  13. // Released under the GPL license - http://www.gnu.org/copyleft/gpl.html
  14. //
  15. // For instructions on user scripts, see:
  16. // https://greasyfork.org/help/installing-user-scripts
  17. //
  18. // To use this library in a userscript you must add to script header:
  19. // @require https://greasyfork.org/scripts/390248-entrylist/code/EntryList.js
  20. // @grant GM_getValue
  21. // @grant GM_setValue
  22. // @grant GM_deleteValue
  23. // @grant GM_listValues
  24. //
  25. // --------------------------------------------------------------------
  26. //
  27. // ==UserScript==
  28. // @namespace https://greasyfork.org/users/373199-guido-villa
  29. // @exclude *
  30. //
  31. // ==UserLibrary==
  32. // @name EntryList
  33. // @description Common functions for working on lists of entries
  34. // @version 1.9
  35. // @author guidovilla
  36. // @date 19.10.2019
  37. // @copyright 2019, Guido Villa (https://greasyfork.org/users/373199-guido-villa)
  38. // @license GPL-3.0-or-later
  39. // @homepageURL https://greasyfork.org/scripts/390248-entrylist
  40. // @supportURL https://gitlab.com/gv-browser/userscripts/issues
  41. // @contributionURL https://tinyurl.com/gv-donate-f9
  42. // @attribution Ricardo Mendonça Ferreira (https://openuserjs.org/users/AltoRetrato)
  43. // ==/UserScript==
  44. //
  45. // ==/UserLibrary==
  46. //
  47. // --------------------------------------------------------------------
  48. //
  49. // To-do (priority: [H]igh, [M]edium, [L]ow):
  50. // - [M] Make private members actually private and not only undocumented
  51. // (only after understanding which ones really can be private)
  52. // - [M] main context as default context
  53. // - [M] changes to a list aren't reflected in page till reload. Change?
  54. // - [M] Better handle case without lists (e.g. optimizations)
  55. // - [M] Add description of flow in usage documentation
  56. // - [M] List regeneration function doesn't handle case where lists are missing
  57. //
  58. // Changelog:
  59. // ----------
  60. // 2019.10.19 [1.9] Add function inList for checking if entry is in list
  61. // Fix use of context in startup()
  62. // 2019.10.18 [1.8] Add possibility to download a user payload with getUser
  63. // 2019.10.10 [1.7] Add possibility of source contexts
  64. // saveList public, add title, ln, deleteList, deleteAllLists
  65. // Add getPageType and processPage callbacks
  66. // Some refactoring and small fixes
  67. // 2019.10.06 [1.6] Changed storage names for future needs (multiple contexts)
  68. // (requires manually adjusting previous storage)
  69. // 2019.10.05 [1.5] Automatically handle case with only one list
  70. // Better handling of list of lists
  71. // Add possibility to permanently skip an entry
  72. // 2019.10.02 [1.4] Add newContext utility function
  73. // 2019.10.01 [1.3] More generic: getUser and getIdFromEntry are now optional
  74. // Correct @namespace and other headers (for public use)
  75. // 2019.09.27 [1.2] Refactoring and name changing: TitleList -> EntryList
  76. // 2019.09.27 [1.1] Code cleanup (string literals, reorder functions)
  77. // Check for validity of the context object
  78. // Add usage documentation
  79. // 2019.09.21 [1.0] First version
  80. // 2019.09.20 [0.1] First test version, private use only
  81. //
  82.  
  83. /* jshint esversion: 6, supernew: true, laxbreak: true */
  84. /* exported EL, Library_Version_ENTRYLIST */
  85.  
  86. const Library_Version_ENTRYLIST = '1.9';
  87.  
  88. /* How to use the library
  89.  
  90. This library instantitates an EL object with a startup method.
  91.  
  92. Call, in order:
  93. 0. EL.newContext(name) to initialize each source and target context
  94. 1. EL.init(ctx), passing a "context" object that is specific to the
  95. website you need to "enhance"
  96. -> not needed if you don't have external sources, just call EL.startup(ctx)
  97. 2. EL.addSource(ctx) for each external source, with its specific context
  98. 3. EL.startup(ctx), ctx is not needed if EL.init(ctx) was called.
  99.  
  100. Other functions and variables:
  101. - title: script name as returned by GM_info
  102.  
  103. - addToggleEventOnClick(button, howToFindEntry[, toggleList[, toggleType]]):
  104. mainly used in ctx.modifyEntry(), adds an event listener that implements
  105. a toggle function:
  106. - button: the DOM object to attach the event listener to
  107. - howToFindEntry: how to go from evt.target to the entry object. It can be:
  108. - a number: # of node.parentNode to hop to get from evt.target to to entry
  109. - a CSS selector: used with evt.target.closest to get to entry
  110. - toggleList: the list where the entry is toggled when the button is pressed
  111. (can be omitted if a default list is to be used)
  112. - toggleType: the processing type that is toggled by the press of the button
  113. (can be omitted if only one processing type is used)
  114. It cannot be a false value (0, null, false, undefined, etc.)
  115. - markInvalid(entry):
  116. marks entry as invalid to skips it in subsequent passes
  117. This function returns false so it can be used in isValidEntry() in this way:
  118. return condition || EL.markInvalid(entry)
  119. This leaves the return value unchanged and marks the entry only if invalid
  120. - ln(ctx, listName): return list name as passed to determineType() (see below)
  121. - saveList(ctx, list, name): save list of entries to storage
  122. - deleteList(ctx, name): remove a list from storage (but not from memory)
  123. - deleteAllLists(ctx): remove all user lists from storage (not from memory)
  124.  
  125.  
  126. Mandatory callback functions and variables in main context:
  127.  
  128. - name: identifier of the site (set with newContext())
  129.  
  130. - getPageEntries():
  131. return (usually with querySelectorAll) an array of entries to be treated
  132. - processItem(entry, tt, processingType):
  133. process the entry based on the processing type or other features of the entry
  134.  
  135.  
  136. Conditionally mandatory callback functions in main context:
  137.  
  138. - getUser(): retrieve and return the username used on the website
  139. mandatory if data are to be stored on a per-user basis
  140. It can return either a single string (the username), or an object with a
  141. 'name' and a 'payload' property. Name is used as the username, payload is
  142. saved in <ctx>.userPayload and can be used by context-specific functions
  143. - getIdFromEntry(entry): return a tt: { id, name } object from the entry
  144. mandatory if you want to save entries to lists
  145. NOTE: if id is not found, entry is skipped but it is not marked as invalid
  146. for subsequent passes (unless you use TL.markInvalid(), see above)
  147. - unProcessItem(entry, tt, processingType):
  148. like processItem, but it should reverse the action
  149. mandatory for entries that have a toggle action added with
  150. EL.addToggleEventOnClick()
  151.  
  152.  
  153. Optional callback functions and variables in main context:
  154.  
  155. - interval: interval (in ms) to re-scan links in the DOM
  156. won't re-scan if < MIN_INTERVAL
  157. dafault: DEFAULT_INTERVAL
  158.  
  159. - isEntryPage():
  160. returns false if page must not be scanned for entries
  161. Default is always true => all pages contain entries
  162. - getPageType():
  163. returns some non false value (true, number, object) if page is significant to
  164. the script for some reason (e.g. it is the page where lists are reloaded),
  165. false otherwise. The result is stored in ctx.pageType.
  166. Default is always false => no special page
  167. - processPage(pageType, isEntryPage):
  168. optionally does operations on page based on pageType (and isEntryPage).
  169. Called only if pageType is truthy, so no need to check if it is false
  170. - isValidEntry(entry):
  171. return false if entry must be skipped
  172. NOTE: if entry is skipped, it is not however marked as invalid for subsequent
  173. passes (unless you use TL.markInvalid(), see above)
  174. Default is always true => all entries returned by getPageEntries() are valid
  175. - modifyEntry(entry):
  176. optionally modify entry when scanned for the first time (e.g. add a button)
  177. see also EL.addToggleEventOnClick() above
  178. - inList(tt, list):
  179. check if tt is in list. Default is a simple lookup by tt.id.
  180. - determineType(lists, tt, entry):
  181. return the processing type for an entry, given the lists it appears in, or a
  182. false value (0, null, false, undefined, etc.) if no processing is required
  183. "lists" is an object with a true property for each list the entry appears in.
  184. The decision can also be taken using name, id and properties of the entry.
  185. If there is a single processing type, the function might as well return true/false
  186. Default: returns true if entry is in at least one list (especially useful in
  187. cases with only one list, so there is no need to tell different lists apart)
  188.  
  189.  
  190. Callback functions and variables in contexts for external sources:
  191.  
  192. - name: identifier of the site (set with newContext())
  193.  
  194. - getUser(): see above
  195. - getSourceUserFromTargetUser(targetContextName, targetUser):
  196. returns the user name on the source site corresponding to the one on target
  197. site. This is needed to look for the saved lists.
  198. Default is looking for the last saved user (single-user scenario).
  199. A user payload can be downloaded as in getUser() (q.v.)
  200. - getPageType(): see above
  201. - processPage(pageType, isEntryPage): see above
  202. - inList(tt, list): see above
  203.  
  204. */
  205.  
  206.  
  207. var EL = new (function() {
  208. 'use strict';
  209. const SEP = '|';
  210. const STORAGE_SEP = '-';
  211. const FAKE_USER = '_';
  212. const DEFAULT_TYPE = '_DEF_';
  213. const MIN_INTERVAL = 100;
  214. const DEFAULT_INTERVAL = 1000;
  215.  
  216. var self = this;
  217.  
  218. var initialized = false;
  219. var failedInit = false;
  220. var mainContext; // target context object
  221. var isEntryPage; // boolean
  222. var allContexts; // array (cointains mainContext, too)
  223.  
  224.  
  225. /* PRIVATE members */
  226.  
  227. // Check if "object" has "property" of "type"
  228. // used to test if object "implements" a specific interface
  229. function checkProperty(object, property, type, optional = false) {
  230.  
  231. if (typeof object[property] !== type && (!optional || typeof object[property] !== 'undefined')) {
  232. console.error((optional ? 'Optionally, c' : 'C') + 'ontext must have a "' + property + '" property of type "' + type + '"');
  233. return false;
  234. }
  235. else return true;
  236. }
  237.  
  238.  
  239. // check if target context has the correct variables and functions
  240. function isValidTargetContext(ctx) {
  241. var valid = true;
  242.  
  243. valid &= checkProperty(ctx, 'name', 'string');
  244. valid &= checkProperty(ctx, 'getPageEntries', 'function');
  245. valid &= checkProperty(ctx, 'processItem', 'function');
  246. valid &= checkProperty(ctx, 'interval', 'number', true);
  247. valid &= checkProperty(ctx, 'isEntryPage', 'function', true);
  248. valid &= checkProperty(ctx, 'getPageType', 'function', true);
  249. valid &= checkProperty(ctx, 'isValidEntry', 'function', true);
  250. valid &= checkProperty(ctx, 'modifyEntry', 'function', true);
  251. valid &= checkProperty(ctx, 'determineType', 'function', true);
  252. valid &= checkProperty(ctx, 'getUser', 'function', true);
  253. valid &= checkProperty(ctx, 'getIdFromEntry', 'function', true);
  254. valid &= checkProperty(ctx, 'unProcessItem', 'function', true);
  255.  
  256. return !!valid;
  257. }
  258.  
  259.  
  260. // check if source context has the correct variables and functions
  261. function isValidSourceContext(ctx) {
  262. var valid = true;
  263.  
  264. valid &= checkProperty(ctx, 'name', 'string');
  265. valid &= checkProperty(ctx, 'getUser', 'function', true);
  266. valid &= checkProperty(ctx, 'getSourceUserFromTargetUser', 'function', true);
  267. valid &= checkProperty(ctx, 'getPageType', 'function', true);
  268.  
  269. return !!valid;
  270. }
  271.  
  272.  
  273. // standardized names for storage variables
  274. var storName = {
  275. 'listIdent': function(ctx) { return STORAGE_SEP + ctx.name + STORAGE_SEP + ctx.user; },
  276. 'listPrefix': function(ctx) { return 'List' + this.listIdent(ctx) + STORAGE_SEP; },
  277.  
  278. 'lastUser': function(ctx) { return ctx.name + STORAGE_SEP + 'lastUser'; },
  279. 'lastUserPayload': function(ctx) { return ctx.name + STORAGE_SEP + 'lastUserPayload'; },
  280. 'listOfLists': function(ctx) { return 'Lists' + this.listIdent(ctx); },
  281. 'listName': function(ctx, listName) { return this.listPrefix(ctx) + listName; },
  282. };
  283.  
  284.  
  285. // Get and save user currently logged on <ctx> site, return true if found
  286. // Get last saved user and log error if no user is found
  287. // Along with the username, a payload may be retrieved and saved in <ctx>
  288. this.getLoggedUser = function(ctx) {
  289. if (!ctx.getUser) return !!(ctx.user = FAKE_USER);
  290.  
  291. var user = ctx.getUser();
  292. var payload;
  293. if (user && typeof user === 'object') {
  294. payload = user.payload;
  295. user = user.name;
  296. }
  297. if (!user) {
  298. console.warn(ctx.name + ": user not logged in (or couldn't get user info) on URL " + document.URL);
  299. user = GM_getValue(storName.lastUser(ctx));
  300. payload = GM_getValue(storName.lastUserPayload(ctx));
  301. if (payload) payload = JSON.parse(payload);
  302. console.info('Using last user:', user);
  303. } else {
  304. GM_setValue(storName.lastUser(ctx), user);
  305. if (payload) {
  306. GM_setValue(storName.lastUserPayload(ctx), JSON.stringify(payload));
  307. } else {
  308. GM_deleteValue(storName.lastUserPayload(ctx));
  309. }
  310. }
  311. ctx.user = user;
  312. ctx.userPayload = payload;
  313. return !!user;
  314. };
  315.  
  316.  
  317. // Get and save user to read for this source <ctx>, corresponding to the
  318. // user on the target context. Return true if found.
  319. // If no mapping function is defined, take the last saved user regardless
  320. // of target user
  321. // Along with the username, a payload may be retrieved and saved in <ctx>
  322. this.getRemoteUser = function(ctx) {
  323. if (ctx.getSourceUserFromTargetUser) {
  324. ctx.user = ctx.getSourceUserFromTargetUser(mainContext.name, mainContext.user);
  325. if (ctx.user && typeof ctx.user === 'object') {
  326. ctx.payload = ctx.user.payload;
  327. ctx.user = ctx.user.name;
  328. }
  329. if (!ctx.user) {
  330. console.error(ctx.name + ": cannot find user corresponding to '" + mainContext.user + "' on " + mainContext.name);
  331. delete ctx.payload;
  332. }
  333. } else {
  334. ctx.user = GM_getValue(storName.lastUser(ctx));
  335. ctx.userPayload = GM_getValue(storName.lastUserPayload(ctx));
  336. if (ctx.userPayload) ctx.userPayload = JSON.parse(ctx.userPayload);
  337. }
  338. return !!(ctx.user);
  339. };
  340.  
  341.  
  342. // Regenerate and save the list of lists stored object, even if empty
  343. // returns the new list
  344. function regenerateListOfLists(ctx) {
  345. var allVariables = GM_listValues();
  346.  
  347. var listNames = allVariables.reduce(function(listNames, variable) {
  348. if (variable.startsWith(storName.listPrefix(ctx))) {
  349. listNames.push(variable.substring(storName.listPrefix(ctx).length));
  350. }
  351. return listNames;
  352. }, []);
  353.  
  354. var jsonData = JSON.stringify(listNames);
  355. GM_setValue(storName.listOfLists(ctx), jsonData);
  356. return listNames;
  357. }
  358.  
  359.  
  360. // Load a single saved lists
  361. function loadSavedList(listName) {
  362. var list;
  363. var jsonData = GM_getValue(listName);
  364. if (jsonData) {
  365. try {
  366. list = JSON.parse(jsonData);
  367. } catch(err) {
  368. alert("Error loading saved list named '" + listName + "'\n" + err.message);
  369. }
  370. }
  371. return list;
  372. }
  373.  
  374.  
  375. // Load the list of lists, regenerating it if necessary
  376. // always returns an array, possibly empty
  377. function loadListOfLists(ctx) {
  378. var listNames = loadSavedList(storName.listOfLists(ctx));
  379.  
  380. if (!Array.isArray(listNames)) listNames = regenerateListOfLists(ctx);
  381. return listNames;
  382. }
  383.  
  384.  
  385. // Load lists for the current user
  386. this.loadSavedLists = function(ctx) {
  387. var listNames = loadListOfLists(ctx);
  388. var lists = {};
  389. var list;
  390. var mustRegenerateListOfLists = false;
  391.  
  392. listNames.forEach(function(listName) {
  393. list = loadSavedList(storName.listName(ctx, listName));
  394. if (list) lists[listName] = list;
  395. else mustRegenerateListOfLists = true;
  396. });
  397. if (mustRegenerateListOfLists) regenerateListOfLists(ctx);
  398. return lists;
  399. };
  400.  
  401.  
  402. // Receives an entry tt and finds all lists where tt.id appears
  403. this.inLists = function(tt) {
  404. var lists = {};
  405.  
  406. allContexts.forEach(function(ctx) {
  407. for (var list in ctx.allLists) {
  408. if (ctx.inList(tt, ctx.allLists[list])) lists[self.ln(ctx, list)] = true;
  409. }
  410. });
  411.  
  412. return lists;
  413. };
  414. function _inList_default(tt, list) {
  415. return !!list[tt.id];
  416. }
  417.  
  418.  
  419. // Wrap ctx.getIdFromEntry and add error logging
  420. function _wrap_getIdFromEntry(ctx, entry) {
  421. var tt = ctx.getIdFromEntry(entry);
  422. if (!tt) console.error('Could not determine id - for entry', entry);
  423. return tt;
  424. }
  425.  
  426.  
  427. // Process a single entry
  428. function processOneEntry(entry, ctx = mainContext) {
  429. var tt, lists, processingType;
  430.  
  431. // if entry has already been previously processed, skip it
  432. if (entry.ELProcessed || entry.ELInvalid) return;
  433.  
  434. // see if entry is valid
  435. if (ctx.isValidEntry && !ctx.isValidEntry(entry)) return;
  436.  
  437. if (ctx.getIdFromEntry) {
  438. tt = _wrap_getIdFromEntry(ctx, entry);
  439. if (!tt) return;
  440. }
  441.  
  442. if (ctx.modifyEntry) ctx.modifyEntry(entry);
  443. lists = ( tt ? self.inLists(tt) : {} );
  444.  
  445. processingType = (ctx.determineType
  446. ? ctx.determineType(lists, tt, entry)
  447. : Object.keys(lists).length > 0);
  448.  
  449. if (processingType) {
  450. ctx.processItem(entry, tt, processingType);
  451. entry.ELProcessingType = processingType;
  452. }
  453.  
  454. entry.ELProcessed = true; // set to "true" after processing (so we skip it on next pass)
  455. }
  456.  
  457.  
  458. // Process all entries in current page
  459. this.processAllEntries = function(ctx = mainContext) {
  460. var entries = ctx.getPageEntries();
  461. if (!entries) return;
  462.  
  463. for (var i = 0; i < entries.length; i++) {
  464. processOneEntry(entries[i], ctx);
  465. }
  466. };
  467.  
  468.  
  469. // handle the toggle event
  470. this.handleToggleButton = function(evt) {
  471. evt.stopPropagation();
  472. evt.preventDefault();
  473. var data = evt.target.dataset;
  474. var toggleList = (typeof data.toggleList === 'undefined' ? DEFAULT_TYPE : data.toggleList);
  475. var toggleType = (typeof data.toggleType === 'undefined' ? DEFAULT_TYPE : data.toggleType);
  476.  
  477. // get corresponding entry
  478. var entry = evt.target;
  479. if (Number.isInteger(Number(data.howToFindEntry))) {
  480. for (var i = 0; i < Number(data.howToFindEntry); i++) entry = entry.parentNode;
  481. } else {
  482. entry = entry.closest(data.howToFindEntry);
  483. }
  484.  
  485. self.toggleEntry(entry, toggleList, toggleType);
  486. };
  487.  
  488.  
  489. // add/remove entry from a list
  490. this.toggleEntry = function(entry, toggleList, toggleType) {
  491. var ctx = mainContext;
  492.  
  493. var tt = _wrap_getIdFromEntry(ctx, entry);
  494. if (!tt) return;
  495.  
  496. // check if item is in list
  497. var list = ctx.allLists[toggleList];
  498. if (!list) list = ctx.allLists[toggleList] = {};
  499. if (list[tt.id]) {
  500. delete list[tt.id];
  501. ctx.unProcessItem(entry, tt, toggleType);
  502. entry.ELProcessingType = '-' + toggleType;
  503. } else {
  504. list[tt.id] = tt.name;
  505. ctx.processItem(entry, tt, toggleType);
  506. entry.ELProcessingType = toggleType;
  507. }
  508. self.saveList(ctx, list, toggleList);
  509. };
  510.  
  511.  
  512.  
  513. /* PUBLIC members */
  514.  
  515. // utility function that creates a new context, initialized with <name>
  516. this.newContext = function(name) {
  517. return { 'name': name };
  518. };
  519.  
  520.  
  521. // init function
  522. this.init = function(ctx) {
  523. initialized = false;
  524. failedInit = true;
  525. mainContext = null;
  526. isEntryPage = false;
  527. allContexts = [];
  528.  
  529. self.title = GM_info.script.name;
  530.  
  531. // check that passed context is good
  532. if (!isValidTargetContext(ctx)) {
  533. console.log('Invalid target context, aborting');
  534. return;
  535. }
  536.  
  537. isEntryPage = ( !ctx.isEntryPage || ctx.isEntryPage() );
  538. ctx.pageType = ( ctx.getPageType && ctx.getPageType() );
  539.  
  540. if (isEntryPage || ctx.pageType) {
  541. // find current logged in user, or quit script
  542. if (!self.getLoggedUser(ctx)) {
  543. console.log(ctx.name + ': no user is defined, aborting');
  544. return;
  545. }
  546. if (ctx.pageType && ctx.processPage) ctx.processPage(ctx.pageType, isEntryPage);
  547. }
  548.  
  549. mainContext = ctx;
  550. initialized = true;
  551. failedInit = false;
  552. };
  553.  
  554.  
  555. // startup function. Don't pass "ctx" arg if init() had been called before
  556. this.startup = function(ctx) {
  557. if (!initialized) {
  558. if (failedInit) return;
  559. self.init(ctx);
  560.  
  561. } else if (ctx) console.warn('Startup called with context parameter after init, ignoring ctx');
  562.  
  563. if (!isEntryPage) return;
  564.  
  565. // Load list data for this user from local storage
  566. mainContext.allLists = self.loadSavedLists(mainContext);
  567. allContexts.push(mainContext);
  568. // Setup the default list checking function, if not provided by context
  569. if (!mainContext.inList) mainContext.inList = _inList_default;
  570.  
  571. // start the entry processing function
  572. self.processAllEntries();
  573. if (typeof mainContext.interval === 'undefined' || mainContext.interval >= MIN_INTERVAL) {
  574. // TODO we might consider using MutationObserver in the future, instead
  575. mainContext.timer = setInterval(self.processAllEntries, ( mainContext.interval || DEFAULT_INTERVAL ));
  576. }
  577. };
  578.  
  579.  
  580. // add a source context
  581. this.addSource = function(ctx) {
  582. if (!initialized) {
  583. console.log('Main context is not initialized, aborting addSource');
  584. return;
  585. }
  586.  
  587. // check that passed context is good
  588. if (!isValidSourceContext(ctx)) {
  589. console.log('Invalid source context, aborting');
  590. return;
  591. }
  592.  
  593. ctx.pageType = ( ctx.getPageType && ctx.getPageType() );
  594.  
  595. if (ctx.pageType) {
  596. // find current logged in user, or quit script
  597. if (!self.getLoggedUser(ctx)) {
  598. console.log(ctx.name + ': no user is defined, aborting');
  599. return;
  600. }
  601. if (ctx.processPage) ctx.processPage(ctx.pageType, isEntryPage);
  602. }
  603.  
  604. if (!isEntryPage) return;
  605.  
  606. // find user corresponding to current logged in user, or quit script
  607. // TODO if (entryPage && pageType), remote user overwrites logged user
  608. if (!self.getRemoteUser(ctx)) {
  609. console.log(ctx.name + ': no remote user is defined, aborting');
  610. return;
  611. }
  612.  
  613. // Load list data for this user from local storage
  614. ctx.allLists = self.loadSavedLists(ctx);
  615. allContexts.push(ctx);
  616. // Setup the default list checking function, if not provided by context
  617. if (!ctx.inList) ctx.inList = _inList_default;
  618. };
  619.  
  620.  
  621. this.addToggleEventOnClick = function(button, howToFindEntry, toggleList = null, toggleType = null) {
  622. button.dataset.howToFindEntry = howToFindEntry;
  623. if (toggleList !== null) button.dataset.toggleList = toggleList;
  624. if (toggleType !== null) button.dataset.toggleType = toggleType;
  625. button.addEventListener('click', self.handleToggleButton, false);
  626. };
  627.  
  628.  
  629. this.markInvalid = function(entry) {
  630. entry.ELInvalid = true;
  631. return false;
  632. };
  633.  
  634.  
  635. // return the list name as generated by inLists (to be used in ctx.determineType())
  636. this.ln = function(ctx, listName) {
  637. return ctx.name + SEP + listName;
  638. };
  639.  
  640.  
  641. // Save single list for the current user
  642. this.saveList = function(ctx, list, name) {
  643. var jsonData;
  644. var listNames = loadListOfLists(ctx);
  645.  
  646. if (listNames.indexOf(name) == -1) {
  647. listNames.push(name);
  648. jsonData = JSON.stringify(listNames);
  649. GM_setValue(storName.listOfLists(ctx), jsonData);
  650. }
  651.  
  652. jsonData = JSON.stringify(list);
  653. GM_setValue(storName.listName(ctx, name), jsonData);
  654. };
  655.  
  656.  
  657. // Delete a single list for the current user
  658. this.deleteList = function(ctx, name) {
  659. var jsonData;
  660. var listNames = loadListOfLists(ctx);
  661.  
  662. var i = listNames.indexOf(name);
  663. if (i != -1) {
  664. listNames.splice(i, 1);
  665. jsonData = JSON.stringify(listNames);
  666. GM_setValue(storName.listOfLists(ctx), jsonData);
  667. }
  668.  
  669. GM_deleteValue(storName.listName(ctx, name));
  670. };
  671.  
  672.  
  673. // Delete all lists for the current user
  674. this.deleteAllLists = function(ctx) {
  675. var listNames = loadListOfLists(ctx);
  676. GM_deleteValue(storName.listOfLists(ctx));
  677.  
  678. listNames.forEach(function(listName) {
  679. GM_deleteValue(storName.listName(ctx, listName));
  680. });
  681. };
  682.  
  683.  
  684. })();