KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

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

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址