GM_config-lib

GM_config is a user script library that allows the user to edit certain saved values through a graphical settings menu.

当前为 2023-05-09 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/459911/1188061/GM_config-lib.js

  1. // ==UserScript==
  2. // @author sizzlemctwizzle
  3. // ==UserLibrary==
  4. // @name GM_config-lib
  5. // @namespace sizzle
  6. // @version fix-2023.05.09_orig-2021.11.21
  7. // @description GM_config is a user script library that allows the user to edit certain saved values through a graphical settings menu.
  8. // @license LGPL-3.0
  9. // ==/UserLibrary==
  10. // ==/UserScript==
  11. /*
  12. Copyright 2009+, GM_config Contributors (https://github.com/sizzlemctwizzle/GM_config)
  13.  
  14. GM_config Collaborators/Contributors:
  15. Mike Medley <medleymind@gmail.com>
  16. Joe Simmons
  17. Izzy Soft
  18. Marti Martz
  19. Adam Thompson-Sharpe
  20.  
  21. GM_config is distributed under the terms of the GNU Lesser General Public License.
  22.  
  23. GM_config is free software: you can redistribute it and/or modify
  24. it under the terms of the GNU Lesser General Public License as published by
  25. the Free Software Foundation, either version 3 of the License, or
  26. (at your option) any later version.
  27.  
  28. This program is distributed in the hope that it will be useful,
  29. but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. GNU Lesser General Public License for more details.
  32.  
  33. You should have received a copy of the GNU Lesser General Public License
  34. along with this program. If not, see <https://www.gnu.org/licenses/>.
  35. */
  36.  
  37. // ==UserScript==
  38. // @exclude *
  39. // @author Mike Medley <medleymind@gmail.com> (https://github.com/sizzlemctwizzle/GM_config)
  40. // @icon https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config_icon_large.png
  41.  
  42. // ==UserLibrary==
  43. // @name GM_config
  44. // @description A lightweight, reusable, cross-browser graphical settings framework for inclusion in user scripts.
  45. // @copyright 2009+, Mike Medley (https://github.com/sizzlemctwizzle)
  46. // @license LGPL-3.0-or-later; https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/LICENSE
  47.  
  48. // @homepageURL https://openuserjs.org/libs/sizzle/GM_config
  49. // @homepageURL https://github.com/sizzlemctwizzle/GM_config
  50. // @supportURL https://github.com/sizzlemctwizzle/GM_config/issues
  51.  
  52. // ==/UserScript==
  53.  
  54. // ==/UserLibrary==
  55.  
  56.  
  57. // The GM_config constructor
  58. function GM_configStruct() {
  59. // call init() if settings were passed to constructor
  60. if (arguments.length) {
  61. GM_configInit(this, arguments);
  62. this.onInit();
  63. }
  64. }
  65.  
  66. // This is the initializer function
  67. function GM_configInit(config, args) {
  68. // Initialize instance variables
  69. if (typeof config.fields == "undefined") {
  70. config.fields = {};
  71. config.onInit = config.onInit || function() {};
  72. config.onOpen = config.onOpen || function() {};
  73. config.onSave = config.onSave || function() {};
  74. config.onClose = config.onClose || function() {};
  75. config.onReset = config.onReset || function() {};
  76. config.isOpen = false;
  77. config.title = 'User Script Settings';
  78. config.css = {
  79. basic: [
  80. "#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }",
  81. "#GM_config { background: #FFF; }",
  82. "#GM_config input[type='radio'] { margin-right: 8px; }",
  83. "#GM_config .indent40 { margin-left: 40%; }",
  84. "#GM_config .field_label { font-size: 12px; font-weight: bold; margin-right: 6px; }",
  85. "#GM_config .radio_label { font-size: 12px; }",
  86. "#GM_config .block { display: block; }",
  87. "#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }",
  88. "#GM_config .reset, #GM_config .reset a," +
  89. " #GM_config_buttons_holder { color: #000; text-align: right; }",
  90. "#GM_config .config_header { font-size: 20pt; margin: 0; }",
  91. "#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
  92. "#GM_config .center { text-align: center; }",
  93. "#GM_config .section_header_holder { margin-top: 8px; }",
  94. "#GM_config .config_var { margin: 0 0 4px; }",
  95. "#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;",
  96. " font-size: 13pt; margin: 0; }",
  97. "#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;" +
  98. " font-size: 9pt; margin: 0 0 6px; }"
  99. ].join('\n') + '\n',
  100. basicPrefix: "GM_config",
  101. stylish: ""
  102. };
  103. }
  104.  
  105. if (args.length == 1 &&
  106. typeof args[0].id == "string" &&
  107. typeof args[0].appendChild != "function") var settings = args[0];
  108. else {
  109. // Provide backwards-compatibility with argument style intialization
  110. var settings = {};
  111.  
  112. // loop through GM_config.init() arguments
  113. for (var i = 0, l = args.length, arg; i < l; ++i) {
  114. arg = args[i];
  115.  
  116. // An element to use as the config window
  117. if (typeof arg.appendChild == "function") {
  118. settings.frame = arg;
  119. continue;
  120. }
  121.  
  122. switch (typeof arg) {
  123. case 'object':
  124. for (var j in arg) { // could be a callback functions or settings object
  125. if (typeof arg[j] != "function") { // we are in the settings object
  126. settings.fields = arg; // store settings object
  127. break; // leave the loop
  128. } // otherwise it must be a callback function
  129. if (!settings.events) settings.events = {};
  130. settings.events[j] = arg[j];
  131. }
  132. break;
  133. case 'function': // passing a bare function is set to open callback
  134. settings.events = {onOpen: arg};
  135. break;
  136. case 'string': // could be custom CSS or the title string
  137. if (/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(arg))
  138. settings.css = arg;
  139. else
  140. settings.title = arg;
  141. break;
  142. }
  143. }
  144. }
  145.  
  146. /* Initialize everything using the new settings object */
  147. // Set the id
  148. if (settings.id) config.id = settings.id;
  149. else if (typeof config.id == "undefined") config.id = 'GM_config';
  150.  
  151. // Set the title
  152. if (settings.title) config.title = settings.title;
  153.  
  154. // Set the custom css
  155. if (settings.css) config.css.stylish = settings.css;
  156.  
  157. // Set the frame
  158. if (settings.frame) config.frame = settings.frame;
  159.  
  160. // Set the event callbacks
  161. if (settings.events) {
  162. var events = settings.events;
  163. for (var e in events)
  164. config["on" + e.charAt(0).toUpperCase() + e.slice(1)] = events[e];
  165. }
  166.  
  167. // Create the fields
  168. if (settings.fields) {
  169. var stored = config.read(), // read the stored settings
  170. fields = settings.fields,
  171. customTypes = settings.types || {},
  172. configId = config.id;
  173.  
  174. for (var id in fields) {
  175. var field = fields[id];
  176.  
  177. // for each field definition create a field object
  178. if (field)
  179. config.fields[id] = new GM_configField(field, stored[id], id,
  180. customTypes[field.type], configId);
  181. else if (config.fields[id]) delete config.fields[id];
  182. }
  183. }
  184.  
  185. // If the id has changed we must modify the default style
  186. if (config.id != config.css.basicPrefix) {
  187. config.css.basic = config.css.basic.replace(
  188. new RegExp('#' + config.css.basicPrefix, 'gm'), '#' + config.id);
  189. config.css.basicPrefix = config.id;
  190. }
  191. }
  192.  
  193. GM_configStruct.prototype = {
  194. // Support old method of initalizing
  195. init: function() {
  196. GM_configInit(this, arguments);
  197. this.onInit();
  198. },
  199.  
  200. // call GM_config.open() from your script to open the menu
  201. open: function () {
  202. // Die if the menu is already open on this page
  203. // You can have multiple instances but you can't open the same instance twice
  204. var match = document.getElementById(this.id);
  205. if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0)) return;
  206.  
  207. // Sometimes "this" gets overwritten so create an alias
  208. var config = this;
  209.  
  210. // Function to build the mighty config window :)
  211. function buildConfigWin (body, head) {
  212. var create = config.create,
  213. fields = config.fields,
  214. configId = config.id,
  215. bodyWrapper = create('div', {id: configId + '_wrapper'});
  216.  
  217. // Append the style which is our default style plus the user style
  218. head.appendChild(
  219. create('style', {
  220. type: 'text/css',
  221. textContent: config.css.basic + config.css.stylish
  222. }));
  223.  
  224. // Add header and title
  225. bodyWrapper.appendChild(create('div', {
  226. id: configId + '_header',
  227. className: 'config_header block center'
  228. }, config.title));
  229.  
  230. // Append elements
  231. var section = bodyWrapper,
  232. secNum = 0; // Section count
  233.  
  234. // loop through fields
  235. for (var id in fields) {
  236. var field = fields[id],
  237. settings = field.settings;
  238.  
  239. if (settings.section) { // the start of a new section
  240. section = bodyWrapper.appendChild(create('div', {
  241. className: 'section_header_holder',
  242. id: configId + '_section_' + secNum
  243. }));
  244.  
  245. if (Object.prototype.toString.call(settings.section) !== '[object Array]')
  246. settings.section = [settings.section];
  247.  
  248. if (settings.section[0])
  249. section.appendChild(create('div', {
  250. className: 'section_header center',
  251. id: configId + '_section_header_' + secNum
  252. }, settings.section[0]));
  253.  
  254. if (settings.section[1])
  255. section.appendChild(create('p', {
  256. className: 'section_desc center',
  257. id: configId + '_section_desc_' + secNum
  258. }, settings.section[1]));
  259. ++secNum;
  260. }
  261.  
  262. // Create field elements and append to current section
  263. section.appendChild((field.wrapper = field.toNode()));
  264. }
  265.  
  266. // Add save and close buttons
  267. bodyWrapper.appendChild(create('div',
  268. {id: configId + '_buttons_holder'},
  269.  
  270. create('button', {
  271. id: configId + '_saveBtn',
  272. textContent: 'Save',
  273. title: 'Save settings',
  274. className: 'saveclose_buttons',
  275. onclick: function () { config.save() }
  276. }),
  277.  
  278. create('button', {
  279. id: configId + '_closeBtn',
  280. textContent: 'Close',
  281. title: 'Close window',
  282. className: 'saveclose_buttons',
  283. onclick: function () { config.close() }
  284. }),
  285.  
  286. create('div',
  287. {className: 'reset_holder block'},
  288.  
  289. // Reset link
  290. create('a', {
  291. id: configId + '_resetLink',
  292. textContent: 'Reset to defaults',
  293. href: '#',
  294. title: 'Reset fields to default values',
  295. className: 'reset',
  296. onclick: function(e) { e.preventDefault(); config.reset() }
  297. })
  298. )));
  299.  
  300. body.appendChild(bodyWrapper); // Paint everything to window at once
  301. config.center(); // Show and center iframe
  302. window.addEventListener('resize', config.center, false); // Center frame on resize
  303.  
  304. // Call the open() callback function
  305. config.onOpen(config.frame.contentDocument || config.frame.ownerDocument,
  306. config.frame.contentWindow || window,
  307. config.frame);
  308.  
  309. // Close frame on window close
  310. window.addEventListener('beforeunload', function () {
  311. config.close();
  312. }, false);
  313.  
  314. // Now that everything is loaded, make it visible
  315. config.frame.style.display = "block";
  316. config.isOpen = true;
  317. }
  318.  
  319. // Change this in the onOpen callback using this.frame.setAttribute('style', '')
  320. var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;'
  321. + ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;'
  322. + ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;'
  323. + ' width: 75%; z-index: 9999;';
  324.  
  325. // Either use the element passed to init() or create an iframe
  326. if (this.frame) {
  327. this.frame.id = this.id; // Allows for prefixing styles with the config id
  328. this.frame.setAttribute('style', defaultStyle);
  329. buildConfigWin(this.frame, this.frame.ownerDocument.getElementsByTagName('head')[0]);
  330. } else {
  331. // Create frame
  332. document.body.appendChild((this.frame = this.create('iframe', {
  333. id: this.id,
  334. style: defaultStyle
  335. })));
  336.  
  337. // In WebKit src can't be set until it is added to the page
  338. this.frame.src = 'about:blank';
  339. // we wait for the iframe to load before we can modify it
  340. var that = this;
  341. this.frame.addEventListener('load', function(e) {
  342. var frame = config.frame;
  343. if (frame.src && !frame.contentDocument) {
  344. // Some agents need this as an empty string for newer context implementations
  345. frame.src = "";
  346. } else if (!frame.contentDocument) {
  347. that.log("GM_config failed to initialize default settings dialog node!");
  348. }
  349. var body = frame.contentDocument.getElementsByTagName('body')[0];
  350. body.id = config.id; // Allows for prefixing styles with the config id
  351. buildConfigWin(body, frame.contentDocument.getElementsByTagName('head')[0]);
  352. }, false);
  353. }
  354. },
  355.  
  356. save: function () {
  357. var forgotten = this.write();
  358. this.onSave(forgotten); // Call the save() callback function
  359. },
  360.  
  361. close: function() {
  362. // fix: Uncaught TypeError: this.frame is null (on page redirect)
  363. if (this.frame) {
  364. // If frame is an iframe then remove it
  365. if (this.frame.contentDocument) {
  366. this.remove(this.frame);
  367. this.frame = null;
  368. } else { // else wipe its content
  369. this.frame.innerHTML = "";
  370. this.frame.style.display = "none";
  371. }
  372. }
  373.  
  374. // Null out all the fields so we don't leak memory
  375. var fields = this.fields;
  376. for (var id in fields) {
  377. var field = fields[id];
  378. field.wrapper = null;
  379. field.node = null;
  380. }
  381.  
  382. this.onClose(); // Call the close() callback function
  383. this.isOpen = false;
  384. },
  385.  
  386. set: function (name, val) {
  387. this.fields[name].value = val;
  388.  
  389. if (this.fields[name].node) {
  390. this.fields[name].reload();
  391. }
  392. },
  393.  
  394. get: function (name, getLive) {
  395. var field = this.fields[name],
  396. fieldVal = null;
  397.  
  398. if (getLive && field.node) {
  399. fieldVal = field.toValue();
  400. }
  401.  
  402. return fieldVal != null ? fieldVal : field.value;
  403. },
  404.  
  405. write: function (store, obj) {
  406. if (!obj) {
  407. var values = {},
  408. forgotten = {},
  409. fields = this.fields;
  410.  
  411. for (var id in fields) {
  412. var field = fields[id];
  413. var value = field.toValue();
  414.  
  415. if (field.save) {
  416. if (value != null) {
  417. values[id] = value;
  418. field.value = value;
  419. } else
  420. values[id] = field.value;
  421. } else
  422. forgotten[id] = value;
  423. }
  424. }
  425. try {
  426. this.setValue(store || this.id, this.stringify(obj || values));
  427. } catch(e) {
  428. this.log("GM_config failed to save settings!");
  429. }
  430.  
  431. return forgotten;
  432. },
  433.  
  434. read: function (store) {
  435. try {
  436. var rval = this.parser(this.getValue(store || this.id, '{}'));
  437. } catch(e) {
  438. this.log("GM_config failed to read saved settings!");
  439. var rval = {};
  440. }
  441. return rval;
  442. },
  443.  
  444. reset: function () {
  445. var fields = this.fields;
  446.  
  447. // Reset all the fields
  448. for (var id in fields) fields[id].reset();
  449.  
  450. this.onReset(); // Call the reset() callback function
  451. },
  452.  
  453. create: function () {
  454. switch(arguments.length) {
  455. case 1:
  456. var A = document.createTextNode(arguments[0]);
  457. break;
  458. default:
  459. var A = document.createElement(arguments[0]),
  460. B = arguments[1];
  461. for (var b in B) {
  462. if (b.indexOf("on") == 0)
  463. A.addEventListener(b.substring(2), B[b], false);
  464. else if (",style,accesskey,id,name,src,href,which,for".indexOf("," +
  465. b.toLowerCase()) != -1)
  466. A.setAttribute(b, B[b]);
  467. else
  468. A[b] = B[b];
  469. }
  470. if (typeof arguments[2] == "string")
  471. A.innerHTML = arguments[2];
  472. else
  473. for (var i = 2, len = arguments.length; i < len; ++i)
  474. A.appendChild(arguments[i]);
  475. }
  476. return A;
  477. },
  478.  
  479. center: function () {
  480. var node = this.frame;
  481. if (!node) return;
  482. var style = node.style,
  483. beforeOpacity = style.opacity;
  484. if (style.display == 'none') style.opacity = '0';
  485. style.display = '';
  486. style.top = Math.floor((window.innerHeight / 2) - (node.offsetHeight / 2)) + 'px';
  487. style.left = Math.floor((window.innerWidth / 2) - (node.offsetWidth / 2)) + 'px';
  488. style.opacity = '1';
  489. },
  490.  
  491. remove: function (el) {
  492. if (el && el.parentNode) el.parentNode.removeChild(el);
  493. }
  494. };
  495.  
  496. // Define a bunch of API stuff
  497. (function() {
  498. var isGM = typeof GM_getValue != 'undefined' &&
  499. typeof GM_getValue('a', 'b') != 'undefined',
  500. setValue, getValue, stringify, parser;
  501.  
  502. // Define value storing and reading API
  503. if (!isGM) {
  504. setValue = function (name, value) {
  505. return localStorage.setItem(name, value);
  506. };
  507. getValue = function(name, def){
  508. var s = localStorage.getItem(name);
  509. return s == null ? def : s
  510. };
  511.  
  512. // We only support JSON parser outside GM
  513. stringify = JSON.stringify;
  514. parser = JSON.parse;
  515. } else {
  516. setValue = GM_setValue;
  517. getValue = GM_getValue;
  518. stringify = typeof JSON == "undefined" ?
  519. function(obj) {
  520. return obj.toSource();
  521. } : JSON.stringify;
  522. parser = typeof JSON == "undefined" ?
  523. function(jsonData) {
  524. return (new Function('return ' + jsonData + ';'))();
  525. } : JSON.parse;
  526. }
  527.  
  528. GM_configStruct.prototype.isGM = isGM;
  529. GM_configStruct.prototype.setValue = setValue;
  530. GM_configStruct.prototype.getValue = getValue;
  531. GM_configStruct.prototype.stringify = stringify;
  532. GM_configStruct.prototype.parser = parser;
  533. GM_configStruct.prototype.log = window.console ?
  534. console.log : (isGM && typeof GM_log != 'undefined' ?
  535. GM_log : (window.opera ?
  536. opera.postError : function(){ /* no logging */ }
  537. ));
  538. })();
  539.  
  540. function GM_configDefaultValue(type, options) {
  541. var value;
  542.  
  543. if (type.indexOf('unsigned ') == 0)
  544. type = type.substring(9);
  545.  
  546. switch (type) {
  547. case 'radio': case 'select':
  548. value = options[0];
  549. break;
  550. case 'checkbox':
  551. value = false;
  552. break;
  553. case 'int': case 'integer':
  554. case 'float': case 'number':
  555. value = 0;
  556. break;
  557. default:
  558. value = '';
  559. }
  560.  
  561. return value;
  562. }
  563.  
  564. function GM_configField(settings, stored, id, customType, configId) {
  565. // Store the field's settings
  566. this.settings = settings;
  567. this.id = id;
  568. this.configId = configId;
  569. this.node = null;
  570. this.wrapper = null;
  571. this.save = typeof settings.save == "undefined" ? true : settings.save;
  572.  
  573. // Buttons are static and don't have a stored value
  574. if (settings.type == "button") this.save = false;
  575.  
  576. // if a default value wasn't passed through init() then
  577. // if the type is custom use its default value
  578. // else use default value for type
  579. // else use the default value passed through init()
  580. this['default'] = typeof settings['default'] == "undefined" ?
  581. customType ?
  582. customType['default']
  583. : GM_configDefaultValue(settings.type, settings.options)
  584. : settings['default'];
  585.  
  586. // Store the field's value
  587. this.value = typeof stored == "undefined" ? this['default'] : stored;
  588.  
  589. // Setup methods for a custom type
  590. if (customType) {
  591. this.toNode = customType.toNode;
  592. this.toValue = customType.toValue;
  593. this.reset = customType.reset;
  594. }
  595. }
  596.  
  597. GM_configField.prototype = {
  598. create: GM_configStruct.prototype.create,
  599.  
  600. toNode: function() {
  601. var field = this.settings,
  602. value = this.value,
  603. options = field.options,
  604. type = field.type,
  605. id = this.id,
  606. configId = this.configId,
  607. labelPos = field.labelPos,
  608. create = this.create;
  609.  
  610. function addLabel(pos, labelEl, parentNode, beforeEl) {
  611. if (!beforeEl) beforeEl = parentNode.firstChild;
  612. switch (pos) {
  613. case 'right': case 'below':
  614. if (pos == 'below')
  615. parentNode.appendChild(create('br', {}));
  616. parentNode.appendChild(labelEl);
  617. break;
  618. default:
  619. if (pos == 'above')
  620. parentNode.insertBefore(create('br', {}), beforeEl);
  621. parentNode.insertBefore(labelEl, beforeEl);
  622. }
  623. }
  624.  
  625. var retNode = create('div', { className: 'config_var',
  626. id: configId + '_' + id + '_var',
  627. title: field.title || '' }),
  628. firstProp;
  629.  
  630. // Retrieve the first prop
  631. for (var i in field) { firstProp = i; break; }
  632.  
  633. var label = field.label && type != "button" ?
  634. create('label', {
  635. id: configId + '_' + id + '_field_label',
  636. for: configId + '_field_' + id,
  637. className: 'field_label'
  638. }, field.label) : null;
  639.  
  640. switch (type) {
  641. case 'textarea':
  642. retNode.appendChild((this.node = create('textarea', {
  643. innerHTML: value,
  644. id: configId + '_field_' + id,
  645. className: 'block',
  646. cols: (field.cols ? field.cols : 20),
  647. rows: (field.rows ? field.rows : 2)
  648. })));
  649. break;
  650. case 'radio':
  651. var wrap = create('div', {
  652. id: configId + '_field_' + id
  653. });
  654. this.node = wrap;
  655.  
  656. for (var i = 0, len = options.length; i < len; ++i) {
  657. var radLabel = create('label', {
  658. className: 'radio_label'
  659. }, options[i]);
  660.  
  661. var rad = wrap.appendChild(create('input', {
  662. value: options[i],
  663. type: 'radio',
  664. name: id,
  665. checked: options[i] == value
  666. }));
  667.  
  668. var radLabelPos = labelPos &&
  669. (labelPos == 'left' || labelPos == 'right') ?
  670. labelPos : firstProp == 'options' ? 'left' : 'right';
  671.  
  672. addLabel(radLabelPos, radLabel, wrap, rad);
  673. }
  674.  
  675. retNode.appendChild(wrap);
  676. break;
  677. case 'select':
  678. var wrap = create('select', {
  679. id: configId + '_field_' + id
  680. });
  681. this.node = wrap;
  682.  
  683. for (var i = 0, len = options.length; i < len; ++i) {
  684. var option = options[i];
  685. wrap.appendChild(create('option', {
  686. value: option,
  687. selected: option == value
  688. }, option));
  689. }
  690.  
  691. retNode.appendChild(wrap);
  692. break;
  693. default: // fields using input elements
  694. var props = {
  695. id: configId + '_field_' + id,
  696. type: type,
  697. value: type == 'button' ? field.label : value
  698. };
  699.  
  700. switch (type) {
  701. case 'checkbox':
  702. props.checked = value;
  703. break;
  704. case 'button':
  705. props.size = field.size ? field.size : 25;
  706. if (field.script) field.click = field.script;
  707. if (field.click) props.onclick = field.click;
  708. break;
  709. case 'hidden':
  710. break;
  711. default:
  712. // type = text, int, or float
  713. props.type = 'text';
  714. props.size = field.size ? field.size : 25;
  715. }
  716.  
  717. retNode.appendChild((this.node = create('input', props)));
  718. }
  719.  
  720. if (label) {
  721. // If the label is passed first, insert it before the field
  722. // else insert it after
  723. if (!labelPos)
  724. labelPos = firstProp == "label" || type == "radio" ?
  725. "left" : "right";
  726.  
  727. addLabel(labelPos, label, retNode);
  728. }
  729.  
  730. return retNode;
  731. },
  732.  
  733. toValue: function() {
  734. var node = this.node,
  735. field = this.settings,
  736. type = field.type,
  737. unsigned = false,
  738. rval = null;
  739.  
  740. if (!node) return rval;
  741.  
  742. if (type.indexOf('unsigned ') == 0) {
  743. type = type.substring(9);
  744. unsigned = true;
  745. }
  746.  
  747. switch (type) {
  748. case 'checkbox':
  749. rval = node.checked;
  750. break;
  751. case 'select':
  752. rval = node[node.selectedIndex].value;
  753. break;
  754. case 'radio':
  755. var radios = node.getElementsByTagName('input');
  756. for (var i = 0, len = radios.length; i < len; ++i)
  757. if (radios[i].checked)
  758. rval = radios[i].value;
  759. break;
  760. case 'button':
  761. break;
  762. case 'int': case 'integer':
  763. case 'float': case 'number':
  764. var num = Number(node.value);
  765. var warn = 'Field labeled "' + field.label + '" expects a' +
  766. (unsigned ? ' positive ' : 'n ') + 'integer value';
  767.  
  768. if (isNaN(num) || (type.substr(0, 3) == 'int' &&
  769. Math.ceil(num) != Math.floor(num)) ||
  770. (unsigned && num < 0)) {
  771. alert(warn + '.');
  772. return null;
  773. }
  774.  
  775. if (!this._checkNumberRange(num, warn))
  776. return null;
  777. rval = num;
  778. break;
  779. default:
  780. rval = node.value;
  781. break;
  782. }
  783.  
  784. return rval; // value read successfully
  785. },
  786.  
  787. reset: function() {
  788. var node = this.node,
  789. field = this.settings,
  790. type = field.type;
  791.  
  792. if (!node) return;
  793.  
  794. switch (type) {
  795. case 'checkbox':
  796. node.checked = this['default'];
  797. break;
  798. case 'select':
  799. for (var i = 0, len = node.options.length; i < len; ++i)
  800. if (node.options[i].textContent == this['default'])
  801. node.selectedIndex = i;
  802. break;
  803. case 'radio':
  804. var radios = node.getElementsByTagName('input');
  805. for (var i = 0, len = radios.length; i < len; ++i)
  806. if (radios[i].value == this['default'])
  807. radios[i].checked = true;
  808. break;
  809. case 'button' :
  810. break;
  811. default:
  812. node.value = this['default'];
  813. break;
  814. }
  815. },
  816.  
  817. remove: function(el) {
  818. GM_configStruct.prototype.remove(el || this.wrapper);
  819. this.wrapper = null;
  820. this.node = null;
  821. },
  822.  
  823. reload: function() {
  824. var wrapper = this.wrapper;
  825. if (wrapper) {
  826. var fieldParent = wrapper.parentNode;
  827. fieldParent.insertBefore((this.wrapper = this.toNode()), wrapper);
  828. this.remove(wrapper);
  829. }
  830. },
  831.  
  832. _checkNumberRange: function(num, warn) {
  833. var field = this.settings;
  834. if (typeof field.min == "number" && num < field.min) {
  835. alert(warn + ' greater than or equal to ' + field.min + '.');
  836. return null;
  837. }
  838.  
  839. if (typeof field.max == "number" && num > field.max) {
  840. alert(warn + ' less than or equal to ' + field.max + '.');
  841. return null;
  842. }
  843. return true;
  844. }
  845. };
  846.  
  847. // Create default instance of GM_config
  848. var GM_config = new GM_configStruct();