Ikariam Core

Framework for Ikariam userscript developers.

当前为 2014-10-08 提交的版本,查看 最新版本

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

  1. /**
  2. * Additional methods for processing strings.
  3. *
  4. * @namespace String
  5. */
  6. /**
  7. * Additional methods for processing arrays.
  8. *
  9. * @namespace Array
  10. */
  11. // This curly bracket is for easy folding of all prototype methods and has not other sense. Please don't remove. :)
  12. {
  13. /**
  14. * Replaces characters or whitespaces at the beginning of a string.
  15. *
  16. * @param {string} toRemove
  17. * A string containing the characters to remove (optional, if not set: trim whitespaces).
  18. *
  19. * @return {string}
  20. * The trimmed string.
  21. */
  22. String.prototype.ltrim = function(toRemove) {
  23. // Is there a string with the characters to remove?
  24. var special = !!toRemove;
  25. // Return the trimmed string.
  26. return special ? this.replace(new RegExp('^[' + toRemove + ']+'), '') : this.replace(/^\s+/, '');
  27. };
  28. /**
  29. * Replaces characters or whitespaces at the end of a string.
  30. *
  31. * @param {string} toRemove
  32. * A string containing the characters to remove (optional, if not set: trim whitespaces).
  33. *
  34. * @return {string}
  35. * The trimmed string.
  36. */
  37. String.prototype.rtrim = function(toRemove) {
  38. // Is there a string with the characters to remove?
  39. var special = !!toRemove;
  40. // Return the trimmed string.
  41. return special ? this.replace(new RegExp('[' + toRemove + ']+$'), '') : this.replace(/\s+$/, '');
  42. };
  43. /**
  44. * Replaces characters or whitespaces at the beginning and end of a string.
  45. *
  46. * @param {string} toRemove
  47. * A string containing the characters to remove (optional, if not set: trim whitespaces).
  48. *
  49. * @return {string}
  50. * The trimmed string.
  51. */
  52. String.prototype.trim = function(toRemove) {
  53. return this.ltrim(toRemove).rtrim(toRemove);
  54. };
  55. /**
  56. * Encodes HTML-special characters in a string.
  57. *
  58. * @return {string}
  59. * The encoded string.
  60. */
  61. String.prototype.encodeHTML = function() {
  62. // Set the characters to encode.
  63. var characters = {
  64. '&': '&',
  65. '"': '"',
  66. '\'': ''',
  67. '<': '&lt;',
  68. '>': '&gt;'
  69. };
  70. // Return the encoded string.
  71. return this.replace(/([\&"'<>])/g, function(string, symbol) { return characters[symbol]; });
  72. };
  73. /**
  74. * Decodes HTML-special characters in a string.
  75. *
  76. * @return {string}
  77. * The decoded string.
  78. */
  79. String.prototype.decodeHTML = function() {
  80. // Set the characters to decode.
  81. var characters = {
  82. '&amp;': '&',
  83. '&quot;': '"',
  84. '&apos;': '\'',
  85. '&lt;': '<',
  86. '&gt;': '>'
  87. };
  88. // Return the decoded string.
  89. return this.replace(/(&quot;|&apos;|&lt;|&gt;|&amp;)/g, function(string, symbol) { return characters[symbol]; });
  90. };
  91. /**
  92. * Repeats a string a specified number of times.
  93. *
  94. * @param {int} nr
  95. * The number of times to repeat the string.
  96. *
  97. * @return {string}
  98. * The repeated string.
  99. */
  100. String.prototype.repeat = function(nr) {
  101. var ret = this;
  102. // Repeat the string.
  103. for(var i = 1; i < nr; i++) {
  104. ret += this;
  105. }
  106. return ret;
  107. };
  108. /**
  109. * Inserts an element at a specified position into an array.
  110. *
  111. * @param {mixed} item
  112. * The item which should be inserted.
  113. * @param {int} index
  114. * The position where the element should be added. If not set, the element will be added at the end.
  115. */
  116. Array.prototype.insert = function (item, index) {
  117. var maxIndex = this.length;
  118. // Get the index to insert.
  119. index = !index && index != 0 ? maxIndex : index;
  120. index = Math.max(index, 0); // No negative index.
  121. index = Math.min(index, maxIndex); // No index bigger than the array length.
  122. this.splice(index, 0, item);
  123. };
  124. /**
  125. * Deletes an element at a specified position from an array.
  126. *
  127. * @param {int} index
  128. * The position of the element which should be deleted.
  129. */
  130. Array.prototype.remove = function(index) {
  131. if(index >= 0 && index < this.length - 1) {
  132. this.splice(index, 1);
  133. }
  134. };
  135. }
  136.  
  137. /**
  138. * Instantiate a new set of core functions.<br>
  139. * {@link https://www.assembla.com/spaces/ikariam-tools/ Script homepage}
  140. *
  141. * @version 1.0
  142. * @author Tobbe <contact@ikascripts.de>
  143. *
  144. * @global
  145. *
  146. * @class
  147. * @classdesc Core functions for Ikariam.
  148. */
  149. function IkariamCore() {
  150. /**
  151. * Storage for accessing <code>this</code> as reference to IkariamCore in subfunctions. Do <b>NOT</b> delete!
  152. *
  153. * @private
  154. * @inner
  155. *
  156. * @type IkariamCore
  157. */
  158. var _this = this;
  159. /**
  160. * A reference to the window / unsafeWindow.
  161. *
  162. * @instance
  163. *
  164. * @type window
  165. */
  166. this.win = typeof unsafeWindow != 'undefined' ? unsafeWindow : window;
  167. /**
  168. * Reference to window.ikariam.
  169. *
  170. * @instance
  171. *
  172. * @type object
  173. */
  174. this.ika = this.win.ikariam;
  175. // Set the console to the "rescued" debugConsole.
  176. var _console = this.win.debugConsole;
  177. // If debugging is disabled or the debug console not available, set all functions to "null".
  178. if(!scriptInfo.debug || !_console) {
  179. _console = {};
  180. }
  181. // Define all Firebug tags.
  182. var _tags = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception',
  183. 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'profile', 'profileEnd',
  184. 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
  185. // Check all firebug functions.
  186. for(var i = 0; i < _tags.length; i++) {
  187. // Get the key.
  188. var _key = _tags[i];
  189. // If the function is not set yet, set it to "null".
  190. if(!_console[_key]) {
  191. _console[_key] = function() { return; };
  192. }
  193. }
  194. /**
  195. * Debugging console.
  196. * For more information about commands that are available for the Firebug console see {@link http://getfirebug.com/wiki/index.php/Console_API Firebug Console API}.<br>
  197. * Available commands: <code>assert, clear, count, debug, dir, dirxml, error, exception, group, groupCollapsed, groupEnd,
  198. * info, log, profile, profileEnd, table, time, timeEnd, timeStamp, trace, warn</code><br>
  199. * <br>
  200. * The console is deactivated by the ikariam page but with the script {@link https://userscripts.org/scripts/show/158528 RescueConsole} you can use it.
  201. *
  202. * @instance
  203. *
  204. * @type console
  205. */
  206. this.con = _console;
  207.  
  208. /**
  209. * Instantiate a new set of myGM functions.
  210. *
  211. * @inner
  212. *
  213. * @class
  214. * @classdesc Functions for cross-browser compatibility of the GM_* functions.<br>Also there are some new functions implemented.
  215. */
  216. function myGM() {
  217. /*--------------------------------------------*
  218. * Private variables, functions and settings. *
  219. *--------------------------------------------*/
  220. /**
  221. * Storage for style sheets which will be added by the script.
  222. *
  223. * @private
  224. * @inner
  225. *
  226. * @type element[]
  227. */
  228. var _styleSheets = {};
  229. /**
  230. * Storage for notification id for possibility to identify a notification popup.
  231. *
  232. * @private
  233. * @inner
  234. *
  235. * @type int
  236. */
  237. var _notificationId = 0;
  238. /**
  239. * The prefix which schuld be added to the values stored in localStorage / cookies.
  240. *
  241. * @private
  242. * @inner
  243. *
  244. * @type string
  245. */
  246. var _prefix = 'script' + scriptInfo.id;
  247. /**
  248. * If the Greasemonkey functions GM_setVaule, GM_getValue, GM_deleteValue and GM_listValues can be used.
  249. *
  250. * @private
  251. * @inner
  252. *
  253. * @type boolean
  254. */
  255. var _canUseGmStorage = !(typeof GM_getValue == 'undefined' || (typeof GM_getValue.toString == 'function' && GM_getValue.toString().indexOf('not supported') > -1))
  256. && !(typeof GM_setValue == 'undefined' || (typeof GM_setValue.toString == 'function' && GM_setValue.toString().indexOf('not supported') > -1))
  257. && !(typeof GM_deleteValue == 'undefined' || (typeof GM_deleteValue.toString == 'function' && GM_deleteValue.toString().indexOf('not supported') > -1))
  258. && !(typeof GM_listValues == 'undefined' || (typeof GM_listValues.toString == 'function' && GM_listValues.toString().indexOf('not supported') > -1));
  259. /**
  260. * If the Greasemonkey function GM_getResourceText can be used.
  261. *
  262. * @private
  263. * @inner
  264. *
  265. * @type boolean
  266. */
  267. var _canUseGmRessource = !(typeof GM_getResourceText == 'undefined' || (typeof GM_getResourceText.toString == 'function' && GM_getResourceText.toString().indexOf('not supported') > -1));
  268. /**
  269. * If the Greasemonkey function GM_xmlhttpRequest can be used.
  270. *
  271. * @private
  272. * @inner
  273. *
  274. * @type boolean
  275. */
  276. var _canUseGmXhr = !(typeof GM_xmlhttpRequest == 'undefined' || (typeof GM_xmlhttpRequest.toString == 'function' && GM_xmlhttpRequest.toString().indexOf('not supported') > -1));
  277. /**
  278. * If the local storage can be used.
  279. *
  280. * @private
  281. * @inner
  282. *
  283. * @type boolean
  284. */
  285. var _canUseLocalStorage = !!_this.win.localStorage;
  286. /*-------------------------------------------*
  287. * Public variables, functions and settings. *
  288. *-------------------------------------------*/
  289. /**
  290. * Read only access to the script identifying prefix.
  291. *
  292. * @instance
  293. *
  294. * @return {string}
  295. * The prefix for storing.
  296. */
  297. this.prefix = function() {
  298. return _prefix;
  299. };
  300. /**
  301. * Store a value specified by a key.
  302. *
  303. * @instance
  304. *
  305. * @param {string} key
  306. * The key of the value.
  307. * @param {mixed} value
  308. * The value to store.
  309. */
  310. this.setValue = function(key, value) {
  311. // Stringify the value to store also arrays.
  312. var toStore = _this.win.JSON.stringify(value);
  313. // If the use of the default GM_setValue ist possible, use it.
  314. if(_canUseGmStorage) {
  315. // Store the value.
  316. GM_setValue(key, toStore);
  317. // Otherwise use the local storage if possible.
  318. } else if(_canUseLocalStorage) {
  319. // Store the value.
  320. _this.win.localStorage.setItem(_prefix + key, toStore);
  321. // Otherwise use cookies.
  322. } else {
  323. // Prepare the cookie name and value.
  324. var data = escape(_prefix + key) + '=' + escape(toStore);
  325. // Set the expire date to January 1st, 2020.
  326. var expire = 'expires=' + (new Date(2020, 0, 1, 0, 0, 0, 0)).toGMTString();
  327. // Set the path to root.
  328. var path = 'path=/';
  329. // Made the cookie accessible from all servers.
  330. var domain = 'domain=ikariam.com';
  331. // Set the cookie.
  332. _this.win.document.cookie = data + ';' + expire + ';' + path + ';' + domain;
  333. }
  334. };
  335. /**
  336. * Get a value and return it.
  337. *
  338. * @instance
  339. *
  340. * @param {string} key
  341. * The key of the value.
  342. * @param {mixed} defaultValue
  343. * The value which is set if the value is not set.
  344. *
  345. * @return {mixed}
  346. * The stored value.
  347. */
  348. this.getValue = function(key, defaultValue) {
  349. // Put the default value to JSON.
  350. defaultValue = _this.win.JSON.stringify(defaultValue);
  351. // Storage for the value.
  352. var value = defaultValue;
  353. // If the use of the default GM_getValue ist possible, use it.
  354. if(_canUseGmStorage) {
  355. // Get the value.
  356. value = GM_getValue(key, defaultValue);
  357. // Otherwise use the local storage if possible.
  358. } else if(_canUseLocalStorage) {
  359. // Get the value.
  360. var valueTmp = _this.win.localStorage.getItem(_prefix + key);
  361. // If the value is not set, let the default value set.
  362. if(valueTmp) {
  363. value = valueTmp;
  364. }
  365. // Otherwise use cookies.
  366. } else {
  367. // Get all cookies.
  368. var allCookies = document.cookie.split("; ");
  369. // Loop over all cookies.
  370. for(var i = 0; i < allCookies.length; i++) {
  371. // Get the key and value of a cookie.
  372. var oneCookie = allCookies[i].split("=");
  373. // If the key is the correct key, get the value.
  374. if(oneCookie[0] == escape(_prefix + key)) {
  375. // Get the value and abort the loop.
  376. value = unescape(oneCookie[1]);
  377. break;
  378. }
  379. }
  380. }
  381. // Return the value (parsed for the correct return type).
  382. return _this.win.JSON.parse(value);
  383. };
  384. /**
  385. * Delete a value specified by a key.
  386. *
  387. * @instance
  388. *
  389. * @param {string} key
  390. * The key of the value.
  391. */
  392. this.deleteValue = function(key) {
  393. // If the use of the default GM_deleteValue ist possible, use it.
  394. if(_canUseGmStorage) {
  395. // Delete the value.
  396. GM_deleteValue(key);
  397. // Otherwise use the local storage if possible.
  398. } else if(_canUseLocalStorage) {
  399. // Remove the value.
  400. _this.win.localStorage.removeItem(_prefix + key);
  401. // Otherwise use cookies.
  402. } else {
  403. // Prepare the cookie name.
  404. var data = escape(_prefix + key) + '=';
  405. // Set the expire date to January 1st, 2000 (this will delete the cookie).
  406. var expire = 'expires=' + (new Date(2000, 0, 1, 0, 0, 0, 0)).toGMTString();
  407. // Set the path to root.
  408. var path = 'path=/';
  409. // Made the cookie accessible from all servers.
  410. var domain = 'domain=ikariam.com';
  411. // Set the cookie.
  412. _this.win.document.cookie = data + ';' + expire + ';' + path + ';' + domain;
  413. }
  414. };
  415. /**
  416. * Returns an array with the keys of all values stored by the script.
  417. *
  418. * @instance
  419. *
  420. * @return {mixed[]}
  421. * The array with all keys.
  422. */
  423. this.listValues = function() {
  424. // Create an array for the storage of the values keys.
  425. var key = new Array();
  426. // If the use of the default GM_listValues ist possible, use it.
  427. if(_canUseGmStorage) {
  428. // Store the key(s) to the array.
  429. key = GM_listValues();
  430. // Otherwise use the local storage if possible.
  431. } else if(_canUseLocalStorage) {
  432. // Loop over all stored values.
  433. for(var i = 0; i < _this.win.localStorage.length; i++) {
  434. // Get the key name of the key with the number i.
  435. var keyName = _this.win.localStorage.key(i);
  436. // If the value is set by the script, push the key name to the array.
  437. if(keyName.indexOf(_prefix) != -1) {
  438. key.push(keyName.replace(_prefix, ''));
  439. }
  440. }
  441. // Otherwise use cookies.
  442. } else {
  443. // Get all cookies.
  444. var allCookies = document.cookie.split("; ");
  445. // Loop over all cookies.
  446. for(var i = 0; i < allCookies.length; i++) {
  447. // Get the key name of a cookie.
  448. var keyName = unescape(allCookies[i].split("=")[0]);
  449. // If the key value is set by the script, push the key name to the array.
  450. if(keyName.indexOf(_prefix) != -1) {
  451. key.push(keyName.replace(_prefix, ''));
  452. }
  453. }
  454. }
  455. // Return all keys.
  456. return key;
  457. };
  458. /**
  459. * Adds a style element to the head of the page and return it.
  460. *
  461. * @instance
  462. *
  463. * @param {string} styleRules
  464. * The style rules to be set.
  465. * @param {string} id
  466. * An id for the style set, to have the possibility to delete it. (optional, if none is set, the stylesheet is not stored)
  467. * @param {boolean} overwrite
  468. * If a style with id should overwrite an existing style.
  469. *
  470. * @return {boolean}
  471. * If the stylesheet was stored with the id.
  472. */
  473. this.addStyle = function(styleRules, id, overwrite) {
  474. // If the element was stored is saved here.
  475. var storedWithId = false;
  476. // If overwrite, remove the old style sheet.
  477. if(overwrite && overwrite == true) {
  478. this.removeStyle(id);
  479. }
  480. // If the stylesheet doesn't exists.
  481. if(!id || (id && !_styleSheets[id])) {
  482. // Create a new style element and set the style rules.
  483. var style = this.addElement('style', document.head);
  484. style.type = 'text/css';
  485. style.innerHTML = styleRules;
  486. // If an id is set, store it.
  487. if(id) {
  488. _styleSheets[id] = style;
  489. storedWithId = true;
  490. }
  491. }
  492. // Return if the stylesheet was stored with an id.
  493. return storedWithId;
  494. };
  495. /**
  496. * Removes a style element set by the script.
  497. *
  498. * @instance
  499. *
  500. * @param {string} id
  501. * The id of the stylesheet to delete.
  502. *
  503. * @return {boolean}
  504. * If the stylesheet could be deleted.
  505. */
  506. this.removeStyle = function(id) {
  507. // Stores if the stylesheet could be removed.
  508. var removed = false;
  509. // If there is an id set and a stylesheet with the id exists.
  510. if(id && _styleSheets[id]) {
  511. // Remove the stylesheet from the page.
  512. document.head.removeChild(_styleSheets[id]);
  513. // Remove the stylesheet from the array.
  514. delete _styleSheets[id];
  515. // Set removed to true.
  516. removed = true;
  517. }
  518. // Return if the stylesheet could be removed.
  519. return removed;
  520. };
  521. /**
  522. * Makes a cross-site XMLHttpRequest.
  523. *
  524. * @instance
  525. *
  526. * @param {mixed[]} args
  527. * The arguments the request needs. (specified here: {@link http://wiki.greasespot.net/GM_xmlhttpRequest GM_xmlhttpRequest})
  528. *
  529. * @return {mixed}
  530. * The response text or a hint indicating an error.
  531. */
  532. this.xhr = function(args) {
  533. // Storage for the result of the request.
  534. var responseText;
  535. // Check if all required data is given.
  536. if(!args.method || !args.url || !args.onload) {
  537. return false;
  538. }
  539. // If the use of the default GM_xmlhttpRequest ist possible, use it.
  540. if(_canUseGmXhr) {
  541. // Sent the request.
  542. var response = GM_xmlhttpRequest(args);
  543. // Get the response text.
  544. responseText = response.responseText;
  545. // Otherwise show a hint for the missing possibility to fetch the data.
  546. } else {
  547. // Storage if the link fetches metadata from userscripts.org
  548. var isJSON = (args.url.search(/\.json$/i) != -1);
  549. // Otherwise if it is JSON.
  550. if(isJSON) {
  551. // Do the request with a string indicating the error.
  552. args.onload('{ "is_error": true }');
  553. // Return a string indicating the error.
  554. responseText = '{ "is_error": true }';
  555. // Otherwise.
  556. } else {
  557. responseText = false;
  558. }
  559. }
  560. // Return the responseText.
  561. return responseText;
  562. };
  563. /**
  564. * Returns the content of a resource parsed with JSON.parse.
  565. *
  566. * @instance
  567. *
  568. * @param {string} name
  569. * The name of the resource to parse.
  570. */
  571. this.getResourceParsed = function(name, xhrUrl) {
  572. // Storage for the response text.
  573. var responseText = '';
  574. // Function for safer parsing.
  575. var safeParse = function(key, value) {
  576. // If the value is a function, return just the string, so it is not executable.
  577. if(typeof value === 'function' || Object.prototype.toString.apply(value) === '[object function]') {
  578. return value.toString();
  579. }
  580.  
  581. // Return the value.
  582. return value;
  583. };
  584. // If the use of the default GM_getRessourceText ist possible, use it.
  585. if(_canUseGmRessource) {
  586. // Set the parsed text.
  587. responseText = GM_getResourceText(name);
  588. // Otherwise perform a xmlHttpRequest.
  589. } else {
  590. // Perform the xmlHttpRequest.
  591. responseText = this.xhr({
  592. method: 'GET',
  593. url: xhrUrl,
  594. headers: { 'User-agent': navigator.userAgent, 'Accept': 'text/html' },
  595. synchronous: true,
  596. onload: function(response) { return false; }
  597. });
  598. }
  599. // Return the parsed resource text.
  600. return _this.win.JSON.parse(responseText, safeParse);
  601. };
  602. /**
  603. * Gets the first matching child element by a query and returns it.
  604. *
  605. * @instance
  606. *
  607. * @param {string} query
  608. * The query for the element.
  609. * @param {element} parent
  610. * The parent element. (optional, default document)
  611. *
  612. * @return {element}
  613. * The element.
  614. */
  615. this.$ = function(query, parent) {
  616. return this.$$(query, parent)[0];
  617. };
  618. /**
  619. * Gets all matching child elements by a query and returns them.
  620. *
  621. * @instance
  622. *
  623. * @param {string} query
  624. * The query for the elements.
  625. * @param {element} parent
  626. * The parent element. (optional, default document)
  627. *
  628. * @return {element[]}
  629. * The elements.
  630. */
  631. this.$$ = function(query, parent) {
  632. // If there is no parent set, set it to document.
  633. if(!parent) parent = document;
  634. // Return the elements.
  635. return Array.prototype.slice.call(parent.querySelectorAll(query));
  636. };
  637. /**
  638. * Returns the value of the selected option of a select field.
  639. *
  640. * @param {string} id
  641. * The last part of the id of the element.
  642. * @param {boolean} hasNoPrefix
  643. * Says if the id has no prefix.
  644. * @param {boolean} addNoSelect
  645. * Says if there should not be added a "Select" at the end of the id.
  646. *
  647. * @return {string}
  648. * The value.
  649. */
  650. this.getSelectValue = function(id, hasNoPrefix, addNoSelect) {
  651. // Get the select field.
  652. var select = this.$('#' + (hasNoPrefix ? '' : _prefix) + id + (addNoSelect ? '' : 'Select'));
  653. // Return the value.
  654. return select.options[select.selectedIndex].value;
  655. };
  656. /**
  657. * Creates a new element and adds it to a parent.
  658. *
  659. * @instance
  660. *
  661. * @param {string} type
  662. * The type of the new element.
  663. * @param {element} parent
  664. * The parent of the new element.
  665. * @param {string} id
  666. * The last part of the id of the element. (optional, if not set, no id will be set)
  667. * @param {string || String[]} classes
  668. * The class(es) of the element. (optional, if not set, no class will be set)
  669. * @param {mixed[]} style
  670. * The styles of the element. (optional, if not set, no style will be set)
  671. * @param {boolean || boolean[]} hasPrefix
  672. * If no prefix should be used. (optional, if not set, a prefix will be used for id and no prefix will be used for classes)
  673. * @param {element} nextSib
  674. * The next sibling of the element. (optional, if not set, the element will be added at the end)
  675. *
  676. * @return {element}
  677. * The new element.
  678. */
  679. this.addElement = function(type, parent, id, classes, style, hasPrefix, nextSib) {
  680. // Create the new Element.
  681. var newElement = document.createElement(type);
  682. // If there is a id, set it.
  683. if(id) {
  684. // Get the id prefix.
  685. var idPrefix = !(hasPrefix == false || (hasPrefix && hasPrefix.id == false)) ? _prefix : '';
  686. // Set the id.
  687. newElement.id = idPrefix + id;
  688. }
  689. // Add all classes.
  690. if(classes && classes != '') {
  691. // Get the class prefix.
  692. var classPrefix = !!(hasPrefix == true || (hasPrefix && hasPrefix.classes == true)) ? _prefix : '';
  693. // Set the class(es).
  694. if(typeof classes == 'string') {
  695. newElement.classList.add(classPrefix + classes);
  696. } else {
  697. for(var i = 0; i < classes.length; i++) {
  698. if(classes[i] != '') {
  699. newElement.classList.add(classPrefix + classes[i]);
  700. }
  701. }
  702. }
  703. }
  704. if(style) {
  705. for(var i = 0; i < style.length; i++) {
  706. newElement.style[style[i][0]] = style[i][1];
  707. }
  708. }
  709. // Insert the element.
  710. parent.insertBefore(newElement, nextSib);
  711. // Return the new element.
  712. return newElement;
  713. };
  714. /**
  715. * Creates new checkboxes and adds it to a parent.
  716. *
  717. * @instance
  718. *
  719. * @param {element} parent
  720. * The parent of the new checkboxes.
  721. * @param {mixed[]} cbData
  722. * An array containing the data (id, label, checked) of each checkbox.
  723. */
  724. this.addCheckboxes = function(parent, cbData) {
  725. // Create the checkboxes.
  726. for(var i = 0; i < cbData.length; i++) {
  727. // Create the wrapper for the checkbox and the label.
  728. var wrapper = _this.myGM.addElement('div', parent, null, 'cbWrapper');
  729. // Create the checkbox and set the attributes.
  730. var cb = _this.myGM.addElement('input', wrapper, cbData[i]['id'] + 'Cb', 'checkbox');
  731. cb.type = 'checkbox';
  732. cb.title = cbData[i]['label'];
  733. cb.checked = cbData[i]['checked'] ? 'checked' : '';
  734. }
  735. // Replace the checkboxes for better appearance.
  736. _this.ika.controller.replaceCheckboxes();
  737. };
  738. /**
  739. * Creates a new select field and adds it to a parent table.
  740. *
  741. * @instance
  742. *
  743. * @param {element} parentTable
  744. * The parent table of the new select field.
  745. * @param {string} id
  746. * The last part of the id of the select field.
  747. * @param {mixed} selected
  748. * The value of the selected option.
  749. * @param {mixed[]} opts
  750. * An array with the names an values of the options.<br>
  751. * Signature: <code>[{ value: 'val', name: 'name' }]</code>
  752. * @param {string} labelText
  753. * The text of the select label.
  754. */
  755. this.addSelect = function(parentTable, id, selected, opts, labelText) {
  756. // Create table row.
  757. var tr = _this.myGM.addElement('tr', parentTable);
  758. // Create cells.
  759. var labelCell = _this.myGM.addElement('td', tr);
  760. var selectCell = _this.myGM.addElement('td', tr, null, 'left');
  761. // Create label.
  762. var selectLabel = _this.myGM.addElement('span', labelCell);
  763. selectLabel.innerHTML = labelText;
  764. // Create the wrapper for the select.
  765. var wrapper = _this.myGM.addElement('div', selectCell, id + 'SelectContainer', ['select_container', 'size175'], new Array(['position', 'relative']));
  766. // Create the select field.
  767. var select = _this.myGM.addElement('select', wrapper, id + 'Select', 'dropdown');
  768. // Add the Options.
  769. for(var i = 0; i < opts.length; i++) {
  770. // Create an option.
  771. var option = _this.myGM.addElement('option', select);
  772. // Set the value and the name.
  773. option.value = opts[i].value;
  774. option.innerHTML = opts[i].name;
  775. // If the option is selected, set selected to true.
  776. if(option.value == selected) {
  777. option.selected = 'selected';
  778. }
  779. }
  780. // Replace the dropdown for better appearance.
  781. _this.ika.controller.replaceDropdownMenus();
  782. };
  783. /**
  784. * Creates a button and adds it to a parent.
  785. *
  786. * @instance
  787. *
  788. * @param {element} parent
  789. * The parent element.
  790. * @param {string} value
  791. * The value of the button.
  792. * @param {function} callback
  793. * A callback which should be called when the user clicks on the button.<br>
  794. * Signature: <code>function() : void</code>
  795. */
  796. this.addButton = function(parent, value, callback) {
  797. // Create the button wrapper.
  798. var buttonWrapper = _this.myGM.addElement('div', parent, null, 'centerButton');
  799. // Create the button.
  800. var button = _this.myGM.addElement('input', buttonWrapper, null, 'button');
  801. button.type = 'button';
  802. button.value = value;
  803. // Add a click action to the button.
  804. button.addEventListener('click', callback, false);
  805. return button;
  806. };
  807. /**
  808. * Shows a notification to the user. You can either create a notification field or an input / output field.
  809. * If the field should be an input field, the field is given to the callbacks as parameter.
  810. * The abort button is only shown if the abort callback is set.
  811. * Also it is possible to have two body parts or just one body part.
  812. * This functionality is set by the notification text.<br><br>
  813. *
  814. * Possible notification texts:<br>
  815. * <code>&#09;text.header (optional)<br>
  816. * &#09;text.body or text.bodyTop & text.bodyBottom<br>
  817. * &#09;text.confirm (optional)<br>
  818. * &#09;text.abort (optional)</code>
  819. *
  820. * @instance
  821. *
  822. * @param {string[]} text
  823. * The notification texts.
  824. * @param {function[]} callback
  825. * The callbacks for confirm and abort. (optional, default: close panel)<br>
  826. * Signature with input: <code>function(textarea : element) : void</code>
  827. * Signature without input: <code>function() : void</code>
  828. * @param {boolean} input
  829. * If a input field should be used. (optional, default: false)
  830. *
  831. * @return {int}
  832. * The notification id.
  833. */
  834. this.notification = function(text, callback, input) {
  835. // Raise the notification id.
  836. _notificationId += 1;
  837. // Set a local notification id to be able to have more than 1 notification panels.
  838. var localNotificationId = _notificationId;
  839. // Function to close the notification panel.
  840. var closeNotificationPanel = function() {
  841. // Remove the notification background.
  842. document.body.removeChild(_this.myGM.$('#' + _prefix + 'notificationBackground' + localNotificationId));
  843. // Remove the notification panel.
  844. document.body.removeChild(_this.myGM.$('#' + _prefix + 'notificationPanelContainer' + localNotificationId));
  845. };
  846. // Create the background and the container.
  847. this.addElement('div', document.body, 'notificationBackground' + localNotificationId, 'notificationBackground', null, true);
  848. var notificationPanelContainer = this.addElement('div', document.body, 'notificationPanelContainer' + localNotificationId, 'notificationPanelContainer', null, true);
  849. var notificationPanel = this.addElement('div', notificationPanelContainer, 'notificationPanel' + localNotificationId, 'notificationPanel', null, true);
  850. // Create the notification panel header.
  851. var notificationPanelHeader = this.addElement('div', notificationPanel, 'notificationPanelHeader' + localNotificationId, 'notificationPanelHeader', null, true);
  852. var notificationPanelHeaderL = this.addElement('div', notificationPanelHeader, 'notificationPanelHeaderL' + localNotificationId, 'notificationPanelHeaderL', null, true);
  853. var notificationPanelHeaderR = this.addElement('div', notificationPanelHeaderL, 'notificationPanelHeaderR' + localNotificationId, 'notificationPanelHeaderR', null, true);
  854. var notificationPanelHeaderM = this.addElement('div', notificationPanelHeaderR, 'notificationPanelHeaderM' + localNotificationId, 'notificationPanelHeaderM', null, true);
  855. notificationPanelHeaderM.innerHTML = (text.header ? text.header : _this.Language.$('default_notification_header'));
  856. var notificationPanelClose = this.addElement('div', notificationPanelHeaderM, 'notificationPanelClose' + localNotificationId, 'notificationPanelClose', null, true);
  857. notificationPanelClose.addEventListener('click', closeNotificationPanel, false);
  858. // Create the notification panel body.
  859. var notificationPanelBody = this.addElement('div', notificationPanel, 'notificationPanelBody' + localNotificationId, 'notificationPanelBody', null, true);
  860. var notificationPanelBodyL = this.addElement('div', notificationPanelBody, 'notificationPanelBodyL' + localNotificationId, 'notificationPanelBodyL', null, true);
  861. var notificationPanelBodyR = this.addElement('div', notificationPanelBodyL, 'notificationPanelBodyR' + localNotificationId, 'notificationPanelBodyR', null, true);
  862. var notificationPanelBodyM = this.addElement('div', notificationPanelBodyR, 'notificationPanelBodyM' + localNotificationId, 'notificationPanelBodyM', null, true);
  863. var bodyType = input ? 'textarea' : 'div';
  864. var body;
  865. if(text.body) {
  866. var notificationPanelBodyMContent = this.addElement(bodyType, notificationPanelBodyM, 'notificationPanelBodyMContent' + localNotificationId, 'notificationPanelBodyMContent', null, true);
  867. notificationPanelBodyMContent.innerHTML = text.body;
  868. body = input ? notificationPanelBodyMContent : null;
  869. } else {
  870. var notificationPanelBodyMTop = this.addElement('div', notificationPanelBodyM, 'notificationPanelBodyMTop' + localNotificationId, 'notificationPanelBodyMTop', null, true);
  871. notificationPanelBodyMTop.innerHTML = text.bodyTop ? text.bodyTop : '';
  872. var notificationPanelBodyMBottom = this.addElement(bodyType, notificationPanelBodyM, 'notificationPanelBodyMBottom' + localNotificationId, 'notificationPanelBodyMBottom', null, true);
  873. notificationPanelBodyMBottom.innerHTML = text.bodyBottom ? text.bodyBottom : '';
  874. body = input ? notificationPanelBodyMBottom : null;
  875. }
  876. this.addElement('div', notificationPanelBodyM, 'notificationPanelBodyPlaceholder' + localNotificationId, 'notificationPanelBodyPlaceholder', null, true);
  877. // Create the notification panel footer.
  878. var notificationPanelFooter = this.addElement('div', notificationPanel, 'notificationPanelFooter' + localNotificationId, 'notificationPanelFooter', null, true);
  879. var notificationPanelFooterL = this.addElement('div', notificationPanelFooter, 'notificationPanelFooterL' + localNotificationId, 'notificationPanelFooterL', null, true);
  880. var notificationPanelFooterR = this.addElement('div', notificationPanelFooterL, 'notificationPanelFooterR' + localNotificationId, 'notificationPanelFooterR', null, true);
  881. var notificationPanelFooterM = this.addElement('div', notificationPanelFooterR, 'notificationPanelFooterM' + localNotificationId, 'notificationPanelFooterM', null, true);
  882. notificationPanelFooterM.innerHTML = scriptInfo.name + ' v' + scriptInfo.version;
  883. // Create the button wrapper.
  884. var notificationPanelButtonWrapper = this.addElement('div', notificationPanel, 'notificationPanelButtonWrapper' + localNotificationId, 'notificationPanelButtonWrapper', null, true);
  885. // Create the confirm button.
  886. var notificationPanelConfirm = this.addElement('input', notificationPanelButtonWrapper, 'notificationPanelConfirm' + localNotificationId, ['notificationPanelButton', 'notificationPanelButtonConfirm'], null, true);
  887. notificationPanelConfirm.type = 'button';
  888. notificationPanelConfirm.value = text.confirm ? text.confirm : _this.Language.$('default_notification_button_confirm');
  889. if(callback && callback.confirm) {
  890. if(body) {
  891. notificationPanelConfirm.addEventListener('click', function() { closeNotificationPanel(); callback.confirm(body); }, false);
  892. } else {
  893. notificationPanelConfirm.addEventListener('click', function() { closeNotificationPanel(); callback.confirm(); }, false);
  894. }
  895. } else {
  896. notificationPanelConfirm.addEventListener('click', closeNotificationPanel, false);
  897. }
  898. // Create the abort button if needed.
  899. if(callback && callback.abort) {
  900. var notificationPanelAbort = this.addElement('input', notificationPanelButtonWrapper, 'notificationPanelAbort' + localNotificationId, ['notificationPanelButton', 'notificationPanelButtonAbort'], null, true);
  901. notificationPanelAbort.type = 'button';
  902. notificationPanelAbort.value = text.abort ? text.abort : _this.Language.$('default_notification_button_abort');
  903. if(body) {
  904. notificationPanelAbort.addEventListener('click', function() { closeNotificationPanel(); callback.abort(body); }, false);
  905. } else {
  906. notificationPanelAbort.addEventListener('click', function() { closeNotificationPanel(); callback.abort(); }, false);
  907. }
  908. }
  909. return localNotificationId;
  910. };
  911. /**
  912. * Toogle the show / hide Button image and title.
  913. *
  914. * @instance
  915. *
  916. * @param {element} button
  917. * The button to toggle.
  918. */
  919. this.toggleShowHideButton = function(button) {
  920. // Switch the button picture.
  921. button.classList.toggle('minimizeImg');
  922. button.classList.toggle('maximizeImg');
  923. // Switch the button title.
  924. button.title = (button.title == _this.Language.$('general_fold')) ? _this.Language.$('general_expand') : _this.Language.$('general_fold');
  925. };
  926. /**
  927. * Runs a callback on every property of an object which is not in the prototype.
  928. *
  929. * @param {object} obj
  930. * The Object where forEach should be used.
  931. * @param {function} callback
  932. * The callback which should be called.<br>
  933. * Signature: <code>function(propertyValue : mixed, propertyKey : string) : void</code>
  934. */
  935. this.forEach = function(obj, callback) {
  936. for(var key in obj) {
  937. if(Object.prototype.hasOwnProperty.call(obj, key)) {
  938. callback(key, obj[key]);
  939. }
  940. }
  941. };
  942. /*--------------------*
  943. * Set some settings. *
  944. *--------------------*/
  945. // Set the notification style.
  946. this.addStyle(
  947. "." + _prefix + "notificationBackground { z-index: 1000000000000; position: fixed; visibility: visible; top: 0px; left: 0px; width: 100%; height: 100%; padding: 0; background-color: #000; opacity: .7; } \
  948. ." + _prefix + "notificationPanelContainer { z-index: 1000000000001; position: fixed; visibility: visible; top: 100px; left: 50%; width: 500px; height: 370px; margin-left: -250px; padding: 0; text-align: left; color: #542C0F; font: 12px Arial,Helvetica,sans-serif; } \
  949. ." + _prefix + "notificationPanel { position: relative; top: 0px; left: 0px; background-color: transparent; border: 0 none; overflow: hidden; } \
  950. ." + _prefix + "notificationPanelHeader { height: 39px; background: none repeat scroll 0 0 transparent; font-weight: bold; line-height: 2; white-space: nowrap; } \
  951. ." + _prefix + "notificationPanelHeaderL { height: 39px; background-image: url('skin/layout/notes_top_left.png'); background-position: left top; background-repeat: no-repeat; } \
  952. ." + _prefix + "notificationPanelHeaderR { height: 39px; background-image: url('skin/layout/notes_top_right.png'); background-position: right top; background-repeat: no-repeat; } \
  953. ." + _prefix + "notificationPanelHeaderM { height: 39px; margin: 0 14px 0 38px; padding: 12px 0 0; background-image: url('skin/layout/notes_top.png'); background-position: left top; background-repeat: repeat-x; color: #811709; line-height: 1.34em; } \
  954. ." + _prefix + "notificationPanelBody { max-height: 311px; height: 100%; background: none repeat scroll 0 0 transparent; } \
  955. ." + _prefix + "notificationPanelBodyL { height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; } \
  956. ." + _prefix + "notificationPanelBodyR { height: 100%; background-image: url('skin/layout/notes_right.png'); background-position: right top; background-repeat: repeat-y; } \
  957. ." + _prefix + "notificationPanelBodyM { height: 100%; background-color: #F7E7C5; background-image: none; margin: 0 6px; padding: 0 10px; font-size: 14px; } \
  958. ." + _prefix + "notificationPanelBodyMTop { max-height: 100px; line-height: 2; } \
  959. ." + _prefix + "notificationPanelBodyMTop b { line-height: 3.5; font-size:110%; } \
  960. ." + _prefix + "notificationPanelBodyM a { color: #811709; font-weight: bold; } \
  961. ." + _prefix + "notificationPanelBodyM h2 { font-weight: bold; } \
  962. ." + _prefix + "notificationPanelBodyMContent { max-height: 270px; padding: 10px; background: url('skin/input/textfield.png') repeat-x scroll 0 0 #FFF7E1; border: 1px dotted #C0C0C0; font: 14px Arial,Helvetica,sans-serif; color: #000000; border-collapse: separate; overflow-y:auto; } \
  963. ." + _prefix + "notificationPanelBodyMBottom { max-height: 170px; padding: 10px; background: url('skin/input/textfield.png') repeat-x scroll 0 0 #FFF7E1; border: 1px dotted #C0C0C0; font: 14px Arial,Helvetica,sans-serif; color: #000000; border-collapse: separate; overflow-y:auto; } \
  964. textarea." + _prefix + "notificationPanelBodyMContent { height: 270px; width: 445px; resize: none; } \
  965. textarea." + _prefix + "notificationPanelBodyMBottom { height: 170px; width: 445px; resize: none; } \
  966. ." + _prefix + "notificationPanelBodyPlaceholder { height: 20px; } \
  967. ." + _prefix + "notificationPanelFooter { height: 20px; background: none repeat scroll 0 0 transparent; } \
  968. ." + _prefix + "notificationPanelFooterL { height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; border: 0 none; } \
  969. ." + _prefix + "notificationPanelFooterR { height: 21px; background-image: url('skin/layout/notes_br.png'); background-position: right bottom; background-repeat: no-repeat; } \
  970. ." + _prefix + "notificationPanelFooterM { background-color: #F7E7C5; border-bottom: 3px solid #D2A860; border-left: 2px solid #D2A860; margin: 0 23px 0 3px; padding: 3px 0 2px 3px; font-size: 77%; } \
  971. ." + _prefix + "notificationPanelClose { cursor: pointer; position: absolute; top: 12px; right: 8px; width: 17px; height: 17px; background-image: url('skin/layout/notes_close.png'); } \
  972. ." + _prefix + "notificationPanelButtonWrapper { bottom: -4px; position: absolute; margin: 10px auto; width: 100%; text-align: center; } \
  973. ." + _prefix + "notificationPanelButton { background: url('skin/input/button.png') repeat-x scroll 0 0 #ECCF8E; border-color: #C9A584 #5D4C2F #5D4C2F #C9A584; border-style: double; border-width: 3px; cursor: pointer; display: inline; font-weight: bold; margin: 0px 5px; padding: 2px 10px; text-align: center; font-size: 12px; width: 100px; } \
  974. ." + _prefix + "notificationPanelButton:hover { color: #B3713F; } \
  975. ." + _prefix + "notificationPanelButton:active { border-color: #5D4C2F #C9A584 #C9A584 #5D4C2F; border-style: double; border-width: 3px; padding: 3px 10px 1px; } \
  976. ." + _prefix + "notificationPanelButtonConfirm { } \
  977. ." + _prefix + "notificationPanelButtonAbort { }",
  978. 'notification', true
  979. );
  980. // Add the buttons for toggle buttons used styles.
  981. this.addStyle(
  982. ".minimizeImg, .maximizeImg { background: url('skin/interface/window_control_sprite.png') no-repeat scroll 0 0 transparent; cursor: pointer; display: block; height: 18px; width: 18px; } \
  983. .minimizeImg { background-position: -144px 0; } \
  984. .minimizeImg:hover { background-position: -144px -19px; } \
  985. .maximizeImg { background-position: -126px 0; } \
  986. .maximizeImg:hover { background-position: -126px -19px; }",
  987. 'toggleShowHideButton', true
  988. );
  989. }
  990. /**
  991. * myGM for cross-browser compatibility of the GM_* functions. (use myGM.* instead of GM_*)<br>
  992. * Also there are general used functions stored.
  993. *
  994. * @instance
  995. *
  996. * @type IkariamCore~myGM
  997. */
  998. this.myGM = new myGM;
  999. /**
  1000. * Instantiate a new set of Ikariam specific functions.
  1001. *
  1002. * @inner
  1003. *
  1004. * @class
  1005. * @classdesc Ikariam specific functions.
  1006. */
  1007. function Ikariam() {
  1008. /*-------------------------------------------*
  1009. * Public variables, functions and settings. *
  1010. *-------------------------------------------*/
  1011. /**
  1012. * Returns the name of the actual selected view (world, island, town).
  1013. *
  1014. * @instance
  1015. *
  1016. * @return {string}
  1017. * The name of the view.
  1018. */
  1019. this.view = function() {
  1020. // Get the id of the body.
  1021. var viewId = document.body.id;
  1022. var view = '';
  1023. // Get the name of the view depending on the body id.
  1024. switch(viewId) {
  1025. case 'worldmap_iso':
  1026. view = 'world';
  1027. break;
  1028. case 'island':
  1029. view = 'island';
  1030. break;
  1031. case 'city':
  1032. view = 'town';
  1033. break;
  1034. }
  1035. // Return the view name.
  1036. return view;
  1037. };
  1038. /**
  1039. * Parses a string number to an int value.
  1040. *
  1041. * @instance
  1042. *
  1043. * @param {string} txt
  1044. * The number to format.
  1045. *
  1046. * @return {int}
  1047. * The formatted value.
  1048. */
  1049. this.getInt = function(txt) {
  1050. // Return the formated number.
  1051. return parseInt(txt.replace(/(\.|,)/g, ''));
  1052. };
  1053. /**
  1054. * Formats a number to that format that is used in Ikariam.
  1055. *
  1056. * @param {int} num
  1057. * The number to format.
  1058. * @param {boolean || boolean[]} addColor
  1059. * If the number should be coloured. (optional, if not set, a color will be used for negative and no color will be used for positive numbers)
  1060. * @param {boolean} usePlusSign
  1061. * If a plus sign should be used for positive numbers.
  1062. *
  1063. * @return {string}
  1064. * The formated number.
  1065. */
  1066. this.formatToIkaNumber = function(num, addColor, usePlusSign) {
  1067. var txt = num + '';
  1068. // Set a seperator every 3 digits from the end.
  1069. txt = txt.replace(/(\d)(?=(\d{3})+\b)/g, '$1' + Language.$('settings_kiloSep'));
  1070. // If the number is negative and it is enabled, write it in red.
  1071. if(num < 0 && !(addColor == false || (addColor && addColor.negative == false))) {
  1072. txt = '<span class="red bold">' + txt + '</span>';
  1073. }
  1074. // If the number is positive.
  1075. if(num > 0) {
  1076. // Add the plus sign if wanted.
  1077. txt = (usePlusSign ? '+' : '') + txt;
  1078. // Color the text green if wanted.
  1079. if(!!(addColor == true || (addColor && addColor.positive == true))) {
  1080. txt = '<span class="green bold">' + txt + '</span>';
  1081. }
  1082. }
  1083. // Return the formated number.
  1084. return txt;
  1085. };
  1086. /**
  1087. * Returns if the user is logged in to the mobile version.
  1088. *
  1089. * @instance
  1090. *
  1091. * @return {boolean}
  1092. * The login-status to mobile.
  1093. */
  1094. this.isMobileVersion = function() {
  1095. return (top.location.href.search(/http:\/\/m/) > -1);
  1096. },
  1097. /**
  1098. * Returns a code consisting of the server name and the country code.
  1099. *
  1100. * @instance
  1101. *
  1102. * @return {string}
  1103. * The code.
  1104. */
  1105. this.getServerCode = function() {
  1106. // Split the host string.
  1107. var code = top.location.host.split('.');
  1108. // Set the language name.
  1109. return (code ? code[1] + '_' + code[0] : 'undefined');
  1110. };
  1111. /**
  1112. * Shows a hint to the user (desktop).
  1113. *
  1114. * @instance
  1115. *
  1116. * @param {string} located
  1117. * The location of the hint. Possible are all advisors, a clicked element or a committed element.
  1118. * @param {string} type
  1119. * The type of the hint. Possible is confirm, error, neutral or follow the mouse.
  1120. * @param {string} msgText
  1121. * The hint text.
  1122. * @param {string} msgBindTo
  1123. * The JQuery selector of the element the tooltip should be bound to.
  1124. * @param {boolean} msgIsMinSize
  1125. * If the message is minimized (only used if type = followMouse).
  1126. */
  1127. this.showTooltip = function(located, type, msgText, msgBindTo, msgIsMinSize) {
  1128. // Get the message location.
  1129. var msgLocation = -1;
  1130. switch(located) {
  1131. case 'cityAdvisor':
  1132. msgLocation = 1;
  1133. break;
  1134. case 'militaryAdvisor':
  1135. msgLocation = 2;
  1136. break;
  1137. case 'researchAdvisor':
  1138. msgLocation = 3;
  1139. break;
  1140. case 'diplomacyAdvisor':
  1141. msgLocation = 4;
  1142. break;
  1143. case 'clickedElement':
  1144. msgLocation = 5;
  1145. break;
  1146. case 'committedElement':
  1147. msgLocation = 6;
  1148. break;
  1149. }
  1150. // Get the message type.
  1151. var msgType = -1;
  1152. switch(type) {
  1153. case 'confirm':
  1154. msgType = 10;
  1155. break;
  1156. case 'error':
  1157. msgType = 11;
  1158. break;
  1159. case 'neutral':
  1160. msgType = 12;
  1161. break;
  1162. case 'followMouse':
  1163. msgType = 13;
  1164. break;
  1165. }
  1166. // Show the tooltip.
  1167. _this.ika.controller.tooltipController.bindBubbleTip(msgLocation, msgType, msgText, null, msgBindTo, msgIsMinSize);
  1168. };
  1169. }
  1170. /**
  1171. * Ikariam specific functions like converting a number from Ikariam format to int.
  1172. *
  1173. * @instance
  1174. *
  1175. * @type IkariamCore~Ikariam
  1176. */
  1177. this.Ikariam = new Ikariam;
  1178. /**
  1179. * Instantiate a new set of localisation functions.
  1180. *
  1181. * @inner
  1182. *
  1183. * @class
  1184. * @classdesc Functions for localisating the script.
  1185. */
  1186. function Language() {
  1187. /*--------------------------------------------*
  1188. * Private variables, functions and settings. *
  1189. *--------------------------------------------*/
  1190. /**
  1191. * Default ikariam language code - default for this server.
  1192. *
  1193. * @private
  1194. * @inner
  1195. *
  1196. * @default en
  1197. *
  1198. * @type string
  1199. */
  1200. var _ikaCode = 'en';
  1201. /**
  1202. * Default ikariam language name - default for this server.
  1203. *
  1204. * @private
  1205. * @inner
  1206. *
  1207. * @type string
  1208. */
  1209. var _ikaLang = 'English';
  1210. /**
  1211. * Used language code.
  1212. *
  1213. * @private
  1214. * @inner
  1215. *
  1216. * @default en
  1217. *
  1218. * @type string
  1219. */
  1220. var _usedCode = 'en';
  1221. /**
  1222. * Used language name.
  1223. *
  1224. * @private
  1225. * @inner
  1226. *
  1227. * @type string
  1228. */
  1229. var _usedLang = '';
  1230. /**
  1231. * Used language texts.
  1232. *
  1233. * @private
  1234. * @inner
  1235. *
  1236. * @type json
  1237. */
  1238. var _usedText = null;
  1239. /**
  1240. * Default language text. To be used if the used language is not available.
  1241. *
  1242. * @private
  1243. * @inner
  1244. *
  1245. * @type json
  1246. */
  1247. var _defaultText = null;
  1248. /**
  1249. * All languages which are registered with their storage type (resource, in-script-array, default).
  1250. *
  1251. * @private
  1252. * @inner
  1253. *
  1254. * @type string[]
  1255. */
  1256. var _registeredLangs = {};
  1257. /**
  1258. * All JSON language resource settings (resource name, url).
  1259. *
  1260. * @private
  1261. * @inner
  1262. *
  1263. * @type mixed[]
  1264. */
  1265. var _jsonLanguageText = {};
  1266. /**
  1267. * All in-script-array language texts.
  1268. *
  1269. * @private
  1270. * @inner
  1271. *
  1272. * @type json[]
  1273. */
  1274. var _languageResources = {};
  1275. // Split the host string.
  1276. var _lang = top.location.host.split('.');
  1277. // Change the language code, if lang exists.
  1278. if(_lang) {
  1279. for(var i = 0; i < _lang.length; i++) {
  1280. if(_lang[i] == 'ikariam') {
  1281. _usedCode = _ikaCode = _lang[i - 1];
  1282. break;
  1283. }
  1284. }
  1285. }
  1286. /**
  1287. * "Translation" of all possible language codes to the corresponding language.
  1288. *
  1289. * @private
  1290. * @inner
  1291. *
  1292. * @type string[]
  1293. */
  1294. var _codeTranslation = {
  1295. ae: 'Arabic', ar: 'Spanish', ba: 'Bosnian', bg: 'Bulgarian', br: 'Portuguese', by: 'Russian',
  1296. cl: 'Spanish', cn: 'Chinese', co: 'Spanish', cz: 'Czech', de: 'German', dk: 'Danish',
  1297. ee: 'Estonian', en: 'English', es: 'Spanish', fi: 'Finish', fr: 'French', gr: 'Greek',
  1298. hk: 'Chinese', hr: 'Bosnian', hu: 'Hungarian', id: 'Indonesian', il: 'Hebrew', it: 'Italian',
  1299. kr: 'Korean', lt: 'Lithuanian', lv: 'Latvian', mx: 'Spanish', nl: 'Dutch', no: 'Norwegian',
  1300. pe: 'Spanish', ph: 'Filipino', pk: 'Urdu', pl: 'Polish', pt: 'Portuguese', ro: 'Romanian',
  1301. rs: 'Serbian', ru: 'Russian', se: 'Swedish', si: 'Slovene', sk: 'Slovak', tr: 'Turkish',
  1302. tw: 'Chinese', ua: 'Ukrainian', us: 'English', ve: 'Spanish', vn: 'Vietnamese', yu: 'Bosnian'
  1303. };
  1304. // Set the language.
  1305. _ikaLang = _codeTranslation[_ikaCode];
  1306. _usedLang = _codeTranslation[_usedCode];
  1307. /**
  1308. * Set the choosen language text for the script.
  1309. *
  1310. * @private
  1311. * @inner
  1312. */
  1313. var _setText = function() {
  1314. if(_registeredLangs[_usedLang]) {
  1315. var type = _registeredLangs[_usedLang];
  1316. if(type == 'resource') {
  1317. if(_languageResources[_usedLang]) {
  1318. // Get the ressource.
  1319. _usedText = _this.myGM.getResourceParsed(_languageResources[_usedLang].resourceName, _languageResources[_usedLang].url);
  1320. } else {
  1321. _usedText = { is_error: true };
  1322. }
  1323. } else if(type == 'default') {
  1324. _usedText = _defaultText;
  1325. } else {
  1326. if(_jsonLanguageText[_usedLang]) {
  1327. // Get the ressource.
  1328. _usedText = _jsonLanguageText[_usedLang];
  1329. } else {
  1330. _usedText = { is_error: true };
  1331. }
  1332. }
  1333. // Store it to Language._usedText.
  1334. _usedText = (_usedText && !_usedText.is_error) ? _usedText : _defaultText;
  1335. // Otherwise: Use the default text.
  1336. } else {
  1337. _usedText = _defaultText;
  1338. }
  1339. };
  1340. /*-------------------------------------------*
  1341. * Public variables, functions and settings. *
  1342. *-------------------------------------------*/
  1343. /**
  1344. * Set the default language.
  1345. *
  1346. * @instance
  1347. *
  1348. * @param {string} name
  1349. * The Name of the default language.
  1350. * @param {json} json
  1351. * JSON with the default language data.
  1352. */
  1353. this.setDefaultLang = function(name, json) {
  1354. // Set the language as registered language.
  1355. _registeredLangs[name] = 'default';
  1356. // Set the default and used language name.
  1357. _usedLang = _usedLang != '' ? _usedLang : name;
  1358. // Set the default language data.
  1359. _defaultText = json;
  1360. // Set the used language data.
  1361. _setText();
  1362. };
  1363. /**
  1364. * Registers a new language without resource usage.
  1365. *
  1366. * @instance
  1367. *
  1368. * @param {string} languageName
  1369. * The name of the language.
  1370. * @param {json} json
  1371. * JSON with the language data.
  1372. */
  1373. this.addLanguageText = function(languageName, json) {
  1374. // Set the language as registered language.
  1375. _registeredLangs[languageName] = 'jsonText';
  1376. // Set the data for this language.
  1377. _jsonLanguageText[languageName] = json;
  1378. // Set the used language data.
  1379. _setText();
  1380. };
  1381. /**
  1382. * Registers a new language resource.
  1383. *
  1384. * @instance
  1385. *
  1386. * @param {string} languageName
  1387. * Name of the language.
  1388. * @param {string} resourceName
  1389. * Name of the resource.
  1390. * @param {string} resourceURL
  1391. * URL, if resources are not supported.
  1392. */
  1393. this.registerLanguageResource = function(languageName, resourceName, resourceURL) {
  1394. // Set the language as registered language.
  1395. _registeredLangs[languageName] = 'resource';
  1396. // Set the data for this language.
  1397. _languageResources[languageName] = { resourceName: resourceName, url: resourceURL };
  1398. // Set the used language data.
  1399. _setText();
  1400. };
  1401. /**
  1402. * Return the name of the actually used language.
  1403. *
  1404. * @instance
  1405. *
  1406. * @return {string}
  1407. * The country code.
  1408. */
  1409. this.getLangName = function() {
  1410. return _usedLang;
  1411. };
  1412. /**
  1413. * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
  1414. * they are replaced with the content of the array at this index.
  1415. *
  1416. * @instance
  1417. *
  1418. * @param {string} name
  1419. * The name of the placeholder.
  1420. * @param {mixed[]} vars
  1421. * An array containing variables for replacing in the language string. (optional)
  1422. *
  1423. * @return {string}
  1424. * The text.
  1425. */
  1426. this.getText = function(name, vars) {
  1427. // Set the text to the placeholder.
  1428. var erg = name;
  1429. // Split the placeholder.
  1430. var parts = name.split('_');
  1431. // If the splitting was successful.
  1432. if(parts) {
  1433. // Set txt to the "next level".
  1434. var txt = _usedText ? _usedText[parts[0]] : null;
  1435. // Loop over all parts.
  1436. for(var i = 1; i < parts.length; i++) {
  1437. // If the "next level" exists, set txt to it.
  1438. if(txt && typeof txt[parts[i]] != 'undefined') {
  1439. txt = txt[parts[i]];
  1440. } else {
  1441. txt = erg;
  1442. break;
  1443. }
  1444. }
  1445. // If the text type is not an object, a function or undefined.
  1446. if(typeof txt != 'object' && typeof txt != 'function' && typeof txt != 'undefined') {
  1447. erg = txt + '';
  1448. }
  1449. if(vars) {
  1450. for(var i = 0; i < vars.length; i++) {
  1451. var regex = new RegExp('%\\$' + (i + 1), 'g');
  1452. erg = erg.replace(regex, vars[i] + '');
  1453. }
  1454. }
  1455. }
  1456. if(erg == name) {
  1457. _this.con.log('Language.getText: No translation available for "' + name + '" in language ' + this.getLangName());
  1458. }
  1459. // Return the text.
  1460. return erg;
  1461. };
  1462. /**
  1463. * Synonymous function for {@link IkariamCore~Language#getText}.<br>
  1464. * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
  1465. * they are replaced with the content of the array at this index.
  1466. *
  1467. * @instance
  1468. *
  1469. * @param {string} name
  1470. * The name of the placeholder.
  1471. * @param {mixed[]} vars
  1472. * An array containing variables for replacing in the language string. (optional)
  1473. *
  1474. * @return {string}
  1475. * The text.
  1476. */
  1477. this.$ = function(name, vars) {
  1478. return this.getText(name, vars);
  1479. };
  1480. }
  1481. /**
  1482. * Functions for localisation of the script.
  1483. *
  1484. * @instance
  1485. *
  1486. * @type IkariamCore~Language
  1487. */
  1488. this.Language = new Language;
  1489. /**
  1490. * Instantiate the handler.
  1491. *
  1492. * @inner
  1493. *
  1494. * @class
  1495. * @classdesc Handler for callbacks for processing DOM modification events.
  1496. */
  1497. function Observer() {
  1498. /*--------------------------------------------*
  1499. * Private variables, functions and settings. *
  1500. *--------------------------------------------*/
  1501. /**
  1502. * Storage for MutationObserver.
  1503. *
  1504. * @private
  1505. * @inner
  1506. *
  1507. * @type MutationObserver
  1508. */
  1509. var _MutationObserver = _this.win.MutationObserver || _this.win.WebKitMutationObserver;
  1510. /**
  1511. * If the MutationObserver can be used or if an workaround must be used.
  1512. *
  1513. * @private
  1514. * @inner
  1515. *
  1516. * @type boolean
  1517. */
  1518. var _canUseObserver = !!_MutationObserver;
  1519. /**
  1520. * List to store the created observers.
  1521. *
  1522. * @private
  1523. * @inner
  1524. *
  1525. * @type MutationObserver[]
  1526. */
  1527. var _observerList = {};
  1528. /*-------------------------------------------*
  1529. * Public variables, functions and settings. *
  1530. *-------------------------------------------*/
  1531. /**
  1532. * Adds a new observer for DOM modification events. If it is possible use MutationObserver. More about the
  1533. * Mutation observer can be found here: {@link https://developer.mozilla.org/en-US/docs/DOM/MutationObserver Mutation Observer on MDN}.<br>
  1534. * If it's not possible to use a MutationObserver a DOMSubtreeModified or DOMAttrModified event listener is used.
  1535. *
  1536. * @instance
  1537. *
  1538. * @param {string} id
  1539. * The id to store the observer.
  1540. * @param {element} target
  1541. * The target to observe.
  1542. * @param {mixed[]} options
  1543. * Options for the observer. All possible options can be found here: {@link https://developer.mozilla.org/en-US/docs/DOM/MutationObserver#MutationObserverInit MutationObserver on MDN}
  1544. * @param {function} callback
  1545. * The callback for the observer.<br>
  1546. * Signature: <code>function(mutations : MutationRecord) : void</code>
  1547. * @param {function} noMutationObserverCallback
  1548. * The callback if the use of the observer is not possible and DOMAttrModified / DOMSubtreeModified is used instead.<br>
  1549. * Signature: <code>function() : void</code>
  1550. */
  1551. this.add = function(id, target, options, callback, noMutationObserverCallback) {
  1552. var observer;
  1553. if(!!target) {
  1554. // If the MutationObserver can be used, do so.
  1555. if(_canUseObserver) {
  1556. // Create the observer.
  1557. observer = new _MutationObserver(callback);
  1558. // Start the observation.
  1559. observer.observe(target, options);
  1560. // Store the observer if the id is unique.
  1561. if(!_observerList[id]) {
  1562. _observerList[id] = observer;
  1563. // Otherwise: Alert that the id is already in use.
  1564. } else {
  1565. _this.con.log('Observer.add: Id "' + id + '" already used for observer, please choose another one!');
  1566. }
  1567. // Otherwise use the event listener.
  1568. } else {
  1569. // Add the event listener.
  1570. if(options.attributes) {
  1571. target.addEventListener('DOMAttrModified', noMutationObserverCallback, false);
  1572. }
  1573. if(options.characterData || options.childList || options.subtree) {
  1574. target.addEventListener('DOMSubtreeModified', noMutationObserverCallback, false);
  1575. }
  1576. }
  1577. } else {
  1578. _this.con.log('Observer.add: Observer target not defined! id: ' + id);
  1579. }
  1580. };
  1581. /**
  1582. * Removes the observer given by the id. If the use of MutationObserver is not possible, this function can not be used.
  1583. *
  1584. * @instance
  1585. *
  1586. * @param {string} id
  1587. * The id of the observer to remove.
  1588. */
  1589. this.remove = function(id) {
  1590. // If the observer is set.
  1591. if(_canUseObserver && _observerList[id]) {
  1592. // Get the observer.
  1593. var observer = _observerList[id];
  1594. // Disconnect the observer if it is a MutationObserver.
  1595. observer.disconnect();
  1596. // Delete the observer data.
  1597. delete _observerList[id];
  1598. } else if(!_canUseObserver) {
  1599. _this.con.log('Observer.remove: It is not possible to use MutationObservers so Observer.remove can not be used.');
  1600. }
  1601. };
  1602. }
  1603. /**
  1604. * Handler for callbacks after modification of DOM elements.
  1605. *
  1606. * @instance
  1607. *
  1608. * @type IkariamCore~Observer
  1609. */
  1610. this.Observer = new Observer;
  1611. /**
  1612. * Instantiate a new set of refresh functions.
  1613. *
  1614. * @inner
  1615. *
  1616. * @class
  1617. * @classdesc Handles functions that should run on ikariam popups and after actualisations of the page data.
  1618. */
  1619. function RefreshHandler() {
  1620. /*--------------------------------------------*
  1621. * Private variables, functions and settings. *
  1622. *--------------------------------------------*/
  1623. /**
  1624. * Storage for the actualisation callbacks.<br>
  1625. * Architecture:<br>
  1626. * <code>_callbacks = {<br>
  1627. * &#09;popupId: {<br>
  1628. * &#09;&#09;callbackId: callback<br>
  1629. * &#09;}<br>
  1630. * }</code>
  1631. *
  1632. * @private
  1633. * @inner
  1634. *
  1635. * @type function[][]
  1636. */
  1637. var _callbacks = {};
  1638. /**
  1639. * Handles the call of the callback functions for the actualisation.
  1640. *
  1641. * @private
  1642. * @inner
  1643. */
  1644. var _handleActualisation = function() {
  1645. // Callbacks for every reload.
  1646. if(_callbacks['*']) {
  1647. _this.myGM.forEach(_callbacks['*'], function(key, callback) {
  1648. callback();
  1649. });
  1650. }
  1651. // If the script was already executed on this popup.
  1652. var isAlreadyExecutedPopup = !!_this.myGM.$('#' + _this.myGM.prefix() + 'alreadyExecutedPopup');
  1653. // Get the popup.
  1654. var popup = _this.myGM.$('.templateView');
  1655. // Get the popup id.
  1656. var popupId = popup ? popup.id.replace('_c', '') : '';
  1657. // If a popup exists, add the hint, that the popup script was executed.
  1658. if(popup && !isAlreadyExecutedPopup) {
  1659. var alreadyExecuted = _this.myGM.addElement('input', _this.myGM.$('.mainContent', popup), 'alreadyExecutedPopup');
  1660. alreadyExecuted.type = 'hidden';
  1661. // Call all callbacks which are set.
  1662. if(_callbacks[popupId]) {
  1663. _this.myGM.forEach(_callbacks[popupId], function(key, callback) {
  1664. callback();
  1665. });
  1666. }
  1667. }
  1668. };
  1669. /**
  1670. * Callback for MutationObserver for calling the popup handler.
  1671. *
  1672. * @private
  1673. * @inner
  1674. *
  1675. * @param {MutationRecord} mutations
  1676. * All recorded mutations.
  1677. */
  1678. var _callback = function(mutations) {
  1679. mutations.forEach(function(mutation) {
  1680. // If the style.display is set to none.
  1681. if(mutation.target.getAttribute('style').search(/display: none/i) != -1) {
  1682. // Timeout to have access to GM_ funtions.
  1683. setTimeout(_handleActualisation, 0);
  1684. }
  1685. });
  1686. };
  1687. /**
  1688. * Callback for calling the popup handler if the MutationObserver could not be used.
  1689. *
  1690. * @private
  1691. * @inner
  1692. *
  1693. * @param {event} e
  1694. * The called event.
  1695. */
  1696. var _callbackNoMutationObserver = function(e) {
  1697. // If the attribute was changed.
  1698. if(e.attrChange == MutationEvent.MODIFICATION) {
  1699. // If the style.display is set to none.
  1700. if(e.attrName.trim() == 'style' && e.newValue.search(/display: none/i) != -1) {
  1701. // Timeout to have access to GM_ funtions.
  1702. setTimeout(_handleActualisation, 0);
  1703. }
  1704. }
  1705. };
  1706. /*-------------------------------------------*
  1707. * Public variables, functions and settings. *
  1708. *-------------------------------------------*/
  1709. /**
  1710. * Add a new popup handler.
  1711. *
  1712. * @instance
  1713. *
  1714. * @param {string} popupId
  1715. * The id of the popup where the callback schould be called (without '_c' at the end).<br>
  1716. * Set to '*' for calling at every actualisation, not just popups.
  1717. * @param {string} callbackId
  1718. * The id of the callback. This must be unique for a popup.
  1719. * @param {function} callback
  1720. * The callback which should be called.<br>
  1721. * Signature: <code>function() : void</code>
  1722. */
  1723. this.add = function(popupId, callbackId, callback) {
  1724. // If no entry for the popup exists, create it.
  1725. if(!_callbacks[popupId]) {
  1726. _callbacks[popupId] = {};
  1727. }
  1728. // If no entry for the callback existst, create one.
  1729. if(!_callbacks[popupId][callbackId]) {
  1730. _callbacks[popupId][callbackId] = callback;
  1731. // Otherwise: Show an error to the user (programmer).
  1732. } else {
  1733. _this.con.log('RefreshHandler.add: Id set "' + popupId + '|' + callbackId + '" already used for observer, please choose another one!');
  1734. }
  1735. };
  1736. /**
  1737. * Removes a popup handler.
  1738. *
  1739. * @instance
  1740. *
  1741. * @param {string} popupId
  1742. * The id of the popup where the callback was called (without '_c' at the end).
  1743. * Set to '*' for allbacks which have been called at every actualisation, not just popups.
  1744. * @param {string} callbackId
  1745. * The id of the callback. This must be unique for a popup.
  1746. */
  1747. this.remove = function(popupId, callbackId) {
  1748. // Remove the callback if it is set.
  1749. if(_callbacks[popupId] && _callbacks[popupId][callbackId]) {
  1750. delete _callbacks[popupId][callbackId];
  1751. }
  1752. };
  1753. /*----------------------------------------------------*
  1754. * Register the observer and handle popups on startup *
  1755. *----------------------------------------------------*/
  1756. // Add the observer for the popups.
  1757. _this.Observer.add('actualisationHandler', _this.myGM.$('#loadingPreview'), { attributes: true, attributeFilter: ['style'] }, _callback, _callbackNoMutationObserver);
  1758. // Execute the handler on popups which are shown on startup.
  1759. setTimeout(_handleActualisation, 0);
  1760. }
  1761. /**
  1762. * Handler for functions that should run on ikariam popups.
  1763. *
  1764. * @instance
  1765. *
  1766. * @type IkariamCore~RefreshHandler
  1767. */
  1768. this.RefreshHandler = new RefreshHandler;
  1769. /**
  1770. * Instantiate a new set of options / settings functions.
  1771. *
  1772. * @inner
  1773. *
  1774. * @class
  1775. * @classdesc Handles options the user can set, provides a "panel" for them to change them.
  1776. */
  1777. function Options() {
  1778. /*--------------------------------------------*
  1779. * Private variables, functions and settings. *
  1780. *--------------------------------------------*/
  1781. /**
  1782. * Storage for option wrapper visibility.
  1783. *
  1784. * @private
  1785. * @inner
  1786. *
  1787. * @type boolean[]
  1788. */
  1789. var _optionWrapperVisibility = {
  1790. moduleOptions: true
  1791. };
  1792. /**
  1793. * Storage for option wrappers.
  1794. *
  1795. * @private
  1796. * @inner
  1797. *
  1798. * @type mixed[]
  1799. */
  1800. var _wrapper = {};
  1801. /**
  1802. * Storage for option wrapper order. (Order in which the wrappers are shown)
  1803. *
  1804. * @private
  1805. * @inner
  1806. *
  1807. * @type string[]
  1808. */
  1809. var _wrapperOrder = new Array();
  1810. /**
  1811. * Storage for the saved options. Gets filled on startup.
  1812. *
  1813. * @private
  1814. * @inner
  1815. *
  1816. * @type mixed[]
  1817. */
  1818. var _savedOptions = _this.myGM.getValue('optionPanel_options', {});
  1819. /**
  1820. * Storage for the options.
  1821. *
  1822. * @private
  1823. * @inner
  1824. *
  1825. * @type mixed[]
  1826. */
  1827. var _options = {};
  1828. /**
  1829. * Storage for the id of the next hr.
  1830. *
  1831. * @private
  1832. * @inner
  1833. *
  1834. * @type int
  1835. */
  1836. var _hrId = 0;
  1837. /**
  1838. * Add a element to a wrapper. ("generic function")
  1839. *
  1840. * @private
  1841. * @inner
  1842. *
  1843. * @param {string} id
  1844. * The id of the element.
  1845. * @param {string} wrapperId
  1846. * The id of the wrapper for the element.
  1847. * @param {string || int} table
  1848. * The id of the table in the wrapper where the element should be added.
  1849. * @param {mixed[]} options
  1850. * Options for the element.
  1851. * @param {mixed} defaultValue
  1852. * Default value for the option.
  1853. * @param {function} create
  1854. * Callback to create the element.<br>
  1855. * Signature: <code>function(parentTable : element, elementId : string, value : mixed, options : mixed) : void</code>
  1856. * @param {function} save
  1857. * Callback for saving the option value. Returns the value for this option.<br>
  1858. * Signature: <code>function(id : string) : mixed</code>
  1859. * @param {int} position
  1860. * Position of the element in the element array.
  1861. */
  1862. var _addElement = function(id, wrapperId, table, options, defaultValue, create, save, position) {
  1863. if(_wrapper[wrapperId]) {
  1864. if(_wrapper[wrapperId].elements[id]) {
  1865. _this.con.log('Options.addElement: Element with id "' + id + '" already defined. Wrapper id: ' + wrapperId);
  1866. } else {
  1867. _wrapper[wrapperId].elements[id] = { table: table + '', create: create };
  1868. _wrapper[wrapperId].elementOrder.insert(id, position);
  1869. if(options != null) {
  1870. _wrapper[wrapperId].elements[id].options = options;
  1871. }
  1872. if(defaultValue != null) {
  1873. _wrapper[wrapperId].elements[id].defaultValue = defaultValue;
  1874. if(_savedOptions[wrapperId] && (_savedOptions[wrapperId][id] || _savedOptions[wrapperId][id] == false)) {
  1875. _options[wrapperId][id] = _savedOptions[wrapperId][id];
  1876. } else {
  1877. _options[wrapperId][id] = defaultValue;
  1878. }
  1879. }
  1880. if(save != null) {
  1881. _wrapper[wrapperId].elements[id].save = save;
  1882. }
  1883. }
  1884. } else {
  1885. _this.con.log('Options.addElement: Wrapper with id "' + wrapperId + '" not defined. Element id: ' + id);
  1886. }
  1887. };
  1888. /**
  1889. * Save the content of <code>_options</code>.
  1890. *
  1891. * @private
  1892. * @inner
  1893. *
  1894. * @param {boolean} showNoSuccessHint
  1895. * If the success hint should not be shown.
  1896. */
  1897. var _saveOptions = function(showNoSuccessHint) {
  1898. // Set the value of saved options.
  1899. _savedOptions = _options;
  1900. // Save the options.
  1901. _this.myGM.setValue('optionPanel_options', _options);
  1902. // Show success hint if enabled.
  1903. if(!showNoSuccessHint) {
  1904. _this.Ikariam.showTooltip('cityAdvisor', 'confirm', _this.Language.$('general_successful'));
  1905. }
  1906. };
  1907. /**
  1908. * Store the actual value of each option to <code>_option</code> and call <code>_saveOptions</code>.
  1909. *
  1910. * @private
  1911. * @inner
  1912. */
  1913. var _savePanelOptions = function() {
  1914. // Store the value of each option element.
  1915. _this.myGM.forEach(_wrapper, function(wrapperId, wrapper) {
  1916. _this.myGM.forEach(wrapper.elements, function(elementId, element) {
  1917. if(element.save) {
  1918. _options[wrapperId][elementId] = element.save(wrapperId + elementId);
  1919. }
  1920. });
  1921. });
  1922. // Save the options.
  1923. _saveOptions();
  1924. };
  1925. /**
  1926. * Scroll the tabmenu in the options popup.
  1927. *
  1928. * @private
  1929. * @inner
  1930. *
  1931. * @param {string} direction
  1932. * The direction to scroll. Possible values are "left" and "right".
  1933. */
  1934. var _scrollOptionsTab = function(direction) {
  1935. // Get the tabmenu and the tabs.
  1936. var tabmenu = _this.myGM.$('#scriptTabmenuWrapper .tabmenu');
  1937. var tabs = _this.myGM.$$('.tab', tabmenu);
  1938. var firstVisible = -1;
  1939. var lastVisible = -1;
  1940. // Store the first and last visible tab id.
  1941. for(var i = 0; i < tabs.length; i++) {
  1942. if(!tabs[i].classList.contains('invisibleTab')) {
  1943. if(firstVisible == -1) {
  1944. firstVisible = i;
  1945. }
  1946. lastVisible = i;
  1947. }
  1948. }
  1949. // Store the id of the tab to show / to hide.
  1950. var toShow;
  1951. var toHide;
  1952. if(direction == 'left') {
  1953. toShow = firstVisible - 1;
  1954. toHide = lastVisible;
  1955. } else {
  1956. toShow = lastVisible + 1;
  1957. toHide = firstVisible;
  1958. }
  1959. // Scroll.
  1960. if(toShow >= 0 && toShow < tabs.length) {
  1961. tabs[toShow].classList.remove('invisibleTab');
  1962. tabs[toHide].classList.add('invisibleTab');
  1963. }
  1964. // Deactivate the scroll left button if it is not possible to scroll left.
  1965. if(toShow <= 0) {
  1966. _this.myGM.$('#scriptTabmenuScrollLeft').classList.add('deactivated');
  1967. } else {
  1968. _this.myGM.$('#scriptTabmenuScrollLeft').classList.remove('deactivated');
  1969. }
  1970. // Deactivate the scroll right button if it is not possible to scroll right.
  1971. if(toShow >= tabs.length - 1) {
  1972. _this.myGM.$('#scriptTabmenuScrollRight').classList.add('deactivated');
  1973. } else {
  1974. _this.myGM.$('#scriptTabmenuScrollRight').classList.remove('deactivated');
  1975. }
  1976. };
  1977. /**
  1978. * Initializes the options tab for the script and adds the scroll function to the tabmenu.
  1979. *
  1980. * @private
  1981. * @inner
  1982. *
  1983. * @return {element}
  1984. * The options tab for the script.
  1985. */
  1986. var _initializeOptionsTab = function() {
  1987. // Get the tabmenu.
  1988. var tabMenuWrapper = _this.myGM.$("#scriptTabmenuWrapper");
  1989. var tabmenu = _this.myGM.$('.tabmenu');
  1990. // If the tabmenu was not modified, add the scroll function.
  1991. if(!tabMenuWrapper) {
  1992. // Add the scroll buttons.
  1993. tabMenuWrapper = _this.myGM.addElement('div', tabmenu.parentNode, 'scriptTabmenuWrapper', null, null, false, tabmenu);
  1994. var scrollLeft = _this.myGM.addElement('div', tabMenuWrapper, 'scriptTabmenuScrollLeft', 'deactivated', null, false);
  1995. var scrollRight = _this.myGM.addElement('div', tabMenuWrapper, 'scriptTabmenuScrollRight', null, null, false);
  1996. scrollLeft.addEventListener('click', function() { _scrollOptionsTab('left'); }, false);
  1997. scrollRight.addEventListener('click', function() { _scrollOptionsTab('right'); }, false);
  1998. tabMenuWrapper.insertBefore(tabmenu, scrollRight);
  1999. // Set the styles.
  2000. var style = '#scriptTabmenuWrapper { background: url("/skin/layout/bg_tabs.jpg") repeat-x scroll 0 50% transparent; position: relative; width: 680px; } \
  2001. #scriptTabmenuWrapper .tab { border-left: 1px solid transparent; border-right: 1px solid transparent; border-top: 1px solid transparent; height: 31px !important; padding: 1px 3px 0 2px !important; } \
  2002. #scriptTabmenuWrapper .invisibleTab { display: none !important; }';
  2003. var useStyle = _this.Options.getOption('optionPanelOptions', 'useStyle');
  2004. if(useStyle == 'roundButton') {
  2005. style += '#scriptTabmenuWrapper .tabmenu { left: 32px; position: relative; top: 0; width: 610px; } \
  2006. #scriptTabmenuScrollLeft, #scriptTabmenuScrollRight { background-image: url("/skin/pirateFortress/button_arrow_70x50_sprite.png"); height: 50px; position: absolute; top: -4px; width: 70px; cursor: pointer; transform: scale(0.7); -webkit-transform: scale(0.7); } \
  2007. #scriptTabmenuScrollLeft { left: -19px; background-position: 0px 0px; } \
  2008. #scriptTabmenuScrollLeft:hover { background-position: 0px -50px; } \
  2009. #scriptTabmenuScrollLeft:active { background-position: 0px -100px; } \
  2010. #scriptTabmenuScrollRight { right: -18px; background-position: -70px -0px; } \
  2011. #scriptTabmenuScrollRight:hover { background-position: -70px -50px; } \
  2012. #scriptTabmenuScrollRight:active { background-position: -70px -100px; } \
  2013. #scriptTabmenuScrollLeft.deactivated, #scriptTabmenuScrollRight.deactivated { display: none; }';
  2014. } else {
  2015. style += '#scriptTabmenuWrapper .tabmenu { left: 15px; position: relative; top: 0; width: 644px; } \
  2016. #scriptTabmenuScrollLeft, #scriptTabmenuScrollRight { background-image: url("/skin/friends/button_up.png"); height: 13px; position: absolute; top: 15px; width: 35px; cursor: pointer; } \
  2017. #scriptTabmenuScrollLeft { left: -10px; transform: rotate(-90deg) scale(0.8); -webkit-transform: rotate(-90deg) scale(0.8); } \
  2018. #scriptTabmenuScrollRight { right: -10px; transform: rotate(90deg) scale(0.8); -webkit-transform: rotate(90deg) scale(0.8); } \
  2019. #scriptTabmenuScrollLeft:hover, #scriptTabmenuScrollRight:hover { background-position: 0 -13px; } \
  2020. #scriptTabmenuScrollLeft.deactivated, #scriptTabmenuScrollRight.deactivated { background-position: 0 -26px; }';
  2021. }
  2022. _this.myGM.addStyle(style, 'scriptTabmenu', true);
  2023. }
  2024. // Get the options tab.
  2025. var tabScriptOptions = _this.myGM.$('#tab' + _this.myGM.prefix() + 'ScriptOptions');
  2026. // If the script options tab doesn't exists, create it.
  2027. if(!tabScriptOptions) {
  2028. // Set the styles.
  2029. _this.myGM.addStyle(
  2030. "#tab" + _this.myGM.prefix() + "Options hr { margin: 0; } \
  2031. #tab" + _this.myGM.prefix() + "Options .scriptTextArea { resize: none; width: calc(100% - 2px); height: 75px; } \
  2032. #tab" + _this.myGM.prefix() + "Options .scriptTextField { width: 173px; } \
  2033. #tab" + _this.myGM.prefix() + "Options .cbWrapper { margin: 0 0 0 10px; }",
  2034. 'scriptOptionsTab', true);
  2035. // Add the script options tab link to the tabmenu.
  2036. var jsTabScriptOptions = _this.myGM.addElement('li', tabmenu, 'js_tab' + _this.myGM.prefix() + 'Options', ['tab', 'invisibleTab'], null, false);
  2037. jsTabScriptOptions.innerHTML = '<b class="tab' + _this.myGM.prefix() + 'Options">' + scriptInfo.name + '</b>';
  2038. jsTabScriptOptions.setAttribute('onclick', "$('#js_tab" + _this.myGM.prefix() + "Options').removeClass('selected'); switchTab('tab" + _this.myGM.prefix() + "Options');");
  2039. // Add the content wrapper for the script options tab to the tabmenu.
  2040. var mainContent = _this.myGM.$('#tabGameOptions').parentNode;
  2041. tabScriptOptions = _this.myGM.addElement('div', mainContent, 'tab' + _this.myGM.prefix() + 'Options', null, new Array(['display', 'none']), false);
  2042. }
  2043. // Get the option wrapper visibility.
  2044. _optionWrapperVisibility = _this.myGM.getValue('optionPanel_optionWrapperVisibility', _optionWrapperVisibility);
  2045. // Return the script options tab.
  2046. return tabScriptOptions;
  2047. };
  2048. /**
  2049. * Add a wraper for options elements to the script option tab.
  2050. *
  2051. * @private
  2052. * @inner
  2053. *
  2054. * @param {element} tab
  2055. * The tab to add the wrapper to.
  2056. * @param {string} id
  2057. * The id of the wrapper.
  2058. * @param {string || string[]} headerText
  2059. * The text for the wrapper header. If the element is defined within the IkariamCore initialisation,
  2060. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2061. * Object signature: <code>{ id: 'idValue' }</code>
  2062. *
  2063. * @return {element}
  2064. * The wrapper.
  2065. */
  2066. var _createOptionsWrapper = function(tab, id, headerText) {
  2067. // Get the header text, if not set yet.
  2068. if(headerText.id) {
  2069. if(headerText.args) {
  2070. headerText = _this.Language.$(headerText.id, headerText.args);
  2071. } else {
  2072. headerText = _this.Language.$(headerText.id);
  2073. }
  2074. }
  2075. // Get the content show status.
  2076. var showContent = !!_optionWrapperVisibility[id];
  2077. // Create the wrapper.
  2078. var optionsWrapper = _this.myGM.addElement('div', tab, id, 'contentBox01h');
  2079. // Create the header.
  2080. var optionsHeader = _this.myGM.addElement('h3', optionsWrapper, null, 'header');
  2081. optionsHeader.innerHTML = headerText;
  2082. // Add the show / hide button.
  2083. var btn = _this.myGM.addElement('div', optionsHeader, null, showContent ? 'minimizeImg' : 'maximizeImg', new Array(['cssFloat', 'left']));
  2084. /*
  2085. * Function to toggle the visibility of an wrapper.
  2086. */
  2087. var toggle = function() {
  2088. // Toggle the button.
  2089. _this.myGM.toggleShowHideButton(this);
  2090. // Toggle the visibility of the content.
  2091. _this.myGM.$('.content', this.parentNode.parentNode).classList.toggle('invisible');
  2092. // Store the visibility.
  2093. var optionId = this.parentNode.parentNode.id.replace(_this.myGM.prefix(), '');
  2094. _optionWrapperVisibility[optionId] = !_optionWrapperVisibility[optionId];
  2095. _this.myGM.setValue('optionPanel_optionWrapperVisibility', _optionWrapperVisibility);
  2096. // Adjust the size of the Scrollbar.
  2097. _this.ika.controller.adjustSizes();
  2098. };
  2099. // Add the toggle function.
  2100. btn.addEventListener('click', toggle, false);
  2101. btn.title = showContent ? _this.Language.$('general_fold') : _this.Language.$('general_expand');
  2102. // Create the content wrapper.
  2103. var optionsWrapperContent = _this.myGM.addElement('div', optionsWrapper, null, showContent ? 'content' : ['content', 'invisible']);
  2104. // Create the footer.
  2105. _this.myGM.addElement('div', optionsWrapper, null, 'footer');
  2106. // Return the content wrapper.
  2107. return optionsWrapperContent;
  2108. };
  2109. /**
  2110. * Show the option script tab.
  2111. *
  2112. * @private
  2113. * @inner
  2114. */
  2115. var _showOptionPanel = function() {
  2116. // Create the options tab, if not existing.
  2117. var tab = _initializeOptionsTab();
  2118. // Add all wrappers with elements.
  2119. for(var i = 0; i < _wrapperOrder.length; i++) {
  2120. // Create the wrapper.
  2121. var wrapperId = _wrapperOrder[i];
  2122. var wrapperOptions = _wrapper[wrapperId];
  2123. var wrapper = _createOptionsWrapper(tab, wrapperId, wrapperOptions.headerText);
  2124. var tables = {};
  2125. // Add all elements to the wrapper.
  2126. for(var j = 0; j < wrapperOptions.elementOrder.length; j++) {
  2127. // Get the element id and the element options
  2128. var elemId = wrapperOptions.elementOrder[j];
  2129. var elemOpt = wrapperOptions.elements[elemId];
  2130. // Create table and tablebody if not existing.
  2131. if(!tables[elemOpt.table]) {
  2132. var table = _this.myGM.addElement('table', wrapper, null, ['moduleContent', 'table01']);
  2133. tables[elemOpt.table] = _this.myGM.addElement('tbody', table);
  2134. }
  2135. // Create the element.
  2136. var value = (_options[wrapperId] && (_options[wrapperId][elemId] || _options[wrapperId][elemId] == false)) ? _options[wrapperId][elemId] : null;
  2137. var options = elemOpt.options ? elemOpt.options : null;
  2138. elemOpt.create(tables[elemOpt.table], wrapperId + elemId, value, options);
  2139. }
  2140. // Add the save button to the wrapper.
  2141. _this.myGM.addButton(wrapper, _this.Language.$('default_optionPanel_save'), function() { setTimeout(_savePanelOptions, 0); });
  2142. }
  2143. };
  2144. /**
  2145. * Show the notification for exporting the options.
  2146. *
  2147. * @private
  2148. * @inner
  2149. */
  2150. var _exportOptionsShowNotification = function() {
  2151. // Get the options as json string.
  2152. var optionString = _this.win.JSON.stringify(_options);
  2153. // Set the notification text.
  2154. var notificationText = {
  2155. header: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_exportNotification_header'),
  2156. bodyTop: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_exportNotification_explanation'),
  2157. bodyBottom: optionString
  2158. };
  2159. // Show the notification.
  2160. _this.myGM.notification(notificationText, null, true);
  2161. };
  2162. /**
  2163. * Callback for importing the options.
  2164. *
  2165. * @private
  2166. * @inner
  2167. *
  2168. * @param {element} textarea
  2169. * The textarea with the options string to import.
  2170. */
  2171. var _importOptionsCallback = function(textarea) {
  2172. // Get the option string.
  2173. var optionString = textarea.value;
  2174. if(optionString) {
  2175. // Function for safer parsing.
  2176. var safeParse = function(key, value) {
  2177. // If the value is a function, return just the string, so it is not executable.
  2178. if(typeof value === 'function' || Object.prototype.toString.apply(value) === '[object function]') {
  2179. return value.toString();
  2180. }
  2181. // Return the value.
  2182. return value;
  2183. };
  2184. try {
  2185. // Parse the option string.
  2186. var parsed = _this.win.JSON.parse(optionString, safeParse);
  2187. // Store the values in the script.
  2188. _this.myGM.forEach(parsed, function(wrapperKey, elements) {
  2189. _this.myGM.forEach(elements, function(elementKey, setting) {
  2190. if(_options[wrapperKey] && (_options[wrapperKey][elementKey] || _options[wrapperKey][elementKey] == false) && typeof setting != 'array' && typeof setting != 'object') {
  2191. _options[wrapperKey][elementKey] = setting;
  2192. }
  2193. });
  2194. });
  2195. // Save the options.
  2196. _saveOptions();
  2197. } catch(e) {
  2198. // Set the notification text.
  2199. var notificationText = {
  2200. header: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_importError_header'),
  2201. body: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_importError_explanation')
  2202. };
  2203. // Log the error message an show the notification to the user.
  2204. _this.con.log(e);
  2205. _this.myGM.notification(notificationText);
  2206. }
  2207. }
  2208. };
  2209. /**
  2210. * Show the notification for importing the options.
  2211. *
  2212. * @private
  2213. * @inner
  2214. */
  2215. var _importOptionsShowNotification = function() {
  2216. // Set the notification text.
  2217. var notificationText = {
  2218. header: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_importNotification_header'),
  2219. bodyTop: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_importNotification_explanation')
  2220. };
  2221. // Set the notification callback.
  2222. var notificationCallback = {
  2223. confirm: _importOptionsCallback
  2224. };
  2225. // Show the notification.
  2226. _this.myGM.notification(notificationText, notificationCallback, true);
  2227. };
  2228. /**
  2229. * Callback for resetting the options.
  2230. *
  2231. * @private
  2232. * @inner
  2233. */
  2234. var _resetOptionsCallback = function() {
  2235. // Clear the options.
  2236. _options = {};
  2237. // Store the default values.
  2238. _this.myGM.forEach(_wrapper, function(wrapperKey, wrapper) {
  2239. _options[wrapperKey] = {};
  2240. _this.myGM.forEach(wrapper.elements, function(elementKey, element) {
  2241. if(element.defaultValue || element.defaultValue == false) {
  2242. _options[wrapperKey][elementKey] = element.defaultValue;
  2243. }
  2244. });
  2245. });
  2246. // Save the options.
  2247. _saveOptions();
  2248. };
  2249. /**
  2250. * Show the notification for resetting the options.
  2251. *
  2252. * @private
  2253. * @inner
  2254. */
  2255. var _resetOptionsShowNotification = function() {
  2256. // Set the notification text.
  2257. var notificationText = {
  2258. header: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_resetNotification_header'),
  2259. body: _this.Language.$('default_optionPanel_section_optionPanelOptions_label_resetNotification_explanation')
  2260. };
  2261. // Set the notification callback.
  2262. var notificationCallback = {
  2263. confirm: _resetOptionsCallback,
  2264. abort: function() { return; }
  2265. };
  2266. // Show the notification.
  2267. _this.myGM.notification(notificationText, notificationCallback);
  2268. };
  2269. /**
  2270. * Create the export options link.
  2271. *
  2272. * @private
  2273. * @inner
  2274. *
  2275. * @param {element} _this
  2276. * Reference to <code>_this</code>.
  2277. * @param {element} parent
  2278. * Parent element for the link.
  2279. */
  2280. var _exportOptions = function(_this, parent) {
  2281. // Create the export link.
  2282. var exportLink = _this.myGM.addElement('a', parent);
  2283. exportLink.href = 'javascript:;';
  2284. exportLink.innerHTML = _this.Language.$('default_optionPanel_section_optionPanelOptions_label_export');
  2285. exportLink.addEventListener('click', _exportOptionsShowNotification, false);
  2286. };
  2287. /**
  2288. * Create the import options link.
  2289. *
  2290. * @private
  2291. * @inner
  2292. *
  2293. * @param {element} _this
  2294. * Reference to <code>_this</code>.
  2295. * @param {element} parent
  2296. * Parent element for the link.
  2297. */
  2298. var _importOptions = function(_this, parent) {
  2299. // Create the import link.
  2300. var importLink = _this.myGM.addElement('a', parent);
  2301. importLink.href = 'javascript:;';
  2302. importLink.innerHTML = _this.Language.$('default_optionPanel_section_optionPanelOptions_label_import');
  2303. importLink.addEventListener('click', _importOptionsShowNotification, false);
  2304. };
  2305. /**
  2306. * Create the reset options link.
  2307. *
  2308. * @private
  2309. * @inner
  2310. *
  2311. * @param {element} _this
  2312. * Reference to <code>_this</code>.
  2313. * @param {element} parent
  2314. * Parent element for the link.
  2315. */
  2316. var _resetOptions = function(_this, parent) {
  2317. // Create the reset link.
  2318. var resetLink = _this.myGM.addElement('a', parent);
  2319. resetLink.href = 'javascript:;';
  2320. resetLink.innerHTML = _this.Language.$('default_optionPanel_section_optionPanelOptions_label_reset');
  2321. resetLink.addEventListener('click', _resetOptionsShowNotification, false);
  2322. };
  2323. /*-------------------------------------------*
  2324. * Public variables, functions and settings. *
  2325. *-------------------------------------------*/
  2326. /**
  2327. * Add a wrapper to the list.
  2328. *
  2329. * @instance
  2330. *
  2331. * @param {string} id
  2332. * The id of the wrapper.
  2333. * @param {string || string[]} headerText
  2334. * The text for the wrapper header. If the element is defined within the IkariamCore initialisation,
  2335. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2336. * Object signature: <code>{ id: 'idValue' }</code>
  2337. * @param {int} position
  2338. * The position of the wrapper on the options tab.
  2339. */
  2340. this.addWrapper = function(id, headerText, position) {
  2341. // If a wrapper with this id already exists, log it.
  2342. if(_wrapper[id]) {
  2343. _this.con.log('Options.addWrapper: Wrapper with id "' + id + '" defined two times.');
  2344. // Otherwise: Store the wrapper.
  2345. } else {
  2346. _wrapper[id] = { headerText: headerText, elements: {}, elementOrder: new Array() };
  2347. _options[id] = {};
  2348. _wrapperOrder.insert(id, position);
  2349. }
  2350. };
  2351. /**
  2352. * Add a new checkbox to the options tab.
  2353. *
  2354. * @instance
  2355. *
  2356. * @param {string} id
  2357. * The id of the checkbox.
  2358. * @param {string} wrapperId
  2359. * The id of the wrapper.
  2360. * @param {string || int} block
  2361. * The block of the wrapper, the checkbox belongs to.
  2362. * @param {boolean} defaultChecked
  2363. * If the checkbox is checked by default.
  2364. * @param {string || string[]} label
  2365. * The text for the label. If the element is defined within the IkariamCore initialisation,
  2366. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2367. * Object signature: <code>{ id: 'idValue' }</code>
  2368. * @param {int} position
  2369. * The position of the checkbox in the wrapper.
  2370. */
  2371. this.addCheckbox = function(id, wrapperId, block, defaultChecked, label, position) {
  2372. /*
  2373. * Function to save the checkbox value.
  2374. */
  2375. var save = function(elementId) {
  2376. // Get the value and return it.
  2377. return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'Cb').checked;
  2378. };
  2379. /*
  2380. * Function to create the checkbox.
  2381. */
  2382. var create = function(parentTable, elementId, value, options) {
  2383. // Get the label text, if not set yet.
  2384. if(options.label.id) {
  2385. if(options.label.args) {
  2386. options.label = _this.Language.$(options.label.id, options.label.args);
  2387. } else {
  2388. options.label = _this.Language.$(options.label.id);
  2389. }
  2390. }
  2391. // Create table row.
  2392. var tr = _this.myGM.addElement('tr', parentTable);
  2393. // Create cell.
  2394. var parent = _this.myGM.addElement('td', tr);
  2395. parent.colSpan = 2;
  2396. parent.classList.add('left');
  2397. // Add checkbox.
  2398. _this.myGM.addCheckboxes(parent, [{ id: elementId, label: options.label, checked: value }]);
  2399. };
  2400. // Add the checkbox to the option panel.
  2401. _addElement(id, wrapperId, block, { label: label }, defaultChecked, create, save, position);
  2402. };
  2403. /**
  2404. * Add a new select field to the options tab.
  2405. *
  2406. * @instance
  2407. *
  2408. * @param {string} id
  2409. * The id of the select field.
  2410. * @param {string} wrapperId
  2411. * The id of the wrapper.
  2412. * @param {string || int} block
  2413. * The block of the wrapper, the select field belongs to.
  2414. * @param {mixed} defaultSelected
  2415. * The value of the option selected by default.
  2416. * @param {string || string[]} label
  2417. * The text for the label. If the element is defined within the IkariamCore initialisation,
  2418. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2419. * Object signature: <code>{ id: 'idValue' }</code>
  2420. * @param {mixed[]} opts
  2421. * An array with the names an values of the options.
  2422. * Signature: <code>[{ value: 'val', name: 'name' }]</code>
  2423. * If the element is defined within the IkariamCore initialisation, the translation string for name is not set.
  2424. * Then you can pass an object containing the string id for name.<br>
  2425. * Object signature: <code>{ id: 'idValue' }</code>
  2426. * @param {int} position
  2427. * The position of the select field in the wrapper.
  2428. */
  2429. this.addSelect = function(id, wrapperId, block, defaultSelected, label, opts, position) {
  2430. /*
  2431. * Function to save the select value.
  2432. */
  2433. var save = function(elementId) {
  2434. // Get value and return it.
  2435. return _this.myGM.getSelectValue(elementId);
  2436. };
  2437. /*
  2438. * Function to create the select.
  2439. */
  2440. var create = function(parentTable, elementId, value, options) {
  2441. // Get the label text, if not set yet.
  2442. if(options.label.id) {
  2443. if(options.label.args) {
  2444. options.label = _this.Language.$(options.label.id, options.label.args);
  2445. } else {
  2446. options.label = _this.Language.$(options.label.id);
  2447. }
  2448. }
  2449. var opts = options.opts;
  2450. // Get the option names, if not set yet.
  2451. for(var i = 0; i < opts.length; i++) {
  2452. if(opts[i].name && opts[i].name.id) {
  2453. if(opts[i].name.args) {
  2454. opts[i].name = _this.Language.$(opts[i].name.id, opts.name.args);
  2455. } else {
  2456. opts[i].name = _this.Language.$(opts[i].name.id);
  2457. }
  2458. }
  2459. }
  2460. // Add select field.
  2461. _this.myGM.addSelect(parentTable, elementId, value, opts, options.label);
  2462. };
  2463. // Add the select field to the option panel.
  2464. _addElement(id, wrapperId, block, { label: label, opts: opts }, defaultSelected, create, save, position);
  2465. };
  2466. /**
  2467. * Add a new textfield to the options tab.
  2468. *
  2469. * @instance
  2470. *
  2471. * @param {string} id
  2472. * The id of the textfield.
  2473. * @param {string} wrapperId
  2474. * The id of the wrapper.
  2475. * @param {string || int} block
  2476. * The block of the wrapper, the textfield belongs to.
  2477. * @param {boolean} defaultValue
  2478. * Default value of the textfield.
  2479. * @param {string || string[]} label
  2480. * The text for the label. If the element is defined within the IkariamCore initialisation,
  2481. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2482. * Object signature: <code>{ id: 'idValue' }</code>
  2483. * @param {int} position
  2484. * The position of the textfield in the wrapper.
  2485. */
  2486. this.addTextField = function(id, wrapperId, block, defaultValue, label, position) {
  2487. /*
  2488. * Function to save the textfield value.
  2489. */
  2490. var save = function(elementId) {
  2491. // Get value and return it.
  2492. return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'TextField').value;
  2493. };
  2494. /*
  2495. * Function to create the textfield.
  2496. */
  2497. var create = function(parentTable, elementId, value, options) {
  2498. // Get the label text, if not set yet.
  2499. if(options.label.id) {
  2500. if(options.label.args) {
  2501. options.label = _this.Language.$(options.label.id, options.label.args);
  2502. } else {
  2503. options.label = _this.Language.$(options.label.id);
  2504. }
  2505. }
  2506. // Create table row.
  2507. var tr = _this.myGM.addElement('tr', parentTable);
  2508. // Create cells.
  2509. var labelCell = _this.myGM.addElement('td', tr);
  2510. var textFieldCell = _this.myGM.addElement('td', tr, null, 'left');
  2511. // Create label.
  2512. var tfLabel = _this.myGM.addElement('span', labelCell);
  2513. tfLabel.innerHTML = options.label;
  2514. // Add textfield.
  2515. var tf = _this.myGM.addElement('input', textFieldCell, elementId + 'TextField', ['textfield', 'scriptTextField']);
  2516. tf.type = 'text';
  2517. tf.value = value;
  2518. };
  2519. // Add the textfield to the option panel.
  2520. _addElement(id, wrapperId, block, { label: label }, defaultValue, create, save, position);
  2521. };
  2522. /**
  2523. * Add a new textarea to the options tab.
  2524. *
  2525. * @instance
  2526. *
  2527. * @param {string} id
  2528. * The id of the textarea.
  2529. * @param {string} wrapperId
  2530. * The id of the wrapper.
  2531. * @param {string || int} block
  2532. * The block of the wrapper, the textarea belongs to.
  2533. * @param {boolean} defaultValue
  2534. * Default value of the textarea.
  2535. * @param {string || string[]} label
  2536. * The text for the label. If the element is defined within the IkariamCore initialisation,
  2537. * the translation string is not set. Then you can pass an object containing the string id.<br>
  2538. * Object signature: <code>{ id: 'idValue' }</code>
  2539. * @param {int} position
  2540. * The position of the textarea in the wrapper.
  2541. */
  2542. this.addTextArea = function(id, wrapperId, block, defaultValue, label, position) {
  2543. /*
  2544. * Function to save the textarea value.
  2545. */
  2546. var save = function(elementId) {
  2547. // Get value and return it.
  2548. return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'TextArea').value;
  2549. };
  2550. /*
  2551. * Function to create the textarea.
  2552. */
  2553. var create = function(parentTable, elementId, value, options) {
  2554. // Get the label text, if not set yet.
  2555. if(options.label.id) {
  2556. if(options.label.args) {
  2557. options.label = _this.Language.$(options.label.id, options.label.args);
  2558. } else {
  2559. options.label = _this.Language.$(options.label.id);
  2560. }
  2561. }
  2562. // Create label table row.
  2563. var labelRow = _this.myGM.addElement('tr', parentTable);
  2564. // Create cell.
  2565. var labelCell = _this.myGM.addElement('td', labelRow);
  2566. labelCell.colSpan = 2;
  2567. labelCell.classList.add('left');
  2568. // Create label.
  2569. var taLabel = _this.myGM.addElement('p', labelCell);
  2570. taLabel.innerHTML = options.label;
  2571. // Create textarea table row.
  2572. var taRow = _this.myGM.addElement('tr', parentTable);
  2573. // Create cell.
  2574. var taCell = _this.myGM.addElement('td', taRow);
  2575. taCell.colSpan = 2;
  2576. taCell.classList.add('left');
  2577. // Add the textarea.
  2578. var textArea = _this.myGM.addElement('textarea', taCell, elementId + 'TextArea', ['textfield', 'scriptTextArea']);
  2579. textArea.value = value;
  2580. };
  2581. // Add the textarea to the options panel.
  2582. _addElement(id, wrapperId, block, { label: label, className: className }, defaultValue, create, save, position);
  2583. };
  2584. /**
  2585. * Add HTML content to the options tab.
  2586. *
  2587. * @instance
  2588. *
  2589. * @param {string} id
  2590. * The id of the HTML content.
  2591. * @param {string} wrapperId
  2592. * The id of the wrapper.
  2593. * @param {string || int} block
  2594. * The block of the wrapper, the HTML content belongs to.
  2595. * @param {string} html
  2596. * HTML string to add to the wrapper.
  2597. * @param {function} callback
  2598. * Callback to run after setting the HTML string. Can also be used to create the HTML content.
  2599. * Gets the this reference and the parent element passed as arguments.<br>
  2600. * Signature: <code>function(thisReference : object, parent : element) : void</code>
  2601. * @param {mixed} thisReference
  2602. * Reference to an object which should be referenced in the callback, because in the callback it is not possible to use some objects. (e.g. _this)
  2603. * @param {int} position
  2604. * The position of the textarea in the wrapper.
  2605. */
  2606. this.addHTML = function(id, wrapperId, block, html, callback, thisReference, position) {
  2607. var create = function(parentTable, elementId, value, options) {
  2608. // Create html table row.
  2609. var htmlRow = _this.myGM.addElement('tr', parentTable);
  2610. // Create cell.
  2611. var htmlCell = _this.myGM.addElement('td', htmlRow);
  2612. htmlCell.colSpan = 2;
  2613. htmlCell.classList.add('center');
  2614. // Add the HTML.
  2615. if(options.html) {
  2616. htmlCell.innerHTML = options.html;
  2617. }
  2618. // Run the callback.
  2619. if(options.callback) {
  2620. options.callback(options.thisReference, htmlCell);
  2621. }
  2622. };
  2623. // Add the HTML text.
  2624. _addElement(id, wrapperId, block, { html: html, thisReference: thisReference, callback: callback }, null, create, null, position);
  2625. };
  2626. /**
  2627. * Add a new horizontal line to the options tab.
  2628. *
  2629. * @instance
  2630. *
  2631. * @param {string} wrapperId
  2632. * The id of the wrapper.
  2633. * @param {string || int} block
  2634. * The block of the wrapper, the horizontal line belongs to.
  2635. * @param {int} position
  2636. * The position of the horizontal line in the wrapper.
  2637. */
  2638. this.addHr = function(wrapperId, block, position) {
  2639. /*
  2640. * Function to create the horizontal line.
  2641. */
  2642. var create = function(parentTable, elementId, value, options) {
  2643. // Create label table row.
  2644. var tr = _this.myGM.addElement('tr', parentTable);
  2645. // Create cell.
  2646. var lineCell = _this.myGM.addElement('td', tr);
  2647. lineCell.colSpan = 2;
  2648. lineCell.classList.add('left');
  2649. // Add the line.
  2650. _this.myGM.addElement('hr', lineCell);
  2651. };
  2652. // Add the line.
  2653. _addElement('hr' + _hrId, wrapperId, block, null, null, create, null, position);
  2654. // Raise the counter.
  2655. _hrId++;
  2656. };
  2657. /**
  2658. * Deletes an wrapper with all option elements contained in it.
  2659. *
  2660. * @instance
  2661. *
  2662. * @param {string} id
  2663. * Id of the wrapper to delete.
  2664. */
  2665. this.deleteWrapper = function(id) {
  2666. // No wrapper with this id => log.
  2667. if(!_wrapper[id]) {
  2668. _this.con.log('Options.deleteWrapper: Wrapper with id "' + id + '" does not exist.');
  2669. } else {
  2670. // Delete the wrapper.
  2671. delete _wrapper[id];
  2672. delete _options[id];
  2673. var position = -1;
  2674. for(var i = 0; i < _wrapperOrder.length; i++) {
  2675. if(_wrapperOrder[i] == id) {
  2676. position = i;
  2677. break;
  2678. }
  2679. }
  2680. _wrapperOrder.remove(position);
  2681. }
  2682. };
  2683. /**
  2684. * Deletes an option element.
  2685. *
  2686. * @instance
  2687. *
  2688. * @param {string} wrapperId
  2689. * The id of the wrapper containing the element.
  2690. * @param {string} elementId
  2691. * The id of the element to delete.
  2692. */
  2693. this.deleteElement = function(wrapperId, elementId) {
  2694. if(!_wrapper[wrapperId] && _wrapper[wrapperId].elements[elementId]) {
  2695. _this.con.log('Options.deleteElement: Element with id "' + wrapperId + '_' + elementId + '" does not exist.');
  2696. } else {
  2697. delete _wrapper[wrapperId].elements[elementId];
  2698. delete _options[wrapperId][elementId];
  2699. var position = -1;
  2700. for(var i = 0; i < _wrapper[wrapperId].elementOrder.length; i++) {
  2701. if(_wrapper[wrapperId].elementOrder[i] == elementId) {
  2702. position = i;
  2703. break;
  2704. }
  2705. }
  2706. _wrapper[wrapperId].elementOrder.remove(position);
  2707. }
  2708. };
  2709. /**
  2710. * Get the stored value of an option.
  2711. *
  2712. * @instance
  2713. *
  2714. * @param {string} wrapperId
  2715. * Id of the wrapper of the option element.
  2716. * @param {string} optionId
  2717. * Id of the option element.
  2718. *
  2719. * @return {mixed}
  2720. * The stored value.
  2721. */
  2722. this.getOption = function(wrapperId, optionId) {
  2723. var option = null;
  2724. // Get the option.
  2725. if(_options[wrapperId] && (_options[wrapperId][optionId] || _options[wrapperId][optionId] == false)) {
  2726. option = _options[wrapperId][optionId];
  2727. } else {
  2728. _this.con.log('Options.getOption: Option with id "' + wrapperId + '_' + optionId + '" not defined.');
  2729. }
  2730. // Return the option.
  2731. return option;
  2732. };
  2733. /**
  2734. * Set the stored value of an option.
  2735. *
  2736. * @instance
  2737. *
  2738. * @param {string} wrapperId
  2739. * Id of the wrapper of the option element.
  2740. * @param {string} optionId
  2741. * Id of the option element.
  2742. * @param {mixed} value
  2743. * The value to store.
  2744. */
  2745. this.setOption = function(wrapperId, optionId, value) {
  2746. // Set the option value.
  2747. if(_options[wrapperId] && (_options[wrapperId][optionId] || _options[wrapperId][optionId] == false)) {
  2748. _options[wrapperId][optionId] = value;
  2749. } else {
  2750. _this.con.log('Options.setOption: Option with id "' + wrapperId + '_' + optionId + '" not yet defined. Value "' + value + '" not stored.');
  2751. }
  2752. // Save the options.
  2753. _saveOptions(true);
  2754. };
  2755. /*----------------------------------------*
  2756. * Register the show option panel handler *
  2757. *----------------------------------------*/
  2758. // Register the option handler to show the options in the option panel.
  2759. _this.RefreshHandler.add('options', 'showOptionPanel', _showOptionPanel);
  2760. /*-------------------------------*
  2761. * Add the option panel options. *
  2762. *-------------------------------*/
  2763. this.addWrapper('optionPanelOptions', { id: 'default_optionPanel_section_optionPanelOptions_title' });
  2764. var opts = new Array(
  2765. { value: 'roundButton', name: { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_option_roundButton' } },
  2766. { value: 'rectangleButton', name: { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_option_rectangularButton' } }
  2767. );
  2768. this.addSelect('useStyle', 'optionPanelOptions', 'selects', 'roundButton', { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_description' }, opts);
  2769. this.addHTML('exportOptions', 'optionPanelOptions', 'links', null, _exportOptions, _this);
  2770. this.addHTML('importOptions', 'optionPanelOptions', 'links', null, _importOptions, _this);
  2771. this.addHTML('resetOptions', 'optionPanelOptions', 'links', null, _resetOptions, _this);
  2772. }
  2773. /**
  2774. * Handler for options the user can set / change.
  2775. *
  2776. * @instance
  2777. *
  2778. * @type IkariamCore~Options
  2779. */
  2780. this.Options = new Options;
  2781. /**
  2782. * Instantiate a new set of updating functions and start an initial update check.
  2783. *
  2784. * @inner
  2785. *
  2786. * @class
  2787. * @classdesc Functions for checking for updates for the script.
  2788. */
  2789. function Updater() {
  2790. /*--------------------------------------------*
  2791. * Private variables, functions and settings. *
  2792. *--------------------------------------------*/
  2793. /**
  2794. * Stores if the update was instructed by the user.
  2795. *
  2796. * @private
  2797. * @inner
  2798. *
  2799. * @default false
  2800. *
  2801. * @type boolean
  2802. */
  2803. var _manualUpdate = false;
  2804. /**
  2805. * Compares two versions and returns if there is a new version.
  2806. *
  2807. * @private
  2808. * @inner
  2809. *
  2810. * @param {string} versionOld
  2811. * The old version number.
  2812. * @param {string} versionNew
  2813. * The new version number.
  2814. * @param {int} maxPartsToCompare
  2815. * The number of parts to compare at most. (optional, default "compare all parts")
  2816. *
  2817. * @return {boolean}
  2818. * If a new version is available.
  2819. */
  2820. var _newerVersion = function(versionOld, versionNew, maxPartsToCompare) {
  2821. // Stores if a new version is available.
  2822. var newVersion = false;
  2823. // Force both versions to be a string.
  2824. versionOld += '';
  2825. versionNew += '';
  2826. // The parts of the versions.
  2827. var versionOldParts = versionOld.split('.');
  2828. var versionNewParts = versionNew.split('.');
  2829. // The bigger number of parts of the versions.
  2830. var biggerNumberOfParts = versionOldParts.length > versionNewParts.length ? versionOldParts.length : versionNewParts.length;
  2831. // If all parts should be compared, set maxPartsToCompare to all parts.
  2832. if(!maxPartsToCompare || maxPartsToCompare < 1) {
  2833. maxPartsToCompare = biggerNumberOfParts + 1;
  2834. }
  2835. // Loop over all parts of the version with less parts.
  2836. for(var i = 0; i < biggerNumberOfParts; i++) {
  2837. // Get the value of the parts.
  2838. var versionPartOld = parseInt(versionOldParts[i] || 0);
  2839. var versionPartNew = parseInt(versionNewParts[i] || 0);
  2840. // If the old part is smaller than the new, return true.
  2841. if(versionPartOld < versionPartNew) {
  2842. newVersion = true;
  2843. break;
  2844. // Else if the old part is bigger than the new it is now new version; return false.
  2845. } else if(versionPartOld > versionPartNew || i == maxPartsToCompare - 1) {
  2846. newVersion = false;
  2847. break;
  2848. }
  2849. }
  2850. // Return if there is a new version.
  2851. return newVersion;
  2852. };
  2853. /**
  2854. * Extract the update history from the metadata.
  2855. *
  2856. * @private
  2857. * @inner
  2858. *
  2859. * @param {string[]} metadata
  2860. * Array with the formated metadata.
  2861. *
  2862. * @return {mixed[]}
  2863. * The extracted update history.
  2864. */
  2865. var _extractUpdateHistory = function(metadata) {
  2866. // Create variable to store the update history.
  2867. var updateHistory = {};
  2868. // Loop over all update history data.
  2869. for(var i = 0; i < metadata['history'].length; i++) {
  2870. // Get the information from the update history data.
  2871. var tmp = metadata['history'][i].match(/^(\S+)\s+(\S+)\s+(.*)$/);
  2872. // If there is no array for this version create one.
  2873. if(!updateHistory[tmp[1]]) {
  2874. updateHistory[tmp[1]] = {};
  2875. }
  2876. switch(tmp[2].trim(':').toLowerCase()) {
  2877. case 'release':
  2878. updateHistory[tmp[1]].release = tmp[3];
  2879. break;
  2880. case 'feature':
  2881. if(!updateHistory[tmp[1]].feature) {
  2882. updateHistory[tmp[1]].feature = new Array(tmp[3]);
  2883. } else {
  2884. updateHistory[tmp[1]].feature.push(tmp[3]);
  2885. }
  2886. break;
  2887. case 'change':
  2888. if(!updateHistory[tmp[1]].change) {
  2889. updateHistory[tmp[1]].change = new Array(tmp[3]);
  2890. } else {
  2891. updateHistory[tmp[1]].change.push(tmp[3]);
  2892. }
  2893. break;
  2894. case 'bugfix':
  2895. if(!updateHistory[tmp[1]].bugfix) {
  2896. updateHistory[tmp[1]].bugfix = new Array(tmp[3]);
  2897. } else {
  2898. updateHistory[tmp[1]].bugfix.push(tmp[3]);
  2899. }
  2900. break;
  2901. case 'language':
  2902. if(!updateHistory[tmp[1]].language) {
  2903. updateHistory[tmp[1]].language = new Array(tmp[3]);
  2904. } else {
  2905. updateHistory[tmp[1]].language.push(tmp[3]);
  2906. }
  2907. break;
  2908. case 'core':
  2909. if(!updateHistory[tmp[1]].core) {
  2910. updateHistory[tmp[1]].core = new Array(tmp[3]);
  2911. } else {
  2912. updateHistory[tmp[1]].core.push(tmp[3]);
  2913. }
  2914. break;
  2915. default:
  2916. if(!updateHistory[tmp[1]].other) {
  2917. updateHistory[tmp[1]].other = new Array(tmp[2] + " " + tmp[3]);
  2918. } else {
  2919. updateHistory[tmp[1]].other.push(tmp[2] + " " + tmp[3]);
  2920. }
  2921. break;
  2922. }
  2923. }
  2924. // Return the update history.
  2925. return updateHistory;
  2926. };
  2927. /**
  2928. * Format the update history using some HTML codes.
  2929. *
  2930. * @private
  2931. * @inner
  2932. *
  2933. * @param {mixed[]} updateHistory
  2934. * The update history.
  2935. *
  2936. * @return {string}
  2937. * The formated update history.
  2938. */
  2939. var _formatUpdateHistory = function(updateHistory) {
  2940. // Create a variable for the formated update history.
  2941. var formatedUpdateHistory = '';
  2942. // Loop over all versions.
  2943. for(var version in updateHistory) {
  2944. if(Object.prototype.hasOwnProperty.call(updateHistory, version)) {
  2945. // Create a headline for each version and start a table.
  2946. formatedUpdateHistory += '<h2>v ' + version + '</h2><span class="smallFont">' + updateHistory[version].release + '</span></small><br><table class="' + _this.myGM.prefix() + 'updateTable"><tbody>';
  2947. // Loop over all types.
  2948. for(var type in updateHistory[version]) {
  2949. if(Object.prototype.hasOwnProperty.call(updateHistory[version], type) && type != 'release') {
  2950. // Create a table row for each type and start a list for the elements.
  2951. formatedUpdateHistory += '<tr><td class="' + _this.myGM.prefix() + 'updateDataType">' + _this.Language.$('default_update_possible_type_' + type) + '</td><td class="' + _this.myGM.prefix() + 'updateDataInfo"><ul>';
  2952. // Loop over the elements and add them to the list.
  2953. for(var i = 0 ; i < updateHistory[version][type].length; i++) {
  2954. formatedUpdateHistory += '<li>' + updateHistory[version][type][i] + '</li>';
  2955. }
  2956. // End the list.
  2957. formatedUpdateHistory += '</ul></td></tr>';
  2958. }
  2959. }
  2960. // End the table.
  2961. formatedUpdateHistory += '</tbody></table><br>';
  2962. }
  2963. }
  2964. // Return the formated update history.
  2965. return formatedUpdateHistory;
  2966. };
  2967. /**
  2968. * Show the update information panel.
  2969. *
  2970. * @private
  2971. * @inner
  2972. *
  2973. * @param {mixed[]} metadata
  2974. * Array with formated metadata
  2975. */
  2976. var _showUpdateInfo = function(metadata) {
  2977. // Get the update history.
  2978. var updateHistory = _extractUpdateHistory(metadata);
  2979. // Set the notification text.
  2980. var notificationText = {
  2981. header: _this.Language.$('default_update_possible_header'),
  2982. bodyTop: _this.Language.$('default_update_possible_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version, metadata.version]) + '<br>&nbsp;&nbsp;<b><u>' + _this.Language.$('default_update_possible_history') + '</u></b>',
  2983. bodyBottom: _formatUpdateHistory(updateHistory),
  2984. confirm: _this.Language.$('default_update_possible_button_install'),
  2985. abort: _this.Language.$('default_update_possible_button_hide')
  2986. };
  2987. // Set the notification callback.
  2988. var notificationCallback = {
  2989. confirm: function() { _this.win.top.location.href = 'http://userscripts.org/scripts/source/' + scriptInfo.id + '.user.js'; },
  2990. abort: function() { _this.myGM.setValue('updater_hideUpdate', metadata.version + ''); }
  2991. };
  2992. // Show a notification.
  2993. _this.myGM.notification(notificationText, notificationCallback);
  2994. };
  2995. /**
  2996. * Format the given metadata.
  2997. *
  2998. * @private
  2999. * @inner
  3000. *
  3001. * @param {string} metadata
  3002. * The metadata to format.
  3003. *
  3004. * @return {string[]}
  3005. * The formatted metadata as array.
  3006. */
  3007. var _formatMetadata = function(metadataIn) {
  3008. // Create an array for the formated metadata.
  3009. var metadataOut = new Array();
  3010. // Extract the tags from the metadata.
  3011. var innerMeta = metadataIn.match(/\/\/ ==UserScript==((.|\n|\r)*?)\/\/ ==\/UserScript==/)[0];
  3012. // If there are some tags.
  3013. if(innerMeta) {
  3014. // Extract all tags.
  3015. var tags = innerMeta.match(/\/\/ @(.*?)(\n|\r)/g);
  3016. // Loop over all tags.
  3017. for(var i = 0; i < tags.length; i++) {
  3018. // Extract the data from the tag.
  3019. var tmp = tags[i].match(/\/\/ @(.*?)\s+(.*)/);
  3020. // If there is no data with this tag create a new array to store all data with this tag.
  3021. if(!metadataOut[tmp[1]]) {
  3022. metadataOut[tmp[1]] = new Array(tmp[2]);
  3023. // Otherwise add the data to the existing array.
  3024. } else {
  3025. metadataOut[tmp[1]].push(tmp[2]);
  3026. }
  3027. }
  3028. }
  3029. // Return the formated metadata.
  3030. return metadataOut;
  3031. };
  3032. /*-------------------------------------------*
  3033. * Public variables, functions and settings. *
  3034. *-------------------------------------------*/
  3035. /**
  3036. * Check for updates for the script. Automatically done on every instantiation of {@link IkariamCore}
  3037. * if the period from the last update is bigger than the check interval.
  3038. *
  3039. * @instance
  3040. */
  3041. this.checkForUpdates = function() {
  3042. // Send a request to the userscripts.org server to get the metadata of the script to check if there is a new Update.
  3043. var notPossible = _this.myGM.xhr({
  3044. method: 'GET',
  3045. url: 'http://userscripts.org/scripts/source/' + scriptInfo.id + '.meta.js',
  3046. headers: {'User-agent': 'Mozilla/5.0', 'Accept': 'text/html'},
  3047. onload: function(response) {
  3048. // Extract the metadata from the response.
  3049. var metadata = _formatMetadata(response.responseText);
  3050. // If a new Update is available and the update hint should be shown.
  3051. if(_newerVersion(scriptInfo.version, metadata.version, _this.Options.getOption('updateOptions', 'updateNotifyLevel')) && (_this.myGM.getValue('updater_hideUpdate', scriptInfo.version) != metadata.version) || _manualUpdate) {
  3052. // Show update dialogue.
  3053. _showUpdateInfo(metadata);
  3054. // If there is no new update and it was a manual update show hint.
  3055. } else if(_manualUpdate) {
  3056. // Set the notification text.
  3057. var notificationText = {
  3058. header: _this.Language.$('default_update_noNewExists_header'),
  3059. body: _this.Language.$('default_update_noNewExists_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version])
  3060. };
  3061. // Show a notification.
  3062. _this.myGM.notification(notificationText);
  3063. }
  3064. }
  3065. });
  3066. if(notPossible && notPossible == true) {
  3067. // Set the update interval to max.
  3068. _this.Options.setOption('updateOptions', 'updateInterval', 2419200);
  3069.  
  3070. // Set the notification text.
  3071. var notificationText = {
  3072. header: _this.Language.$('default_update_notPossible_header'),
  3073. body: _this.Language.$('default_update_notPossible_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version])
  3074. };
  3075.  
  3076. // Show a notification.
  3077. _this.myGM.notification(notificationText);
  3078. }
  3079. };
  3080. /**
  3081. * Search manually for updates. Forces to search for updates. Even shows a popup if no new update is available.
  3082. *
  3083. * @instance
  3084. */
  3085. this.doManualUpdate = function() {
  3086. // Manual Update.
  3087. _manualUpdate = true;
  3088. // Check for Updates.
  3089. _this.Updater.checkForUpdates();
  3090. // Set the time for the last update check to now.
  3091. _this.myGM.setValue('updater_lastUpdateCheck', (new Date()).getTime() + '');
  3092. };
  3093. /*------------------------*
  3094. * Add the updater styles *
  3095. *------------------------*/
  3096. // Set the updater style.
  3097. _this.myGM.addStyle(
  3098. "." + _this.myGM.prefix() + "updateTable { border-collapse: separate; border-spacing: 2px; } \
  3099. ." + _this.myGM.prefix() + "updateDataType { width: 100px; padding: 5px 0px 5px 5px; border: 1px solid #D2A860; } \
  3100. ." + _this.myGM.prefix() + "updateDataInfo { width: 300px; padding: 5px 5px 5px 20px; border: 1px solid #D2A860; } \
  3101. ." + _this.myGM.prefix() + "updateDataInfo ul li { list-style: disc outside none; }",
  3102. 'updater', true
  3103. );
  3104. /*----------------------*
  3105. * Register the options *
  3106. *----------------------*/
  3107. _this.Options.addWrapper('moduleOptions', { id: 'default_optionPanel_section_module_title' }, 0);
  3108. _this.Options.addCheckbox('updateActive', 'moduleOptions', 1, true, { id: 'default_optionPanel_section_module_label_updateActive' });
  3109. _this.Options.addHr('moduleOptions', 1);
  3110. // Array for update interval values and names.
  3111. var _updateIntervalOpts = new Array(
  3112. { value: 3600, name: { id: 'default_optionPanel_section_update_label_interval_option_hour' } },
  3113. { value: 43200, name: { id: 'default_optionPanel_section_update_label_interval_option_hour12' } },
  3114. { value: 86400, name: { id: 'default_optionPanel_section_update_label_interval_option_day' } },
  3115. { value: 259200, name: { id: 'default_optionPanel_section_update_label_interval_option_day3' } },
  3116. { value: 604800, name: { id: 'default_optionPanel_section_update_label_interval_option_week' } },
  3117. { value: 1209600, name: { id: 'default_optionPanel_section_update_label_interval_option_week2' } },
  3118. { value: 2419200, name: { id: 'default_optionPanel_section_update_label_interval_option_week4' } }
  3119. );
  3120. var _updateNotifyLevelOpts = new Array(
  3121. { value: 0, name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_all' } },
  3122. { value: 1, name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_major' } },
  3123. { value: 2, name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_minor' } },
  3124. { value: 3, name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_patch' } }
  3125. );
  3126. var _searchUpdates = function(_this, parent) {
  3127. var updateLink = _this.myGM.addElement('a', parent);
  3128. updateLink.href = 'javascript:;';
  3129. updateLink.innerHTML = _this.Language.$('default_optionPanel_section_update_label_manual', new Array(scriptInfo.name));
  3130. updateLink.addEventListener('click', _this.Updater.doManualUpdate, false);
  3131. };
  3132. _this.Options.addWrapper('updateOptions', { id: 'default_optionPanel_section_update_title' }, 1);
  3133. _this.Options.addSelect('updateInterval', 'updateOptions', 'generalOptions', 3600, { id: 'default_optionPanel_section_update_label_interval_description' }, _updateIntervalOpts);
  3134. _this.Options.addSelect('updateNotifyLevel', 'updateOptions', 'generalOptions', 0, { id: 'default_optionPanel_section_update_label_notifyLevel_description' }, _updateNotifyLevelOpts);
  3135. _this.Options.addHTML('manualUpdateLink', 'updateOptions', 'generalOptions', null, _searchUpdates, _this);
  3136. /*-------------------------------------*
  3137. * Check automatically for new updates *
  3138. *-------------------------------------*/
  3139. // Get the difference between now and the last check.
  3140. var _lastCheck = _this.myGM.getValue('updater_lastUpdateCheck', 0);
  3141. var _millis = (new Date()).getTime();
  3142. var _diff = _millis - _lastCheck;
  3143. // If the module is active and the period from the last update is bigger than the check interval, check for updates.
  3144. if(_this.Options.getOption('moduleOptions', 'updateActive') && _diff > _this.Options.getOption('updateOptions', 'updateInterval') * 1000) {
  3145. // No manual Update.
  3146. _manualUpdate = false;
  3147.  
  3148. // Check for Updates.
  3149. this.checkForUpdates();
  3150.  
  3151. // Set the time for the last update check to now.
  3152. _this.myGM.setValue('updater_lastUpdateCheck', _millis + '');
  3153. }
  3154. }
  3155. /**
  3156. * Updater to check for updates on Userscripts.org.
  3157. *
  3158. * @instance
  3159. *
  3160. * @type IkariamCore~Updater
  3161. */
  3162. this.Updater = new Updater;
  3163. }

QingJ © 2025

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