您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Settings module for KameSame Open Framework
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/451521/1113600/KameSame%20Open%20Framework%20-%20Settings%20module.js
"use strict"; // ==UserScript== // @name KameSame Open Framework - Settings module // @namespace timberpile // @description Settings module for KameSame Open Framework // @version 0.3 // @copyright 2022+, Robin Findley, Timberpile // @license MIT http://opensource.org/licenses/MIT // ==/UserScript== var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); 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"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); 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"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; ((async function (global) { var _KSOFSettings_instances, _KSOFSettings_openDialog, _KSOFSettings_settingChanged; const ksof = global.ksof; const backgroundFuncs = () => { return { open: () => { const anchor = installAnchor(); let bkgd = anchor.find('> #ksofs_bkgd'); if (bkgd.length === 0) { bkgd = $('<div id="ksofs_bkgd" refcnt="0"></div>'); anchor.prepend(bkgd); } const refcnt = Number(bkgd.attr('refcnt')); bkgd.attr('refcnt', refcnt + 1); }, close: () => { const bkgd = $('#ksof_ds > #ksofs_bkgd'); if (bkgd.length === 0) return; const refcnt = Number(bkgd.attr('refcnt')); if (refcnt <= 0) return; bkgd.attr('refcnt', refcnt - 1); }, }; }; //######################################################################## //------------------------------ // Constructor //------------------------------ class KSOFSettings { constructor(config) { _KSOFSettings_instances.add(this); _KSOFSettings_openDialog.set(this, void 0); this.cfg = config; this.configList = {}; __classPrivateFieldSet(this, _KSOFSettings_openDialog, $(), "f"); this.background = backgroundFuncs(); } //------------------------------ // Open the settings dialog. //------------------------------ static save(context) { const scriptId = ((typeof context === 'string') ? context : context.cfg.scriptId); const settings = ksof.settings[scriptId]; if (!settings) return Promise.resolve(''); return ksof.fileCache.save(`ksof.settings.${scriptId}`, settings); } save() { return KSOFSettings.save(this); } //------------------------------ // Open the settings dialog. //------------------------------ static async load(context, defaults) { const scriptId = ((typeof context === 'string') ? context : context.cfg.scriptId); const finish = (settings) => { if (defaults) ksof.settings[scriptId] = deepMerge(defaults, settings); else ksof.settings[scriptId] = settings; return ksof.settings[scriptId]; }; try { const settings = await ksof.fileCache.load(`ksof.settings.${scriptId}`); return finish(settings); } catch (error) { return finish.call(null, {}); } } load(defaults) { return KSOFSettings.load(this, defaults); } //------------------------------ // Save button handler. //------------------------------ saveBtn() { const scriptId = this.cfg.scriptId; const settings = ksof.settings[scriptId]; if (settings) { const activeTabs = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.ui-tabs-active').toArray() .map((tab) => { return `#${tab.attributes.getNamedItem('id')?.value || ''}`; }); if (activeTabs.length > 0) settings.ksofActiveTabs = activeTabs; } if (this.cfg.autosave === undefined || this.cfg.autosave === true) { this.save(); } if (this.cfg.onSave) { this.cfg.onSave(ksof.settings[this.cfg.scriptId]); } ksof.trigger('ksof.settings.save'); this.keepSettings = true; __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('close'); } //------------------------------ // Cancel button handler. //------------------------------ cancel() { __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('close'); if (typeof this.cfg.onCancel === 'function') this.cfg.onCancel(ksof.settings[this.cfg.scriptId]); } //------------------------------ // Open the settings dialog. //------------------------------ open() { if (!ready) return; if (__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").length > 0) return; installAnchor(); if (this.cfg.background !== false) this.background.open(); __classPrivateFieldSet(this, _KSOFSettings_openDialog, $(`<div id="ksofs_${this.cfg.scriptId}" class="ksof_settings" style="display:none"></div>`), "f"); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").html(configToHTML(this)); const resize = (event, ui) => { const isNarrow = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").hasClass('narrow'); ui; if (isNarrow && ui.size.width >= 510) { __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").removeClass('narrow'); } else if (!isNarrow && ui.size.width < 490) { __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").addClass('narrow'); } }; const tabActivated = () => { const wrapper = $(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('widget')); if ((wrapper.outerHeight() || 0) + wrapper.position().top > document.body.clientHeight) { __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('option', 'maxHeight', document.body.clientHeight); } }; let width = 500; if (window.innerWidth < 510) { width = 280; __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").addClass('narrow'); } __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog({ title: this.cfg.title, buttons: [ { text: 'Save', click: this.saveBtn.bind(this), }, { text: 'Cancel', click: this.cancel.bind(this), }, ], width, maxHeight: document.body.clientHeight, modal: false, autoOpen: false, appendTo: '#ksof_ds', resize: resize.bind(this), close: () => { this.close(false); }, }); $(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('widget')).css('position', 'fixed'); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").parent().addClass('ksof_settings_dialog'); $('.ksof_stabs').tabs({ activate: tabActivated.bind(null) }); const settings = ksof.settings[this.cfg.scriptId]; if (settings && settings.ksofActiveTabs instanceof Array) { const activeTabs = settings.ksofActiveTabs; for (let tabIndex = 0; tabIndex < activeTabs.length; tabIndex++) { const tab = $(activeTabs[tabIndex]); tab.closest('.ui-tabs').tabs({ active: tab.index() }); } } const toggleMulti = (e) => { if (e.button != 0) return true; const multi = $(e.currentTarget); const scroll = e.currentTarget.scrollTop; e.target.selected = !e.target.selected; setTimeout(function () { e.currentTarget.scrollTop = scroll; multi.focus(); // TODO what should this do? it's deprecated }, 0); return __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).call(this, e); }; const settingButtonClicked = (e) => { const name = e.target.attributes.name.value; const _item = this.configList[name]; if (_item.type == 'button') { const item = _item; item.onClick.call(e, name, item, __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).bind(this, e)); } }; __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('open'); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.setting[multiple]').on('mousedown', toggleMulti.bind(this)); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.setting').on('change', __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).bind(this)); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('form').on('submit', function () { return false; }); __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('button.setting').on('click', settingButtonClicked.bind(this)); if (typeof this.cfg.preOpen === 'function') this.cfg.preOpen(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f")); this.reversions = deepMerge({}, ksof.settings[this.cfg.scriptId]); this.refresh(); } //------------------------------ // Close and destroy the dialog. //------------------------------ close(keepSettings) { if (!this.keepSettings && keepSettings !== true) { // Revert settings ksof.settings[this.cfg.scriptId] = deepMerge({}, this.reversions || {}); delete this.reversions; } delete this.keepSettings; __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('destroy'); __classPrivateFieldSet(this, _KSOFSettings_openDialog, $(), "f"); if (this.cfg.background !== false) this.background.close(); if (typeof this.cfg.onClose === 'function') this.cfg.onClose(ksof.settings[this.cfg.scriptId]); } //------------------------------ // Update the dialog to reflect changed settings. //------------------------------ refresh() { const scriptId = this.cfg.scriptId; const settings = ksof.settings[scriptId]; for (const name in this.configList) { const elem = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find(`#${scriptId}_${name}`); const _config = this.configList[name]; const value = getValue(this, settings, name); if (_config.type == 'dropdown') { elem.find(`option[name="${value}"]`).prop('selected', true); } else if (_config.type == 'list') { const config = _config; if (config.multi === true) { elem.find('option').each(function (i, e) { const optionName = e.getAttribute('name') || `#${e.index}`; e.selected = value[optionName]; }); } else { elem.find(`option[name="${value}"]`).prop('selected', true); } } else if (_config.type == 'checkbox') { elem.prop('checked', value); } else { elem.val(value); } } if (typeof this.cfg.onRefresh === 'function') this.cfg.onRefresh(ksof.settings[this.cfg.scriptId]); } } _KSOFSettings_openDialog = new WeakMap(), _KSOFSettings_instances = new WeakSet(), _KSOFSettings_settingChanged = function _KSOFSettings_settingChanged(event) { const elem = $(event.currentTarget); const name = elem.attr('name'); if (!name) return false; const _item = this.configList[name]; // Extract the value let value; if (_item.type == 'dropdown') { value = elem.find(':checked').attr('name'); } else if (_item.type == 'list') { const item = _item; if (item.multi === true) { value = {}; elem.find('option').each(function (i, e) { const optionName = e.getAttribute('name') || `#${e.index}`; value[optionName] = e.selected; }); } else { value = elem.find(':checked').attr('name'); } } else if (_item.type == 'input') { const item = _item; if (item.subtype === 'number') { value = Number(elem.val()); } } else if (_item.type == 'checkbox') { value = elem.is(':checked'); } else if (_item.type == 'number') { value = Number(elem.val()); } else { value = elem.val(); } // Validation let valid = { valid: true, msg: '' }; { const item = _item; if (item.validate) { const _valid = item.validate.call(event.target, value, item); if (typeof _valid === 'boolean') valid = { valid: _valid, msg: '' }; else if (typeof _valid === 'string') valid = { valid: false, msg: _valid }; } } if (_item.type == 'number') { const item = _item; if (item.min && Number(value) < item.min) { valid.valid = false; if (valid.msg.length === 0) { if (typeof item.max === 'number') valid.msg = `Must be between ${item.min} and ${item.max}`; else valid.msg = `Must be ${item.min} or higher`; } } else if (item.max && Number(value) > item.max) { valid.valid = false; if (valid.msg.length === 0) { if (typeof item.min === 'number') valid.msg = `Must be between ${item.min} and ${item.max}`; else valid.msg = `Must be ${item.max} or lower`; } } } else if (_item.type == 'text') { const item = _item; if (item.match !== undefined && value.match(item.match) === null) { valid.valid = false; if (valid.msg.length === 0) // valid.msg = item.error_msg || 'Invalid value' // TODO no item has a error_msg? valid.msg = 'Invalid value'; } } // Style for valid/invalid const parent = elem.closest('.right'); parent.find('.note').remove(); if (typeof valid.msg === 'string' && valid.msg.length > 0) parent.append(`<div class="note${valid.valid ? '' : ' error'}">${valid.msg}</div>`); if (!valid.valid) { elem.addClass('invalid'); } else { elem.removeClass('invalid'); } const scriptId = this.cfg.scriptId; const settings = ksof.settings[scriptId]; if (valid.valid) { const item = _item; // if (item.no_save !== true) set_value(this, settings, name, value) // TODO what is no_save supposed to do? setValue(this, settings, name, value); if (item.onChange) item.onChange.call(event.target, name, value, item); if (this.cfg.onChange) this.cfg.onChange.call(event.target, name, value, item); if (item.refreshOnChange === true) this.refresh(); } return false; }; const createSettings = () => { const settingsObj = (config) => { return new KSOFSettings(config); }; settingsObj.save = (context) => { return KSOFSettings.save(context); }; settingsObj.load = (context, defaults) => { return KSOFSettings.load(context, defaults); }; settingsObj.background = backgroundFuncs(); return settingsObj; }; ksof.Settings = createSettings(); ksof.settings = {}; //######################################################################## let ready = false; //======================================================================== const deepMerge = (...objects) => { const merged = {}; const recursiveMerge = (dest, src) => { for (const prop in src) { if (typeof src[prop] === 'object' && src[prop] !== null) { const srcProp = src[prop]; if (Array.isArray(srcProp)) { dest[prop] = srcProp.slice(); } else { dest[prop] = dest[prop] || {}; recursiveMerge(dest[prop], srcProp); } } else { dest[prop] = src[prop]; } } return dest; }; for (const obj in objects) { recursiveMerge(merged, objects[obj]); } return merged; }; //------------------------------ // Convert a config object to html dialog. //------------------------------ /* eslint-disable no-case-declarations */ const configToHTML = (context) => { context.configList = {}; if (!ksof.settings) { return ''; } const assemblePages = (id, tabs, pages) => { return `<div id="${id}" class="ksof_stabs"><ul>${tabs.join('')}</ul>${pages.join('')}</div>`; }; const wrapRow = (html, full, hoverTip) => { return `<div class="row${full ? ' full' : ''}"${toTitle(hoverTip)}>${html}</div>`; }; const wrapLeft = (html) => { return `<div class="left">${html}</div>`; }; const wrapRight = (html) => { return `<div class="right">${html}</div>`; }; const escapeText = (text) => { return text.replace(/[<>]/g, (ch) => { if (ch == '<') return '<'; if (ch == '>') return '>'; return ''; }); }; const escapeAttr = (text) => { return text.replace(/"/g, '"'); }; const toTitle = (tip) => { if (!tip) return ''; return ` title="${tip.replace(/"/g, '"')}"`; }; const parseItem = (name, _item, passback) => { if (typeof _item.type !== 'string') return ''; const id = `${context.cfg.scriptId}_${name}`; let cname, html = '', childPassback, nonPage = ''; const makeLabel = (item) => { if (typeof item.label !== 'string') return ''; return wrapLeft(`<label for="${id}">${item.label}</label>`); }; const _type = _item.type; if (_type == 'tabset') { const item = _item; childPassback = {}; for (cname in item.content) { nonPage += parseItem(cname, item.content[cname], childPassback); } if (childPassback.tabs && childPassback.pages) { html = assemblePages(id, childPassback.tabs, childPassback.pages); } } else if (_type == 'page') { const item = _item; if (typeof item.content !== 'object') item.content = {}; if (!passback.tabs) { passback.tabs = []; } if (!passback.pages) { passback.pages = []; } passback.tabs.push(`<li id="${id}_tab"${toTitle(item.hoverTip)}><a href="#${id}">${item.label}</a></li>`); childPassback = {}; for (cname in item.content) nonPage += parseItem(cname, item.content[cname], childPassback); if (childPassback.tabs && childPassback.pages) html = assemblePages(id, childPassback.tabs, childPassback.pages); passback.pages.push(`<div id="${id}">${html}${nonPage}</div>`); passback.isPage = true; html = ''; } else if (_type == 'group') { const item = _item; if (typeof item.content !== 'object') item.content = {}; childPassback = {}; for (cname in item.content) nonPage += parseItem(cname, item.content[cname], childPassback); if (childPassback.tabs && childPassback.pages) html = assemblePages(id, childPassback.tabs, childPassback.pages); html = `<fieldset id="${id}" class="ksof_group"><legend>${item.label}</legend>${html}${nonPage}</fieldset>`; } else if (_type == 'dropdown') { const item = _item; context.configList[name] = item; let value = getValue(context, base, name); if (value === undefined) { if (item.default !== undefined) { value = item.default; } else { value = Object.keys(item.content)[0]; } setValue(context, base, name, value); } html = `<select id="${id}" name="${name}" class="setting"${toTitle(item.hoverTip)}>`; for (cname in item.content) html += `<option name="${cname}">${escapeText(item.content[cname])}</option>`; html += '</select>'; html = makeLabel(item) + wrapRight(html); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'list') { const item = _item; context.configList[name] = item; let value = getValue(context, base, name); if (value === undefined) { if (item.default !== undefined) { value = item.default; } else { if (item.multi === true) { value = {}; Object.keys(item.content).forEach(function (key) { value[key] = false; }); } else { value = Object.keys(item.content)[0]; } } setValue(context, base, name, value); } let attribs = ` size="${item.size || Object.keys(item.content).length || 4}"`; if (item.multi === true) attribs += ' multiple'; html = `<select id="${id}" name="${name}" class="setting list"${attribs}${toTitle(item.hoverTip)}>`; for (cname in item.content) html += `<option name="${cname}">${escapeText(item.content[cname])}</option>`; html += '</select>'; html = makeLabel(item) + wrapRight(html); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'checkbox') { const item = _item; context.configList[name] = item; html = makeLabel(item); let value = getValue(context, base, name); if (value === undefined) { value = (item.default || false); setValue(context, base, name, value); } html += wrapRight(`<input id="${id}" class="setting" type="checkbox" name="${name}">`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'input') { const item = _item; const itype = item.subtype || 'text'; context.configList[name] = item; html += makeLabel(item); let value = getValue(context, base, name); if (value === undefined) { const isNumber = (item.subtype === 'number'); value = (item.default || (isNumber ? 0 : '')); setValue(context, base, name, value); } html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'number') { const item = _item; const itype = item.type; context.configList[name] = item; html += makeLabel(item); let value = getValue(context, base, name); if (value === undefined) { const isNumber = (item.type === 'number'); value = (item.default || (isNumber ? 0 : '')); setValue(context, base, name, value); } html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'text') { const item = _item; const itype = item.type; context.configList[name] = item; html += makeLabel(item); let value = getValue(context, base, name); if (value === undefined) { value = (item.default || ''); setValue(context, base, name, value); } html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'color') { const item = _item; context.configList[name] = item; html += makeLabel(item); let value = getValue(context, base, name); if (value === undefined) { value = (item.default || '#000000'); setValue(context, base, name, value); } html += wrapRight(`<input id="${id}" class="setting" type="color" name="${name}">`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'button') { const item = _item; context.configList[name] = item; html += makeLabel(item); const text = escapeText(item.text || 'Click'); html += wrapRight(`<button type="button" class="setting" name="${name}">${text}</button>`); html = wrapRow(html, item.fullWidth, item.hoverTip); } else if (_type == 'divider') { html += '<hr>'; } else if (_type == 'section') { const item = _item; html += `<section>${item.label || ''}</section>`; } else if (_type == 'html') { const item = _item; html += makeLabel(item); html += item.html; switch (item.wrapper) { case 'row': html = wrapRow(html, undefined, item.hoverTip); break; case 'left': html = wrapLeft(html); break; case 'right': html = wrapRight(html); break; } } return html; }; let base = ksof.settings[context.cfg.scriptId]; if (base === undefined) ksof.settings[context.cfg.scriptId] = base = {}; let html = ''; const childPassback = {}; const id = `${context.cfg.scriptId}_dialog`; for (const name in context.cfg.content) { html += parseItem(name, context.cfg.content[name], childPassback); } if (childPassback.tabs && childPassback.pages) html = assemblePages(id, childPassback.tabs, childPassback.pages) + html; return `<form>${html}</form>`; }; const getValue = (context, base, name) => { const item = context.configList[name]; const evaluate = (item.path !== undefined); const path = (item.path || name); try { if (!evaluate) return base[path]; return eval(path.replace(/@/g, 'base.')); } catch (e) { return; } }; const setValue = (context, base, name, value) => { const item = context.configList[name]; const evaluate = (item.path !== undefined); const path = (item.path || name); try { if (!evaluate) return base[path] = value; let depth = 0; let newPath = ''; let param = ''; let c; for (let idx = 0; idx < path.length; idx++) { c = path[idx]; if (c === '[') { if (depth++ === 0) { newPath += '['; param = ''; } else { param += '['; } } else if (c === ']') { if (--depth === 0) { newPath += `${JSON.stringify(eval(param))}]`; } else { param += ']'; } } else { if (c === '@') c = 'base.'; if (depth === 0) newPath += c; else param += c; } } eval(`${newPath}=value`); } catch (e) { return; } }; const installAnchor = () => { let anchor = $('#ksof_ds'); if (anchor.length === 0) { anchor = $('<div id="ksof_ds"></div></div>'); $('body').prepend(anchor); $('#ksof_ds').on('keydown keyup keypress', '.ksof_settings_dialog', function (e) { // Stop keys from bubbling beyond the background overlay. e.stopPropagation(); }); } return anchor; }; //------------------------------ // Load jquery UI and the appropriate CSS based on location. //------------------------------ const cssUrl = ksof.supportFiles['jqui_ksmain.css']; ksof.include('Jquery'); await ksof.ready('document, Jquery'); await Promise.all([ ksof.loadScript(ksof.supportFiles['jquery_ui.js'], true /* cache */), ksof.loadCSS(cssUrl, true /* cache */), ]); ready = true; // Workaround... https://community.wanikani.com/t/19984/55 try { const temp = $.fn; delete temp.autocomplete; } catch (e) { // do nothing } // Notify listeners that we are ready. // Delay guarantees include() callbacks are called before ready() callbacks. setTimeout(function () { ksof.setState('ksof.Settings', 'ready'); }, 0); })(window));