GM_config 中文版
สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/447340/1252278/GM_config_zh.js
// ==UserScript== // @name GM_config_zh // @author Mike Medley & zxf10608 & maomao1### // @version 1.0.1 // @description GM_config 中文版 // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @exclude * // @license LGPL 3 // ==/UserScript== // The GM_config constructor function GM_configStruct() { // call init() if settings were passed to constructor if (arguments.length) { GM_configInit(this, arguments) this.onInit() } } function noop() {} // This is the initializer function function GM_configInit(config, args) { // Initialize instance variables if (typeof config.fields == 'undefined') { config.fields = {} config.onInit = config.onInit || noop config.onOpen = config.onOpen || noop config.onSave = config.onSave || noop config.onClose = config.onClose || noop config.onReset = config.onReset || noop config.isOpen = false config.title = '用户脚本设置' config.css = { basic: [ '#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }', '#GM_config { background: #FFF; }', "#GM_config input[type='radio'] { margin-right: 8px; }", '#GM_config .indent40 { margin-left: 40%; }', '#GM_config .field_label { font-size: 14px; font-weight: bold; margin-right: 6px; }', '#GM_config .radio_label { font-size: 14px; }', '#GM_config .block { display: block; }', '#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }', '#GM_config .reset, #GM_config .reset a,' + ' #GM_config_buttons_holder { color: #000; text-align: right; }', '#GM_config .config_header { font-size: 20pt; margin: 0; }', '#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }', '#GM_config .center { text-align: center; }', '#GM_config .section_header_holder { margin-top: 8px; }', '#GM_config .config_var { margin: 0 0 4px; }', '#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;' + ' font-size: 12pt; margin: 0; }', '#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;' + ' font-size: 10pt; margin: 0 0 6px; }', // newer "#GM_config input[type='number'] { width: 60px; }", '#GM_config .nav-tabs { margin: 10 0}', '#GM_config .nav-tabs > div { display: inline; padding: 3px 10px; }', '#pv-prefs .section_header_holder { padding-left: 10px; }', ].join('\n') + '\n', skin_tab: [ '#GM_config { background: #EEE; }', '#GM_config textarea { width: 98%; height: 45px; margin-top: 5px; }', '#GM_config .field_label { display: inline-block; font-weight: normal; }', // 在同一行内的设置 "#GM_config .inline input[type='checkbox'] {margin: 3px 3px 3px 0px;}", '#GM_config .inline .config_var { margin-left: 15px; }', // 内容样式 '#GM_config .config_var { font-size: 14px; padding: 5px; margin: 0; }', '#GM_config .config_header a { text-decoration: none; color: #000; }', '#GM_config .nav-tabs { margin: 20 0}', '#GM_config .nav-tabs > div { font-size: 15px; color: #999; cursor: pointer; padding: 10px 20px; }', '#GM_config .nav-tabs > .active { cursor: default; color: #FFF; }', '#GM_config .nav-tabs > div:hover { color: #FFF; }', ].join('\n') + '\n', skin_1: [ // 仿 Mouseover Popup Image Viewer 样式 '#GM_config { background: #EEE; }', '#GM_config textarea { width: 98%; height: 45px; margin-top: 5px; }', '#GM_config .config_var { font-size: 12px; }', '#GM_config .inline .config_var { margin-left: 15px; }', '#GM_config .field_label { display: inline-block; font-weight: normal; }', '#GM_config { padding: 20px 30px; margin: 0; }', '#GM_config .config_header { margin-bottom: 10px; }', '#GM_config div.config_var { padding: 7px 0; }', ].join('\n') + '\n', basicPrefix: 'GM_config', stylish: '', } } if (args.length == 1 && typeof args[0].id == 'string' && typeof args[0].appendChild != 'function') var settings = args[0] else { // Provide backwards-compatibility with argument style intialization var settings = {} // loop through GM_config.init() arguments for (var i = 0, l = args.length, arg; i < l; ++i) { arg = args[i] // An element to use as the config window if (typeof arg.appendChild == 'function') { settings.frame = arg continue } switch (typeof arg) { case 'object': for (var j in arg) { // could be a callback functions or settings object if (typeof arg[j] != 'function') { // we are in the settings object if (typeof arg[j] == 'string') { settings.frameStyle = arg } else { settings.fields = arg // store settings object } break // leave the loop } // otherwise it must be a callback function if (!settings.events) settings.events = {} settings.events[j] = arg[j] } break case 'function': // passing a bare function is set to open callback settings.events = { open: arg } break case 'string': // could be custom CSS or the title string // if (/[\w\.]+\s*\{\s*[\w-]+\s*:\s*\w+[\s|\S]*\}/.test(arg)) if (/[\w\.]+\s*\{\s*[\w-]+\s*:[\s|\S]*\}/.test(arg)) settings.css = arg else if (arg) settings.title = arg break } } } /* Initialize everything using the new settings object */ // Set the id if (settings.id) config.id = settings.id else if (typeof config.id == 'undefined') config.id = 'GM_config' // Set the title if (settings.title) config.title = settings.title // Set the custom css if (settings.css) config.css.stylish = settings.css if (settings.skin) { var skin = config.css['skin_' + settings.skin] if (skin) { config.css.basic += skin } } // Set the frame if (settings.frame) config.frame = settings.frame if (settings.frameStyle) config.frameStyle = settings.frameStyle config.isTabs = settings.isTabs // Set the event callbacks if (settings.events) { var events = settings.events for (var e in events) config['on' + e.charAt(0).toUpperCase() + e.slice(1)] = events[e] } // Create the fields if (settings.fields) { var stored = config.read(), // read the stored settings fields = settings.fields, customTypes = settings.types || {} for (var id in fields) { var field = fields[id] // for each field definition create a field object if (field) config.fields[id] = new GM_configField(field, stored[id], id, customTypes[field.type]) else if (config.fields[id]) delete config.fields[id] } } // If the id has changed we must modify the default style if (config.id != config.css.basicPrefix) { config.css.basic = config.css.basic.replace( new RegExp('#' + config.css.basicPrefix, 'gm'), '#' + config.id, ) config.css.basicPrefix = config.id } } GM_configStruct.prototype = { // Support old method of initalizing init: function () { GM_configInit(this, arguments) this.onInit() }, // call GM_config.open() from your script to open the menu open: function () { // Die if the menu is already open on this page // You can have multiple instances but you can't open the same instance twice var match = top.document.getElementById(this.id) if (match && (match.tagName == 'IFRAME' || match.childNodes.length > 0)) return // Sometimes "this" gets overwritten so create an alias var config = this // Function to build the mighty config window :) function buildConfigWin(body, head) { var create = config.create, fields = config.fields, configId = config.id, bodyWrapper = create('div', { id: configId + '_wrapper' }) // Append the style which is our default style plus the user style head.appendChild( create('style', { type: 'text/css', textContent: config.css.basic + config.css.stylish, }), ) // Add header and title bodyWrapper.appendChild( create( 'div', { id: configId + '_header', className: 'config_header block center', }, config.title, ), ) // Append elements var section = bodyWrapper, secNum = 0 // Section count var lastParentNode = null // loop through fields for (var id in fields) { var field = fields[id], settings = field.settings if (settings.section) { // the start of a new section section = bodyWrapper.appendChild( create('div', { className: 'section_header_holder', id: configId + '_section_' + secNum, }), ) if (Object.prototype.toString.call(settings.section) !== '[object Array]') settings.section = [settings.section] if (settings.section[0]) section.appendChild( create( 'div', { className: 'section_header center', id: configId + '_section_header_' + secNum, }, settings.section[0], ), ) if (settings.section[1]) section.appendChild( create( 'p', { className: 'section_desc center', id: configId + '_section_desc_' + secNum, }, settings.section[1], ), ) ++secNum } if (settings.line == 'start' && lastParentNode) { // 切换到下一行 lastParentNode = null } // Create field elements and append to current section ;(lastParentNode || section).appendChild( (field.wrapper = field.toNode(configId, lastParentNode)), ) if (settings.line == 'start') { lastParentNode = field.wrapper lastParentNode.classList.add('inline') } else if (settings.line == 'end') { lastParentNode = null } } // Add save and close buttons bodyWrapper.appendChild( create( 'div', { id: configId + '_buttons_holder' }, create('button', { id: configId + '_saveBtn', textContent: '确定', title: '部分选项需要刷新页面才能生效', className: 'saveclose_buttons', onclick: function () { config.save() config.close() }, }), create('button', { id: configId + '_closeBtn', textContent: '取消', title: '取消本次设置,所有选项还原', className: 'saveclose_buttons', onclick: function () { config.close() }, }), create( 'div', { className: 'reset_holder block' }, // Reset link create('a', { id: configId + '_resetLink', textContent: '恢复默认设置', href: '#', title: '恢复所有设置的内容为默认值', className: 'reset', onclick: function (e) { e.preventDefault() config.reset() }, }), ), ), ) body.appendChild(bodyWrapper) // Paint everything to window at once config.center() // Show and center iframe top.addEventListener('resize', config.center, false) // Center frame on resize // Call the open() callback function config.onOpen( config.frame.contentDocument || config.frame.ownerDocument, config.frame.contentWindow || window, config.frame, ) if (config.isTabs) { config.toTabs() } // Close frame on window close window.addEventListener( 'beforeunload', function () { config.close() }, false, ) // Now that everything is loaded, make it visible config.frame.style.display = 'block' config.isOpen = true } // Change this in the onOpen callback using this.frame.setAttribute('style', '') var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;' + ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;' + ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;' + ' width: 75%; z-index: 999999999;' // Either use the element passed to init() or create an iframe if (this.frame) { this.frame.id = this.id // Allows for prefixing styles with the config id this.frame.setAttribute('style', defaultStyle) buildConfigWin(this.frame, this.frame.ownerDocument.getElementsByTagName('head')[0]) } else { // Create frame top.document.body.appendChild( (this.frame = this.create('iframe', { id: this.id, style: defaultStyle, })), ) if (this.frameStyle) { Object.keys(this.frameStyle).forEach(function (key) { config.frame.style[key] = config.frameStyle[key] }) } // In WebKit src can't be set until it is added to the page this.frame.src = 'about:blank' // we wait for the iframe to load before we can modify it this.frame.addEventListener( 'load', function (e) { var frame = config.frame var body = frame.contentDocument.getElementsByTagName('body')[0] body.id = config.id // Allows for prefixing styles with the config id buildConfigWin(body, frame.contentDocument.getElementsByTagName('head')[0]) }, false, ) } }, save: function () { var forgotten = this.write() this.onSave(forgotten) // Call the save() callback function }, close: function () { if (!this.frame) return // If frame is an iframe then remove it if (this.frame.contentDocument) { this.remove(this.frame) this.frame = null } else { // else wipe its content this.frame.innerHTML = '' this.frame.style.display = 'none' } // Null out all the fields so we don't leak memory var fields = this.fields for (var id in fields) { var field = fields[id] field.wrapper = null field.node = null } this.onClose() // Call the close() callback function this.isOpen = false }, set: function (name, val) { this.fields[name].value = val if (this.fields[name].node) { this.fields[name].reload() } }, get: function (name, getLive) { var field = this.fields[name], fieldVal = null if (getLive && field.node) { fieldVal = field.toValue() } return fieldVal != null ? fieldVal : field.value }, write: function (store, obj) { if (!obj) { var values = {}, forgotten = {}, fields = this.fields for (var id in fields) { var field = fields[id] var value = field.toValue() if (field.save) { if (value != null) { values[id] = value field.value = value } else values[id] = field.value } else forgotten[id] = value } } try { this.setValue(store || this.id, this.stringify(obj || values)) } catch (e) { this.log('GM_config failed to save settings!') } return forgotten }, read: function (store) { try { var rval = this.parser(this.getValue(store || this.id, '{}')) } catch (e) { this.log('GM_config failed to read saved settings!') var rval = {} } return rval }, reset: function () { var fields = this.fields // Reset all the fields for (var id in fields) fields[id].reset() this.onReset() // Call the reset() callback function }, create: function () { switch (arguments.length) { case 1: var A = document.createTextNode(arguments[0]) break default: var A = document.createElement(arguments[0]), B = arguments[1] for (var b in B) { if (b.indexOf('on') == 0) A.addEventListener(b.substring(2), B[b], false) else if ( ',style,accesskey,id,name,src,href,which,for'.indexOf(',' + b.toLowerCase()) != -1 ) A.setAttribute(b, B[b]) else if (typeof B[b] != 'undefined') A[b] = B[b] } if (typeof arguments[2] == 'string') A.innerHTML = arguments[2] else for (var i = 2, len = arguments.length; i < len; ++i) A.appendChild(arguments[i]) } return A }, center: function () { var node = this.frame if (!node) return var style = node.style, beforeOpacity = style.opacity if (style.display == 'none') style.opacity = '0' style.display = '' style.top = Math.floor(top.innerHeight / 2 - node.offsetHeight / 2) + 'px' style.left = Math.floor(top.innerWidth / 2 - node.offsetWidth / 2) + 'px' style.opacity = '1' }, remove: function (el) { if (el && el.parentNode) el.parentNode.removeChild(el) }, toTabs: function () { // 转为 tab 的形式 var body = this.frame.tagName == 'IFRAME' ? this.frame.contentWindow.document : this.frame, configId = this.id var $ = function (id) { return body.getElementById(configId + '_' + id) } var headers = body.querySelectorAll('.section_header') if (!headers.length) return var anch = this.create('div', { // id: configId + '_tab_holder', className: 'nav-tabs', }) for (var i = 0, header; i < headers.length; i++) { header = headers[i] if (i == 0) { header.classList.add('active') } anch.appendChild(header) } anch.addEventListener('click', this.toggleTab.bind(this), false) $('section_0').parentNode.insertBefore(anch, $('section_0')) var curTab = localStorage.getItem('picviewerCE.config.curTab') || 0 this.toggleTab(parseInt(curTab, 10)) }, toggleTab: function (e) { var body = this.frame.tagName == 'IFRAME' ? this.frame.contentWindow.document : this.frame, configId = this.id var curTab = typeof e == 'number' ? e : /\_(\d+)/.exec(e.target.id)[1] ;[].forEach.call(body.querySelectorAll('.section_header'), function (header, i) { if (i == curTab) { header.classList.add('active') } else { header.classList.remove('active') } }) ;[].forEach.call(body.querySelectorAll('.section_header_holder'), function (holder, i) { holder.style.display = i == curTab ? 'block' : 'none' }) localStorage.setItem('picviewerCE.config.curTab', curTab) }, } // Define a bunch of API stuff ;(function () { var isGM = typeof GM_getValue != 'undefined' && typeof GM_getValue('a', 'b') != 'undefined', setValue, getValue, stringify, parser // Define value storing and reading API if (!isGM) { setValue = function (name, value) { return localStorage.setItem(name, value) } getValue = function (name, def) { var s = localStorage.getItem(name) return s == null ? def : s } // We only support JSON parser outside GM stringify = JSON.stringify parser = JSON.parse } else { setValue = GM_setValue getValue = GM_getValue stringify = typeof JSON == 'undefined' ? function (obj) { return obj.toSource() } : JSON.stringify parser = typeof JSON == 'undefined' ? function (jsonData) { return new Function('return ' + jsonData + ';')() } : JSON.parse } GM_configStruct.prototype.isGM = isGM GM_configStruct.prototype.setValue = setValue GM_configStruct.prototype.getValue = getValue GM_configStruct.prototype.stringify = stringify GM_configStruct.prototype.parser = parser GM_configStruct.prototype.log = window.console ? console.log : isGM && typeof GM_log != 'undefined' ? GM_log : window.opera ? opera.postError : function () { /* no logging */ } })() function GM_configDefaultValue(type, options) { var value if (type && type.indexOf('unsigned ') == 0) type = type.substring(9) switch (type) { case 'radio': case 'select': value = options[0] break case 'checkbox': value = false break case 'int': case 'integer': case 'float': case 'number': value = 0 break default: value = '' } return value } function GM_configField(settings, stored, id, customType) { // Store the field's settings this.settings = settings this.id = id this.node = null this.wrapper = null this.save = typeof settings.save == 'undefined' ? true : settings.save // Buttons are static and don't have a stored value if (settings.type == 'button') this.save = false if (settings.type == 'span') this.save = false // if a default value wasn't passed through init() then // if the type is custom use its default value // else use default value for type // else use the default value passed through init() this['default'] = typeof settings['default'] == 'undefined' ? customType ? customType['default'] : GM_configDefaultValue(settings.type, settings.options) : settings['default'] // Store the field's value this.value = typeof stored == 'undefined' ? this['default'] : stored // Setup methods for a custom type if (customType) { this.toNode = customType.toNode this.toValue = customType.toValue this.reset = customType.reset } } GM_configField.prototype = { create: GM_configStruct.prototype.create, toNode: function (configId, lastParentNode) { var field = this.settings, value = this.value, options = field.options, type = field.type, id = this.id, labelPos = field.labelPos, create = this.create function addLabel(pos, labelEl, parentNode, beforeEl) { if (!beforeEl) { beforeEl = lastParentNode ? parentNode.lastChild : parentNode.firstChild // oneLine 的修正 } switch (pos) { case 'right': case 'below': if (pos == 'below') parentNode.appendChild(create('br', {})) parentNode.appendChild(labelEl) break default: if (pos == 'above') parentNode.insertBefore(create('br', {}), beforeEl) parentNode.insertBefore(labelEl, beforeEl) } } var retNode = create('div', { className: 'config_var', id: configId + '_' + id + '_var', title: field.title || '', }), firstProp // Retrieve the first prop for (var i in field) { firstProp = i break } var label = field.label && type != 'button' ? create( 'label', { id: configId + '_' + id + '_field_label', for: configId + '_field_' + id, className: 'field_label', }, field.label, ) : null switch (type) { case 'span': label = null this.node = create('span', { innerHTML: field.label, className: 'field_label', title: field.title, style: field.style, }) retNode = this.node break case 'textarea': retNode.appendChild( (this.node = create('textarea', { innerHTML: value, id: configId + '_field_' + id, className: 'block' + (field.className ? ' ' + field.className : ''), cols: field.cols ? field.cols : 20, rows: field.rows ? field.rows : 2, placeholder: field.placeholder, })), ) break case 'radio': var wrap = create('div', { id: configId + '_field_' + id, className: field.className, }) this.node = wrap for (var i = 0, len = options.length; i < len; ++i) { var radLabel = create( 'label', { className: 'radio_label', }, options[i], ) var rad = wrap.appendChild( create('input', { value: options[i], type: 'radio', name: id, checked: options[i] == value, }), ) var radLabelPos = labelPos && (labelPos == 'left' || labelPos == 'right') ? labelPos : firstProp == 'options' ? 'left' : 'right' addLabel(radLabelPos, radLabel, wrap, rad) } retNode.appendChild(wrap) break case 'select': var wrap = create('select', { id: configId + '_field_' + id, }) this.node = wrap for (var i = 0, len = options.length; i < len; ++i) { var option = options[i] wrap.appendChild( create( 'option', { value: option, selected: option == value, }, option, ), ) } retNode.appendChild(wrap) break default: // fields using input elements var props = { id: configId + '_field_' + id, type: type, value: type == 'button' ? field.label : value, } switch (type) { case 'checkbox': props.checked = value break case 'button': props.size = field.size ? field.size : 25 if (field.script) field.click = field.script if (field.click) props.onclick = field.click break case 'hidden': break default: // type = text, int, or float props.type = 'text' props.size = field.size ? field.size : 25 } retNode.appendChild((this.node = create('input', props))) } if (label) { // If the label is passed first, insert it before the field // else insert it after if (!labelPos) labelPos = firstProp == 'label' || type == 'radio' ? 'left' : 'right' addLabel(labelPos, label, retNode) } return retNode }, toValue: function () { var node = this.node, field = this.settings, type = field.type, unsigned = false, rval = null if (!node) return rval if (type.indexOf('unsigned ') == 0) { type = type.substring(9) unsigned = true } switch (type) { case 'checkbox': rval = node.checked break case 'select': rval = node[node.selectedIndex].value break case 'radio': var radios = node.getElementsByTagName('input') for (var i = 0, len = radios.length; i < len; ++i) if (radios[i].checked) rval = radios[i].value break case 'button': break case 'int': case 'integer': case 'float': case 'number': var num = Number(node.value) var warn = '字符 "' + field.label + '" 必须输入' + (unsigned ? ' 正 ' : 'n ') + '整数值' if ( isNaN(num) || (type.substr(0, 3) == 'int' && Math.ceil(num) != Math.floor(num)) || (unsigned && num < 0) ) { alert(warn + '.') return null } if (!this._checkNumberRange(num, warn)) return null rval = num break default: rval = node.value break } return rval // value read successfully }, reset: function () { var node = this.node, field = this.settings, type = field.type if (!node) return switch (type) { case 'checkbox': node.checked = this['default'] break case 'select': for (var i = 0, len = node.options.length; i < len; ++i) if (node.options[i].value == this['default']) node.selectedIndex = i break case 'radio': var radios = node.getElementsByTagName('input') for (var i = 0, len = radios.length; i < len; ++i) if (radios[i].value == this['default']) radios[i].checked = true break case 'button': break default: node.value = this['default'] break } }, remove: function (el) { GM_configStruct.prototype.remove(el || this.wrapper) this.wrapper = null this.node = null }, reload: function () { var wrapper = this.wrapper if (wrapper) { var fieldParent = wrapper.parentNode fieldParent.insertBefore((this.wrapper = this.toNode()), wrapper) this.remove(wrapper) } }, _checkNumberRange: function (num, warn) { var field = this.settings if (typeof field.min == 'number' && num < field.min) { alert(warn + '必须大于或等于' + field.min + '.') return null } if (typeof field.max == 'number' && num > field.max) { alert(warn + '必须小于或等于' + field.max + '.') return null } return true }, } // Create default instance of GM_config var GM_config = new GM_configStruct()