GM_config_CN

sizzlemctwizzle 的 GM_config 库中文版本 - Modified by pana

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/403716/954794/GM_config_CN.js

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址