GM_config_lz-string

ConfigLzString/Refactor GM_config, this version uses lz-string to access data for a Library script

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

  1. // ==UserScript==
  2. // @namespace http://tampermonkey.net/
  3. // @exclude *
  4.  
  5. // ==UserLibrary==
  6. // @name GM_config_lz-string
  7. // @description ConfigLzString/Refactor GM_config, this version uses lz-string to access data for a Library script
  8. // @author avan
  9. // @license MIT
  10. // @version 0.6
  11.  
  12. // ==/UserScript==
  13.  
  14. // ==/UserLibrary==
  15. GM_configStruct.prototype.create = function() {
  16. switch (arguments.length) {
  17. case 1:
  18. var A = document.createTextNode(arguments[0]);
  19. break;
  20. default:
  21. var A = document.createElement(arguments[0]),
  22. B = arguments[1];
  23. for (var b in B) {
  24. if (b.indexOf("on") == 0)
  25. A.addEventListener(b.substring(2), B[b], false);
  26. //else if (",style,accesskey,id,name,src,href,which,for".indexOf("," + b.toLowerCase()) != -1)
  27. else if (b.match(/^(style|accesskey|id|name|src|href|which|for|data-)/i))
  28. A.setAttribute(b, B[b]);
  29. else
  30. A[b] = B[b];
  31. }
  32. if (typeof arguments[2] == "string")
  33. A.innerHTML = arguments[2];
  34. else
  35. for (var i = 2, len = arguments.length; i < len; ++i)
  36. A.appendChild(arguments[i]);
  37. }
  38. return A;
  39. };
  40. GM_configStruct.prototype.read = function (store) {
  41. var rval, cKey, dValue;
  42. try {
  43. cKey = LZString.compressToUTF16(store || this.id);
  44. dValue = LZString.decompressFromUTF16(this.getValue(cKey, '{}'));
  45. rval = this.parser(dValue);
  46. } catch(e) {
  47. this.log("GM_config failed to read saved settings!");
  48. rval = {};
  49. }
  50. return rval;
  51. };
  52. GM_configStruct.prototype.write = function (store, obj) {
  53. if (!obj) {
  54. var values = {},
  55. forgotten = {},
  56. fields = this.fields;
  57.  
  58. for (var id in fields) {
  59. var field = fields[id];
  60. var value = field.toValue();
  61.  
  62. if (field.save) {
  63. if (value !== null) {
  64. values[id] = value;
  65. field.value = value;
  66. } else
  67. values[id] = field.value;
  68. } else
  69. forgotten[id] = value;
  70. }
  71. }
  72. try {
  73. var cKey = LZString.compressToUTF16(store || this.id),
  74. cValue = LZString.compressToUTF16(this.stringify(obj || values));
  75. this.setValue(cKey, cValue);
  76. } catch(e) {
  77. this.log("GM_config failed to save settings!");
  78. }
  79.  
  80. return forgotten;
  81. };
  82. GM_configField.prototype.create = GM_configStruct.prototype.create;
  83. GM_configField.prototype.toNode = function() {
  84. var field = this.settings,
  85. value = this.value,
  86. options = field.options,
  87. type = field.type,
  88. className = field.class,
  89. style = field.style,
  90. id = this.id,
  91. configId = this.configId,
  92. labelPos = field.labelPos,
  93. create = this.create;
  94. function addLabel(pos, labelEl, parentNode, beforeEl) {
  95. if (!beforeEl) beforeEl = parentNode.firstChild;
  96. switch (pos) {
  97. case 'right':
  98. case 'below':
  99. if (pos == 'below')
  100. parentNode.appendChild(create('br', {}));
  101. parentNode.appendChild(labelEl);
  102. break;
  103. default:
  104. if (pos == 'above')
  105. parentNode.insertBefore(create('br', {}), beforeEl);
  106. parentNode.insertBefore(labelEl, beforeEl);
  107. }
  108. }
  109. var retNode = create('div', {
  110. className: 'config_var',
  111. id: configId + '_' + id + '_var',
  112. title: field.title || '',
  113.  
  114. }),
  115. firstProp;
  116. // Retrieve the first prop
  117. for (var i in field) {
  118. firstProp = i;
  119. break;
  120. }
  121. var label = field.label && type != "button" ?
  122. create('label', {
  123. id: configId + '_' + id + '_field_label',
  124. for: configId + '_field_' + id,
  125. className: 'field_label'
  126. }, field.label) : null;
  127. var props = {className: className || '', style: style || ''};
  128. for (var key in field) {
  129. var val = field[key];
  130. if (key.match(/^data-/i) && !props[key]) props[key] = val;
  131. };
  132. switch (type) {
  133. case 'textarea':
  134. props.innerHTML = value;
  135. props.id = configId + '_field_' + id;
  136. props.className = (props.className ? props.className + ' ' : '') + 'block';
  137. props.cols = (field.cols ? field.cols : 20);
  138. props.rows = (field.rows ? field.rows : 2);
  139. retNode.appendChild((this.node = create('textarea', props)));
  140. break;
  141. case 'radio':
  142. props.id = configId + '_field_' + id;
  143. var wrap = create('div', props);
  144. this.node = wrap;
  145. for (var i = 0, len = options.length; i < len; ++i) {
  146. var radLabel = create('label', {
  147. className: 'radio_label'
  148. }, options[i]);
  149. var rad = wrap.appendChild(create('input', {
  150. value: options[i],
  151. type: 'radio',
  152. name: id,
  153. checked: options[i] == value
  154. }));
  155. var radLabelPos = labelPos &&
  156. (labelPos == 'left' || labelPos == 'right') ?
  157. labelPos : firstProp == 'options' ? 'left' : 'right';
  158. addLabel(radLabelPos, radLabel, wrap, rad);
  159. }
  160. retNode.appendChild(wrap);
  161. break;
  162. case 'select':
  163. props.id = configId + '_field_' + id;
  164. var wrap = create('select', props);
  165. this.node = wrap;
  166. for (var i = 0, len = options.length; i < len; ++i) {
  167. var option = options[i];
  168. wrap.appendChild(create('option', {
  169. value: option,
  170. selected: option == value
  171. }, option));
  172. }
  173. retNode.appendChild(wrap);
  174. break;
  175. default: // fields using input elements
  176. props.id = configId + '_field_' + id;
  177. props.type = type;
  178. props.value = type == 'button' ? field.label : value;
  179. switch (type) {
  180. case 'checkbox':
  181. props.checked = value;
  182. break;
  183. case 'button':
  184. props.size = field.size ? field.size : 25;
  185. if (field.script) field.click = field.script;
  186. if (field.click) props.onclick = field.click;
  187. break;
  188. case 'hidden':
  189. break;
  190. case 'password':
  191. props.size = field.size ? field.size : 25;
  192. break;
  193. default:
  194. // type = text, int, or float
  195. props.type = 'text';
  196. props.size = field.size ? field.size : 25;
  197. }
  198. retNode.appendChild((this.node = create('input', props)));
  199. }
  200. if (label) {
  201. // If the label is passed first, insert it before the field
  202. // else insert it after
  203. if (!labelPos)
  204. labelPos = firstProp == "label" || type == "radio" ?
  205. "left" : "right";
  206. addLabel(labelPos, label, retNode);
  207. }
  208. return retNode;
  209. };
  210. var ConfigLzString = function () {
  211. GM_configStruct.apply(this, arguments);
  212. if (arguments.length > 0 && arguments[0].src) {
  213. this.srcs = arguments[0].src.replace(/ *[ ;,]+ */g, ' ').split(/[ ;,]/);
  214. }
  215. }
  216. ConfigLzString.prototype = GM_configStruct.prototype;
  217. ConfigLzString.prototype.open = function() {
  218. // Die if the menu is already open on this page
  219. // You can have multiple instances but you can't open the same instance twice
  220. var match = document.getElementById(this.id);
  221. if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0)) return;
  222. // Sometimes "this" gets overwritten so create an alias
  223. var config = this;
  224. // Function to build the mighty config window :)
  225. function buildConfigWin(body, head) {
  226. var create = config.create,
  227. fields = config.fields,
  228. configId = config.id,
  229. bodyWrapper = create('div', {
  230. id: configId + '_wrapper'
  231. });
  232. // Append the style which is our default style plus the user style
  233. head.appendChild(
  234. create('style', {
  235. type: 'text/css',
  236. textContent: config.css.basic + config.css.stylish
  237. }));
  238. // Add header and title
  239. bodyWrapper.appendChild(create('div', {
  240. id: configId + '_header',
  241. className: 'config_header block center'
  242. }, config.title));
  243. // Append elements
  244. var section = bodyWrapper,
  245. secNum = 0; // Section count
  246. // loop through fields
  247. for (var id in fields) {
  248. var field = fields[id],
  249. settings = field.settings;
  250. if (settings.section) { // the start of a new section
  251. section = bodyWrapper.appendChild(create('div', {
  252. className: 'section_header_holder',
  253. id: configId + '_section_' + secNum
  254. }));
  255. if (Object.prototype.toString.call(settings.section) !== '[object Array]')
  256. settings.section = [settings.section];
  257. if (settings.section[0])
  258. section.appendChild(create('div', {
  259. className: 'section_header center',
  260. id: configId + '_section_header_' + secNum
  261. }, settings.section[0]));
  262. if (settings.section[1])
  263. section.appendChild(create('p', {
  264. className: 'section_desc center',
  265. id: configId + '_section_desc_' + secNum
  266. }, settings.section[1]));
  267. ++secNum;
  268. }
  269. // Create field elements and append to current section
  270. section.appendChild((field.wrapper = field.toNode()));
  271. }
  272. // Add save and close buttons
  273. bodyWrapper.appendChild(
  274. create('div', {
  275. id: configId + '_buttons_holder'
  276. }, create('button', {
  277. id: configId + '_saveBtn',
  278. textContent: 'Save',
  279. title: 'Save settings',
  280. className: 'saveclose_buttons',
  281. onclick: function() {
  282. config.save()
  283. }
  284. }), create('button', {
  285. id: configId + '_closeBtn',
  286. textContent: 'Close',
  287. title: 'Close window',
  288. className: 'saveclose_buttons',
  289. onclick: function() {
  290. config.close()
  291. }
  292. }), create('div', {
  293. className: 'reset_holder block'
  294. }, create('a', { // Reset link
  295. id: configId + '_resetLink',
  296. textContent: 'Reset to defaults',
  297. href: '#',
  298. title: 'Reset fields to default values',
  299. className: 'reset',
  300. onclick: function(e) {
  301. e.preventDefault();
  302. config.reset()
  303. }
  304. })
  305. )));
  306. body.appendChild(bodyWrapper); // Paint everything to window at once
  307. config.center(); // Show and center iframe
  308. window.addEventListener('resize', config.center, false); // Center frame on resize
  309. // Call the open() callback function
  310. config.onOpen(config.frame.contentDocument || config.frame.ownerDocument,
  311. config.frame.contentWindow || window,
  312. config.frame);
  313. // Close frame on window close
  314. window.addEventListener('beforeunload', function() {
  315. config.close();
  316. }, false);
  317. // Now that everything is loaded, make it visible
  318. config.frame.style.display = "block";
  319. config.isOpen = true;
  320. }
  321. // Change this in the onOpen callback using this.frame.setAttribute('style', '')
  322. var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;' +
  323. ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;' +
  324. ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;' +
  325. ' width: 75%; z-index: 9999;';
  326. // Either use the element passed to init() or create an iframe
  327. if (this.frame) {
  328. this.frame.id = this.id; // Allows for prefixing styles with the config id
  329. this.frame.setAttribute('style', defaultStyle);
  330. var head = this.frame.ownerDocument.getElementsByTagName('head')[0];
  331. if (this.srcs && this.srcs.length > 0 ) {
  332. this.srcs.forEach(function(src) {
  333. head.appendChild(this.create('script', {src: src}));
  334. })
  335. }
  336. buildConfigWin(this.frame, head);
  337. } else {
  338. // Create frame
  339. document.body.appendChild((this.frame = this.create('iframe', {
  340. id: this.id,
  341. style: defaultStyle
  342. })));
  343. // In WebKit src can't be set until it is added to the page
  344. this.frame.src = 'about:blank';
  345. // we wait for the iframe to load before we can modify it
  346. this.frame.addEventListener('load', function(e) {
  347. var frame = config.frame;
  348. var body = frame.contentDocument.getElementsByTagName('body')[0];
  349. body.id = config.id; // Allows for prefixing styles with the config id
  350. var head = frame.contentDocument.getElementsByTagName('head')[0];
  351. if (config.src) head.appendChild(config.create('script', {src: config.src}));
  352. if (config.srcs && config.srcs.length > 0 ) {
  353. config.srcs.forEach(function(src) {
  354. head.appendChild(config.create('script', {src: src}));
  355. })
  356. }
  357. buildConfigWin(body, head);
  358. }, false);
  359. }
  360. };