KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

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

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