Entry List

Library for managing pages with many similar items (youtube, twitter, ...)

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

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