KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

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

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