您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
SettingPanel for wenku8++
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/450209/1085800/SettingPanel.js
/* eslint-disable no-multi-spaces */ /* eslint-disable no-implicit-globals */ /* eslint-disable userscripts/no-invalid-headers */ /* eslint-disable userscripts/no-invalid-grant */ // ==UserScript== // @name SettingPanel // @displayname SettingPanel // @namespace Wenku8++ // @version 0.1.2 // @description SettingPanel for wenku8++ // @author PY-DNG // @license GPL-v3 // @regurl NONE // @require https://greasyfork.org/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1085783 // @require https://greasyfork.org/scripts/449583-configmanager/code/ConfigManager.js?version=1085356 // @grant none // @protect // ==/UserScript== (function __MAIN__() { 'use strict'; const alertify = require('alertify'); const CONST = { Text: {}, Manager_Config_Ruleset: { 'version-key': 'config-version', 'ignores': ["LOCAL-CDN"], 'defaultValues': { //'config-key': {}, } } }; // initialize alertify.dialog('setpanel', function factory(){ return { // The dialog startup function // This will be called each time the dialog is invoked // For example: alertify.myDialog( data ); main:function(content, header, onsave, onunsave){ this.resizeTo('80%', '80%'); content && this.setContent(content); header && this.setHeader(header); content && this.set('onsave', onsave); header && this.set('onunsave', onunsave); return this; }, // The dialog setup function // This should return the dialog setup object ( buttons, focus and options overrides ). setup:function(){ return { /* buttons collection */ buttons:[{ /* button label */ text: '不保存', /*bind a keyboard key to the button */ key: undefined, /* indicate if closing the dialog should trigger this button action */ invokeOnClose: true, /* custom button class name */ className: alertify.defaults.theme.cancel, /* custom button attributes */ attrs: {}, /* Defines the button scope, either primary (default) or auxiliary */ scope:'auxiliary', /* The will conatin the button DOMElement once buttons are created */ element: undefined },{ /* button label */ text: '保存', /*bind a keyboard key to the button */ key: undefined, /* indicate if closing the dialog should trigger this button action */ invokeOnClose: false, /* custom button class name */ className: alertify.defaults.theme.ok, /* custom button attributes */ attrs: {}, /* Defines the button scope, either primary (default) or auxiliary */ scope:'primary', /* The will conatin the button DOMElement once buttons are created */ element: undefined }], /* default focus */ focus:{ /* the element to receive default focus, has differnt meaning based on value type: number: action button index. string: querySelector to select from dialog body contents. function: when invoked, should return the focus element. DOMElement: the focus element. object: an object that implements .focus() and .select() functions. */ element: 0, /* indicates if the element should be selected on focus or not*/ select: true }, /* dialog options, these override the defaults */ options: { title: 'Setting Panel', modal: true, basic: false, frameless: false, pinned: false, movable: true, moveBounded: false, resizable: true, autoReset: false, closable: true, closableByDimmer: true, maximizable: false, startMaximized: false, pinnable: false, transition: 'fade', padding: true, overflow: true, /* onshow:..., onclose:..., onfocus:..., onmove:..., onmoved:..., onresize:..., onresized:..., onmaximize:..., onmaximized:..., onrestore:..., onrestored:... */ } }; }, // This will be called once the dialog DOM has been created, just before its added to the document. // Its invoked only once. build:function(){ // Do custom DOM manipulation here, accessible via this.elements // this.elements.root ==> Root div // this.elements.dimmer ==> Modal dimmer div // this.elements.modal ==> Modal div (dialog wrapper) // this.elements.dialog ==> Dialog div // this.elements.reset ==> Array containing the tab reset anchor links // this.elements.reset[0] ==> First reset element (button). // this.elements.reset[1] ==> Second reset element (button). // this.elements.header ==> Dialog header div // this.elements.body ==> Dialog body div // this.elements.content ==> Dialog body content div // this.elements.footer ==> Dialog footer div // this.elements.resizeHandle ==> Dialog resize handle div // Dialog commands (Pin/Maximize/Close) // this.elements.commands ==> Object containing dialog command buttons references // this.elements.commands.container ==> Root commands div // this.elements.commands.pin ==> Pin command button // this.elements.commands.maximize ==> Maximize command button // this.elements.commands.close ==> Close command button // Dialog action buttons (Ok, cancel ... etc) // this.elements.buttons ==> Object containing dialog action buttons references // this.elements.buttons.primary ==> Primary buttons div // this.elements.buttons.auxiliary ==> Auxiliary buttons div // Each created button will be saved with the button definition inside buttons collection // this.__internal.buttons[x].element }, // This will be called each time the dialog is shown prepare:function(){ // Do stuff that should be done every time the dialog is shown. }, // This will be called each time an action button is clicked. callback:function(closeEvent){ //The closeEvent has the following properties // // index: The index of the button triggering the event. // button: The button definition object. // cancel: When set true, prevent the dialog from closing. switch (closeEvent.index) { case 0: { const onunsave = this.get('onunsave'); typeof onunsave === 'function' && onunsave(); //alertify.alert('Not saved'); break; } case 1: { const onsave = this.get('onsave'); typeof onsave === 'function' && onsave(); //alertify.alert('Saved'); } } }, // To make use of AlertifyJS settings API, group your custom settings into a settings object. settings:{ onsave: function() {}, onunsave: function() {} }, // AlertifyJS will invoke this each time a settings value gets updated. settingUpdated:function(key, oldValue, newValue){/* // Use this to respond to specific setting updates. switch(key){ case 'myProp': // Do something when 'myProp' changes break; } */}, // listen to internal dialog events. hooks:{ // triggered when the dialog is shown, this is seperate from user defined onshow onshow: function(){ this.resizeTo('80%', '80%'); }, // triggered when the dialog is closed, this is seperate from user defined onclose onclose: function(){ }, // triggered when a dialog option gets updated. // IMPORTANT: This will not be triggered for dialog custom settings updates ( use settingUpdated instead). onupdate: function(){ } } } }, true); SettingPanel.SettingOption = SettingOption; exports = { SettingPanel: SettingPanel, SettingOption: SettingOption, isOption: isOption }; // A table-based setting panel using alertify-js // For wenku8++ only version // Use 'new' keyword // Usage: /* var panel = new SettingPanel({ header: ''; className: '', id: '', name: '', tables: [ { className: '', id: '', name: '', rows: [ { className: '', id: '', name: '', blocks: [ { isHeader: false, width: '', height: '', innerHTML / innerText: '' colSpan: 1, rowSpan: 1, className: '', id: '', name: '', options: [SettingOption, ...] children: [HTMLElement, ...] }, ... ] }, ... ] }, ... ] }); */ function SettingPanel(details={}) { const SP = this; SP.insertTable = insertTable; SP.appendTable = appendTable; SP.removeTable = removeTable; SP.remove = remove; SP.PanelTable = PanelTable; SP.PanelRow = PanelRow; SP.PanelBlock = PanelBlock; SP.PanelOption = PanelOption; SP.PanelOption.isOption = isOption; // <div> element const elm = $CrE('div'); copyProps(details, elm, ['id', 'name', 'className']); elm.classList.add('settingpanel-container'); // Configure object let css='', usercss=''; SP.element = elm; SP.elements = {}; SP.children = {}; SP.tables = []; SP.length = 0; details.id !== undefined && (SP.elements[details.id] = elm); copyProps(details, SP, ['id', 'name']); Object.defineProperty(SP, 'css', { configurable: false, enumerable: true, get: function() { return css; }, set: function(_css) { addStyle(_css, 'settingpanel-css'); css = _css; } }); Object.defineProperty(SP, 'usercss', { configurable: false, enumerable: true, get: function() { return usercss; }, set: function(_usercss) { addStyle(_usercss, 'settingpanel-usercss'); usercss = _usercss; } }); SP.css = '.settingpanel-table {border-spacing: 0px; border-collapse: collapse; width: 100%; margin: 2em 0;} .settingpanel-block {border: 1px solid; text-align: center; vertical-align: middle; padding: 3px; text-align: left;}' // Create tables if (details.tables) { for (const table of details.tables) { if (table instanceof PanelTable) { appendTable(table); } else { appendTable(new PanelTable(table)); } } } // Make alerity box const box = SP.alertifyBox = alertify.setpanel ? alertify.setpanel('') : alertify.alert(); clearChildNodes(box.elements.content); box.elements.content.appendChild(elm); box.elements.content.style.overflow = 'auto'; box.setHeader(details.header); box.setting({ maximizable: true, overflow: true }); !box.isOpen() && box.show(); // Insert a Panel-Row // Returns Panel object function insertTable(table, index) { // Insert table !(table instanceof PanelTable) && (table = new PanelTable(table)); index < SP.length ? elm.insertBefore(table.element, elm.children[index]) : elm.appendChild(table.element); insertItem(SP.tables, table, index); table.id !== undefined && (SP.children[table.id] = table); SP.length++; // Set parent table.parent = SP; // Inherit elements for (const [id, subelm] of Object.entries(table.elements)) { SP.elements[id] = subelm; } // Inherit children for (const [id, child] of Object.entries(table.children)) { SP.children[id] = child; } return SP; } // Append a Panel-Row // Returns Panel object function appendTable(table) { return insertTable(table, SP.length); } // Remove a Panel-Row // Returns Panel object function removeTable(index) { const table = SP.tables[index]; SP.element.removeChild(table.element); removeItem(SP.rows, index); return SP; } // Remove itself from parentElement // Returns Panel object function remove() { SP.element.parentElement && SP.parentElement.removeChild(SP.element); return SP; } // Panel-Table object // Use 'new' keyword function PanelTable(details={}) { const PT = this; PT.insertRow = insertRow; PT.appendRow = appendRow; PT.removeRow = removeRow; PT.remove = remove // <table> element const elm = $CrE('table'); copyProps(details, elm, ['id', 'name', 'className']); elm.classList.add('settingpanel-table'); // Configure PT.element = elm; PT.elements = {}; PT.children = {}; PT.rows = []; PT.length = 0; details.id !== undefined && (PT.elements[details.id] = elm); copyProps(details, PT, ['id', 'name']); // Append rows if (details.rows) { for (const row of details.rows) { if (row instanceof PanelRow) { insertRow(row); } else { insertRow(new PanelRow(row)); } } } // Insert a Panel-Row // Returns Panel-Table object function insertRow(row, index) { // Insert row !(row instanceof PanelRow) && (row = new PanelRow(row)); index < PT.length ? elm.insertBefore(row.element, elm.children[index]) : elm.appendChild(row.element); insertItem(PT.rows, row, index); row.id !== undefined && (PT.children[row.id] = row); PT.length++; // Set parent row.parent = PT; // Inherit elements for (const [id, subelm] of Object.entries(row.elements)) { PT.elements[id] = subelm; } // Inherit children for (const [id, child] of Object.entries(row.children)) { PT.children[id] = child; } return PT; } // Append a Panel-Row // Returns Panel-Table object function appendRow(row) { return insertRow(row, PT.length); } // Remove a Panel-Row // Returns Panel-Table object function removeRow(index) { const row = PT.rows[index]; PT.element.removeChild(row.element); removeItem(PT.rows, index); return PT; } // Remove itself from parentElement // Returns Panel-Table object function remove() { PT.parent instanceof SettingPanel && PT.parent.removeTable(PT.tables.indexOf(PT)); return PT; } } // Panel-Row object // Use 'new' keyword function PanelRow(details={}) { const PR = this; PR.insertBlock = insertBlock; PR.appendBlock = appendBlock; PR.removeBlock = removeBlock; PR.remove = remove; // <tr> element const elm = $CrE('tr'); copyProps(details, elm, ['id', 'name', 'className']); elm.classList.add('settingpanel-row'); // Configure object PR.element = elm; PR.elements = {}; PR.children = {}; PR.blocks = []; PR.length = 0; details.id !== undefined && (PR.elements[details.id] = elm); copyProps(details, PR, ['id', 'name']); // Append blocks if (details.blocks) { for (const block of details.blocks) { if (block instanceof PanelBlock) { appendBlock(block); } else { appendBlock(new PanelBlock(block)); } } } // Insert a Panel-Block // Returns Panel-Row object function insertBlock(block, index) { // Insert block !(block instanceof PanelBlock) && (block = new PanelBlock(block)); index < PR.length ? elm.insertBefore(block.element, elm.children[index]) : elm.appendChild(block.element); insertItem(PR.blocks, block, index); block.id !== undefined && (PR.children[block.id] = block); PR.length++; // Set parent block.parent = PR; // Inherit elements for (const [id, subelm] of Object.entries(block.elements)) { PR.elements[id] = subelm; } // Inherit children for (const [id, child] of Object.entries(block.children)) { PR.children[id] = child; } return PR; }; // Append a Panel-Block // Returns Panel-Row object function appendBlock(block) { return insertBlock(block, PR.length); } // Remove a Panel-Block // Returns Panel-Row object function removeBlock(index) { const block = PR.blocks[index]; PR.element.removeChild(block.element); removeItem(PR.blocks, index); return PR; } // Remove itself from parent // Returns Panel-Row object function remove() { PR.parent instanceof PanelTable && PR.parent.removeRow(PR.parent.rows.indexOf(PR)); return PR; } } // Panel-Block object // Use 'new' keyword function PanelBlock(details={}) { const PB = this; PB.remove = remove; // <td> element const elm = $CrE(details.isHeader ? 'th' : 'td'); copyProps(details, elm, ['innerText', 'innerHTML', 'colSpan', 'rowSpan', 'id', 'name', 'className']); copyProps(details, elm.style, ['width', 'height']); elm.classList.add('settingpanel-block'); // Configure object PB.element = elm; PB.elements = {}; PB.children = {}; details.id !== undefined && (PB.elements[details.id] = elm); copyProps(details, PB, ['id', 'name']); // Append to parent if need details.parent instanceof PanelRow && (PB.parent = details.parent.appendBlock(PB)); // Append SettingOptions if exist if (details.options) { details.options.map((o) => (isOption(o) ? o : new SettingOption(o))).forEach(function(option) { elm.appendChild(option.element); }); } // Append child elements if exist if (details.children) { for (const child of details.children) { elm.appendChild(child); } } // Remove itself from parent // Returns Panel-Block object function remove() { PB.parent instanceof PanelRow && PB.parent.removeBlock(PB.parent.blocks.indexOf(PB)); return PB; } } function $R(e) {return $(e) && $(e).parentElement.removeChild($(e));} function insertItem(arr, item, index) { for (let i = arr.length; i > index ; i--) { arr[i] = arr[i-1]; } arr[index] = item; return arr; } function removeItem(arr, index) { for (let i = index; i < arr.length-1; i++) { arr[i] = arr[i+1]; } delete arr[arr.length-1]; return arr; } function MakeReadonlyObj(val) { return isObject(val) ? new Proxy(val, { get: function(target, property, receiver) { return MakeReadonlyObj(target[property]); }, set: function(target, property, value, receiver) {}, has: function(target, prop) {} }) : val; function isObject(value) { return ['object', 'function'].includes(typeof value) && value !== null; } } } // details = {path='config path', type='config type', data='option data'} function SettingOption(storage, details={}) { const SO = this; SO.save = save; // Initialize ConfigManager !storage && Err('SettingOption requires GM_storage functions'); const CM = new ConfigManager(CONST.Manager_Config_Ruleset, storage); const CONFIG = CM.Config; // Get args const options = ['path', 'type', 'checker', 'data']; copyProps(details, SO, options); // Create element const original_value = CM.getConfig(SO.path); const valueElement = { 'string': $CrE('input'), 'number': (() => {const e = $CrE('input'); e.type = 'number'; return e;}) (), 'boolean': (() => {const e = $CrE('input'); e.type = 'checkbox'; return e;}) (), 'select': $CrE('select') }; const valueSetter = { 'string': (elm, val) => (elm.value = val), 'number': (elm, val) => (elm.value = val), 'boolean': (elm, val) => (elm.checked = val), 'select': (elm, val) => (Array.from(elm.children).find((opt) => (opt.value === val)).selected = true), }; !Object.keys(valueElement).includes(SO.type) && Err('Unsupported Panel-Option type'); SO.element = valueElement[SO.type]; valueSetter[SO.type](SO.element, original_value); // Bind change-checker-saver ![false, null].includes(SO.checker) && SO.element.addEventListener('change', function(e) { if (!SO.checker || SO.checker(e, SO.element.value)) { // Allows checker to modify saved value CM.setConfig(SO.path, SO.element.value); } else { valueSetter[SO.type](SO.element, original_value); } }); function save() { CM.setConfig(SO.path, SO.element.value); } } function isOption(obj) { return obj instanceof PanelOption; } })();