GM_config_zh

GM_config 中文版

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/447340/1252278/GM_config_zh.js

// ==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()