GM_config CN

sizzlemctwizzle 的 GM_config 库中文版本

Dette scriptet burde ikke installeres direkte. Det er et bibliotek for andre script å inkludere med det nye metadirektivet // @require https://update.greatest.deepsurf.us/scripts/6158/207250/GM_config%20CN.js

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