GM_config_zh

GM_config 中文版

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/447340/1252278/GM_config_zh.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          GM_config_zh
// @author        Mike Medley & zxf10608 & maomao1996
// @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()