KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

当前为 2022-10-08 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/451521/1102250/KameSame%20Open%20Framework%20-%20Settings%20module.js

  1. "use strict";
  2. // ==UserScript==
  3. // @name KameSame Open Framework - Settings module
  4. // @namespace timberpile
  5. // @description Settings module for KameSame Open Framework
  6. // @version 0.1
  7. // @copyright 2022+, Robin Findley, Timberpile
  8. // @license MIT; http://opensource.org/licenses/MIT
  9. // ==/UserScript==
  10. // These lines are necessary to make sure that TSC does not put any exports in the
  11. // compiled js, which causes the script to crash
  12. // eslint-disable-next-line no-var, @typescript-eslint/no-unused-vars
  13. var module = {};
  14. (async (global) => {
  15. const ksof = global.ksof;
  16. const background_funcs = {
  17. open: () => {
  18. const anchor = install_anchor();
  19. let bkgd = anchor.find('> #ksofs_bkgd');
  20. if (bkgd.length === 0) {
  21. bkgd = $('<div id="ksofs_bkgd" refcnt="0"></div>');
  22. anchor.prepend(bkgd);
  23. }
  24. const refcnt = Number(bkgd.attr('refcnt'));
  25. bkgd.attr('refcnt', refcnt + 1);
  26. },
  27. close: () => {
  28. const bkgd = $('#ksof_ds > #ksofs_bkgd');
  29. if (bkgd.length === 0)
  30. return;
  31. const refcnt = Number(bkgd.attr('refcnt'));
  32. if (refcnt <= 0)
  33. return;
  34. bkgd.attr('refcnt', refcnt - 1);
  35. }
  36. };
  37. //########################################################################
  38. //------------------------------
  39. // Constructor
  40. //------------------------------
  41. class KSOFSettings {
  42. constructor(config) {
  43. this.cfg = config;
  44. this.config_list = {};
  45. this.open_dialog = $();
  46. this.background = background_funcs;
  47. }
  48. //------------------------------
  49. // Open the settings dialog.
  50. //------------------------------
  51. static save(context) {
  52. if (!ksof.settings)
  53. throw new Error('ksof.settings not defined');
  54. if (!ksof.Settings)
  55. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  56. const script_id = ((typeof context === 'string') ? context : context.cfg.script_id);
  57. const settings = ksof.settings[script_id];
  58. if (!settings)
  59. return Promise.resolve('');
  60. return ksof.file_cache.save('ksof.settings.' + script_id, settings);
  61. }
  62. save() {
  63. return KSOFSettings.save(this);
  64. }
  65. //------------------------------
  66. // Open the settings dialog.
  67. //------------------------------
  68. static async load(context, defaults) {
  69. const script_id = ((typeof context === 'string') ? context : context.cfg.script_id);
  70. try {
  71. const settings = await ksof.file_cache.load('ksof.settings.' + script_id);
  72. return finish(settings);
  73. }
  74. catch (error) {
  75. return finish.call(null, {});
  76. }
  77. function finish(settings) {
  78. if (!ksof.settings)
  79. throw new Error('ksof.settings not defined');
  80. if (!ksof.Settings)
  81. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  82. if (defaults)
  83. ksof.settings[script_id] = deep_merge(defaults, settings);
  84. else
  85. ksof.settings[script_id] = settings;
  86. return ksof.settings[script_id];
  87. }
  88. }
  89. load(defaults) {
  90. return KSOFSettings.load(this, defaults);
  91. }
  92. //------------------------------
  93. // Save button handler.
  94. //------------------------------
  95. save_btn() {
  96. if (!ksof.settings)
  97. throw new Error('ksof.settings not defined');
  98. if (!ksof.Settings)
  99. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  100. const script_id = this.cfg.script_id;
  101. const settings = ksof.settings[script_id];
  102. if (settings) {
  103. const active_tabs = this.open_dialog.find('.ui-tabs-active').toArray().map(function (tab) { return '#' + tab.attributes.getNamedItem('id')?.value || ''; });
  104. if (active_tabs.length > 0)
  105. settings.ksofs_active_tabs = active_tabs;
  106. }
  107. if (this.cfg.autosave === undefined || this.cfg.autosave === true) {
  108. this.save();
  109. }
  110. if (this.cfg.on_save) {
  111. this.cfg.on_save(ksof.settings[this.cfg.script_id]);
  112. }
  113. ksof.trigger('ksof.settings.save');
  114. this.keep_settings = true;
  115. this.open_dialog.dialog('close');
  116. }
  117. //------------------------------
  118. // Cancel button handler.
  119. //------------------------------
  120. cancel() {
  121. if (!ksof.settings)
  122. throw new Error('ksof.settings not defined');
  123. if (!ksof.Settings)
  124. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  125. this.open_dialog.dialog('close');
  126. if (typeof this.cfg.on_cancel === 'function')
  127. this.cfg.on_cancel(ksof.settings[this.cfg.script_id]);
  128. }
  129. //------------------------------
  130. // Open the settings dialog.
  131. //------------------------------
  132. open() {
  133. if (!ksof.settings)
  134. throw new Error('ksof.settings not defined');
  135. if (!ksof.Settings)
  136. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  137. if (!ready)
  138. return;
  139. if (this.open_dialog.length > 0)
  140. return;
  141. install_anchor();
  142. if (this.cfg.background !== false)
  143. this.background.open();
  144. this.open_dialog = $('<div id="ksofs_' + this.cfg.script_id + '" class="ksof_settings" style="display:none;"></div>');
  145. this.open_dialog.html(config_to_html(this));
  146. const resize = (event, ui) => {
  147. const is_narrow = this.open_dialog.hasClass('narrow');
  148. ui;
  149. if (is_narrow && ui.size.width >= 510) {
  150. this.open_dialog.removeClass('narrow');
  151. }
  152. else if (!is_narrow && ui.size.width < 490) {
  153. this.open_dialog.addClass('narrow');
  154. }
  155. };
  156. const tab_activated = () => {
  157. const wrapper = $(this.open_dialog.dialog('widget'));
  158. if ((wrapper.outerHeight() || 0) + wrapper.position().top > document.body.clientHeight) {
  159. this.open_dialog.dialog('option', 'maxHeight', document.body.clientHeight);
  160. }
  161. };
  162. let width = 500;
  163. if (window.innerWidth < 510) {
  164. width = 280;
  165. this.open_dialog.addClass('narrow');
  166. }
  167. this.open_dialog.dialog({
  168. title: this.cfg.title,
  169. buttons: [
  170. { text: 'Save', click: this.save_btn.bind(this) },
  171. { text: 'Cancel', click: this.cancel.bind(this) }
  172. ],
  173. width: width,
  174. maxHeight: document.body.clientHeight,
  175. modal: false,
  176. autoOpen: false,
  177. appendTo: '#ksof_ds',
  178. resize: resize.bind(this),
  179. close: () => { this.close(false); }
  180. });
  181. $(this.open_dialog.dialog('widget')).css('position', 'fixed');
  182. this.open_dialog.parent().addClass('ksof_settings_dialog');
  183. $('.ksof_stabs').tabs({ activate: tab_activated.bind(null) });
  184. const settings = ksof.settings[this.cfg.script_id];
  185. if (settings && settings.ksofs_active_tabs instanceof Array) {
  186. const active_tabs = settings.ksofs_active_tabs;
  187. for (let tab_idx = 0; tab_idx < active_tabs.length; tab_idx++) {
  188. const tab = $(active_tabs[tab_idx]);
  189. tab.closest('.ui-tabs').tabs({ active: tab.index() });
  190. }
  191. }
  192. const toggle_multi = (e) => {
  193. if (e.button != 0)
  194. return true;
  195. const multi = $(e.currentTarget);
  196. const scroll = e.currentTarget.scrollTop;
  197. e.target.selected = !e.target.selected;
  198. setTimeout(function () {
  199. e.currentTarget.scrollTop = scroll;
  200. multi.focus(); // TODO what should this do? it's deprecated
  201. }, 0);
  202. return this.setting_changed(e);
  203. };
  204. const setting_button_clicked = (e) => {
  205. const name = e.target.attributes.name.value;
  206. const _item = this.config_list[name];
  207. if (_item.type == 'button') {
  208. const item = _item;
  209. item.on_click.call(e, name, item, this.setting_changed.bind(this, e));
  210. }
  211. };
  212. this.open_dialog.dialog('open');
  213. this.open_dialog.find('.setting[multiple]').on('mousedown', toggle_multi.bind(this));
  214. this.open_dialog.find('.setting').on('change', this.setting_changed.bind(this));
  215. this.open_dialog.find('form').on('submit', function () { return false; });
  216. this.open_dialog.find('button.setting').on('click', setting_button_clicked.bind(this));
  217. if (typeof this.cfg.pre_open === 'function')
  218. this.cfg.pre_open(this.open_dialog);
  219. this.reversions = deep_merge({}, ksof.settings[this.cfg.script_id]);
  220. this.refresh();
  221. }
  222. //------------------------------
  223. // Handler for live settings changes. Handles built-in validation and user callbacks.
  224. //------------------------------
  225. setting_changed(event) {
  226. if (!ksof.settings)
  227. throw new Error('ksof.settings not defined');
  228. if (!ksof.Settings)
  229. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  230. const elem = $(event.currentTarget);
  231. const name = elem.attr('name');
  232. if (!name)
  233. return false;
  234. const _item = this.config_list[name];
  235. // Extract the value
  236. let value;
  237. if (_item.type == 'dropdown') {
  238. value = elem.find(':checked').attr('name');
  239. }
  240. else if (_item.type == 'list') {
  241. const item = _item;
  242. if (item.multi === true) {
  243. value = {};
  244. elem.find('option').each(function (i, e) {
  245. const opt_name = e.getAttribute('name') || '#' + e.index;
  246. value[opt_name] = e.selected;
  247. });
  248. }
  249. else {
  250. value = elem.find(':checked').attr('name');
  251. }
  252. }
  253. else if (_item.type == 'input') {
  254. const item = _item;
  255. if (item.subtype === 'number') {
  256. value = Number(elem.val());
  257. }
  258. }
  259. else if (_item.type == 'checkbox') {
  260. value = elem.is(':checked');
  261. }
  262. else if (_item.type == 'number') {
  263. value = Number(elem.val());
  264. }
  265. else {
  266. value = elem.val();
  267. }
  268. // Validation
  269. let valid = { valid: true, msg: '' };
  270. {
  271. const item = _item;
  272. if (item.validate) {
  273. const _valid = item.validate.call(event.target, value, item);
  274. if (typeof _valid === 'boolean')
  275. valid = { valid: _valid, msg: '' };
  276. else if (typeof _valid === 'string')
  277. valid = { valid: false, msg: _valid };
  278. }
  279. }
  280. if (_item.type == 'number') {
  281. const item = _item;
  282. if (item.min && Number(value) < item.min) {
  283. valid.valid = false;
  284. if (valid.msg.length === 0) {
  285. if (typeof item.max === 'number')
  286. valid.msg = 'Must be between ' + item.min + ' and ' + item.max;
  287. else
  288. valid.msg = 'Must be ' + item.min + ' or higher';
  289. }
  290. }
  291. else if (item.max && Number(value) > item.max) {
  292. valid.valid = false;
  293. if (valid.msg.length === 0) {
  294. if (typeof item.min === 'number')
  295. valid.msg = 'Must be between ' + item.min + ' and ' + item.max;
  296. else
  297. valid.msg = 'Must be ' + item.max + ' or lower';
  298. }
  299. }
  300. }
  301. else if (_item.type == 'text') {
  302. const item = _item;
  303. if (item.match !== undefined && value.match(item.match) === null) {
  304. valid.valid = false;
  305. if (valid.msg.length === 0)
  306. // valid.msg = item.error_msg || 'Invalid value'; // TODO no item has a error_msg?
  307. valid.msg = 'Invalid value';
  308. }
  309. }
  310. // Style for valid/invalid
  311. const parent = elem.closest('.right');
  312. parent.find('.note').remove();
  313. if (typeof valid.msg === 'string' && valid.msg.length > 0)
  314. parent.append('<div class="note' + (valid.valid ? '' : ' error') + '">' + valid.msg + '</div>');
  315. if (!valid.valid) {
  316. elem.addClass('invalid');
  317. }
  318. else {
  319. elem.removeClass('invalid');
  320. }
  321. const script_id = this.cfg.script_id;
  322. const settings = ksof.settings[script_id];
  323. if (valid.valid) {
  324. const item = _item;
  325. // if (item.no_save !== true) set_value(this, settings, name, value); // TODO what is no_save supposed to do?
  326. set_value(this, settings, name, value);
  327. if (item.on_change)
  328. item.on_change.call(event.target, name, value, item);
  329. if (this.cfg.on_change)
  330. this.cfg.on_change.call(event.target, name, value, item);
  331. if (item.refresh_on_change === true)
  332. this.refresh();
  333. }
  334. return false;
  335. }
  336. //------------------------------
  337. // Close and destroy the dialog.
  338. //------------------------------
  339. close(keep_settings) {
  340. if (!ksof.settings)
  341. throw new Error('ksof.settings not defined');
  342. if (!ksof.Settings)
  343. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  344. if (!this.keep_settings && keep_settings !== true) {
  345. // Revert settings
  346. ksof.settings[this.cfg.script_id] = deep_merge({}, this.reversions || {});
  347. delete this.reversions;
  348. }
  349. delete this.keep_settings;
  350. this.open_dialog.dialog('destroy');
  351. this.open_dialog = $();
  352. if (this.cfg.background !== false)
  353. this.background.close();
  354. if (typeof this.cfg.on_close === 'function')
  355. this.cfg.on_close(ksof.settings[this.cfg.script_id]);
  356. }
  357. //------------------------------
  358. // Update the dialog to reflect changed settings.
  359. //------------------------------
  360. refresh() {
  361. if (!ksof.settings)
  362. throw new Error('ksof.settings not defined');
  363. if (!ksof.Settings)
  364. throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
  365. const script_id = this.cfg.script_id;
  366. const settings = ksof.settings[script_id];
  367. for (const name in this.config_list) {
  368. const elem = this.open_dialog.find('#' + script_id + '_' + name);
  369. const _config = this.config_list[name];
  370. const value = get_value(this, settings, name);
  371. if (_config.type == 'dropdown') {
  372. elem.find('option[name="' + value + '"]').prop('selected', true);
  373. }
  374. else if (_config.type == 'list') {
  375. const config = _config;
  376. if (config.multi === true) {
  377. elem.find('option').each(function (i, e) {
  378. const opt_name = e.getAttribute('name') || '#' + e.index;
  379. e.selected = value[opt_name];
  380. });
  381. }
  382. else {
  383. elem.find('option[name="' + value + '"]').prop('selected', true);
  384. }
  385. }
  386. else if (_config.type == 'checkbox') {
  387. elem.prop('checked', value);
  388. }
  389. else {
  390. elem.val(value);
  391. }
  392. }
  393. if (typeof this.cfg.on_refresh === 'function')
  394. this.cfg.on_refresh(ksof.settings[this.cfg.script_id]);
  395. }
  396. }
  397. function createSettings() {
  398. const settings_obj = (config) => {
  399. return new KSOFSettings(config);
  400. };
  401. settings_obj.save = (context) => { return KSOFSettings.save(context); };
  402. settings_obj.load = (context, defaults) => { return KSOFSettings.load(context, defaults); };
  403. settings_obj.background = background_funcs;
  404. return settings_obj;
  405. }
  406. //########################################################################
  407. let ready = false;
  408. //========================================================================
  409. function deep_merge(...objects) {
  410. const merged = {};
  411. function recursive_merge(dest, src) {
  412. for (const prop in src) {
  413. if (typeof src[prop] === 'object' && src[prop] !== null) {
  414. const srcProp = src[prop];
  415. if (Array.isArray(srcProp)) {
  416. dest[prop] = srcProp.slice();
  417. }
  418. else {
  419. dest[prop] = dest[prop] || {};
  420. recursive_merge(dest[prop], srcProp);
  421. }
  422. }
  423. else {
  424. dest[prop] = src[prop];
  425. }
  426. }
  427. return dest;
  428. }
  429. for (const obj in objects) {
  430. recursive_merge(merged, objects[obj]);
  431. }
  432. return merged;
  433. }
  434. //------------------------------
  435. // Convert a config object to html dialog.
  436. //------------------------------
  437. /* eslint-disable no-case-declarations */
  438. function config_to_html(context) {
  439. context.config_list = {};
  440. if (!ksof.settings) {
  441. return '';
  442. }
  443. let base = ksof.settings[context.cfg.script_id];
  444. if (base === undefined)
  445. ksof.settings[context.cfg.script_id] = base = {};
  446. let html = '';
  447. const child_passback = {};
  448. const id = context.cfg.script_id + '_dialog';
  449. for (const name in context.cfg.content) {
  450. html += parse_item(name, context.cfg.content[name], child_passback);
  451. }
  452. if (child_passback.tabs && child_passback.pages)
  453. html = assemble_pages(id, child_passback.tabs, child_passback.pages) + html;
  454. return '<form>' + html + '</form>';
  455. //============
  456. function parse_item(name, _item, passback) {
  457. if (typeof _item.type !== 'string')
  458. return '';
  459. const id = context.cfg.script_id + '_' + name;
  460. let cname, html = '', child_passback, non_page = '';
  461. const _type = _item.type;
  462. if (_type == 'tabset') {
  463. const item = _item;
  464. child_passback = {};
  465. for (cname in item.content) {
  466. non_page += parse_item(cname, item.content[cname], child_passback);
  467. }
  468. if (child_passback.tabs && child_passback.pages) {
  469. html = assemble_pages(id, child_passback.tabs, child_passback.pages);
  470. }
  471. }
  472. else if (_type == 'page') {
  473. const item = _item;
  474. if (typeof item.content !== 'object')
  475. item.content = {};
  476. if (!passback.tabs) {
  477. passback.tabs = [];
  478. }
  479. if (!passback.pages) {
  480. passback.pages = [];
  481. }
  482. passback.tabs.push('<li id="' + id + '_tab"' + to_title(item.hover_tip) + '><a href="#' + id + '">' + item.label + '</a></li>');
  483. child_passback = {};
  484. for (cname in item.content)
  485. non_page += parse_item(cname, item.content[cname], child_passback);
  486. if (child_passback.tabs && child_passback.pages)
  487. html = assemble_pages(id, child_passback.tabs, child_passback.pages);
  488. passback.pages.push('<div id="' + id + '">' + html + non_page + '</div>');
  489. passback.is_page = true;
  490. html = '';
  491. }
  492. else if (_type == 'group') {
  493. const item = _item;
  494. if (typeof item.content !== 'object')
  495. item.content = {};
  496. child_passback = {};
  497. for (cname in item.content)
  498. non_page += parse_item(cname, item.content[cname], child_passback);
  499. if (child_passback.tabs && child_passback.pages)
  500. html = assemble_pages(id, child_passback.tabs, child_passback.pages);
  501. html = '<fieldset id="' + id + '" class="ksof_group"><legend>' + item.label + '</legend>' + html + non_page + '</fieldset>';
  502. }
  503. else if (_type == 'dropdown') {
  504. const item = _item;
  505. context.config_list[name] = item;
  506. let value = get_value(context, base, name);
  507. if (value === undefined) {
  508. if (item.default !== undefined) {
  509. value = item.default;
  510. }
  511. else {
  512. value = Object.keys(item.content)[0];
  513. }
  514. set_value(context, base, name, value);
  515. }
  516. html = `<select id="${id}" name="${name}" class="setting"${to_title(item.hover_tip)}>`;
  517. for (cname in item.content)
  518. html += '<option name="' + cname + '">' + escape_text(item.content[cname]) + '</option>';
  519. html += '</select>';
  520. html = make_label(item) + wrap_right(html);
  521. html = wrap_row(html, item.full_width, item.hover_tip);
  522. }
  523. else if (_type == 'list') {
  524. const item = _item;
  525. context.config_list[name] = item;
  526. let value = get_value(context, base, name);
  527. if (value === undefined) {
  528. if (item.default !== undefined) {
  529. value = item.default;
  530. }
  531. else {
  532. if (item.multi === true) {
  533. value = {};
  534. Object.keys(item.content).forEach(function (key) {
  535. value[key] = false;
  536. });
  537. }
  538. else {
  539. value = Object.keys(item.content)[0];
  540. }
  541. }
  542. set_value(context, base, name, value);
  543. }
  544. let attribs = ' size="' + (item.size || Object.keys(item.content).length || 4) + '"';
  545. if (item.multi === true)
  546. attribs += ' multiple';
  547. html = `<select id="${id}" name="${name}" class="setting list"${attribs}${to_title(item.hover_tip)}>`;
  548. for (cname in item.content)
  549. html += '<option name="' + cname + '">' + escape_text(item.content[cname]) + '</option>';
  550. html += '</select>';
  551. html = make_label(item) + wrap_right(html);
  552. html = wrap_row(html, item.full_width, item.hover_tip);
  553. }
  554. else if (_type == 'checkbox') {
  555. const item = _item;
  556. context.config_list[name] = item;
  557. html = make_label(item);
  558. let value = get_value(context, base, name);
  559. if (value === undefined) {
  560. value = (item.default || false);
  561. set_value(context, base, name, value);
  562. }
  563. html += wrap_right('<input id="' + id + '" class="setting" type="checkbox" name="' + name + '">');
  564. html = wrap_row(html, item.full_width, item.hover_tip);
  565. }
  566. else if (_type == 'input') {
  567. const item = _item;
  568. const itype = item.subtype || 'text';
  569. context.config_list[name] = item;
  570. html += make_label(item);
  571. let value = get_value(context, base, name);
  572. if (value === undefined) {
  573. const is_number = (item.subtype === 'number');
  574. value = (item.default || (is_number ? 0 : ''));
  575. set_value(context, base, name, value);
  576. }
  577. html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
  578. html = wrap_row(html, item.full_width, item.hover_tip);
  579. }
  580. else if (_type == 'number') {
  581. const item = _item;
  582. const itype = item.type;
  583. context.config_list[name] = item;
  584. html += make_label(item);
  585. let value = get_value(context, base, name);
  586. if (value === undefined) {
  587. const is_number = (item.type === 'number');
  588. value = (item.default || (is_number ? 0 : ''));
  589. set_value(context, base, name, value);
  590. }
  591. html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
  592. html = wrap_row(html, item.full_width, item.hover_tip);
  593. }
  594. else if (_type == 'text') {
  595. const item = _item;
  596. const itype = item.type;
  597. context.config_list[name] = item;
  598. html += make_label(item);
  599. let value = get_value(context, base, name);
  600. if (value === undefined) {
  601. value = (item.default || '');
  602. set_value(context, base, name, value);
  603. }
  604. html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
  605. html = wrap_row(html, item.full_width, item.hover_tip);
  606. }
  607. else if (_type == 'color') {
  608. const item = _item;
  609. context.config_list[name] = item;
  610. html += make_label(item);
  611. let value = get_value(context, base, name);
  612. if (value === undefined) {
  613. value = (item.default || '#000000');
  614. set_value(context, base, name, value);
  615. }
  616. html += wrap_right('<input id="' + id + '" class="setting" type="color" name="' + name + '">');
  617. html = wrap_row(html, item.full_width, item.hover_tip);
  618. }
  619. else if (_type == 'button') {
  620. const item = _item;
  621. context.config_list[name] = item;
  622. html += make_label(item);
  623. const text = escape_text(item.text || 'Click');
  624. html += wrap_right('<button type="button" class="setting" name="' + name + '">' + text + '</button>');
  625. html = wrap_row(html, item.full_width, item.hover_tip);
  626. }
  627. else if (_type == 'divider') {
  628. html += '<hr>';
  629. }
  630. else if (_type == 'section') {
  631. const item = _item;
  632. html += '<section>' + (item.label || '') + '</section>';
  633. }
  634. else if (_type == 'html') {
  635. const item = _item;
  636. html += make_label(item);
  637. html += item.html;
  638. switch (item.wrapper) {
  639. case 'row':
  640. html = wrap_row(html, undefined, item.hover_tip);
  641. break;
  642. case 'left':
  643. html = wrap_left(html);
  644. break;
  645. case 'right':
  646. html = wrap_right(html);
  647. break;
  648. }
  649. }
  650. return html;
  651. function make_label(item) {
  652. if (typeof item.label !== 'string')
  653. return '';
  654. return wrap_left('<label for="' + id + '">' + item.label + '</label>');
  655. }
  656. }
  657. /* eslint-enable no-case-declarations */
  658. //============
  659. function assemble_pages(id, tabs, pages) { return '<div id="' + id + '" class="ksof_stabs"><ul>' + tabs.join('') + '</ul>' + pages.join('') + '</div>'; }
  660. function wrap_row(html, full, hover_tip) { return '<div class="row' + (full ? ' full' : '') + '"' + to_title(hover_tip) + '>' + html + '</div>'; }
  661. function wrap_left(html) { return '<div class="left">' + html + '</div>'; }
  662. function wrap_right(html) { return '<div class="right">' + html + '</div>'; }
  663. function escape_text(text) {
  664. return text.replace(/[<>]/g, (ch) => {
  665. if (ch == '<')
  666. return '&lt';
  667. if (ch == '>')
  668. return '&gt';
  669. return ch;
  670. });
  671. }
  672. function escape_attr(text) { return text.replace(/"/g, '&quot;'); }
  673. function to_title(tip) { if (!tip)
  674. return ''; return ' title="' + tip.replace(/"/g, '&quot;') + '"'; }
  675. }
  676. function get_value(context, base, name) {
  677. const item = context.config_list[name];
  678. const evaluate = (item.path !== undefined);
  679. const path = (item.path || name);
  680. try {
  681. if (!evaluate)
  682. return base[path];
  683. return eval(path.replace(/@/g, 'base.'));
  684. }
  685. catch (e) {
  686. return;
  687. }
  688. }
  689. function set_value(context, base, name, value) {
  690. const item = context.config_list[name];
  691. const evaluate = (item.path !== undefined);
  692. const path = (item.path || name);
  693. try {
  694. if (!evaluate)
  695. return base[path] = value;
  696. let depth = 0;
  697. let new_path = '';
  698. let param = '';
  699. let c;
  700. for (let idx = 0; idx < path.length; idx++) {
  701. c = path[idx];
  702. if (c === '[') {
  703. if (depth++ === 0) {
  704. new_path += '[';
  705. param = '';
  706. }
  707. else {
  708. param += '[';
  709. }
  710. }
  711. else if (c === ']') {
  712. if (--depth === 0) {
  713. new_path += JSON.stringify(eval(param)) + ']';
  714. }
  715. else {
  716. param += ']';
  717. }
  718. }
  719. else {
  720. if (c === '@')
  721. c = 'base.';
  722. if (depth === 0)
  723. new_path += c;
  724. else
  725. param += c;
  726. }
  727. }
  728. eval(new_path + '=value');
  729. }
  730. catch (e) {
  731. return;
  732. }
  733. }
  734. function install_anchor() {
  735. let anchor = $('#ksof_ds');
  736. if (anchor.length === 0) {
  737. anchor = $('<div id="ksof_ds"></div></div>');
  738. $('body').prepend(anchor);
  739. $('#ksof_ds').on('keydown keyup keypress', '.ksof_settings_dialog', function (e) {
  740. // Stop keys from bubbling beyond the background overlay.
  741. e.stopPropagation();
  742. });
  743. }
  744. return anchor;
  745. }
  746. //------------------------------
  747. // Load jquery UI and the appropriate CSS based on location.
  748. //------------------------------
  749. const css_url = ksof.support_files['jqui_ksmain.css'];
  750. ksof.include('Jquery');
  751. await ksof.ready('document, Jquery');
  752. await Promise.all([
  753. ksof.load_script(ksof.support_files['jquery_ui.js'], true /* cache */),
  754. ksof.load_css(css_url, true /* cache */)
  755. ]);
  756. ksof.Settings = createSettings();
  757. ksof.settings = {};
  758. ready = true;
  759. // Workaround... https://community.wanikani.com/t/19984/55
  760. try {
  761. const temp = $.fn;
  762. delete temp.autocomplete;
  763. }
  764. catch (e) {
  765. // do nothing
  766. }
  767. // Notify listeners that we are ready.
  768. // Delay guarantees include() callbacks are called before ready() callbacks.
  769. setTimeout(() => { ksof.set_state('ksof.Settings', 'ready'); }, 0);
  770. })(window);
  771. module.exports = null;