KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

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

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