KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

当前为 2022-11-05 提交的版本,查看 最新版本

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