GM_config_zh-CN

A lightweight, reusable, cross-browser graphical settings framework for inclusion in user scripts.

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/398240/832590/GM_config_zh-CN.js

  1. // ==UserScript==
  2. // @name GM_config_zh-CN
  3. // @author Mike Medley & zxf10608
  4. // @version 1.3.7
  5. // @description GM_config_中文版
  6. // @grant GM_getValue
  7. // @grant GM_setValue
  8. // @grant GM_deleteValue
  9. // @exclude *
  10. // @license LGPL 3
  11. // ==/UserScript==
  12.  
  13. /*
  14. 优化说明
  15. 1、改成中文 "确定"、"取消" 按钮。
  16. 2、select 新增了 textContents 数组。
  17. 3、新增了skin: 'tab'换页切换菜单样式
  18. 4、更新部分翻译
  19. 5、优化字体显示效果
  20. 6、优化同一行内CSS样式
  21. */
  22.  
  23.  
  24. // The GM_config constructor
  25. function GM_configStruct() {
  26. // call init() if settings were passed to constructor
  27. if (arguments.length) {
  28. GM_configInit(this, arguments);
  29. this.onInit();
  30. }
  31. }
  32.  
  33. // This is the initializer function
  34. function GM_configInit(config, args) {
  35. // Initialize instance variables
  36. if (typeof config.fields == "undefined") {
  37. config.fields = {};
  38. config.onInit = config.onInit || function() {};
  39. config.onOpen = config.onOpen || function() {};
  40. config.onSave = config.onSave || function() {};
  41. config.onClose = config.onClose || function() {};
  42. config.onReset = config.onReset || function() {};
  43. config.isOpen = false;
  44. config.title = '用户脚本设置';
  45. config.css = {
  46. basic: [
  47. "#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }",
  48. "#GM_config { background: #FFF; }",
  49. "#GM_config input[type='radio'] { margin-right: 8px; }",
  50. "#GM_config .indent40 { margin-left: 40%; }",
  51. "#GM_config .field_label { font-size: 14px; font-weight: bold; margin-right: 6px; }",
  52. "#GM_config .radio_label { font-size: 14px; }",
  53. "#GM_config .block { display: block; }",
  54. "#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }",
  55. "#GM_config .reset, #GM_config .reset a," +
  56. " #GM_config_buttons_holder { color: #000; text-align: right; }",
  57. "#GM_config .config_header { font-size: 20pt; margin: 0; }",
  58. "#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
  59. "#GM_config .center { text-align: center; }",
  60. "#GM_config .section_header_holder { margin-top: 8px; }",
  61. "#GM_config .config_var { margin: 0 0 4px; }",
  62. "#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;" +
  63. " font-size: 12pt; margin: 0; }",
  64. "#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;" +
  65. " font-size: 10pt; margin: 0 0 6px; }",
  66. // newer
  67. "#GM_config input[type='number'] { width: 60px; }",
  68. "#GM_config .nav-tabs { margin: 10 0}",
  69. "#GM_config .nav-tabs > div { display: inline; padding: 3px 10px; }",
  70. "#pv-prefs .section_header_holder { padding-left: 10px; }",
  71. ].join('\n') + '\n',
  72. skin_tab: [
  73. "#GM_config { background: #EEE; }",
  74. "#GM_config textarea { width: 98%; height: 45px; margin-top: 5px; }",
  75. "#GM_config .field_label { display: inline-block; font-weight: normal; }",
  76. // 在同一行内的设置
  77. "#GM_config .inline input[type='checkbox'] {margin: 3px 3px 3px 0px;}",
  78. "#GM_config .inline .config_var { margin-left: 15px; }",
  79. // 内容样式
  80. "#GM_config .config_var { font-size: 14px; padding: 5px; margin: 0; }",
  81. "#GM_config .config_header a { text-decoration: none; color: #000; }",
  82. "#GM_config .nav-tabs { margin: 20 0}",
  83. "#GM_config .nav-tabs > div { font-size: 15px; color: #999; cursor: pointer; padding: 10px 20px; }",
  84. "#GM_config .nav-tabs > .active { cursor: default; color: #FFF; }",
  85. "#GM_config .nav-tabs > div:hover { color: #FFF; }",
  86. ].join('\n') + '\n',
  87. skin_1: [ // 仿 Mouseover Popup Image Viewer 样式
  88. "#GM_config { background: #EEE; }",
  89. "#GM_config textarea { width: 98%; height: 45px; margin-top: 5px; }",
  90. "#GM_config .config_var { font-size: 12px; }",
  91. "#GM_config .inline .config_var { margin-left: 15px; }",
  92. "#GM_config .field_label { display: inline-block; font-weight: normal; }",
  93. "#GM_config { padding: 20px 30px; margin: 0; }",
  94. "#GM_config .config_header { margin-bottom: 10px; }",
  95. "#GM_config div.config_var { padding: 7px 0; }",
  96. ].join('\n') + '\n',
  97. basicPrefix: "GM_config",
  98. stylish: ""
  99. };
  100. }
  101.  
  102. if (args.length == 1 &&
  103. typeof args[0].id == "string" &&
  104. typeof args[0].appendChild != "function") var settings = args[0];
  105. else {
  106. // Provide backwards-compatibility with argument style intialization
  107. var settings = {};
  108.  
  109. // loop through GM_config.init() arguments
  110. for (var i = 0, l = args.length, arg; i < l; ++i) {
  111. arg = args[i];
  112.  
  113. // An element to use as the config window
  114. if (typeof arg.appendChild == "function") {
  115. settings.frame = arg;
  116. continue;
  117. }
  118.  
  119. switch (typeof arg) {
  120. case 'object':
  121. for (var j in arg) { // could be a callback functions or settings object
  122. if (typeof arg[j] != "function") { // we are in the settings object
  123. if (typeof arg[j] == 'string') {
  124. settings.frameStyle = arg;
  125. } else {
  126. settings.fields = arg; // store settings object
  127. }
  128. break; // leave the loop
  129. } // otherwise it must be a callback function
  130. if (!settings.events) settings.events = {};
  131. settings.events[j] = arg[j];
  132. }
  133. break;
  134. case 'function': // passing a bare function is set to open callback
  135. settings.events = {open: arg};
  136. break;
  137. case 'string': // could be custom CSS or the title string
  138. // if (/[\w\.]+\s*\{\s*[\w-]+\s*:\s*\w+[\s|\S]*\}/.test(arg))
  139. if (/[\w\.]+\s*\{\s*[\w-]+\s*:[\s|\S]*\}/.test(arg))
  140. settings.css = arg;
  141. else if (arg)
  142. settings.title = arg;
  143. break;
  144. }
  145. }
  146. }
  147.  
  148. /* Initialize everything using the new settings object */
  149. // Set the id
  150. if (settings.id) config.id = settings.id;
  151. else if (typeof config.id == "undefined") config.id = 'GM_config';
  152.  
  153. // Set the title
  154. if (settings.title) config.title = settings.title;
  155.  
  156. // Set the custom css
  157. if (settings.css) config.css.stylish = settings.css;
  158.  
  159. if (settings.skin) {
  160. var skin = config.css['skin_' + settings.skin];
  161. if (skin) {
  162. config.css.basic += skin;
  163. }
  164. }
  165.  
  166. // Set the frame
  167. if (settings.frame) config.frame = settings.frame;
  168. if (settings.frameStyle) config.frameStyle = settings.frameStyle;
  169.  
  170. config.isTabs = settings.isTabs;
  171.  
  172. // Set the event callbacks
  173. if (settings.events) {
  174. var events = settings.events;
  175. for (var e in events)
  176. config["on" + e.charAt(0).toUpperCase() + e.slice(1)] = events[e];
  177. }
  178.  
  179. // Create the fields
  180. if (settings.fields) {
  181. var stored = config.read(), // read the stored settings
  182. fields = settings.fields,
  183. customTypes = settings.types || {};
  184.  
  185. for (var id in fields) {
  186. var field = fields[id];
  187.  
  188. // for each field definition create a field object
  189. if (field)
  190. config.fields[id] = new GM_configField(field, stored[id], id,
  191. customTypes[field.type]);
  192. else if (config.fields[id]) delete config.fields[id];
  193. }
  194. }
  195.  
  196. // If the id has changed we must modify the default style
  197. if (config.id != config.css.basicPrefix) {
  198. config.css.basic = config.css.basic.replace(
  199. new RegExp('#' + config.css.basicPrefix, 'gm'), '#' + config.id);
  200. config.css.basicPrefix = config.id;
  201. }
  202. }
  203.  
  204. GM_configStruct.prototype = {
  205. // Support old method of initalizing
  206. init: function() {
  207. GM_configInit(this, arguments);
  208. this.onInit();
  209. },
  210.  
  211. // call GM_config.open() from your script to open the menu
  212. open: function () {
  213. // Die if the menu is already open on this page
  214. // You can have multiple instances but you can't open the same instance twice
  215. var match = document.getElementById(this.id);
  216. if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0)) return;
  217.  
  218. // Sometimes "this" gets overwritten so create an alias
  219. var config = this;
  220.  
  221. // Function to build the mighty config window :)
  222. function buildConfigWin (body, head) {
  223. var create = config.create,
  224. fields = config.fields,
  225. configId = config.id,
  226. bodyWrapper = create('div', {id: configId + '_wrapper'});
  227.  
  228. // Append the style which is our default style plus the user style
  229. head.appendChild(
  230. create('style', {
  231. type: 'text/css',
  232. textContent: config.css.basic + config.css.stylish
  233. }));
  234.  
  235. // Add header and title
  236. bodyWrapper.appendChild(create('div', {
  237. id: configId + '_header',
  238. className: 'config_header block center'
  239. }, config.title));
  240.  
  241. // Append elements
  242. var section = bodyWrapper,
  243. secNum = 0; // Section count
  244. var lastParentNode = null;
  245.  
  246. // loop through fields
  247. for (var id in fields) {
  248. var field = fields[id],
  249. settings = field.settings;
  250.  
  251. if (settings.section) { // the start of a new section
  252. section = bodyWrapper.appendChild(create('div', {
  253. className: 'section_header_holder',
  254. id: configId + '_section_' + secNum
  255. }));
  256.  
  257. if (Object.prototype.toString.call(settings.section) !== '[object Array]')
  258. settings.section = [settings.section];
  259.  
  260. if (settings.section[0])
  261. section.appendChild(create('div', {
  262. className: 'section_header center',
  263. id: configId + '_section_header_' + secNum
  264. }, settings.section[0]));
  265.  
  266. if (settings.section[1])
  267. section.appendChild(create('p', {
  268. className: 'section_desc center',
  269. id: configId + '_section_desc_' + secNum
  270. }, settings.section[1]));
  271. ++secNum;
  272. }
  273.  
  274. if (settings.line == 'start' && lastParentNode) { // 切换到下一行
  275. lastParentNode = null;
  276. }
  277.  
  278. // Create field elements and append to current section
  279. (lastParentNode || section).appendChild((field.wrapper = field.toNode(configId, lastParentNode)));
  280.  
  281. if (settings.line == 'start') {
  282. lastParentNode = field.wrapper;
  283. lastParentNode.classList.add('inline')
  284. } else if (settings.line == 'end') {
  285. lastParentNode = null;
  286. }
  287. }
  288.  
  289. // Add save and close buttons
  290. bodyWrapper.appendChild(create('div',
  291. {id: configId + '_buttons_holder'},
  292.  
  293. create('button', {
  294. id: configId + '_saveBtn',
  295. textContent: '确定',
  296. title: '部分选项需要刷新页面才能生效',
  297. className: 'saveclose_buttons',
  298. onclick: function () {
  299. config.save();
  300. config.close();
  301. }
  302. }),
  303.  
  304. create('button', {
  305. id: configId + '_closeBtn',
  306. textContent: '取消',
  307. title: '取消本次设置,所有选项还原',
  308. className: 'saveclose_buttons',
  309. onclick: function () {
  310. config.close()
  311. }
  312. }),
  313.  
  314. create('div',
  315. {className: 'reset_holder block'},
  316.  
  317. // Reset link
  318. create('a', {
  319. id: configId + '_resetLink',
  320. textContent: '恢复默认设置',
  321. href: '#',
  322. title: '恢复所有设置的内容为默认值',
  323. className: 'reset',
  324. onclick: function (e) {
  325. e.preventDefault();
  326. config.reset()
  327. }
  328. })
  329. )));
  330.  
  331. body.appendChild(bodyWrapper); // Paint everything to window at once
  332. config.center(); // Show and center iframe
  333. window.addEventListener('resize', config.center, false); // Center frame on resize
  334.  
  335. // Call the open() callback function
  336. config.onOpen(config.frame.contentDocument || config.frame.ownerDocument,
  337. config.frame.contentWindow || window,
  338. config.frame);
  339.  
  340. if (config.isTabs) {
  341. config.toTabs();
  342. }
  343.  
  344. // Close frame on window close
  345. window.addEventListener('beforeunload', function () {
  346. config.close();
  347. }, false);
  348.  
  349. // Now that everything is loaded, make it visible
  350. config.frame.style.display = "block";
  351. config.isOpen = true;
  352. }
  353.  
  354. // Change this in the onOpen callback using this.frame.setAttribute('style', '')
  355. var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;'
  356. + ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;'
  357. + ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;'
  358. + ' width: 75%; z-index: 999999999;';
  359.  
  360. // Either use the element passed to init() or create an iframe
  361. if (this.frame) {
  362. this.frame.id = this.id; // Allows for prefixing styles with the config id
  363. this.frame.setAttribute('style', defaultStyle);
  364. buildConfigWin(this.frame, this.frame.ownerDocument.getElementsByTagName('head')[0]);
  365. } else {
  366. // Create frame
  367. document.body.appendChild((this.frame = this.create('iframe', {
  368. id: this.id,
  369. style: defaultStyle
  370. })));
  371.  
  372. if (this.frameStyle) {
  373. Object.keys(this.frameStyle).forEach(function(key) {
  374. config.frame.style[key] = config.frameStyle[key];
  375. })
  376. }
  377.  
  378. // In WebKit src can't be set until it is added to the page
  379. this.frame.src = 'about:blank';
  380. // we wait for the iframe to load before we can modify it
  381. this.frame.addEventListener('load', function(e) {
  382. var frame = config.frame;
  383. var body = frame.contentDocument.getElementsByTagName('body')[0];
  384. body.id = config.id; // Allows for prefixing styles with the config id
  385. buildConfigWin(body, frame.contentDocument.getElementsByTagName('head')[0]);
  386. }, false);
  387. }
  388. },
  389.  
  390. save: function () {
  391. var forgotten = this.write();
  392. this.onSave(forgotten); // Call the save() callback function
  393. },
  394.  
  395. close: function() {
  396. if (!this.frame) return;
  397. // If frame is an iframe then remove it
  398. if (this.frame.contentDocument) {
  399. this.remove(this.frame);
  400. this.frame = null;
  401. } else { // else wipe its content
  402. this.frame.innerHTML = "";
  403. this.frame.style.display = "none";
  404. }
  405.  
  406. // Null out all the fields so we don't leak memory
  407. var fields = this.fields;
  408. for (var id in fields) {
  409. var field = fields[id];
  410. field.wrapper = null;
  411. field.node = null;
  412. }
  413.  
  414. this.onClose(); // Call the close() callback function
  415. this.isOpen = false;
  416. },
  417.  
  418. set: function (name, val) {
  419. this.fields[name].value = val;
  420.  
  421. if (this.fields[name].node) {
  422. this.fields[name].reload();
  423. }
  424. },
  425.  
  426. get: function (name, getLive) {
  427. var field = this.fields[name],
  428. fieldVal = null;
  429.  
  430. if (getLive && field.node) {
  431. fieldVal = field.toValue();
  432. }
  433.  
  434. return fieldVal != null ? fieldVal : field.value;
  435. },
  436.  
  437. write: function (store, obj) {
  438. if (!obj) {
  439. var values = {},
  440. forgotten = {},
  441. fields = this.fields;
  442.  
  443. for (var id in fields) {
  444. var field = fields[id];
  445. var value = field.toValue();
  446.  
  447. if (field.save) {
  448. if (value != null) {
  449. values[id] = value;
  450. field.value = value;
  451. } else
  452. values[id] = field.value;
  453. } else
  454. forgotten[id] = value;
  455. }
  456. }
  457. try {
  458. this.setValue(store || this.id, this.stringify(obj || values));
  459. } catch(e) {
  460. this.log("GM_config failed to save settings!");
  461. }
  462.  
  463. return forgotten;
  464. },
  465.  
  466. read: function (store) {
  467. try {
  468. var rval = this.parser(this.getValue(store || this.id, '{}'));
  469. } catch(e) {
  470. this.log("GM_config failed to read saved settings!");
  471. var rval = {};
  472. }
  473. return rval;
  474. },
  475.  
  476. reset: function () {
  477. var fields = this.fields;
  478.  
  479. // Reset all the fields
  480. for (var id in fields) fields[id].reset();
  481.  
  482. this.onReset(); // Call the reset() callback function
  483. },
  484.  
  485. create: function () {
  486. switch(arguments.length) {
  487. case 1:
  488. var A = document.createTextNode(arguments[0]);
  489. break;
  490. default:
  491. var A = document.createElement(arguments[0]),
  492. B = arguments[1];
  493. for (var b in B) {
  494. if (b.indexOf("on") == 0)
  495. A.addEventListener(b.substring(2), B[b], false);
  496. else if (",style,accesskey,id,name,src,href,which,for".indexOf("," +
  497. b.toLowerCase()) != -1)
  498. A.setAttribute(b, B[b]);
  499. else if (typeof B[b] != 'undefined')
  500. A[b] = B[b];
  501. }
  502. if (typeof arguments[2] == "string")
  503. A.innerHTML = arguments[2];
  504. else
  505. for (var i = 2, len = arguments.length; i < len; ++i)
  506. A.appendChild(arguments[i]);
  507. }
  508. return A;
  509. },
  510.  
  511. center: function () {
  512. var node = this.frame;
  513. if (!node) return;
  514. var style = node.style,
  515. beforeOpacity = style.opacity;
  516. if (style.display == 'none') style.opacity = '0';
  517. style.display = '';
  518. style.top = Math.floor((window.innerHeight / 2) - (node.offsetHeight / 2)) + 'px';
  519. style.left = Math.floor((window.innerWidth / 2) - (node.offsetWidth / 2)) + 'px';
  520. style.opacity = '1';
  521. },
  522.  
  523. remove: function (el) {
  524. if (el && el.parentNode) el.parentNode.removeChild(el);
  525. },
  526.  
  527. toTabs: function() { // 转为 tab 的形式
  528. var body = this.frame.tagName == 'IFRAME' ? this.frame.contentWindow.document : this.frame,
  529. configId = this.id;
  530. var $ = function(id) {
  531. return body.getElementById(configId + '_' + id);
  532. };
  533.  
  534. var headers = body.querySelectorAll('.section_header');
  535. if (!headers.length) return;
  536.  
  537. var anch = this.create('div', {
  538. // id: configId + '_tab_holder',
  539. className: 'nav-tabs',
  540. });
  541.  
  542. for (var i = 0, header; i < headers.length; i++) {
  543. header = headers[i];
  544. if (i == 0) {
  545. header.classList.add('active');
  546. }
  547. anch.appendChild(header);
  548. }
  549.  
  550. anch.addEventListener('click', this.toggleTab.bind(this), false);
  551.  
  552. $('section_0').parentNode.insertBefore(anch, $('section_0'));
  553.  
  554. var curTab = localStorage.getItem('picviewerCE.config.curTab') || 0;
  555. this.toggleTab(parseInt(curTab, 10));
  556. },
  557. toggleTab: function(e) {
  558. var body = this.frame.tagName == 'IFRAME' ? this.frame.contentWindow.document : this.frame,
  559. configId = this.id;
  560.  
  561. var curTab = typeof e == 'number' ? e : /\_(\d+)/.exec(e.target.id)[1];
  562.  
  563. [].forEach.call(body.querySelectorAll('.section_header'), function(header, i) {
  564. if (i == curTab) {
  565. header.classList.add('active');
  566. } else {
  567. header.classList.remove('active');
  568. }
  569. });
  570.  
  571. [].forEach.call(body.querySelectorAll('.section_header_holder'), function(holder, i) {
  572. holder.style.display = (i == curTab) ? 'block' : 'none';
  573. });
  574.  
  575. localStorage.setItem('picviewerCE.config.curTab', curTab)
  576. }
  577. };
  578.  
  579. // Define a bunch of API stuff
  580. (function() {
  581. var isGM = typeof GM_getValue != 'undefined' &&
  582. typeof GM_getValue('a', 'b') != 'undefined',
  583. setValue, getValue, stringify, parser;
  584.  
  585. // Define value storing and reading API
  586. if (!isGM) {
  587. setValue = function (name, value) {
  588. return localStorage.setItem(name, value);
  589. };
  590. getValue = function(name, def){
  591. var s = localStorage.getItem(name);
  592. return s == null ? def : s
  593. };
  594.  
  595. // We only support JSON parser outside GM
  596. stringify = JSON.stringify;
  597. parser = JSON.parse;
  598. } else {
  599. setValue = GM_setValue;
  600. getValue = GM_getValue;
  601. stringify = typeof JSON == "undefined" ?
  602. function(obj) {
  603. return obj.toSource();
  604. } : JSON.stringify;
  605. parser = typeof JSON == "undefined" ?
  606. function(jsonData) {
  607. return (new Function('return ' + jsonData + ';'))();
  608. } : JSON.parse;
  609. }
  610.  
  611. GM_configStruct.prototype.isGM = isGM;
  612. GM_configStruct.prototype.setValue = setValue;
  613. GM_configStruct.prototype.getValue = getValue;
  614. GM_configStruct.prototype.stringify = stringify;
  615. GM_configStruct.prototype.parser = parser;
  616. GM_configStruct.prototype.log = window.console ?
  617. console.log : (isGM && typeof GM_log != 'undefined' ?
  618. GM_log : (window.opera ?
  619. opera.postError : function(){ /* no logging */ }
  620. ));
  621. })();
  622.  
  623. function GM_configDefaultValue(type, options) {
  624. var value;
  625.  
  626. if (type && type.indexOf('unsigned ') == 0)
  627. type = type.substring(9);
  628.  
  629. switch (type) {
  630. case 'radio': case 'select':
  631. value = options[0];
  632. break;
  633. case 'checkbox':
  634. value = false;
  635. break;
  636. case 'int': case 'integer':
  637. case 'float': case 'number':
  638. value = 0;
  639. break;
  640. default:
  641. value = '';
  642. }
  643.  
  644. return value;
  645. }
  646.  
  647. function GM_configField(settings, stored, id, customType) {
  648. // Store the field's settings
  649. this.settings = settings;
  650. this.id = id;
  651. this.node = null;
  652. this.wrapper = null;
  653. this.save = typeof settings.save == "undefined" ? true : settings.save;
  654.  
  655. // Buttons are static and don't have a stored value
  656. if (settings.type == "button") this.save = false;
  657. if (settings.type == "span") this.save = false;
  658.  
  659. // if a default value wasn't passed through init() then
  660. // if the type is custom use its default value
  661. // else use default value for type
  662. // else use the default value passed through init()
  663. this['default'] = typeof settings['default'] == "undefined" ?
  664. customType ?
  665. customType['default']
  666. : GM_configDefaultValue(settings.type, settings.options)
  667. : settings['default'];
  668.  
  669. // Store the field's value
  670. this.value = typeof stored == "undefined" ? this['default'] : stored;
  671.  
  672. // Setup methods for a custom type
  673. if (customType) {
  674. this.toNode = customType.toNode;
  675. this.toValue = customType.toValue;
  676. this.reset = customType.reset;
  677. }
  678. }
  679.  
  680. GM_configField.prototype = {
  681. create: GM_configStruct.prototype.create,
  682.  
  683. toNode: function(configId, lastParentNode) {
  684. var field = this.settings,
  685. value = this.value,
  686. options = field.options,
  687. type = field.type,
  688. id = this.id,
  689. labelPos = field.labelPos,
  690. create = this.create;
  691.  
  692. function addLabel(pos, labelEl, parentNode, beforeEl) {
  693. if (!beforeEl) {
  694. beforeEl = lastParentNode ? parentNode.lastChild : parentNode.firstChild; // oneLine 的修正
  695. }
  696.  
  697. switch (pos) {
  698. case 'right': case 'below':
  699. if (pos == 'below')
  700. parentNode.appendChild(create('br', {}));
  701. parentNode.appendChild(labelEl);
  702. break;
  703. default:
  704. if (pos == 'above')
  705. parentNode.insertBefore(create('br', {}), beforeEl);
  706. parentNode.insertBefore(labelEl, beforeEl);
  707. }
  708. }
  709.  
  710. var retNode = create('div', { className: 'config_var',
  711. id: configId + '_' + id + '_var',
  712. title: field.title || '' }),
  713. firstProp;
  714.  
  715. // Retrieve the first prop
  716. for (var i in field) { firstProp = i; break; }
  717.  
  718. var label = field.label && type != "button" ?
  719. create('label', {
  720. id: configId + '_' + id + '_field_label',
  721. for: configId + '_field_' + id,
  722. className: 'field_label'
  723. }, field.label) : null;
  724.  
  725. switch (type) {
  726. case 'span':
  727. label = null;
  728.  
  729. this.node = create('span', {
  730. innerHTML: field.label,
  731. className: 'field_label',
  732. title: field.title,
  733. style: field.style
  734. });
  735. retNode = this.node;
  736. break;
  737. case 'textarea':
  738. retNode.appendChild((this.node = create('textarea', {
  739. innerHTML: value,
  740. id: configId + '_field_' + id,
  741. className: 'block' + (field.className ? (" " + field.className) : ''),
  742. cols: (field.cols ? field.cols : 20),
  743. rows: (field.rows ? field.rows : 2),
  744. placeholder: field.placeholder
  745. })));
  746. break;
  747. case 'radio':
  748. var wrap = create('div', {
  749. id: configId + '_field_' + id,
  750. className: field.className
  751. });
  752. this.node = wrap;
  753.  
  754. for (var i = 0, len = options.length; i < len; ++i) {
  755. var radLabel = create('label', {
  756. className: 'radio_label'
  757. }, options[i]);
  758.  
  759. var rad = wrap.appendChild(create('input', {
  760. value: options[i],
  761. type: 'radio',
  762. name: id,
  763. checked: options[i] == value
  764. }));
  765.  
  766. var radLabelPos = labelPos &&
  767. (labelPos == 'left' || labelPos == 'right') ?
  768. labelPos : firstProp == 'options' ? 'left' : 'right';
  769.  
  770. addLabel(radLabelPos, radLabel, wrap, rad);
  771. }
  772.  
  773. retNode.appendChild(wrap);
  774. break;
  775. case 'select':
  776. var wrap = create('select', {
  777. id: configId + '_field_' + id
  778. });
  779. this.node = wrap;
  780.  
  781. for (var i = 0, len = options.length; i < len; ++i) {
  782. var option = options[i];
  783. wrap.appendChild(create('option', {
  784. value: option,
  785. selected: option == value
  786. }, option));
  787. }
  788.  
  789. retNode.appendChild(wrap);
  790. break;
  791. default: // fields using input elements
  792. var props = {
  793. id: configId + '_field_' + id,
  794. type: type,
  795. value: type == 'button' ? field.label : value
  796. };
  797.  
  798. switch (type) {
  799. case 'checkbox':
  800. props.checked = value;
  801. break;
  802. case 'button':
  803. props.size = field.size ? field.size : 25;
  804. if (field.script) field.click = field.script;
  805. if (field.click) props.onclick = field.click;
  806. break;
  807. case 'hidden':
  808. break;
  809. default:
  810. // type = text, int, or float
  811. props.type = 'text';
  812. props.size = field.size ? field.size : 25;
  813. }
  814.  
  815. retNode.appendChild((this.node = create('input', props)));
  816. }
  817.  
  818. if (label) {
  819. // If the label is passed first, insert it before the field
  820. // else insert it after
  821. if (!labelPos)
  822. labelPos = firstProp == "label" || type == "radio" ?
  823. "left" : "right";
  824.  
  825. addLabel(labelPos, label, retNode);
  826. }
  827.  
  828. return retNode;
  829. },
  830.  
  831. toValue: function() {
  832. var node = this.node,
  833. field = this.settings,
  834. type = field.type,
  835. unsigned = false,
  836. rval = null;
  837.  
  838. if (!node) return rval;
  839.  
  840. if (type.indexOf('unsigned ') == 0) {
  841. type = type.substring(9);
  842. unsigned = true;
  843. }
  844.  
  845. switch (type) {
  846. case 'checkbox':
  847. rval = node.checked;
  848. break;
  849. case 'select':
  850. rval = node[node.selectedIndex].value;
  851. break;
  852. case 'radio':
  853. var radios = node.getElementsByTagName('input');
  854. for (var i = 0, len = radios.length; i < len; ++i)
  855. if (radios[i].checked)
  856. rval = radios[i].value;
  857. break;
  858. case 'button':
  859. break;
  860. case 'int': case 'integer':
  861. case 'float': case 'number':
  862. var num = Number(node.value);
  863. var warn = '输入字符 "' + field.label + '" 要求必须为' +
  864. (unsigned ? ' 正 ' : 'n ') + '整数值';
  865.  
  866. if (isNaN(num) || (type.substr(0, 3) == 'int' &&
  867. Math.ceil(num) != Math.floor(num)) ||
  868. (unsigned && num < 0)) {
  869. alert(warn + '.');
  870. return null;
  871. }
  872.  
  873. if (!this._checkNumberRange(num, warn))
  874. return null;
  875. rval = num;
  876. break;
  877. default:
  878. rval = node.value;
  879. break;
  880. }
  881.  
  882. return rval; // value read successfully
  883. },
  884.  
  885. reset: function() {
  886. var node = this.node,
  887. field = this.settings,
  888. type = field.type;
  889.  
  890. if (!node) return;
  891.  
  892. switch (type) {
  893. case 'checkbox':
  894. node.checked = this['default'];
  895. break;
  896. case 'select':
  897. for (var i = 0, len = node.options.length; i < len; ++i)
  898. if (node.options[i].value == this['default'])
  899. node.selectedIndex = i;
  900. break;
  901. case 'radio':
  902. var radios = node.getElementsByTagName('input');
  903. for (var i = 0, len = radios.length; i < len; ++i)
  904. if (radios[i].value == this['default'])
  905. radios[i].checked = true;
  906. break;
  907. case 'button' :
  908. break;
  909. default:
  910. node.value = this['default'];
  911. break;
  912. }
  913. },
  914.  
  915. remove: function(el) {
  916. GM_configStruct.prototype.remove(el || this.wrapper);
  917. this.wrapper = null;
  918. this.node = null;
  919. },
  920.  
  921. reload: function() {
  922. var wrapper = this.wrapper;
  923. if (wrapper) {
  924. var fieldParent = wrapper.parentNode;
  925. fieldParent.insertBefore((this.wrapper = this.toNode()), wrapper);
  926. this.remove(wrapper);
  927. }
  928. },
  929.  
  930. _checkNumberRange: function(num, warn) {
  931. var field = this.settings;
  932. if (typeof field.min == "number" && num < field.min) {
  933. alert(warn + ' greater than or equal to ' + field.min + '.');
  934. return null;
  935. }
  936.  
  937. if (typeof field.max == "number" && num > field.max) {
  938. alert(warn + ' less than or equal to ' + field.max + '.');
  939. return null;
  940. }
  941. return true;
  942. }
  943. };
  944.  
  945. // Create default instance of GM_config
  946. var GM_config = new GM_configStruct();