Wanikani Open Framework

Framework for writing scripts for Wanikani

目前为 2024-11-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Wanikani Open Framework
  3. // @namespace rfindley
  4. // @description Framework for writing scripts for Wanikani
  5. // @version 1.2.9
  6. // @match https://www.wanikani.com/*
  7. // @match https://preview.wanikani.com/*
  8. // @copyright 2018-2024, Robin Findley
  9. // @license MIT; http://opensource.org/licenses/MIT
  10. // @run-at document-start
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function(global) {
  15. 'use strict';
  16.  
  17. /* eslint no-multi-spaces: off */
  18. /* globals wkof */
  19.  
  20. const version = '1.2.9';
  21. let ignore_missing_indexeddb = false;
  22.  
  23. //########################################################################
  24. //------------------------------
  25. // Supported Modules
  26. //------------------------------
  27. const supported_modules = {
  28. Apiv2: { url: 'https://update.greasyfork.org/scripts/38581/1402158/Wanikani%20Open%20Framework%20-%20Apiv2%20module.js'},
  29. ItemData: { url: 'https://update.greasyfork.org/scripts/38580/1187212/Wanikani%20Open%20Framework%20-%20ItemData%20module.js'},
  30. Jquery: { url: 'https://update.greasyfork.org/scripts/451078/1091794/Wanikani%20Open%20Framework%20-%20Jquery%20module.js'},
  31. Menu: { url: 'https://update.greasyfork.org/scripts/38578/1489081/Wanikani%20Open%20Framework%20-%20Menu%20module.js'},
  32. Progress: { url: 'https://update.greasyfork.org/scripts/38577/1091792/Wanikani%20Open%20Framework%20-%20Progress%20module.js'},
  33. Settings: { url: 'https://update.greasyfork.org/scripts/38576/1091793/Wanikani%20Open%20Framework%20-%20Settings%20module.js'},
  34. };
  35.  
  36. //########################################################################
  37. //------------------------------
  38. // Published interface
  39. //------------------------------
  40. const published_interface = {
  41. on_pageload: on_pageload, // on_pageload(urls, load_handler [, unload_handler])
  42.  
  43. include: include, // include(module_list) => Promise
  44. ready: ready, // ready(module_list) => Promise
  45.  
  46. load_file: load_file, // load_file(url, use_cache) => Promise
  47. load_css: load_css, // load_css(url, use_cache) => Promise
  48. load_script: load_script, // load_script(url, use_cache) => Promise
  49.  
  50. file_cache: {
  51. dir: {}, // Object containing directory of files.
  52. ls: file_cache_list, // ls()
  53. clear: file_cache_clear, // clear() => Promise
  54. delete: file_cache_delete, // delete(name) => Promise
  55. flush: file_cache_flush, // flush() => Promise
  56. load: file_cache_load, // load(name) => Promise
  57. save: file_cache_save, // save(name, content) => Promise
  58. no_cache:file_nocache, // no_cache(modules)
  59. },
  60.  
  61. on: wait_event, // on(event, callback)
  62. trigger: trigger_event, // trigger(event[, data1[, data2[, ...]]])
  63.  
  64. get_state: get_state, // get(state_var)
  65. set_state: set_state, // set(state_var, value)
  66. wait_state: wait_state, // wait(state_var, value[, callback[, persistent]]) => if no callback, return one-shot Promise
  67.  
  68. version: {
  69. value: version,
  70. compare_to: compare_to, // compare_version(version)
  71. }
  72. };
  73.  
  74. published_interface.support_files = {
  75. 'jquery.js': 'https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js',
  76. 'jquery_ui.js': 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js',
  77. 'jqui_wkmain.css': 'https://raw.githubusercontent.com/rfindley/wanikani-open-framework/1550af8383ec28ad406cf401aee2de4c52446f6c/jqui-wkmain.css',
  78. };
  79.  
  80. //########################################################################
  81.  
  82. function split_list(str) {return str.replace(/、/g,',').replace(/[\s ]+/g,' ').trim().replace(/ *, */g, ',').split(',').filter(function(name) {return (name.length > 0);});}
  83. function promise(){let a,b,c=new Promise(function(d,e){a=d;b=e;});c.resolve=a;c.reject=b;return c;}
  84. function is_turbo_page() {return (document.querySelector('script[type="importmap"]')?.innerHTML.match('@hotwired/turbo') != null);}
  85.  
  86. //########################################################################
  87.  
  88. //------------------------------
  89. // Compare the framework version against a specific version.
  90. //------------------------------
  91. function compare_to(client_version) {
  92. let client_ver = client_version.split('.').map(d => Number(d));
  93. let wkof_ver = version.split('.').map(d => Number(d));
  94. let len = Math.max(client_ver.length, wkof_ver.length);
  95. for (let idx = 0; idx < len; idx++) {
  96. let a = client_ver[idx] || 0;
  97. let b = wkof_ver[idx] || 0;
  98. if (a === b) continue;
  99. if (a < b) return 'newer';
  100. return 'older';
  101. }
  102. return 'same';
  103. }
  104.  
  105. //------------------------------
  106. // Include a list of modules.
  107. //------------------------------
  108. let include_promises = {};
  109.  
  110. function include(module_list) {
  111. if (wkof.get_state('wkof.wkof') !== 'ready') {
  112. return wkof.ready('wkof').then(function(){return wkof.include(module_list);});
  113. }
  114. let include_promise = promise();
  115. let module_names = split_list(module_list);
  116. let script_cnt = module_names.length;
  117. if (script_cnt === 0) {
  118. include_promise.resolve({loaded:[], failed:[]});
  119. return include_promise;
  120. }
  121.  
  122. let done_cnt = 0;
  123. let loaded = [], failed = [];
  124. let no_cache = split_list(localStorage.getItem('wkof.include.nocache') || '');
  125. for (let idx = 0; idx < module_names.length; idx++) {
  126. let module_name = module_names[idx];
  127. let module = supported_modules[module_name];
  128. if (!module) {
  129. failed.push({name:module_name, url:undefined});
  130. check_done();
  131. continue;
  132. }
  133. let await_load = include_promises[module_name];
  134. let use_cache = (no_cache.indexOf(module_name) < 0) && (no_cache.indexOf('*') < 0);
  135. if (!use_cache) file_cache_delete(module.url);
  136. if (await_load === undefined) include_promises[module_name] = await_load = load_script(module.url, use_cache);
  137. await_load.then(push_loaded, push_failed);
  138. }
  139.  
  140. return include_promise;
  141.  
  142. function push_loaded(url) {
  143. loaded.push(url);
  144. check_done();
  145. }
  146.  
  147. function push_failed(url) {
  148. failed.push(url);
  149. check_done();
  150. }
  151.  
  152. function check_done() {
  153. if (++done_cnt < script_cnt) return;
  154. if (failed.length === 0) include_promise.resolve({loaded:loaded, failed:failed});
  155. else include_promise.reject({error:'Failure loading module', loaded:loaded, failed:failed});
  156. }
  157. }
  158.  
  159. //------------------------------
  160. // Wait for all modules to report that they are ready
  161. //------------------------------
  162. function ready(module_list) {
  163. let module_names = split_list(module_list);
  164.  
  165. let ready_promises = [ ];
  166. for (let idx in module_names) {
  167. let module_name = module_names[idx];
  168. ready_promises.push(wait_state('wkof.' + module_name, 'ready'));
  169. }
  170.  
  171. if (ready_promises.length === 0) {
  172. return Promise.resolve();
  173. } else if (ready_promises.length === 1) {
  174. return ready_promises[0];
  175. } else {
  176. return Promise.all(ready_promises);
  177. }
  178. }
  179. //########################################################################
  180.  
  181. //------------------------------
  182. // Load a file asynchronously, and pass the file as resolved Promise data.
  183. //------------------------------
  184. function load_file(url, use_cache) {
  185. let fetch_promise = promise();
  186. let no_cache = split_list(localStorage.getItem('wkof.load_file.nocache') || '');
  187. if (no_cache.indexOf(url) >= 0 || no_cache.indexOf('*') >= 0) use_cache = false;
  188. if (use_cache === true) {
  189. return file_cache_load(url, use_cache).catch(fetch_url);
  190. } else {
  191. return fetch_url();
  192. }
  193.  
  194. // Retrieve file from server
  195. function fetch_url(){
  196. let request = new XMLHttpRequest();
  197. request.onreadystatechange = process_result;
  198. request.open('GET', url, true);
  199. request.send();
  200. return fetch_promise;
  201. }
  202.  
  203. function process_result(event){
  204. if (event.target.readyState !== 4) return;
  205. if (event.target.status >= 400 || event.target.status === 0) return fetch_promise.reject(event.target.status);
  206. if (use_cache) {
  207. file_cache_save(url, event.target.response)
  208. .then(fetch_promise.resolve.bind(null,event.target.response));
  209. } else {
  210. fetch_promise.resolve(event.target.response);
  211. }
  212. }
  213. }
  214.  
  215. //------------------------------
  216. // Load and install a specific file type into the DOM.
  217. //------------------------------
  218. function load_and_append(url, tag_name, location, use_cache) {
  219. url = url.replace(/"/g,'\'');
  220. if (document.querySelector(tag_name+'[uid="'+url+'"]') !== null) return Promise.resolve();
  221. return load_file(url, use_cache).then(append_to_tag);
  222.  
  223. function append_to_tag(content) {
  224. let tag = document.createElement(tag_name);
  225. tag.innerHTML = content;
  226. tag.setAttribute('uid', url);
  227. document.querySelector(location).appendChild(tag);
  228. return url;
  229. }
  230. }
  231.  
  232. //------------------------------
  233. // Load and install a CSS file.
  234. //------------------------------
  235. function load_css(url, use_cache) {
  236. return load_and_append(url, 'style', 'head', use_cache);
  237. }
  238.  
  239. //------------------------------
  240. // Load and install Javascript.
  241. //------------------------------
  242. function load_script(url, use_cache) {
  243. return load_and_append(url, 'script', 'head', use_cache);
  244. }
  245. //########################################################################
  246.  
  247. let state_listeners = {};
  248. let state_values = {};
  249.  
  250. //------------------------------
  251. // Get the value of a state variable, and notify listeners.
  252. //------------------------------
  253. function get_state(state_var) {
  254. return state_values[state_var];
  255. }
  256.  
  257. //------------------------------
  258. // Set the value of a state variable, and notify listeners.
  259. //------------------------------
  260. function set_state(state_var, value) {
  261. let old_value = state_values[state_var];
  262. if (old_value === value) return;
  263. state_values[state_var] = value;
  264.  
  265. // Do listener callbacks, and remove non-persistent listeners
  266. let listeners = state_listeners[state_var];
  267. let persistent_listeners = [ ];
  268. for (let idx in listeners) {
  269. let listener = listeners[idx];
  270. let keep = true;
  271. if (listener.value === value || listener.value === '*') {
  272. keep = listener.persistent;
  273. try {
  274. listener.callback(value, old_value);
  275. } catch (e) {}
  276. }
  277. if (keep) persistent_listeners.push(listener);
  278. }
  279. state_listeners[state_var] = persistent_listeners;
  280. }
  281.  
  282. //------------------------------
  283. // When state of state_var changes to value, call callback.
  284. // If persistent === true, continue listening for additional state changes
  285. // If value is '*', callback will be called for all state changes.
  286. //------------------------------
  287. function wait_state(state_var, value, callback, persistent) {
  288. let promise;
  289. if (callback === undefined) {
  290. promise = new Promise(function(resolve, reject) {
  291. callback = resolve;
  292. });
  293. }
  294. if (state_listeners[state_var] === undefined) state_listeners[state_var] = [ ];
  295. persistent = (persistent === true);
  296. let current_value = state_values[state_var];
  297. if (persistent || value !== current_value) state_listeners[state_var].push({callback:callback, persistent:persistent, value:value});
  298.  
  299. // If it's already at the desired state, call the callback immediately.
  300. if (value === current_value) {
  301. try {
  302. callback(value, current_value);
  303. } catch (err) {}
  304. }
  305. return promise;
  306. }
  307. //########################################################################
  308.  
  309. let event_listeners = {};
  310.  
  311. //------------------------------
  312. // Fire an event, which then calls callbacks for any listeners.
  313. //------------------------------
  314. function trigger_event(event) {
  315. let listeners = event_listeners[event];
  316. if (listeners === undefined) return;
  317. let args = [];
  318. Array.prototype.push.apply(args,arguments);
  319. args.shift();
  320. for (let idx in listeners) { try {
  321. listeners[idx].apply(null,args);
  322. } catch (err) {} }
  323. return global.wkof;
  324. }
  325.  
  326. //------------------------------
  327. // Add a listener for an event.
  328. //------------------------------
  329. function wait_event(event, callback) {
  330. if (event_listeners[event] === undefined) event_listeners[event] = [];
  331. event_listeners[event].push(callback);
  332. return global.wkof;
  333. }
  334.  
  335. //------------------------------
  336. // Add handlers for page load events for a list of URLs.
  337. //------------------------------
  338. let regex_store = {};
  339. let pgld_req_store = [];
  340. let current_page = '!';
  341. function on_pageload(url_patterns, load_handler, unload_handler) {
  342. if (!Array.isArray(url_patterns)) url_patterns = [url_patterns];
  343.  
  344. let pgld_req = {url_patterns, load_handler, unload_handler, pattern_idx:-1, next_pattern_idx:-1};
  345. pgld_req_store.push(pgld_req);
  346.  
  347. url_patterns.forEach((pattern, pattern_idx) => {
  348. let regex;
  349. if (typeof pattern === 'string') {
  350. regex = new RegExp('^'+pattern.replace(/[.+?^${}()|[\]\\]/g,'\\$&').replaceAll('*','.*')+'$');
  351. } else if (pattern instanceof RegExp) {
  352. regex = pattern;
  353. } else {
  354. pgld_req.regexes.push(null);
  355. return;
  356. }
  357.  
  358. let regex_str = regex.toString();
  359. let regex_entry;
  360. if (!regex_store[regex_str]) {
  361. regex_store[regex_str] = regex_entry = {regex, pgld_reqs:[]};
  362. } else {
  363. regex_entry = regex_store[regex_str];
  364. regex = regex_entry.regex;
  365. }
  366. regex_entry.pgld_reqs.push({pgld_req, pattern_idx});
  367.  
  368. // Call 'load' callback now if the current URL matches.
  369. if (pgld_req.pattern_idx !== -1) return;
  370. if (regex.test(current_page)) {
  371. pgld_req.pattern_idx = pattern_idx;
  372. try {
  373. load_handler(current_page, pattern_idx);
  374. } catch(e) {}
  375. }
  376. });
  377. }
  378.  
  379. //------------------------------
  380. // Call pageload handlers.
  381. //------------------------------
  382. function handle_pageload(event) {
  383. let last_page = current_page;
  384. if (event) {
  385. current_page = (new URL(event.detail.url)).pathname;
  386. } else {
  387. current_page = window.location.pathname;
  388. }
  389.  
  390. // Update the active status of all monitored URL patterns.
  391. Object.keys(regex_store).forEach((key, key_idx) => {
  392. let regex_entry = regex_store[key];
  393. let is_active = regex_entry.regex.test(current_page);
  394. regex_entry.pgld_reqs.forEach((regex_pgld_entry, regex_pgld_entry_idx) => {
  395. if (regex_pgld_entry.pgld_req.next_pattern_idx === -1 && is_active) {
  396. regex_pgld_entry.pgld_req.next_pattern_idx = regex_pgld_entry.pattern_idx;
  397. }
  398. });
  399. });
  400.  
  401. // Call all 'unload' handlers.
  402. pgld_req_store.forEach(pgld_req => {
  403. // If page was active, but not anymore, call the unload handler
  404. if ((pgld_req.pattern_idx !== -1) && (pgld_req.next_pattern_idx === -1)) {
  405. try {
  406. pgld_req.unload_handler(last_page, pgld_req.pattern_idx);
  407. } catch(e) {}
  408. }
  409. pgld_req.pattern_idx = pgld_req.next_pattern_idx;
  410. pgld_req.next_pattern_idx = -1;
  411. });
  412.  
  413. // Call all 'load' handlers.
  414. pgld_req_store.forEach(pgld_req => {
  415. if (pgld_req.pattern_idx !== -1) {
  416. try {
  417. pgld_req.load_handler(current_page, pgld_req.pattern_idx);
  418. } catch(e) {}
  419. }
  420. });
  421. }
  422.  
  423. let first_pageload = true;
  424. let skip_next_turbo_load = false;
  425. function delayed_pageload(event) {
  426. if (!event) { // If 'doc ready'
  427. if (!first_pageload) return; // Shouldn't happen, but just in case...
  428. skip_next_turbo_load = true;
  429. } else { // If 'turbo:load'
  430. if (skip_next_turbo_load) {
  431. skip_next_turbo_load = false;
  432. return;
  433. }
  434. }
  435. first_pageload = false;
  436. setTimeout(handle_pageload.bind(null, event), 10);
  437. }
  438.  
  439. //########################################################################
  440.  
  441. let file_cache_open_promise;
  442.  
  443. //------------------------------
  444. // Open the file_cache database (or return handle if open).
  445. //------------------------------
  446. function file_cache_open() {
  447. if (file_cache_open_promise) return file_cache_open_promise;
  448. let open_promise = promise();
  449. file_cache_open_promise = open_promise;
  450. let request;
  451. request = indexedDB.open('wkof.file_cache');
  452. request.onupgradeneeded = upgrade_db;
  453. request.onsuccess = get_dir;
  454. request.onerror = error;
  455. return open_promise;
  456.  
  457. function error() {
  458. console.log('indexedDB could not open!');
  459. wkof.file_cache.dir = {};
  460. if (ignore_missing_indexeddb) {
  461. open_promise.resolve(null);
  462. } else {
  463. open_promise.reject();
  464. }
  465. }
  466.  
  467. function upgrade_db(event){
  468. let db = event.target.result;
  469. let store = db.createObjectStore('files', {keyPath:'name'});
  470. }
  471.  
  472. function get_dir(event){
  473. let db = event.target.result;
  474. let transaction = db.transaction('files', 'readonly');
  475. let store = transaction.objectStore('files');
  476. let request = store.get('[dir]');
  477. request.onsuccess = process_dir;
  478. transaction.oncomplete = open_promise.resolve.bind(null, db);
  479. open_promise.then(setTimeout.bind(null, file_cache_cleanup, 10000));
  480. }
  481.  
  482. function process_dir(event){
  483. if (event.target.result === undefined) {
  484. wkof.file_cache.dir = {};
  485. } else {
  486. wkof.file_cache.dir = JSON.parse(event.target.result.content);
  487. }
  488. }
  489. }
  490.  
  491. //------------------------------
  492. // Lists the content of the file_cache.
  493. //------------------------------
  494. function file_cache_list() {
  495. console.log(Object.keys(wkof.file_cache.dir).sort().join('\n'));
  496. }
  497.  
  498. //------------------------------
  499. // Clear the file_cache database.
  500. //------------------------------
  501. function file_cache_clear() {
  502. return file_cache_open().then(clear);
  503.  
  504. function clear(db) {
  505. let clear_promise = promise();
  506. wkof.file_cache.dir = {};
  507. if (db === null) return clear_promise.resolve();
  508. let transaction = db.transaction('files', 'readwrite');
  509. let store = transaction.objectStore('files');
  510. store.clear();
  511. transaction.oncomplete = clear_promise.resolve;
  512. }
  513. }
  514.  
  515. //------------------------------
  516. // Delete a file from the file_cache database.
  517. //------------------------------
  518. function file_cache_delete(pattern) {
  519. return file_cache_open().then(del);
  520.  
  521. function del(db) {
  522. let del_promise = promise();
  523. if (db === null) return del_promise.resolve();
  524. let transaction = db.transaction('files', 'readwrite');
  525. let store = transaction.objectStore('files');
  526. let files = Object.keys(wkof.file_cache.dir).filter(function(file){
  527. if (pattern instanceof RegExp) {
  528. return file.match(pattern) !== null;
  529. } else {
  530. return (file === pattern);
  531. }
  532. });
  533. files.forEach(function(file){
  534. store.delete(file);
  535. delete wkof.file_cache.dir[file];
  536. });
  537. file_cache_dir_save();
  538. transaction.oncomplete = del_promise.resolve.bind(null, files);
  539. return del_promise;
  540. }
  541. }
  542.  
  543. //------------------------------
  544. // Force immediate save of file_cache directory.
  545. //------------------------------
  546. function file_cache_flush() {
  547. file_cache_dir_save(true /* immediately */);
  548. }
  549.  
  550. //------------------------------
  551. // Load a file from the file_cache database.
  552. //------------------------------
  553. function file_cache_load(name) {
  554. let load_promise = promise();
  555. return file_cache_open().then(load);
  556.  
  557. function load(db) {
  558. if (wkof.file_cache.dir[name] === undefined) {
  559. load_promise.reject(name);
  560. return load_promise;
  561. }
  562. let transaction = db.transaction('files', 'readonly');
  563. let store = transaction.objectStore('files');
  564. let request = store.get(name);
  565. wkof.file_cache.dir[name].last_loaded = new Date().toISOString();
  566. file_cache_dir_save();
  567. request.onsuccess = finish;
  568. request.onerror = error;
  569. return load_promise;
  570.  
  571. function finish(event){
  572. if (event.target.result === undefined || event.target.result === null) {
  573. load_promise.reject(name);
  574. } else {
  575. load_promise.resolve(event.target.result.content);
  576. }
  577. }
  578.  
  579. function error(event){
  580. load_promise.reject(name);
  581. }
  582. }
  583. }
  584.  
  585. //------------------------------
  586. // Save a file into the file_cache database.
  587. //------------------------------
  588. function file_cache_save(name, content, extra_attribs) {
  589. return file_cache_open().then(save);
  590.  
  591. function save(db) {
  592. let save_promise = promise();
  593. if (db === null) return save_promise.resolve(name);
  594. let transaction = db.transaction('files', 'readwrite');
  595. let store = transaction.objectStore('files');
  596. store.put({name:name,content:content});
  597. let now = new Date().toISOString();
  598. wkof.file_cache.dir[name] = Object.assign({added:now, last_loaded:now}, extra_attribs);
  599. file_cache_dir_save(true /* immediately */);
  600. transaction.oncomplete = save_promise.resolve.bind(null, name);
  601. }
  602. }
  603.  
  604. //------------------------------
  605. // Save a the file_cache directory contents.
  606. //------------------------------
  607. let fc_sync_timer;
  608. function file_cache_dir_save(immediately) {
  609. if (fc_sync_timer !== undefined) clearTimeout(fc_sync_timer);
  610. let delay = (immediately ? 0 : 2000);
  611. fc_sync_timer = setTimeout(save, delay);
  612.  
  613. function save(){
  614. file_cache_open().then(save2);
  615. }
  616.  
  617. function save2(db){
  618. fc_sync_timer = undefined;
  619. let transaction = db.transaction('files', 'readwrite');
  620. let store = transaction.objectStore('files');
  621. store.put({name:'[dir]',content:JSON.stringify(wkof.file_cache.dir)});
  622. }
  623. }
  624.  
  625. //------------------------------
  626. // Remove files that haven't been accessed in a while.
  627. //------------------------------
  628. function file_cache_cleanup() {
  629. let threshold = new Date() - 14*86400000; // 14 days
  630. let old_files = [];
  631. for (var fname in wkof.file_cache.dir) {
  632. if (fname.match(/^wkof\.settings\./)) continue; // Don't flush settings files.
  633. let fdate = new Date(wkof.file_cache.dir[fname].last_loaded);
  634. if (fdate < threshold) old_files.push(fname);
  635. }
  636. if (old_files.length === 0) return;
  637. console.log('Cleaning out '+old_files.length+' old file(s) from "wkof.file_cache":');
  638. for (let fnum in old_files) {
  639. console.log(' '+(Number(fnum)+1)+': '+old_files[fnum]);
  640. wkof.file_cache.delete(old_files[fnum]);
  641. }
  642. }
  643.  
  644. //------------------------------
  645. // Process no-cache requests.
  646. //------------------------------
  647. function file_nocache(list) {
  648. if (list === undefined) {
  649. list = split_list(localStorage.getItem('wkof.include.nocache') || '');
  650. list = list.concat(split_list(localStorage.getItem('wkof.load_file.nocache') || ''));
  651. console.log(list.join(','));
  652. } else if (typeof list === 'string') {
  653. let no_cache = split_list(list);
  654. let idx, modules = [], urls = [];
  655. for (idx = 0; idx < no_cache.length; idx++) {
  656. let item = no_cache[idx];
  657. if (supported_modules[item] !== undefined) {
  658. modules.push(item);
  659. } else {
  660. urls.push(item);
  661. }
  662. }
  663. console.log('Modules: '+modules.join(','));
  664. console.log(' URLs: '+urls.join(','));
  665. localStorage.setItem('wkof.include.nocache', modules.join(','));
  666. localStorage.setItem('wkof.load_file.nocache', urls.join(','));
  667. }
  668. }
  669.  
  670. function doc_ready() {
  671. wkof.set_state('wkof.document', 'ready');
  672. }
  673.  
  674. //########################################################################
  675. // Bootloader Startup
  676. //------------------------------
  677. function startup() {
  678. global.wkof = published_interface;
  679.  
  680. // Handle page-loading/unloading events.
  681. function install_load_listener() {
  682. if (!document.documentElement) {
  683. setTimeout(install_load_listener, 10);
  684. return;
  685. }
  686. document.documentElement.addEventListener('turbo:load', delayed_pageload);
  687. }
  688. install_load_listener();
  689.  
  690. ready('document').then((e) => {
  691. if (first_pageload) delayed_pageload();
  692. });
  693.  
  694. // Mark document state as 'ready'.
  695. if (document.readyState === 'complete') {
  696. doc_ready();
  697. } else {
  698. window.addEventListener("load", doc_ready, false); // Notify listeners that we are ready.
  699. }
  700.  
  701. // Open cache, so wkof.file_cache.dir is available to console immediately.
  702. file_cache_open();
  703. wkof.set_state('wkof.wkof', 'ready');
  704. }
  705. startup();
  706.  
  707. })(window);