Ikariam Core

Framework for Ikariam userscript developers.

目前为 2015-03-04 提交的版本。查看 最新版本

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

  1. // ==UserScript==
  2. // @name Ikariam Core
  3. // @description Framework for Ikariam userscript developers.
  4. // @namespace IkariamCore
  5. // @author Tobbe
  6. // @version 2.0
  7. // @license MIT License
  8. //
  9. // @name:de Ikariam Core
  10. // @description:de Framework für Ikariam Benutzerscript Entwickler.
  11. //
  12. // @include http://s*.ikariam.gameforge.com/*
  13. //
  14. // @exclude http://support.*.ikariam.gameforge.com/*
  15. //
  16. //
  17. // @resource core_de http://resources.ikascripts.de/IkariamCore/2.0/core_de.json
  18. // @resource core_de_settings http://resources.ikascripts.de/IkariamCore/2.0/core_de_settings.json
  19. // @resource core_gr http://resources.ikascripts.de/IkariamCore/2.0/core_gr.json
  20. // @resource core_gr_settings http://resources.ikascripts.de/IkariamCore/2.0/core_gr_settings.json
  21. // @resource core_it http://resources.ikascripts.de/IkariamCore/2.0/core_it.json
  22. // @resource core_it_settings http://resources.ikascripts.de/IkariamCore/2.0/core_it_settings.json
  23. // @resource core_lv http://resources.ikascripts.de/IkariamCore/2.0/core_lv.json
  24. // @resource core_lv_settings http://resources.ikascripts.de/IkariamCore/2.0/core_lv_settings.json
  25. // @resource core_ru http://resources.ikascripts.de/IkariamCore/2.0/core_ru.json
  26. // @resource core_ru_settings http://resources.ikascripts.de/IkariamCore/2.0/core_ru_settings.json
  27. //
  28. // @grant unsafeWindow
  29. // @grant GM_setValue
  30. // @grant GM_getValue
  31. // @grant GM_deleteValue
  32. // @grant GM_listValues
  33. // @grant GM_getResourceText
  34. // @grant GM_xmlhttpRequest
  35. // ==/UserScript==
  36.  
  37. // Add some functions to the String and Array prototype, namespaced by "IC".
  38. (function() {
  39. // "Internal" function to add functions to native objects with the usage of an namespace.
  40. var _addNamespacedFunctions = function(io_parent, is_namespaceName, io_functionsToAdd) {
  41. var lo_functionStorage = {};
  42. var lf_createGetter = function(is_functionName) {
  43. return function() {
  44. return function() {
  45. return io_functionsToAdd[is_functionName].apply(go_self, arguments);
  46. };
  47. };
  48. };
  49. for(var ls_functionName in io_functionsToAdd) {
  50. if(io_functionsToAdd.hasOwnProperty(ls_functionName)) {
  51. lo_functionStorage.__defineGetter__(ls_functionName, lf_createGetter(ls_functionName));
  52. }
  53. }
  54. io_parent.prototype.__defineGetter__(is_namespaceName, function() {
  55. go_self = this;
  56. return lo_functionStorage;
  57. });
  58. };
  59. /**
  60. * Additional methods for processing strings.
  61. *
  62. * @external "String.IC"
  63. */
  64. _addNamespacedFunctions(String, 'IC', {
  65. /**
  66. * Replaces characters or whitespaces at the beginning of a string.
  67. *
  68. * @function external:"String.IC".ltrim
  69. *
  70. * @param {?String} [is_toRemove=whitespaces]
  71. * A string containing the characters to remove.
  72. *
  73. * @return {String}
  74. * The trimmed string.
  75. */
  76. ltrim: function(is_toRemove) {
  77. return !!is_toRemove ? this.replace(new RegExp('^[' + is_toRemove + ']+'), '') : this.replace(/^\s+/, '');
  78. },
  79. /**
  80. * Replaces characters or whitespaces at the end of a string.
  81. *
  82. * @function external:"String.IC".rtrim
  83. *
  84. * @param {?String} [is_toRemove=whitespaces]
  85. * A string containing the characters to remove.
  86. *
  87. * @return {String}
  88. * The trimmed string.
  89. */
  90. rtrim: function(is_toRemove) {
  91. return !!is_toRemove ? this.replace(new RegExp('[' + is_toRemove + ']+$'), '') : this.replace(/\s+$/, '');
  92. },
  93. /**
  94. * Replaces characters or whitespaces at the beginning and end of a string.
  95. *
  96. * @function external:"String.IC".trim
  97. *
  98. * @param {?String} [is_toRemove=whitespaces]
  99. * A string containing the characters to remove.
  100. *
  101. * @return {String}
  102. * The trimmed string.
  103. */
  104. trim: function(is_toRemove) {
  105. return this.IC.ltrim(is_toRemove).IC.rtrim(is_toRemove);
  106. },
  107. /**
  108. * Encodes HTML-special characters in a string.
  109. *
  110. * @function external:"String.IC".encodeHTML
  111. *
  112. * @return {String}
  113. * The encoded string.
  114. */
  115. encodeHTML: function() {
  116. // Set the characters to encode.
  117. var lo_characters = {
  118. '&': '&',
  119. '"': '"',
  120. '\'': ''',
  121. '<': '&lt;',
  122. '>': '&gt;'
  123. };
  124. return this.replace(/([\&"'<>])/g, function(is_string, is_symbol) { return lo_characters[is_symbol]; });
  125. },
  126. /**
  127. * Decodes HTML-special characters in a string.
  128. *
  129. * @function external:"String.IC".decodeHTML
  130. *
  131. * @return {String}
  132. * The decoded string.
  133. */
  134. decodeHTML: function() {
  135. // Set the characters to decode.
  136. var lo_characters = {
  137. '&amp;': '&',
  138. '&quot;': '"',
  139. '&apos;': '\'',
  140. '&lt;': '<',
  141. '&gt;': '>'
  142. };
  143. return this.replace(/(&quot;|&apos;|&lt;|&gt;|&amp;)/g, function(is_string, is_symbol) { return lo_characters[is_symbol]; });
  144. },
  145. /**
  146. * Repeats a string a specified number of times.
  147. *
  148. * @function external:"String.IC".repeat
  149. *
  150. * @param {int} ii_nr
  151. * The number of times to repeat the string.
  152. *
  153. * @return {String}
  154. * The repeated string.
  155. */
  156. repeat: function(ii_nr) {
  157. var rs_repeated = this;
  158. for(var i = 1; i < ii_nr; i++) {
  159. rs_repeated += this;
  160. }
  161. return rs_repeated;
  162. }
  163. });
  164. /**
  165. * Additional methods for processing arrays.
  166. *
  167. * @external "Array.IC"
  168. */
  169. _addNamespacedFunctions(Array, 'IC', {
  170. /**
  171. * Inserts an element at a specified position into an array.
  172. *
  173. * @function external:"Array.IC".insert
  174. *
  175. * @param {*} im_item
  176. * The item which should be inserted.
  177. * @param {?int} [ii_index=this.length]
  178. * The position where the element should be added. If not set, the element will be added at the end.
  179. */
  180. insert: function(im_item, ii_index) {
  181. var li_maxIndex = this.length;
  182. // Get the index to insert.
  183. var li_index = !ii_index && ii_index != 0 ? li_maxIndex : ii_index;
  184. li_index = Math.max(li_index, 0); // No negative index.
  185. li_index = Math.min(li_index, li_maxIndex); // No index bigger than the array length.
  186. this.splice(li_index, 0, im_item);
  187. },
  188. /**
  189. * Deletes an element at a specified position from an array.
  190. *
  191. * @function external:"Array.IC".remove
  192. *
  193. * @param {int} ii_index
  194. * The position of the element which should be deleted.
  195. */
  196. remove: function(ii_index) {
  197. if(ii_index >= 0 && ii_index < this.length) {
  198. this.splice(ii_index, 1);
  199. }
  200. }
  201. });
  202. /**
  203. * Additional methods for processing dates.
  204. *
  205. * @external "Date.IC"
  206. */
  207. _addNamespacedFunctions(Date, 'IC', {
  208. /**
  209. * Formats a date / time.
  210. *
  211. * @example
  212. * (new Date()).IC.format('yyyy-MM-dd HH:mm:ss.SSS');
  213. *
  214. * @function external:"Date.IC".format
  215. *
  216. * @param {String} is_pattern
  217. * The pattern for the output.<br>
  218. * <br>
  219. * Options:<br>
  220. * <pre> yyyy year, four digits
  221. * yy year, two digits
  222. * MM month, leading 0
  223. * M month, no leading 0
  224. * dd day, leading 0
  225. * d day, no leading 0
  226. * hh hour, 1-12, leading 0
  227. * h hour, 1-12, no leading 0
  228. * HH hour, 0-23, leading 0
  229. * H hour, 0-23, no leading 0
  230. * mm minute, leading 0
  231. * m minute, no leading 0
  232. * ss seconds, leading 0
  233. * s seconds, no leading 0
  234. * SSS milliseconds, leading 0
  235. * S milliseconds, no leading 0
  236. * a AM / PM</pre>
  237. */
  238. format: function(is_pattern) {
  239. var lo_possibleOptions = {
  240. 'yyyy': this.getFullYear(), // year, four digits
  241. 'yy': this.getYear() % 100, // year, two digits
  242. 'MM': this.getMonth() + 1, // month, leading 0
  243. 'M': this.getMonth() + 1, // month, no leading 0
  244. 'dd': this.getDate(), // day, leading 0
  245. 'd': this.getDate(), // day, no leading 0
  246. 'hh': this.getHours() + 1, // hour, 1-12, leading 0
  247. 'h': this.getHours() + 1, // hour, 1-12, no leading 0
  248. 'HH': this.getHours(), // hour, 0-23, leading 0
  249. 'H': this.getHours(), // hour, 0-23, no leading 0
  250. 'mm': this.getMinutes(), // minute, leading 0
  251. 'm': this.getMinutes(), // minute, no leading 0
  252. 'ss': this.getSeconds(), // seconds, leading 0
  253. 's': this.getSeconds(), // seconds, no leading 0
  254. 'SSS': this.getMilliseconds(), // milliseconds, ledaing 0
  255. 'S': this.getMilliseconds(), // milliseconds, no leading 0
  256. 'a': 'AM' // AM / PM
  257. };
  258. if(lo_possibleOptions.MM < 10) lo_possibleOptions.MM = '0' + lo_possibleOptions.MM;
  259. if(lo_possibleOptions.dd < 10) lo_possibleOptions.dd = '0' + lo_possibleOptions.dd;
  260. if(lo_possibleOptions.h > 12) lo_possibleOptions.hh = lo_possibleOptions.h = lo_possibleOptions.h - 12;
  261. if(lo_possibleOptions.hh < 10) lo_possibleOptions.hh = '0' + lo_possibleOptions.hh;
  262. if(lo_possibleOptions.HH < 10) lo_possibleOptions.HH = '0' + lo_possibleOptions.HH;
  263. if(lo_possibleOptions.mm < 10) lo_possibleOptions.mm = '0' + lo_possibleOptions.mm;
  264. if(lo_possibleOptions.ss < 10) lo_possibleOptions.ss = '0' + lo_possibleOptions.ss;
  265. if(lo_possibleOptions.S < 100) lo_possibleOptions.SSS = '0' + lo_possibleOptions.SSS;
  266. if(lo_possibleOptions.S < 10) lo_possibleOptions.SSS = '0' + lo_possibleOptions.SSS;
  267. if(lo_possibleOptions.H > 11) lo_possibleOptions.a = 'PM';
  268. var rs_pattern = is_pattern;
  269. for(var ls_option in lo_possibleOptions) {
  270. rs_pattern = rs_pattern.replace(new RegExp('(' + ls_option + ')', 'g'), lo_possibleOptions[ls_option]);
  271. }
  272. return rs_pattern;
  273. }
  274. });
  275. })();
  276.  
  277. /**
  278. * Instantiate a new set of core functions.<br>
  279. * {@link https://greasyfork.org/scripts/5574-ikariam-core Script on Greasy Fork}<br>
  280. * {@link https://github.com/tobias-engelmann/IkariamCore Script on GitHub}
  281. *
  282. * @version 2.0
  283. * @author Tobbe <contact@ikascripts.de>
  284. *
  285. * @global
  286. *
  287. * @class
  288. * @classdesc Framework for Ikariam userscript developers.
  289. *
  290. * @param {String} is_scriptVersion
  291. * The version of the script using Ikariam Core.
  292. * @param {int} ii_scriptId
  293. * The id of the script using Ikariam Core.
  294. * @param {String} is_scriptName
  295. * The name of the script using Ikariam Core.
  296. * @param {String} is_scriptAuthor
  297. * The author of the script using Ikariam Core.
  298. * @param {boolean} ib_debug
  299. * If debugging is enabled.
  300. */
  301. function IkariamCore(is_scriptVersion, ii_scriptId, is_scriptName, is_scriptAuthor, ib_debug) {
  302. /**
  303. * Storage for accessing <code>this</code> as reference to IkariamCore in subfunctions. Do <b>NOT</b> delete!
  304. *
  305. * @private
  306. * @inner
  307. *
  308. * @type IkariamCore
  309. */
  310. var go_self = this;
  311. /**
  312. * Storage for information on the script using Ikariam Core.
  313. *
  314. * @private
  315. * @inner
  316. *
  317. * @type Object
  318. *
  319. * @property {String} version - The script version.
  320. * @property {int} id - The script id.
  321. * @property {String} name - The script name.
  322. * @property {String} author - The script author.
  323. */
  324. var go_script = {
  325. version: is_scriptVersion,
  326. id: ii_scriptId,
  327. name: is_scriptName,
  328. author: is_scriptAuthor
  329. };
  330. /**
  331. * General settings like debugging switched on / off.
  332. *
  333. * @private
  334. * @inner
  335. *
  336. * @type Object
  337. *
  338. * @property {boolean} debug - If debugging is enabled.
  339. */
  340. var go_settings = {
  341. debug: ib_debug
  342. };
  343. /**
  344. * A reference to <code>window</code> / <code>unsafeWindow</code>.
  345. *
  346. * @instance
  347. *
  348. * @type window
  349. */
  350. this.win = typeof unsafeWindow != 'undefined' ? unsafeWindow : window;
  351. /**
  352. * Reference to <code>window.ikariam</code>.
  353. *
  354. * @instance
  355. *
  356. * @type Object
  357. */
  358. this.ika = this.win.ikariam;
  359. /**
  360. * Debugging console. 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>
  361. * Available commands:<br>
  362. * <code>assert, clear, count, debug, dir, dirxml, error, exception, group, groupCollapsed, groupEnd,
  363. * info, log, profile, profileEnd, table, time, timeEnd, timeStamp, trace, warn</code><br>
  364. * <br>
  365. * The console is deactivated by the Ikariam page but with the script {@link https://greasyfork.org/de/scripts/6310-rescue-console Rescue Console} you can use it.
  366. *
  367. * @instance
  368. *
  369. * @type console
  370. */
  371. this.con = (function() {
  372. // Set the console to the "rescued" debugConsole.
  373. var lo_console = go_self.win.debugConsole;
  374. if(!go_settings.debug || !lo_console) {
  375. lo_console = {};
  376. }
  377. // Define all Firebug tags.
  378. var la_tags = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception',
  379. 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'profile', 'profileEnd',
  380. 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
  381. var lo_counters = {};
  382. var lo_timers = {};
  383. // Define the backup functions.
  384. var lo_selfDefinedFunctions = {
  385. assert: function(im_toCheck, im_toLog) {
  386. if(im_toCheck === false || im_toCheck === 0 || im_toCheck === null || im_toCheck === undefined) {
  387. this.error(im_toLog || 'Assertion Failure');
  388. }
  389. },
  390. count: function(is_name) {
  391. if(!lo_counters[is_name] === true)
  392. lo_counters[is_name] = 0;
  393. lo_counters[is_name]++;
  394. this.log(is_name + ': ' + lo_counters[is_name]);
  395. },
  396. debug: function() {
  397. this.log.apply(arguments);
  398. },
  399. error: function() {
  400. this.log.apply(arguments);
  401. },
  402. exception: function() {
  403. this.log.apply(arguments);
  404. },
  405. info: function() {
  406. this.log.apply(arguments);
  407. },
  408. time: function(is_name) {
  409. lo_timers[is_name] = new Date();
  410. },
  411. timeEnd: function(is_name) {
  412. var ld_now = new Date();
  413. var li_timeElapsed = ld_now.getMilliseconds() - lo_timers[is_name].getMilliseconds();
  414. delete lo_timers[is_name];
  415. this.info(is_name + ': ' + li_timeElapsed + 'ms');
  416. },
  417. timeStamp: function(iv_name) {
  418. this.log((new Date()).IC.format('HH:mm:ss.SSS') + ' ' + iv_name);
  419. },
  420. warn: function() {
  421. this.log.apply(arguments);
  422. }
  423. };
  424. for(var i = 0; i < la_tags.length; i++) {
  425. var ls_key = la_tags[i];
  426. // If the function is not set yet, set it to the backup function or an empty function.
  427. if(!lo_console[ls_key]) {
  428. if(!go_settings.debug && lo_selfDefinedFunctions[ls_key]) {
  429. lo_console[ls_key] = lo_selfDefinedFunctions[ls_key];
  430. } else {
  431. lo_console[ls_key] = function() { return; };
  432. }
  433. }
  434. }
  435. return lo_console;
  436. })();
  437.  
  438. this.con.groupCollapsed('IkariamCore initalization ...');
  439. /**
  440. * Instantiate a new set of myGM functions.
  441. *
  442. * @inner
  443. *
  444. * @class
  445. * @classdesc Functions for cross-browser compatibility of the GM_* functions.<br>Also there are some new functionalities implemented.
  446. */
  447. function myGM() {
  448. /*--------------------------------------------*
  449. * Private variables, functions and settings. *
  450. *--------------------------------------------*/
  451. /**
  452. * Storage for style sheets which will be added by the script.
  453. *
  454. * @private
  455. * @inner
  456. *
  457. * @type Object.<String, Element>
  458. */
  459. var _go_styleSheets = {};
  460. /**
  461. * Storage for notification id for possibility to identify a notification popup.
  462. *
  463. * @private
  464. * @inner
  465. *
  466. * @type int
  467. */
  468. var _gi_notificationId = 0;
  469. /**
  470. * If the Greasemonkey functions <code>GM_setVaule</code>, <code>GM_getValue</code>, <code>GM_deleteValue</code> and <code>GM_listValues</code> can be used.
  471. *
  472. * @private
  473. * @inner
  474. *
  475. * @type boolean
  476. */
  477. var _gb_canUseGmStorage = !(typeof GM_getValue == 'undefined' /*|| (typeof GM_getValue.toString == 'function' && GM_getValue.toString().indexOf('not supported') > -1)*/)
  478. && !(typeof GM_setValue == 'undefined' /*|| (typeof GM_setValue.toString == 'function' && GM_setValue.toString().indexOf('not supported') > -1)*/)
  479. && !(typeof GM_deleteValue == 'undefined' /*|| (typeof GM_deleteValue.toString == 'function' && GM_deleteValue.toString().indexOf('not supported') > -1)*/)
  480. && !(typeof GM_listValues == 'undefined' /*|| (typeof GM_listValues.toString == 'function' && GM_listValues.toString().indexOf('not supported') > -1)*/);
  481. /**
  482. * If the Greasemonkey function <code>GM_getResourceText</code> can be used.
  483. *
  484. * @private
  485. * @inner
  486. *
  487. * @type boolean
  488. */
  489. var _gb_canUseGmRessource = !(typeof GM_getResourceText == 'undefined' /*|| (typeof GM_getResourceText.toString == 'function' && GM_getResourceText.toString().indexOf('not supported') > -1)*/);
  490. /**
  491. * If the Greasemonkey function <code>GM_xmlhttpRequest</code> can be used.
  492. *
  493. * @private
  494. * @inner
  495. *
  496. * @type boolean
  497. */
  498. var _gb_canUseGmXhr = !(typeof GM_xmlhttpRequest == 'undefined' /*|| (typeof GM_xmlhttpRequest.toString == 'function' && GM_xmlhttpRequest.toString().indexOf('not supported') > -1)*/);
  499. /**
  500. * If the local storage can be used.
  501. *
  502. * @private
  503. * @inner
  504. *
  505. * @type boolean
  506. */
  507. var _gb_canUseLocalStorage = !!go_self.win.localStorage;
  508. /**
  509. * The domain for storing cookies.
  510. *
  511. * @private
  512. * @inner
  513. *
  514. * @type String
  515. */
  516. var _gs_cookieDomain = 'ikariam.gameforge.com';
  517. /**
  518. * Create the header for a notification panel.
  519. *
  520. * @private
  521. * @inner
  522. *
  523. * @param {int} ii_id
  524. * The id of the notification.
  525. * @param {Element} ie_panel
  526. * The panel of the notification.
  527. * @param {String} is_headerText
  528. * The text for the header.
  529. * @param {IkariamCore~myGM~ConfirmAbortWithoutInput} if_closePanel
  530. * The function to close the notification panel.
  531. */
  532. var _createNotificationPanelHeader = function(ii_id, ie_panel, is_headerText, if_closePanel) {
  533. var le_wrapper = go_self.myGM.addElement('div', ie_panel, { 'id': 'notificationPanelHeader' + ii_id, 'class': 'notificationPanelHeader' }, true);
  534. var le_left = go_self.myGM.addElement('div', le_wrapper, { 'id': 'notificationPanelHeaderL' + ii_id, 'class': 'notificationPanelHeaderL' }, true);
  535. var le_right = go_self.myGM.addElement('div', le_left, { 'id': 'notificationPanelHeaderR' + ii_id, 'class': 'notificationPanelHeaderR' }, true);
  536. var le_center = go_self.myGM.addElement('div', le_right, { 'id': 'notificationPanelHeaderM' + ii_id, 'class': 'notificationPanelHeaderM', 'innerHTML': is_headerText }, true);
  537. go_self.myGM.addElement('div', le_center, { 'id': 'notificationPanelClose' + ii_id, 'class': 'notificationPanelClose', 'click': if_closePanel }, true);
  538. };
  539. /**
  540. * Create the header for a notification.
  541. *
  542. * @private
  543. * @inner
  544. *
  545. * @param {int} ii_id
  546. * The id of the notification.
  547. * @param {Element} ie_panel
  548. * The panel of the notification.
  549. * @param {IkariamCore~myGM~NotificationBodyOptions} io_options
  550. * Options for the body.<br>
  551. * @param {IkariamCore~myGM~NotificationBodyText} io_texts
  552. * The texts for the body.
  553. */
  554. var _createNotificationPanelBody = function(ii_id, ie_panel, io_options, io_texts) {
  555. var le_wrapper = go_self.myGM.addElement('div', ie_panel, { 'id': 'notificationPanelBody' + ii_id, 'class': 'notificationPanelBody' }, true);
  556. var le_left = go_self.myGM.addElement('div', le_wrapper, { 'id': 'notificationPanelBodyL' + ii_id, 'class': 'notificationPanelBodyL' }, true);
  557. var le_right = go_self.myGM.addElement('div', le_left, { 'id': 'notificationPanelBodyR' + ii_id, 'class': 'notificationPanelBodyR' }, true);
  558. var le_center = go_self.myGM.addElement('div', le_right, { 'id': 'notificationPanelBodyM' + ii_id, 'class': 'notificationPanelBodyM' }, true);
  559. var ls_bodyType = 'div';
  560. var re_body;
  561. var lo_generalOptions = {};
  562. if(io_options.textarea === true) {
  563. ls_bodyType = 'textarea';
  564. if(io_options.readonly === true)
  565. lo_generalOptions['readonly'] = 'readonly';
  566. if(io_options.autoselect === true)
  567. lo_generalOptions['focus'] = function() { this.select(); };
  568. }
  569. if(!!io_texts.body === true) {
  570. re_body = go_self.myGM.addElement(ls_bodyType, le_center, go_self.myGM.merge({
  571. 'id': 'notificationPanelBodyMContent' + ii_id,
  572. 'class': 'notificationPanelBodyMContent',
  573. 'innerHTML': io_texts.body
  574. }, lo_generalOptions), true);
  575. } else {
  576. go_self.myGM.addElement('div', le_center, { 'id': 'notificationPanelBodyMTop' + ii_id, 'class': 'notificationPanelBodyMTop', 'innerHTML': io_texts.top }, true);
  577. re_body = go_self.myGM.addElement(ls_bodyType, le_center, go_self.myGM.merge({
  578. 'id': 'notificationPanelBodyMBottom' + ii_id,
  579. 'class': 'notificationPanelBodyMBottom',
  580. 'innerHTML': io_texts.bottom
  581. }, lo_generalOptions), true);
  582. }
  583. if(io_options.textarea !== true)
  584. re_body = null;
  585. go_self.myGM.addElement('div', le_center, { 'id': 'notificationPanelBodyPlaceholder' + ii_id, 'class': 'notificationPanelBodyPlaceholder' }, true);
  586. return re_body;
  587. };
  588. /**
  589. * Create the footer for a notification panel.
  590. *
  591. * @private
  592. * @inner
  593. *
  594. * @param {int} ii_id
  595. * The id of the notification.
  596. * @param {Element} ie_panel
  597. * The panel of the notification.
  598. */
  599. var _createNotificationPanelFooter = function(ii_id, ie_panel) {
  600. var le_wrapper = go_self.myGM.addElement('div', ie_panel, { 'id': 'notificationPanelFooter' + ii_id, 'class': 'notificationPanelFooter' }, true);
  601. var le_left = go_self.myGM.addElement('div', le_wrapper, { 'id': 'notificationPanelFooterL' + ii_id, 'class': 'notificationPanelFooterL' }, true);
  602. var le_right = go_self.myGM.addElement('div', le_left, { 'id': 'notificationPanelFooterR' + ii_id, 'class': 'notificationPanelFooterR' }, true);
  603. go_self.myGM.addElement('div', le_right, {
  604. 'id': 'notificationPanelFooterM' + ii_id,
  605. 'class': 'notificationPanelFooterM',
  606. 'innerHTML': go_script.name + ' v' + go_script.version
  607. }, true);
  608. };
  609. /**
  610. * Create the buttons for a notification panel.
  611. *
  612. * @private
  613. * @inner
  614. *
  615. * @param {int} ii_id
  616. * The id of the notification.
  617. * @param {Element} ie_panel
  618. * The panel of the notification.
  619. * @param {Element} ie_body
  620. * The body of the notification.
  621. * @param {IkariamCore~myGM~NotificationButtonsText} io_texts
  622. * The texts for the buttons.
  623. * @param {IkariamCore~myGM~NotificationButtonCallbacks} io_callbacks
  624. * The callbacks for the buttons.
  625. */
  626. var _createNotificationPanelButtons = function(ii_id, ie_panel, ie_body, io_texts, io_callbacks) {
  627. var le_wrapper = go_self.myGM.addElement('div', ie_panel, {
  628. 'id': 'notificationPanelButtonWrapper' + ii_id,
  629. 'class': 'notificationPanelButtonWrapper'
  630. }, true);
  631. var lf_confirm;
  632. if(!!io_callbacks.confirm === true)
  633. lf_confirm = function() { io_callbacks.close(); io_callbacks.confirm(ie_body); };
  634. else
  635. lf_confirm = io_callbacks.close;
  636. go_self.myGM.addElement('input', le_wrapper, {
  637. 'id': 'notificationPanelConfirm' + ii_id,
  638. 'classes': ['notificationPanelButton', 'notificationPanelButtonConfirm'],
  639. 'type': 'button',
  640. 'value': io_texts.confirm ? io_texts.confirm : go_self.Language.$('default.notification.button.confirm'),
  641. 'click': lf_confirm
  642. }, true);
  643. if(!!io_callbacks.abort === true) {
  644. go_self.myGM.addElement('input', le_wrapper, {
  645. 'id': 'notificationPanelAbort' + ii_id,
  646. 'classes': ['notificationPanelButton', 'notificationPanelButtonAbort'],
  647. 'type': 'button',
  648. 'value': io_texts.abort ? io_texts.abort : go_self.Language.$('default.notification.button.abort'),
  649. 'click': function() { io_callbacks.close(); io_callbacks.abort(ie_body); }
  650. }, true);
  651. }
  652. };
  653. /*-------------------------------------------*
  654. * Public variables, functions and settings. *
  655. *-------------------------------------------*/
  656. /**
  657. * Script identifying prefix.
  658. *
  659. * @instance
  660. * @readonly
  661. * @name prefix
  662. * @memberof IkariamCore~myGM
  663. *
  664. * @type {String}
  665. */
  666. Object.defineProperty(this, 'prefix', { get: function() {
  667. return 'script' + go_script.id;
  668. } });
  669. /**
  670. * Returns if the script is already executed on this page.
  671. *
  672. * @instance
  673. * @readonly
  674. * @name alreadyExecuted
  675. * @memberof IkariamCore~myGM
  676. *
  677. * @type {boolean}
  678. */
  679. Object.defineProperty(this, 'alreadyExecuted', { get: function() {
  680. if(this.$('#' + this.prefix + 'alreadyExecuted'))
  681. return true;
  682. // Add the hint, that the script was already executed.
  683. this.addElement('input', this.$('body'), { 'id': 'alreadyExecuted', 'type': 'hidden' });
  684. return false;
  685. } });
  686. /**
  687. * Store a value specified by a key.
  688. *
  689. * @instance
  690. *
  691. * @param {String} is_key
  692. * The key of the value.
  693. * @param {*} im_value
  694. * The value to store.
  695. */
  696. this.setValue = function(is_key, im_value) {
  697. // Stringify the value to store also arrays.
  698. var ls_toStore = JSON.stringify(im_value);
  699. // If the use of the default GM_setValue ist possible, use it.
  700. if(_gb_canUseGmStorage) {
  701. GM_setValue(is_key, ls_toStore);
  702. // Otherwise use the local storage if possible.
  703. } else if(_gb_canUseLocalStorage) {
  704. go_self.win.localStorage.setItem(this.prefix + is_key, ls_toStore);
  705. // Otherwise use cookies.
  706. } else {
  707. var ls_data = escape(this.prefix + is_key) + '=' + escape(ls_toStore);
  708. var ls_expire = 'expires=' + (new Date(2020, 0, 1, 0, 0, 0, 0)).toGMTString();
  709. var ls_path = 'path=/';
  710. var ls_domain = 'domain=' + _gs_cookieDomain;
  711. go_self.win.document.cookie = ls_data + ';' + ls_expire + ';' + ls_path + ';' + ls_domain;
  712. }
  713. };
  714. /**
  715. * Get a value and return it.
  716. *
  717. * @instance
  718. *
  719. * @param {String} is_key
  720. * The key of the value.
  721. * @param {*} im_defaultValue
  722. * The value which is set if the value is not set.
  723. *
  724. * @return {*}
  725. * The stored value.
  726. */
  727. this.getValue = function(is_key, im_defaultValue) {
  728. // Put the default value to JSON.
  729. var rs_value = JSON.stringify(im_defaultValue);
  730. // If the use of the default GM_getValue ist possible, use it.
  731. if(_gb_canUseGmStorage) {
  732. rs_value = GM_getValue(is_key, rs_value);
  733. // Otherwise use the local storage if possible.
  734. } else if(_gb_canUseLocalStorage) {
  735. var ls_value = go_self.win.localStorage.getItem(this.prefix + is_key);
  736. if(ls_value) {
  737. rs_value = ls_value;
  738. }
  739. // Otherwise use cookies.
  740. } else {
  741. var la_allCookies = document.cookie.split("; ");
  742. for(var i = 0; i < la_allCookies.length; i++) {
  743. var la_oneCookie = la_allCookies[i].split("=");
  744. if(la_oneCookie[0] == escape(this.prefix + is_key)) {
  745. rs_value = unescape(la_oneCookie[1]);
  746. break;
  747. }
  748. }
  749. }
  750. // Return the value (parsed for the correct return type).
  751. return JSON.parse(rs_value);
  752. };
  753. /**
  754. * Delete a value specified by a key.
  755. *
  756. * @instance
  757. *
  758. * @param {String} is_key
  759. * The key of the value.
  760. */
  761. this.deleteValue = function(is_key) {
  762. // If the use of the default GM_deleteValue is possible, use it.
  763. if(_gb_canUseGmStorage) {
  764. GM_deleteValue(is_key);
  765. // Otherwise use the local storage if possible.
  766. } else if(_gb_canUseLocalStorage) {
  767. go_self.win.localStorage.removeItem(this.prefix + is_key);
  768. // Otherwise use cookies.
  769. } else {
  770. var ls_data = escape(this.prefix + is_key) + '=';
  771. var ls_expire = 'expires=' + (new Date(2000, 0, 1, 0, 0, 0, 0)).toGMTString();
  772. var ls_path = 'path=/';
  773. var ls_domain = 'domain=' + _gs_cookieDomain;
  774. go_self.win.document.cookie = ls_data + ';' + ls_expire + ';' + ls_path + ';' + ls_domain;
  775. }
  776. };
  777. /**
  778. * Returns an array with the keys of all values stored by the script.
  779. *
  780. * @instance
  781. *
  782. * @return {Array.<String>}
  783. * The array with all keys.
  784. */
  785. this.listValues = function() {
  786. // Create an array for the storage of the values keys.
  787. var ra_key = new Array();
  788. // If the use of the default GM_listValues ist possible, use it.
  789. if(_gb_canUseGmStorage) {
  790. ra_key = GM_listValues();
  791. // Otherwise use the local storage if possible.
  792. } else if(_gb_canUseLocalStorage) {
  793. for(var i = 0; i < go_self.win.localStorage.length; i++) {
  794. var ls_keyName = go_self.win.localStorage.key(i);
  795. if(ls_keyName.indexOf(this.prefix) != -1) {
  796. ra_key.push(ls_keyName.replace(this.prefix, ''));
  797. }
  798. }
  799. // Otherwise use cookies.
  800. } else {
  801. var la_allCookies = document.cookie.split("; ");
  802. for(var i = 0; i < la_allCookies.length; i++) {
  803. var ls_keyName = unescape(la_allCookies[i].split("=")[0]);
  804. if(ls_keyName.indexOf(this.prefix) != -1) {
  805. ra_key.push(ls_keyName.replace(this.prefix, ''));
  806. }
  807. }
  808. }
  809. // Return all keys.
  810. return ra_key;
  811. };
  812. /**
  813. * Adds a style element to the head of the page and return it.
  814. *
  815. * @instance
  816. *
  817. * @param {String} is_styleRules
  818. * The style rules to be set.
  819. * @param {?String} [is_id=stylesheet not stored]
  820. * An id for the style set, to have the possibility to delete it.
  821. * @param {?boolean} [ib_overwrite=false]
  822. * If a style with id should overwrite an existing style.
  823. *
  824. * @return {boolean}
  825. * If the stylesheet was stored with the id.
  826. */
  827. this.addStyle = function(is_styleRules, is_id, ib_overwrite) {
  828. var rb_storedWithId = false;
  829. if(ib_overwrite && ib_overwrite == true)
  830. this.removeStyle(is_id);
  831. if(!is_id || (is_id && !_go_styleSheets[is_id])) {
  832. var le_style = this.addElement('style', document.head, { 'type': 'text/css', 'innerHTML': is_styleRules });
  833. if(is_id) {
  834. _go_styleSheets[is_id] = le_style;
  835. rb_storedWithId = true;
  836. }
  837. }
  838. return rb_storedWithId;
  839. };
  840. /**
  841. * Removes a style element set by the script.
  842. *
  843. * @instance
  844. *
  845. * @param {String} is_id
  846. * The id of the stylesheet to delete.
  847. *
  848. * @return {boolean}
  849. * If the stylesheet could be deleted.
  850. */
  851. this.removeStyle = function(is_id) {
  852. var rb_removed = false;
  853. if(is_id && _go_styleSheets[is_id]) {
  854. document.head.removeChild(_go_styleSheets[is_id]);
  855. delete _go_styleSheets[is_id];
  856. rb_removed = true;
  857. }
  858. return rb_removed;
  859. };
  860. /**
  861. * Makes a cross-site XMLHttpRequest.
  862. *
  863. * @instance
  864. *
  865. * @param {Object} io_args
  866. * The arguments the request needs. (specified here: {@link http://wiki.greasespot.net/GM_xmlhttpRequest GM_xmlhttpRequest})
  867. *
  868. * @return {(String|boolean)}
  869. * The response text or a hint indicating an error.
  870. */
  871. this.xhr = function(io_args) {
  872. var rm_responseText;
  873. // Check if all required data is given.
  874. if(!io_args.method || !io_args.url || !io_args.onload) {
  875. return false;
  876. }
  877. // If the use of the default GM_xmlhttpRequest ist possible, use it.
  878. if(_gb_canUseGmXhr) {
  879. var lm_response = GM_xmlhttpRequest(io_args);
  880. rm_responseText = lm_response.responseText;
  881. // Otherwise show a hint for the missing possibility to fetch the data.
  882. } else {
  883. // Storage if the link fetches metadata from userscripts.org
  884. var lb_isJSON = (io_args.url.search(/\.json$/i) != -1);
  885. // Otherwise if it is JSON.
  886. if(lb_isJSON) {
  887. io_args.onload('{ "is_error": true }');
  888. rm_responseText = '{ "is_error": true }';
  889. // Otherwise.
  890. } else {
  891. rm_responseText = false;
  892. }
  893. }
  894. // Return the responseText.
  895. return rm_responseText;
  896. };
  897. /**
  898. * Returns the content of a resource parsed with JSON.parse.
  899. *
  900. * @instance
  901. *
  902. * @param {String} is_name
  903. * The name of the resource to parse.
  904. * @param {String} is_xhrUrl
  905. * The resource to fetch the resource file from if the use of <code>GM_getResourceText</code> is not possible.
  906. *
  907. * @return {Object}
  908. * The parsed resource.
  909. */
  910. this.getResourceParsed = function(is_name, is_xhrUrl) {
  911. var ls_responseText = '';
  912. // Function for safer parsing.
  913. var lf_safeParse = function(is_key, im_value) {
  914. // If the value is a function, return just the string, so it is not executable.
  915. if(typeof im_value === 'function' || Object.prototype.toString.apply(im_value) === '[object function]') {
  916. return im_value.toString();
  917. }
  918.  
  919. return im_value;
  920. };
  921. // If the use of the default GM_getRessourceText ist possible, use it.
  922. if(_gb_canUseGmRessource) {
  923. ls_responseText = GM_getResourceText(is_name);
  924. // Otherwise perform a xmlHttpRequest.
  925. } else {
  926. ls_responseText = this.xhr({
  927. method: 'GET',
  928. url: is_xhrUrl,
  929. headers: { 'User-agent': navigator.userAgent, 'Accept': 'text/html' },
  930. synchronous: true,
  931. onload: function(im_response) { return false; }
  932. });
  933. }
  934. return JSON.parse(ls_responseText, lf_safeParse);
  935. };
  936. /**
  937. * Gets the first matching child element by a (css) query and returns it.
  938. *
  939. * @instance
  940. *
  941. * @param {String} is_query
  942. * The query for the element.
  943. * @param {?Element} [ie_parent=document]
  944. * The parent element.
  945. *
  946. * @return {Element}
  947. * The element.
  948. */
  949. this.$ = function(is_query, ie_parent) {
  950. return this.$$(is_query, ie_parent)[0];
  951. };
  952. /**
  953. * Gets all matching child elements by a (css) query and returns them.
  954. *
  955. * @instance
  956. *
  957. * @param {String} is_query
  958. * The query for the elements.
  959. * @param {?Element} [ie_parent=document]
  960. * The parent element.
  961. *
  962. * @return {Array.<Element>}
  963. * The elements.
  964. */
  965. this.$$ = function(is_query, ie_parent) {
  966. var le_parent = ie_parent || document;
  967. // Return the elements as array, not as element list.
  968. return Array.prototype.slice.call(le_parent.querySelectorAll(is_query));
  969. };
  970. /**
  971. * Returns the value of the selected option of a select field.
  972. *
  973. * @param {String} is_id
  974. * The last part of the id of the element.
  975. * @param {?boolean} [ib_hasNoPrefix=false]
  976. * If the id has no prefix.
  977. * @param {?boolean} [ib_addNoSelect=false]
  978. * If there should be no "Select" at the end of the id.
  979. *
  980. * @return {String}
  981. * The value.
  982. */
  983. this.getSelectValue = function(is_id, ib_hasNoPrefix, ib_addNoSelect) {
  984. var le_select = this.$('#' + (ib_hasNoPrefix ? '' : this.prefix) + is_id + (ib_addNoSelect ? '' : 'Select'));
  985. return le_select.options[le_select.selectedIndex].value;
  986. };
  987. /**
  988. * Returns the value of the selected radio button of a radio button group.
  989. *
  990. * @param {String} is_name
  991. * The last part of the name of the element.
  992. * @param {?boolean} [ib_hasNoPrefix=false]
  993. * If the name has no prefix.
  994. *
  995. * @return {String}
  996. * The value.
  997. */
  998. this.getRadioValue = function(is_name, ib_hasNoPrefix) {
  999. var le_radios = this.$$('input[name="' + (ib_hasNoPrefix ? '' : this.prefix) + is_name + '"]');
  1000. var rs_value = '';
  1001. for(var i = 0; i < le_radios.length; i++) {
  1002. if(le_radios[i].checked) {
  1003. rs_value = le_radios[i].value;
  1004. break;
  1005. }
  1006. }
  1007. return rs_value;
  1008. };
  1009. /**
  1010. * Creates a new element and adds it to a parent.
  1011. *
  1012. * @instance
  1013. *
  1014. * @param {String} is_type
  1015. * The type of the new element.
  1016. * @param {Element} ie_parent
  1017. * The parent of the new element.
  1018. * @param {?IkariamCore~myGM~NewElementOptions} [io_options]
  1019. * Options for the new element like id, class(es), style, type etc.
  1020. * @param {?(boolean|IkariamCore~myGM~HasPrefix)} [im_hasPrefix={id: true, classes: false}]
  1021. * If a prefix should be used.
  1022. * @param {?Element} [ie_nextSibling=end of parent]
  1023. * The next sibling of the element.
  1024. *
  1025. * @return {Element}
  1026. * The new element.
  1027. */
  1028. this.addElement = function(is_type, ie_parent, io_options, im_hasPrefix, ie_nextSibling) {
  1029. var re_newElement = document.createElement(is_type);
  1030. if(!!io_options === true) {
  1031. this.forEach(io_options, function(is_key, im_property) {
  1032. var ls_prefix = '';
  1033. if('id' === is_key && !(im_hasPrefix === false || (im_hasPrefix && im_hasPrefix.id === false))) {
  1034. ls_prefix = go_self.myGM.prefix;
  1035. }
  1036. if('class' === is_key) {
  1037. if(im_hasPrefix === true || (im_hasPrefix && im_hasPrefix.classes === true))
  1038. ls_prefix = go_self.myGM.prefix;
  1039. if(im_property !== '')
  1040. re_newElement.classList.add(ls_prefix + im_property);
  1041. return;
  1042. }
  1043. if('classes' === is_key) {
  1044. if(im_hasPrefix === true || (im_hasPrefix && im_hasPrefix.classes === true))
  1045. ls_prefix = go_self.myGM.prefix;
  1046. for(var i = 0; i < im_property.length; i++) {
  1047. if(im_property[i] != '')
  1048. re_newElement.classList.add(ls_prefix + im_property[i]);
  1049. }
  1050. return;
  1051. }
  1052. if('style' === is_key) {
  1053. for(var i = 0; i < im_property.length; i++) {
  1054. re_newElement.style[im_property[i][0]] = im_property[i][1];
  1055. }
  1056. return;
  1057. }
  1058. if('click' === is_key || 'focus' === is_key) {
  1059. re_newElement.addEventListener(is_key, im_property, false);
  1060. return;
  1061. }
  1062. if('innerHTML' === is_key) {
  1063. re_newElement[is_key] = im_property;
  1064. return;
  1065. }
  1066. re_newElement.setAttribute(is_key, ls_prefix + im_property);
  1067. });
  1068. }
  1069. ie_parent.insertBefore(re_newElement, ie_nextSibling);
  1070. return re_newElement;
  1071. };
  1072. /**
  1073. * Removes an element from its parent.
  1074. *
  1075. * @instance
  1076. *
  1077. * @param {(Element|Array.<Element>)} im_toRemove
  1078. * The element to remove.
  1079. */
  1080. this.removeElement = function(im_toRemove) {
  1081. if(!!im_toRemove === false)
  1082. return;
  1083. var la_toRemove = im_toRemove;
  1084. if(Array.isArray(im_toRemove) === false)
  1085. la_toRemove = [im_toRemove];
  1086. for(var i = 0; i < la_toRemove.length; i++) {
  1087. la_toRemove[i].parentNode.removeChild(la_toRemove[i]);
  1088. }
  1089. };
  1090. /**
  1091. * Creates new checkboxes and adds it to a parent.
  1092. *
  1093. * @instance
  1094. *
  1095. * @param {Element} ie_parent
  1096. * The parent of the new checkboxes.
  1097. * @param {Array.<IkariamCore~myGM~NewCheckboxData>} ia_cbData
  1098. * The data of the checkboxes.
  1099. */
  1100. this.addCheckboxes = function(ie_parent, ia_cbData) {
  1101. for(var i = 0; i < ia_cbData.length; i++) {
  1102. var le_wrapper = this.addElement('div', ie_parent, { 'class': 'cbWrapper' });
  1103. var la_options = {
  1104. 'id': ia_cbData[i]['id'] + 'Cb',
  1105. 'class': 'checkbox',
  1106. 'type': 'checkbox',
  1107. 'title': ia_cbData[i]['label']
  1108. };
  1109. if(!!ia_cbData[i]['checked'] === true)
  1110. la_options['checked'] = 'checked';
  1111. this.addElement('input', le_wrapper, la_options);
  1112. }
  1113. };
  1114. /**
  1115. * Creates a new radio button group and adds it to a parent table.
  1116. *
  1117. * @instance
  1118. *
  1119. * @param {Element} ie_parentTable
  1120. * The parent table of the new select field.
  1121. * @param {String} is_name
  1122. * The last part of the name of the radio button group.
  1123. * @param {(String|int)} im_checked
  1124. * The value of the selected option.
  1125. * @param {Array.<IkariamCore~myGM~ValueAndLabel>} ia_options
  1126. * An array with the names an values of the options.
  1127. * @param {String} is_labelText
  1128. * The text of the select label.
  1129. */
  1130. this.addRadios = function(ie_parentTable, is_name, im_checked, ia_options, is_labelText) {
  1131. var le_row = this.addElement('tr', ie_parentTable);
  1132. var le_labelCell = this.addElement('td', le_row, { 'class': 'vertical_top' });
  1133. var le_radioCell = this.addElement('td', le_row, { 'class': 'left' });
  1134. this.addElement('span', le_labelCell, { 'innerHTML': is_labelText });
  1135. for(var i = 0; i < ia_options.length; i++) {
  1136. var le_wrapper = this.addElement('div', le_radioCell, { 'class': 'radioWrapper' });
  1137. this.addElement('input', le_wrapper, {
  1138. 'class': 'checkbox',
  1139. 'type': 'radio',
  1140. 'name': this.prefix + is_name,
  1141. 'value': ia_options[i].value,
  1142. 'title': ia_options[i].label,
  1143. 'checked': ia_options[i].value == im_checked ? 'checked' : ''
  1144. });
  1145. }
  1146. };
  1147. /**
  1148. * Creates a new select field and adds it to a parent table.
  1149. *
  1150. * @instance
  1151. *
  1152. * @param {Element} ie_parentTable
  1153. * The parent table of the new select field.
  1154. * @param {String} is_id
  1155. * The last part of the id of the select field.
  1156. * @param {(String|int)} im_selected
  1157. * The value of the selected option.
  1158. * @param {Array.<IkariamCore~myGM~ValueAndLabel>} ia_options
  1159. * An array with the names an values of the options.
  1160. * @param {String} is_labelText
  1161. * The text of the select label.
  1162. */
  1163. this.addSelect = function(ie_parentTable, is_id, im_selected, ia_options, is_labelText) {
  1164. var le_row = this.addElement('tr', ie_parentTable);
  1165. var le_labelCell = this.addElement('td', le_row);
  1166. var le_selectCell = this.addElement('td', le_row, { 'class': 'left' });
  1167. this.addElement('span', le_labelCell, { 'innerHTML': is_labelText });
  1168. var le_wrapper = this.addElement('div', le_selectCell, {
  1169. 'id': is_id + 'SelectContainer',
  1170. 'classes': ['select_container', 'size175'],
  1171. 'style': [['position', 'relative']]
  1172. });
  1173. var le_select = this.addElement('select', le_wrapper, { 'id': is_id + 'Select', 'class': 'dropdown' });
  1174. for(var i = 0; i < ia_options.length; i++) {
  1175. var le_option = this.addElement('option', le_select, { 'value': ia_options[i].value, 'innerHTML': ia_options[i].label });
  1176. if(le_option.value == im_selected) {
  1177. le_option.selected = 'selected';
  1178. }
  1179. }
  1180. };
  1181. /**
  1182. * Creates a button and adds it to a parent.
  1183. *
  1184. * @instance
  1185. *
  1186. * @param {Element} ie_parent
  1187. * The parent element.
  1188. * @param {String} is_value
  1189. * The value of the button.
  1190. * @param {function} if_callback
  1191. * A callback which should be called when the user clicks on the button.
  1192. * @param {boolean} [ib_parentIsWrapper=false]
  1193. * If the element provided as parent is also the button wrapper.
  1194. */
  1195. this.addButton = function(ie_parent, is_value, if_callback, ib_parentIsWrapper) {
  1196. var le_buttonWrapper = ie_parent;
  1197. if(ib_parentIsWrapper !== true)
  1198. le_buttonWrapper = this.addElement('div', ie_parent, { 'class': 'centerButton' });
  1199.  
  1200. var re_button = this.addElement('input', le_buttonWrapper, {
  1201. 'class': 'button',
  1202. 'type': 'button',
  1203. 'value': is_value,
  1204. 'click': if_callback
  1205. });
  1206. return re_button;
  1207. };
  1208. /**
  1209. * Shows a notification to the user. You can either create a notification field or an input / output field. If the
  1210. * field should be an input field, the field is given to the callbacks as parameter. The abort button is only shown
  1211. * if the abort callback is set. It is also possible to have two body parts or just one body part. This functionality
  1212. * is set by the notification text.
  1213. *
  1214. * @instance
  1215. *
  1216. * @param {IkariamCore~myGM~NotificationText} im_text
  1217. * The notification texts.
  1218. * @param {?IkariamCore~myGM~NotificationCallbacks} [im_callback]
  1219. * The callbacks for confirm and abort.
  1220. * @param {IkariamCore~myGM~NotificationBodyOptions} [io_options]
  1221. * Options for the body.
  1222. *
  1223. * @return {int}
  1224. * The notification id.
  1225. */
  1226. this.notification = function(im_text, im_callback, io_options) {
  1227. _gi_notificationId++;
  1228. var lo_options = io_options || {};
  1229. // Set a local notification id to be able to have more than 1 notification panels.
  1230. var ri_notificationId = _gi_notificationId;
  1231. // Function to close the notification panel.
  1232. var lf_closePanel = function() {
  1233. go_self.myGM.removeElement([
  1234. go_self.myGM.$('#' + go_self.myGM.prefix + 'notificationBackground' + ri_notificationId),
  1235. go_self.myGM.$('#' + go_self.myGM.prefix + 'notificationPanelContainer' + ri_notificationId)
  1236. ]);
  1237. };
  1238. // Create the background and the container.
  1239. this.addElement('div', document.body, { 'id': 'notificationBackground' + ri_notificationId, 'class': 'notificationBackground' }, true);
  1240. var le_panelContainer = this.addElement('div', document.body, { 'id': 'notificationPanelContainer' + ri_notificationId, 'class': 'notificationPanelContainer' }, true);
  1241. var le_panel = this.addElement('div', le_panelContainer, { 'id': 'notificationPanel' + ri_notificationId, 'class': 'notificationPanel' }, true);
  1242. // Create the notification panel header.
  1243. var ls_headerText = im_text.header ? im_text.header : go_self.Language.$('default.notification.header');
  1244. _createNotificationPanelHeader(ri_notificationId, le_panel, ls_headerText, lf_closePanel);
  1245. // Create the notification panel body.
  1246. var lo_bodyTexts = {
  1247. body: im_text.body,
  1248. top: im_text.bodyTop ? im_text.bodyTop : '',
  1249. bottom: im_text.bodyBottom ? im_text.bodyBottom : ''
  1250. };
  1251. var le_body = _createNotificationPanelBody(ri_notificationId, le_panel, lo_options, lo_bodyTexts);
  1252. // Create the notification panel footer.
  1253. _createNotificationPanelFooter(ri_notificationId, le_panel);
  1254. // Create the buttons.
  1255. var lo_buttonTexts = {
  1256. confirm: im_text.confirm ? im_text.confirm : null,
  1257. abort: im_text.abort ? im_text.abort : null
  1258. };
  1259. var lo_buttonCallbacks = {
  1260. close: lf_closePanel,
  1261. confirm: im_callback && im_callback.confirm ? im_callback.confirm : null,
  1262. abort: im_callback && im_callback.abort ? im_callback.abort : null
  1263. };
  1264. _createNotificationPanelButtons(ri_notificationId, le_panel, le_body, lo_buttonTexts, lo_buttonCallbacks);
  1265. return ri_notificationId;
  1266. };
  1267. /**
  1268. * Toogle the show / hide Button image and title.
  1269. *
  1270. * @instance
  1271. *
  1272. * @param {Element} ie_button
  1273. * The button to toggle.
  1274. */
  1275. this.toggleShowHideButton = function(ie_button) {
  1276. ie_button.classList.toggle('minimizeImg');
  1277. ie_button.classList.toggle('maximizeImg');
  1278. ie_button.title = (ie_button.title == go_self.Language.$('general.fold')) ? go_self.Language.$('general.expand') : go_self.Language.$('general.fold');
  1279. };
  1280. /**
  1281. * Runs a callback on every property of an object which is not in the prototype.
  1282. *
  1283. * @instance
  1284. *
  1285. * @param {Object} io_object
  1286. * The Object where forEach should be used.
  1287. * @param {IkariamCore~myGM~ForEachCallback} if_callback
  1288. * The callback which should be called.
  1289. */
  1290. this.forEach = function(io_object, if_callback) {
  1291. for(var ls_key in io_object) {
  1292. if(Object.prototype.hasOwnProperty.call(io_object, ls_key)) {
  1293. if_callback(ls_key, io_object[ls_key]);
  1294. }
  1295. }
  1296. };
  1297. /**
  1298. * Merges objects.
  1299. *
  1300. * @instance
  1301. *
  1302. * @param {...Object} arguments
  1303. * All objects to merge into each other.
  1304. *
  1305. * @return {Object}
  1306. * The merged object.
  1307. */
  1308. this.merge = function() {
  1309. var ro_merged = {};
  1310. for(var i = 0; i < arguments.length; i++) {
  1311. go_self.myGM.forEach(arguments[i], function(is_key, im_value) {
  1312. if(typeof ro_merged[is_key] === 'object' && typeof im_value === 'object')
  1313. go_self.myGM.merge(ro_merged[is_key], im_value);
  1314. else
  1315. ro_merged[is_key] = im_value;
  1316. });
  1317. }
  1318. return ro_merged;
  1319. };
  1320. /*--------------------*
  1321. * Set some settings. *
  1322. *--------------------*/
  1323. // Set the notification style.
  1324. this.addStyle(
  1325. "." + this.prefix + "notificationBackground { z-index: 1000000000000; position: fixed; visibility: visible; top: 0px; left: 0px; width: 100%; height: 100%; padding: 0; background-color: #000; opacity: .7; } \
  1326. ." + this.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; } \
  1327. ." + this.prefix + "notificationPanel { position: relative; top: 0px; left: 0px; background-color: transparent; border: 0 none; overflow: hidden; } \
  1328. ." + this.prefix + "notificationPanelHeader { height: 39px; background: none repeat scroll 0 0 transparent; font-weight: bold; line-height: 2; white-space: nowrap; } \
  1329. ." + this.prefix + "notificationPanelHeaderL { height: 39px; background-image: url('skin/layout/notes_top_left.png'); background-position: left top; background-repeat: no-repeat; } \
  1330. ." + this.prefix + "notificationPanelHeaderR { height: 39px; background-image: url('skin/layout/notes_top_right.png'); background-position: right top; background-repeat: no-repeat; } \
  1331. ." + this.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; } \
  1332. ." + this.prefix + "notificationPanelBody { max-height: 311px; height: 100%; background: none repeat scroll 0 0 transparent; } \
  1333. ." + this.prefix + "notificationPanelBodyL { height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; } \
  1334. ." + this.prefix + "notificationPanelBodyR { height: 100%; background-image: url('skin/layout/notes_right.png'); background-position: right top; background-repeat: repeat-y; } \
  1335. ." + this.prefix + "notificationPanelBodyM { height: 100%; background-color: #F7E7C5; background-image: none; margin: 0 6px; padding: 0 10px; font-size: 14px; } \
  1336. ." + this.prefix + "notificationPanelBodyMTop { max-height: 100px; line-height: 2; } \
  1337. ." + this.prefix + "notificationPanelBodyMTop b { line-height: 3.5; font-size:110%; } \
  1338. ." + this.prefix + "notificationPanelBodyM a { color: #811709; font-weight: bold; } \
  1339. ." + this.prefix + "notificationPanelBodyM h2 { font-weight: bold; } \
  1340. ." + this.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; } \
  1341. ." + this.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; } \
  1342. textarea." + this.prefix + "notificationPanelBodyMContent { height: 270px; width: 445px; resize: none; } \
  1343. textarea." + this.prefix + "notificationPanelBodyMBottom { height: 170px; width: 445px; resize: none; } \
  1344. ." + this.prefix + "notificationPanelBodyPlaceholder { height: 20px; } \
  1345. ." + this.prefix + "notificationPanelFooter { height: 20px; background: none repeat scroll 0 0 transparent; } \
  1346. ." + this.prefix + "notificationPanelFooterL { height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; border: 0 none; } \
  1347. ." + this.prefix + "notificationPanelFooterR { height: 21px; background-image: url('skin/layout/notes_br.png'); background-position: right bottom; background-repeat: no-repeat; } \
  1348. ." + this.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%; } \
  1349. ." + this.prefix + "notificationPanelClose { cursor: pointer; position: absolute; top: 12px; right: 8px; width: 17px; height: 17px; background-image: url('skin/layout/notes_close.png'); } \
  1350. ." + this.prefix + "notificationPanelButtonWrapper { bottom: -4px; position: absolute; margin: 10px auto; width: 100%; text-align: center; } \
  1351. ." + this.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; } \
  1352. ." + this.prefix + "notificationPanelButton:hover { color: #B3713F; } \
  1353. ." + this.prefix + "notificationPanelButton:active { border-color: #5D4C2F #C9A584 #C9A584 #5D4C2F; border-style: double; border-width: 3px; padding: 3px 10px 1px; } \
  1354. ." + this.prefix + "notificationPanelButtonConfirm { } \
  1355. ." + this.prefix + "notificationPanelButtonAbort { }",
  1356. 'notification', true
  1357. );
  1358. // Add the buttons for toggle buttons used styles.
  1359. this.addStyle(
  1360. ".minimizeImg, .maximizeImg { background: url('skin/interface/window_control_sprite.png') no-repeat scroll 0 0 transparent; cursor: pointer; display: block; height: 18px; width: 18px; } \
  1361. .minimizeImg { background-position: -144px 0; } \
  1362. .minimizeImg:hover { background-position: -144px -19px; } \
  1363. .maximizeImg { background-position: -126px 0; } \
  1364. .maximizeImg:hover { background-position: -126px -19px; }",
  1365. 'toggleShowHideButton', true
  1366. );
  1367. // Fixe the tab scroll to prevent the scroll left / right button to have a widht more than 40px.
  1368. this.addStyle(
  1369. "#container .tabmenu .tab { width: unset; } \
  1370. #container .tabmenu .tab.tabPrevPage, #container .tabmenu .tab.tabNextPage { width: 40px; }",
  1371. 'fixTabScroll', true
  1372. );
  1373. /*---------------------------------------------------------------------*
  1374. * Types for documentation purposes (e.g. callback functions, objects) *
  1375. *---------------------------------------------------------------------*/
  1376. /**
  1377. * Confirm / abort callback for a notification with an input text field.
  1378. *
  1379. * @callback IkariamCore~myGM~ConfirmAbortWithInput
  1380. *
  1381. * @param {Element} textarea
  1382. * The textarea element which contains the user input.
  1383. */
  1384. /**
  1385. * Confirm / abort callback for a notification without an input text field.
  1386. *
  1387. * @callback IkariamCore~myGM~ConfirmAbortWithoutInput
  1388. */
  1389. /**
  1390. * Callbacks to confirm / abort a notification.
  1391. *
  1392. * @typedef IkariamCore~myGM~NotificationCallbacks
  1393. *
  1394. * @property {?(IkariamCore~myGM~ConfirmAbortWithInput|IkariamCore~myGM~ConfirmAbortWithoutInput)} [confirm=close panel] - The callback for the confirm button.
  1395. * @property {?(IkariamCore~myGM~ConfirmAbortWithInput|IkariamCore~myGM~ConfirmAbortWithoutInput)} [abort=close panel] - The callback for the abort button.
  1396. */
  1397. /**
  1398. * Callbacks for the buttons of the notification panel.
  1399. *
  1400. * @typedef IkariamCore~myGM~NotificationButtonCallbacks
  1401. *
  1402. * @private
  1403. * @inner
  1404. *
  1405. * @mixes IkariamCore~myGM~NotificationCallbacks
  1406. *
  1407. * @property {IkariamCore~myGM~ConfirmAbortWithoutInput} close - The callback to close the panel.
  1408. */
  1409. /**
  1410. * Options for the notification body.
  1411. *
  1412. * @typedef {Object} IkariamCore~myGM~NotificationBodyOptions
  1413. *
  1414. * @property {boolean} [textarea=false] - If the body should be a textarea.
  1415. * @property {boolean} [readonly=false] - If the textarea is readonly. Only used if textarea=true.
  1416. * @property {boolean} [autofocus=false] - If the textarea content is autoselected on click. Only used if textarea=true.
  1417. */
  1418. /**
  1419. * Text for the notification body. Either body or top AND bottom must be specified.
  1420. *
  1421. * @typedef {Object} IkariamCore~myGM~NotificationBodyText
  1422. *
  1423. * @property {?String} [body] - Text if there is only one text in the body.
  1424. * @property {?String} [top] - Upper text if the body is splitted.
  1425. * @property {?String} [bottom] - Lower text if the body is splitted.
  1426. */
  1427. /**
  1428. * Text for the notification panel buttons.
  1429. *
  1430. * @typedef {Object} IkariamCore~myGM~NotificationButtonsText
  1431. *
  1432. * @property {?String} [confirm=default.notification.button.confirm] - Text for the confirm button.
  1433. * @property {?String} [abort=default.notification.button.abort] - Text for the abort button.
  1434. */
  1435. /**
  1436. * Texts for the notification panel.
  1437. *
  1438. * @typedef IkariamCore~myGM~NotificationText
  1439. *
  1440. * @mixes IkariamCore~myGM~NotificationBodyText
  1441. * @mixes IkariamCore~myGM~NotificationButtonsText
  1442. *
  1443. * @property {?String} [header=default.notification.header] - The notification panel header.
  1444. */
  1445. /**
  1446. * CSS Styles for an element.<br>
  1447. * Structure of the array: <code>[ [ &lt;styleName&gt;, &lt;styleValue&gt; ] ]</code>
  1448. *
  1449. * @typedef {Array.<Array.<String>>} IkariamCore~myGM~CssStyles
  1450. */
  1451. /**
  1452. * Options for a new element.
  1453. *
  1454. * @typedef {Object} IkariamCore~myGM~NewElementOptions
  1455. *
  1456. * @property {String} [id] - The id of the element.
  1457. * @property {String} [class] - A single class of the element.
  1458. * @property {String[]} [classes] - Multiple classes for the element.
  1459. * @property {IkariamCore~myGM~CssStyles} [style] - Styles for the element.
  1460. * @property {function} [click] - An onclick callback.
  1461. * @property {function} [focus] - An onfocus callback.
  1462. * @property {String} [*] - All other element options.
  1463. */
  1464. /**
  1465. * Define if id and classes should have a prefix.
  1466. *
  1467. * @typedef {Object} IkariamCore~myGM~HasPrefix
  1468. *
  1469. * @property {boolean} [id=true] - If the id should have a prefix.
  1470. * @property {boolean} [classes=false] - If the classes should have a prefix.
  1471. */
  1472. /**
  1473. * Data for a new checkbox.
  1474. *
  1475. * @typedef {Object} IkariamCore~myGM~NewCheckboxData
  1476. *
  1477. * @property {String} id - The id of the checkbox.
  1478. * @property {String} label - The label of the checkbox.
  1479. * @property {boolean} checked - If the checkbox is checked.
  1480. */
  1481.  
  1482. /**
  1483. * Data set consisting of value and label.
  1484. *
  1485. * @typedef {Object} IkariamCore~myGM~ValueAndLabel
  1486. *
  1487. * @property {(String|int)} value - The value of the data set.
  1488. * @property {String} label - The label of the data set.
  1489. */
  1490. /**
  1491. * Callback for a forEach iteration on an object.
  1492. *
  1493. * @callback IkariamCore~myGM~ForEachCallback
  1494. *
  1495. * @param {String} propertyKey
  1496. * The key of the property of the object.
  1497. * @param {*} propertyValue
  1498. * The value of the property.
  1499. */
  1500. }
  1501. /**
  1502. * myGM for cross-browser compatibility of the GM_* functions. (use myGM.* instead of GM_*)<br>
  1503. * Also there are general used functions stored.
  1504. *
  1505. * @instance
  1506. *
  1507. * @type IkariamCore~myGM
  1508. */
  1509. this.myGM = new myGM();
  1510. this.con.timeStamp('IkariamCore.myGM created');
  1511. /**
  1512. * Instantiate a new set of localization functions.
  1513. *
  1514. * @inner
  1515. *
  1516. * @class
  1517. * @classdesc Functions for localizing the script.
  1518. */
  1519. function Language() {
  1520. /*--------------------------------------------*
  1521. * Private variables, functions and settings. *
  1522. *--------------------------------------------*/
  1523. /**
  1524. * Default Ikariam language code for this server.
  1525. *
  1526. * @private
  1527. * @inner
  1528. *
  1529. * @default en
  1530. *
  1531. * @type String
  1532. */
  1533. var _gs_ikaCode = (function() {
  1534. var uri = top.location.host.match(/^s[0-9]+-([a-zA-Z]+)\.ikariam\.gameforge\.com$/)[1];
  1535. return !!uri === true ? uri : 'en';
  1536. })();
  1537. /**
  1538. * Default language code - code of language registered as default.
  1539. *
  1540. * @private
  1541. * @inner
  1542. *
  1543. * @default en
  1544. *
  1545. * @type String
  1546. */
  1547. var _gs_defaultCode = 'en';
  1548. /**
  1549. * Used language code.
  1550. *
  1551. * @private
  1552. * @inner
  1553. *
  1554. * @default en
  1555. *
  1556. * @type String
  1557. */
  1558. var _gs_usedCode = _gs_defaultCode;
  1559. /**
  1560. * Used language texts. Used if a translation is requested.
  1561. *
  1562. * @private
  1563. * @inner
  1564. *
  1565. * @type json
  1566. */
  1567. var _go_usedText = {};
  1568. /**
  1569. * Default language text. To be used if the used language is not available.
  1570. *
  1571. * @private
  1572. * @inner
  1573. *
  1574. * @type json
  1575. */
  1576. var _go_defaultText = {};
  1577. /**
  1578. * All languages which are registered with their storage type (resource, in-script-object).
  1579. *
  1580. * @private
  1581. * @inner
  1582. *
  1583. * @type Object.<String, Array.<IkariamCore~Language~LanguageSettings>>
  1584. */
  1585. var _go_registeredLangs = {};
  1586. /**
  1587. * "Translation" of all possible language codes to the corresponding language.
  1588. *
  1589. * @TODO Check that only those codes and languages are available that are used by Ikariam itself.
  1590. *
  1591. * @private
  1592. * @inner
  1593. *
  1594. * @type Object.<String, String>
  1595. */
  1596. var _go_codeTranslation = {
  1597. ae: 'Arabic', ar: 'Spanish', ba: 'Bosnian', bg: 'Bulgarian', br: 'Portuguese', by: 'Russian',
  1598. cl: 'Spanish', cn: 'Chinese', co: 'Spanish', cz: 'Czech', de: 'German', dk: 'Danish',
  1599. ee: 'Estonian', en: 'English', es: 'Spanish', fi: 'Finish', fr: 'French', gr: 'Greek',
  1600. hk: 'Chinese', hr: 'Bosnian', hu: 'Hungarian', id: 'Indonesian', il: 'Hebrew', it: 'Italian',
  1601. kr: 'Korean', lt: 'Lithuanian', lv: 'Latvian', mx: 'Spanish', nl: 'Dutch', no: 'Norwegian',
  1602. pe: 'Spanish', ph: 'Filipino', pk: 'Urdu', pl: 'Polish', pt: 'Portuguese', ro: 'Romanian',
  1603. rs: 'Serbian', ru: 'Russian', se: 'Swedish', si: 'Slovene', sk: 'Slovak', tr: 'Turkish',
  1604. tw: 'Chinese', ua: 'Ukranian', us: 'English', ve: 'Spanish', vn: 'Vietnamese', yu: 'Bosnian'
  1605. };
  1606. /**
  1607. * Set the default language text for the script.
  1608. *
  1609. * @private
  1610. * @inner
  1611. */
  1612. var _setDefaultText = function() {
  1613. var lo_merged = _mergeTexts(_gs_defaultCode);
  1614. if(lo_merged.is_empty === true || lo_merged.not_set === true)
  1615. _go_defaultText = {};
  1616. else
  1617. _go_defaultText = lo_merged;
  1618. };
  1619. /**
  1620. * Set the chosen language text for the script.
  1621. *
  1622. * @private
  1623. * @inner
  1624. *
  1625. * @param {String} is_languageCode
  1626. * The code of the last selected language.
  1627. */
  1628. var _setText = function(is_languageCode) {
  1629. if(is_languageCode === _gs_defaultCode)
  1630. _setDefaultText();
  1631. if(!!_go_registeredLangs[_gs_ikaCode] === true)
  1632. _gs_usedCode = _gs_ikaCode;
  1633. if(is_languageCode === _gs_usedCode) {
  1634. var lo_merged = _mergeTexts(is_languageCode);
  1635. if(lo_merged.is_empty === true || lo_merged.not_set === true)
  1636. _go_usedText = _go_defaultText;
  1637. else
  1638. _go_usedText = lo_merged;
  1639. }
  1640. };
  1641. /**
  1642. * Merges the texts for a given language.
  1643. *
  1644. * @private
  1645. * @inner
  1646. *
  1647. * @param {String} is_languageCode
  1648. * The code of the language to merge.
  1649. *
  1650. * @return {json}
  1651. * The merged texts.
  1652. */
  1653. var _mergeTexts = function(is_languageCode) {
  1654. var ro_merged = {};
  1655. if(!!_go_registeredLangs[is_languageCode] === true) {
  1656. var lb_initial = true;
  1657. _go_registeredLangs[is_languageCode].forEach(function(io_element) {
  1658. if(io_element.type === 'resource') {
  1659. var lo_resource = go_self.myGM.getResourceParsed(io_element.data.name, io_element.data.url);
  1660. if(!lo_resource.is_error === true) {
  1661. ro_merged = go_self.myGM.merge(ro_merged, lo_resource);
  1662. lb_initial = false;
  1663. }
  1664. } else if(io_element.type === 'json') {
  1665. ro_merged = go_self.myGM.merge(ro_merged, io_element.data);
  1666. lb_initial = false;
  1667. }
  1668. });
  1669. if(lb_initial === true)
  1670. ro_merged = { is_empty: true };
  1671. } else {
  1672. ro_merged = { not_set: true };
  1673. }
  1674. return ro_merged;
  1675. };
  1676. /**
  1677. * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
  1678. * they are replaced with the content of the array at this index.
  1679. *
  1680. * @private
  1681. * @inner
  1682. *
  1683. * @param {String} is_name
  1684. * The name of the placeholder.
  1685. * @param {?Array.<*>} [ia_variables]
  1686. * An array containing variables to replace the placeholders in the language string.
  1687. * @param {?boolean} [ib_useDefault=false]
  1688. * If the default language should be used instead of the selected.
  1689. *
  1690. * @return {String}
  1691. * The text.
  1692. */
  1693. var _getText = function(is_name, ia_variables, ib_useDefault) {
  1694. // Set the text to the placeholder.
  1695. var rs_text = is_name;
  1696. // Split the placeholder.
  1697. var la_parts = is_name.split('.');
  1698. if(!!la_parts === true) {
  1699. // Set ls_text to the "next level".
  1700. var ls_text = _go_usedText ? _go_usedText[la_parts[0]] : null;
  1701. if(ib_useDefault === true)
  1702. ls_text = _go_defaultText ? _go_defaultText[la_parts[0]] : null;
  1703. // Loop over all parts.
  1704. for(var i = 1; i < la_parts.length; i++) {
  1705. // If the "next level" exists, set txt to it.
  1706. if(ls_text && typeof ls_text[la_parts[i]] != 'undefined') {
  1707. ls_text = ls_text[la_parts[i]];
  1708. } else {
  1709. ls_text = rs_text;
  1710. break;
  1711. }
  1712. }
  1713. // If the text type is not an object, a function or undefined.
  1714. if(typeof ls_text != 'object' && typeof ls_text != 'function' && typeof ls_text != 'undefined')
  1715. rs_text = ls_text + '';
  1716. if(!!ia_variables === true && Array.isArray(ia_variables) === true) {
  1717. for(var i = 0; i < ia_variables.length; i++) {
  1718. var lr_regex = new RegExp('%\\$' + (i + 1), 'g');
  1719. rs_text = rs_text.replace(lr_regex, ia_variables[i] + '');
  1720. }
  1721. }
  1722. }
  1723. if(ib_useDefault === true) {
  1724. return rs_text;
  1725. }
  1726. if(rs_text == is_name || rs_text == "") {
  1727. go_self.con.info('Language.getText: No translation available for "' + is_name + '" in language ' + this.usedLanguageCode);
  1728. rs_text = _getText(is_name, ia_variables, true);
  1729. }
  1730. return rs_text;
  1731. };
  1732. /*-------------------------------------------*
  1733. * Public variables, functions and settings. *
  1734. *-------------------------------------------*/
  1735. /**
  1736. * Code of the used language.
  1737. *
  1738. * @instance
  1739. * @readonly
  1740. * @name usedLanguageCode
  1741. * @memberof IkariamCore~Language
  1742. *
  1743. * @type {String}
  1744. */
  1745. Object.defineProperty(this, 'usedLanguageCode', { get: function() {
  1746. return _gs_usedCode;
  1747. } });
  1748. /**
  1749. * Name of the used language.
  1750. *
  1751. * @instance
  1752. * @readonly
  1753. * @name usedLanguageName
  1754. * @memberof IkariamCore~Language
  1755. *
  1756. * @type {String}
  1757. */
  1758. Object.defineProperty(this, 'usedLanguageName', { get: function() {
  1759. return _go_codeTranslation[_gs_usedCode];
  1760. } });
  1761. /**
  1762. * Set the default language.
  1763. *
  1764. * @instance
  1765. *
  1766. * @param {String} is_languageCode
  1767. * The code of the default language.
  1768. */
  1769. this.setDefaultLanguage = function(is_languageCode) {
  1770. _gs_defaultCode = is_languageCode;
  1771. _setDefaultText();
  1772. };
  1773. /**
  1774. * Registers a new language without resource usage.
  1775. *
  1776. * @instance
  1777. *
  1778. * @param {String} is_languageCode
  1779. * The code of the language.
  1780. * @param {json} io_json
  1781. * JSON with the language data.
  1782. */
  1783. this.addLanguageText = function(is_languageCode, io_json) {
  1784. if(!_go_registeredLangs[is_languageCode] === true)
  1785. _go_registeredLangs[is_languageCode] = [];
  1786. _go_registeredLangs[is_languageCode].push({
  1787. type: 'json',
  1788. data: io_json
  1789. });
  1790. _setText(is_languageCode);
  1791. };
  1792. /**
  1793. * Registers a new language resource.
  1794. *
  1795. * @instance
  1796. *
  1797. * @param {String} is_languageCode
  1798. * Code of the language.
  1799. * @param {String} is_resourceName
  1800. * Name of the resource.
  1801. * @param {String} is_resourceURL
  1802. * URL, if resources are not supported.
  1803. */
  1804. this.registerLanguageResource = function(is_languageCode, is_resourceName, is_resourceURL) {
  1805. if(!_go_registeredLangs[is_languageCode] === true)
  1806. _go_registeredLangs[is_languageCode] = [];
  1807. _go_registeredLangs[is_languageCode].push({
  1808. type: 'resource',
  1809. data: { name: is_resourceName, url: is_resourceURL }
  1810. });
  1811. _setText(is_languageCode);
  1812. };
  1813. /**
  1814. * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
  1815. * they are replaced with the content of the array at this index.
  1816. *
  1817. * @instance
  1818. *
  1819. * @param {String} is_name
  1820. * The name of the placeholder.
  1821. * @param {?Array.<*>} [ia_variables]
  1822. * An array containing variables to replace the placeholders in the language string.
  1823. *
  1824. * @return {String}
  1825. * The text.
  1826. */
  1827. this.getText = function(is_name, ia_variables) {
  1828. return _getText(is_name, ia_variables);
  1829. };
  1830. /**
  1831. * Synonymous function for {@link IkariamCore~Language#getText}.<br>
  1832. *
  1833. * @instance
  1834. *
  1835. * @see IkariamCore~Language#getText
  1836. *
  1837. * @param {String} is_name
  1838. * The name of the placeholder.
  1839. * @param {?Array.<*>} [ia_variables]
  1840. * An array containing variables to replace the placeholders in the language string.
  1841. *
  1842. * @return {String}
  1843. * The text.
  1844. */
  1845. this.$ = function(is_name, ia_variables) {
  1846. return this.getText(is_name, ia_variables);
  1847. };
  1848. /*----------------------------------------------*
  1849. * Register the language resources for the core *
  1850. *----------------------------------------------*/
  1851. this.addLanguageText('en', {"core": {"update": {"notPossible": {"header":"No Update possible","text":"It is not possible to check for updates for %$1. Please check manually for Updates for the script. The actual installed version is %$2. This message will appear again in four weeks."},"possible": {"header":"Update available","text":"There is an update for %$1 available.<br>At the moment there is version %$2 installed. The newest version is %$3.","history":"Version History","type": {"feature":"Feature(s)","change":"Change(s)","bugfix":"Bugfix(es)","language":"Language(s)","core":"Ikariam Core","other":"Other"},"button": {"install":"Install","hide":"Hide"}},"noNewExists": {"header":"No Update available","text":"There is no new version for %$1 available. The newest version %$2 is installed."}},"notification": {"header":"Script notification","button": {"confirm":"OK","abort":"Abort"}},"optionPanel": {"save":"Save settings!","section": {"update": {"title":"Update","label": {"interval": {"description": "Interval to search for updates:","option": {"never":"Never","hour":"1 hour","hour12":"12 hours","day":"1 day","day3":"3 days","week":"1 week","week2":"2 weeks","week4":"4 weeks"}},"notifyLevel": {"description": "Notify on new script versions up to this level:","option": {"all":"All Versions","major":"Major (x)","minor":"Minor (x.x)","patch":"Patch (x.x.x)"}},"manual":"Search for updates for \"%$1\"!"}},"optionPanelOptions": {"title":"Option Panel","label": {"import":"Import the script options","export":"Export the script options","reset":"Reset the script options","importNotification": {"header":"Import","explanation":"Put your JSON to import in the area below and click OK. The options will be imported then. Please ensure that no character is missing. Otherwise the import will not work."},"exportNotification": {"header":"Export","explanation":"Please copy the JSON below. You can import it on any computer to get the options there. Please ensure that no character is missing. Otherwise the import will not work."},"importError": {"header":"Import error!","explanation":"There was an error while importing the options. It seems that the JSON is broken. Please validate it (e.g. with <a href=\"http://jsonlint.com/\" target=\"_blank\">JSONLint</a>)."},"resetNotification": {"header":"Reset options","explanation":"Are you sure to reset all script options to their default value?"}}}}}},"general": {"successful":"Your order has been carried out.","error":"There was an error in your request.","fold":"Fold","expand":"Expand","ctrl":"Ctrl","alt":"Alt","shift":"Shift","yes":"Yes","no":"No"}});
  1852. this.addLanguageText('en', {"settings": {"kiloSep":",","decSep":".","ltr":true}});
  1853. var la_language = ['de', 'gr', 'it', 'lv', 'ru'];
  1854. for(var i = 0; i < la_language.length; i++) {
  1855. this.registerLanguageResource(la_language[i], 'core_' + la_language[i], 'http://resources.ikascripts.de/IkariamCore/2.0/core_' + la_language[i] + '.json');
  1856. this.registerLanguageResource(la_language[i], 'core_' + la_language[i] + '_settings', 'http://resources.ikascripts.de/IkariamCore/2.0/core_' + la_language[i] + '_settings.json');
  1857. }
  1858. /*---------------------------------------------------------------------*
  1859. * Types for documentation purposes (e.g. callback functions, objects) *
  1860. *---------------------------------------------------------------------*/
  1861. /**
  1862. * Storage for language settings.
  1863. *
  1864. * @callback IkariamCore~Language~LanguageSettings
  1865. *
  1866. * @private
  1867. * @inner
  1868. *
  1869. * @param {String} type
  1870. * The type of the language resources. Currently supported: resource, json
  1871. * @param {({name: String, url: String}|json)} data
  1872. * The data required to fetch the translations of this language.
  1873. */
  1874. }
  1875. /**
  1876. * Functions for localization of the script.
  1877. *
  1878. * @instance
  1879. *
  1880. * @type IkariamCore~Language
  1881. */
  1882. this.Language = new Language();
  1883. this.con.timeStamp('IkariamCore.Language created');
  1884. /**
  1885. * Instantiate a new set of Ikariam specific functions.
  1886. *
  1887. * @inner
  1888. *
  1889. * @class
  1890. * @classdesc Ikariam specific functions.
  1891. */
  1892. function Ikariam() {
  1893. /*-------------------------------------------*
  1894. * Public variables, functions and settings. *
  1895. *-------------------------------------------*/
  1896. /**
  1897. * Name of the shown view (world, island, town).
  1898. *
  1899. * @instance
  1900. * @readonly
  1901. * @name view
  1902. * @memberof IkariamCore~Ikariam
  1903. *
  1904. * @type {String}
  1905. */
  1906. Object.defineProperty(this, 'view', { get: function() {
  1907. var ls_viewId = go_self.myGM.$('body').id;
  1908. if(ls_viewId == 'worldmap_iso')
  1909. return 'world';
  1910. if(ls_viewId == 'island')
  1911. return 'island';
  1912. if(ls_viewId == 'city')
  1913. return 'town';
  1914. return '';
  1915. } });
  1916. /**
  1917. * All possible view names.
  1918. *
  1919. * @instance
  1920. * @readonly
  1921. * @name viewNames
  1922. * @memberof IkariamCore~Ikariam
  1923. *
  1924. * @type {Array.<String>}
  1925. */
  1926. Object.defineProperty(this, 'viewNames', { get: function() {
  1927. return ['world', 'island', 'town'];
  1928. } });
  1929. /**
  1930. * All possible resource names.
  1931. *
  1932. * @instance
  1933. * @readonly
  1934. * @name resourceNames
  1935. * @memberof IkariamCore~Ikariam
  1936. *
  1937. * @type {Array.<String>}
  1938. */
  1939. Object.defineProperty(this, 'resourceNames', { get: function() {
  1940. return ['wood', 'wine', 'marble', 'glass', 'sulfur'];
  1941. } });
  1942. /**
  1943. * Code consisting of server id and country code.<br>
  1944. * Structure: <code>&lt;country-code&gt;_&lt;server-id&gt;</code>
  1945. *
  1946. * @instance
  1947. * @readonly
  1948. * @name serverCode
  1949. * @memberof IkariamCore~Ikariam
  1950. *
  1951. * @type {String}
  1952. */
  1953. Object.defineProperty(this, 'serverCode', { get: function() {
  1954. var la_code = top.location.host.match(/^s([0-9]+)-([a-zA-Z]+)\.ikariam\.gameforge\.com$/);
  1955. if(!!la_code)
  1956. return la_code[2] + '_' + la_code[1];
  1957. return 'undefined';
  1958. } });
  1959. /**
  1960. * Parses a string number to an int value.
  1961. *
  1962. * @instance
  1963. *
  1964. * @param {String} is_text
  1965. * The number to format.
  1966. *
  1967. * @return {int}
  1968. * The formatted value.
  1969. */
  1970. this.getInt = function(is_text) {
  1971. var ls_text = is_text + '';
  1972. return parseInt(ls_text.replace(/(\.|,)/g, ''));
  1973. };
  1974. /**
  1975. * Formats a number to the format which is used in Ikariam.
  1976. *
  1977. * @param {int} ii_number
  1978. * The number to format.
  1979. * @param {?(boolean|Object.<String, boolean>)} [im_addColor={ positive: false, negative: true }]
  1980. * If the number should be colored.
  1981. * @param {?boolean} [ib_usePlusSign=false]
  1982. * If a plus sign should be used for positive numbers.
  1983. *
  1984. * @return {String}
  1985. * The formated number.
  1986. */
  1987. this.formatToIkaNumber = function(ii_number, im_addColor, ib_usePlusSign) {
  1988. var rs_text = ii_number + '';
  1989. // Set a seperator every 3 digits from the end.
  1990. rs_text = rs_text.replace(/(\d)(?=(\d{3})+\b)/g, '$1' + go_self.Language.$('settings.kiloSep'));
  1991. if(ii_number < 0 && !(im_addColor == false || (im_addColor && im_addColor.negative == false))) {
  1992. rs_text = '<span class="red bold">' + rs_text + '</span>';
  1993. }
  1994. if(ii_number > 0) {
  1995. rs_text = (ib_usePlusSign ? '+' : '') + rs_text;
  1996. if(!!(im_addColor == true || (im_addColor && im_addColor.positive == true))) {
  1997. rs_text = '<span class="green bold">' + rs_text + '</span>';
  1998. }
  1999. }
  2000. return rs_text;
  2001. };
  2002. /**
  2003. * Shows a hint to the user.
  2004. *
  2005. * @instance
  2006. *
  2007. * @param {String} is_located
  2008. * The location of the hint.<br>
  2009. * Possible values: <code>cityAdvisor</code>, <code>militaryAdvisor</code>, <code>researchAdvisor</code>, <code>diplomacyAdvisor</code>, <code>clickedElement</code>, <code>committedElement</code>
  2010. * @param {String} is_type
  2011. * The type of the hint.<br>
  2012. * Possible values: <code>confirm</code>, <code>error</code>, <code>neutral</code>, <code>followMouse</code>
  2013. * @param {String} is_text
  2014. * The hint text.
  2015. * @param {?String} [is_bindTo=null]
  2016. * The JQuery selector of the element the tooltip should be bound to (only used if location = committedElement).
  2017. * @param {?boolean} [ib_hasAutoWidth=false]
  2018. * If the message has auto width (only used if type = followMouse).
  2019. */
  2020. this.showTooltip = function(is_located, is_type, is_text, is_bindTo, ib_hasAutoWidth) {
  2021. // Get the message location.
  2022. var li_location = -1;
  2023. switch(is_located) {
  2024. case 'cityAdvisor':
  2025. li_location = 1;
  2026. break;
  2027. case 'militaryAdvisor':
  2028. li_location = 2;
  2029. break;
  2030. case 'researchAdvisor':
  2031. li_location = 3;
  2032. break;
  2033. case 'diplomacyAdvisor':
  2034. li_location = 4;
  2035. break;
  2036. case 'clickedElement':
  2037. li_location = 5;
  2038. break;
  2039. case 'committedElement':
  2040. li_location = 6;
  2041. break;
  2042. }
  2043. // Get the message type.
  2044. var li_type = -1;
  2045. switch(is_type) {
  2046. case 'confirm':
  2047. li_type = 10;
  2048. break;
  2049. case 'error':
  2050. li_type = 11;
  2051. break;
  2052. case 'neutral':
  2053. li_type = 12;
  2054. break;
  2055. case 'followMouse':
  2056. li_type = 13;
  2057. break;
  2058. }
  2059. go_self.ika.controller.tooltipController.bindBubbleTip(li_location, li_type, is_text, null, is_bindTo, ib_hasAutoWidth);
  2060. };
  2061. /**
  2062. * Creates new checkboxes in Ikariam style and adds them to a parent.
  2063. *
  2064. * @instance
  2065. *
  2066. * @see IkariamCore~myGM#addCheckboxes
  2067. *
  2068. * @param {Element} ie_parent
  2069. * The parent of the new checkboxes.
  2070. * @param {Array.<IkariamCore~myGM~NewCheckboxData>} ia_cbData
  2071. * An array containing the data of each checkbox.
  2072. */
  2073. this.addCheckboxes = function(ie_parent, ia_cbData) {
  2074. go_self.myGM.addCheckboxes(ie_parent, ia_cbData);
  2075. // Replace the checkboxes for better appearance.
  2076. go_self.ika.controller.replaceCheckboxes();
  2077. };
  2078. /**
  2079. * Creates a new radio button group in ikariam style and adds it to a parent table.
  2080. *
  2081. * @instance
  2082. *
  2083. * @see IkariamCore~myGM#addRadios
  2084. *
  2085. * @param {Element} ie_parentTable
  2086. * The parent table of the new select field.
  2087. * @param {String} is_name
  2088. * The last part of the name of the radio button group.
  2089. * @param {(String|int)} im_checked
  2090. * The value of the selected option.
  2091. * @param {Array.<IkariamCore~myGM~ValueAndLabel>} ia_options
  2092. * An array with the names an values of the options.
  2093. * @param {String} is_labelText
  2094. * The text of the select label.
  2095. */
  2096. this.addRadios = function(ie_parentTable, is_name, im_checked, ia_options, is_labelText) {
  2097. go_self.myGM.addRadios(ie_parentTable, is_name, im_checked, ia_options, is_labelText);
  2098. // Replace the radiobuttons for better appearance.
  2099. go_self.ika.controller.replaceCheckboxes();
  2100. };
  2101. /**
  2102. * Creates a new select field in ikariam style and adds it to a parent table.
  2103. *
  2104. * @instance
  2105. *
  2106. * @see IkariamCore~myGM#addSelect
  2107. *
  2108. * @param {Element} ie_parentTable
  2109. * The parent table of the new select field.
  2110. * @param {String} is_id
  2111. * The last part of the id of the select field.
  2112. * @param {(String|int)} im_selected
  2113. * The value of the selected option.
  2114. * @param {Array.<IkariamCore~myGM~ValueAndLabel>} ia_options
  2115. * An array with the names an values of the options.
  2116. * @param {String} is_labelText
  2117. * The text of the select label.
  2118. */
  2119. this.addSelect = function(ie_parentTable, is_id, im_selected, ia_options, is_labelText) {
  2120. go_self.myGM.addSelect(ie_parentTable, is_id, im_selected, ia_options, is_labelText);
  2121. // Replace the dropdown for better appearance.
  2122. go_self.ika.controller.replaceDropdownMenus();
  2123. };
  2124. }
  2125. /**
  2126. * Ikariam specific functions like converting a number from Ikariam format to int.
  2127. *
  2128. * @instance
  2129. *
  2130. * @type IkariamCore~Ikariam
  2131. */
  2132. this.Ikariam = new Ikariam();
  2133. this.con.timeStamp('IkariamCore.Ikariam created');
  2134. /**
  2135. * Instantiate the handler.
  2136. *
  2137. * @inner
  2138. *
  2139. * @class
  2140. * @classdesc Handler for callbacks for processing DOM modification events.
  2141. */
  2142. function Observer() {
  2143. /*--------------------------------------------*
  2144. * Private variables, functions and settings. *
  2145. *--------------------------------------------*/
  2146. /**
  2147. * Storage for MutationObserver.
  2148. *
  2149. * @private
  2150. * @inner
  2151. *
  2152. * @type MutationObserver
  2153. */
  2154. var _go_MutationObserver = MutationObserver || WebKitMutationObserver;
  2155. /**
  2156. * If the MutationObserver can be used or if an workaround must be used.
  2157. *
  2158. * @private
  2159. * @inner
  2160. *
  2161. * @type boolean
  2162. */
  2163. var _gb_canUseObserver = !!_go_MutationObserver;
  2164. /**
  2165. * List to store the created observers.
  2166. *
  2167. * @private
  2168. * @inner
  2169. *
  2170. * @type Object.<String, MutationObserver>
  2171. */
  2172. var _go_observerList = {};
  2173. /*-------------------------------------------*
  2174. * Public variables, functions and settings. *
  2175. *-------------------------------------------*/
  2176. /**
  2177. * Adds a new observer for DOM modification events. If it is possible use MutationObservers. More about the
  2178. * Mutation observer can be found here: {@link https://developer.mozilla.org/en-US/docs/DOM/MutationObserver Mutation Observer on MDN}.<br>
  2179. * If it's not possible to use a MutationObserver a DOMSubtreeModified or DOMAttrModified event listener is used.
  2180. *
  2181. * @instance
  2182. *
  2183. * @param {String} is_id
  2184. * The id to store the observer.
  2185. * @param {element} ie_target
  2186. * The target to observe.
  2187. * @param {Array.<*>} io_options
  2188. * 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}
  2189. * @param {IkariamCore~Observer~MutationCallback} if_callback
  2190. * The callback for the mutation observer.<br>
  2191. * @param {IkariamCore~Observer~NoMutationCallback} if_noMutationObserverCallback
  2192. * The callback if the use of the mutation observer is not possible and DOMAttrModified / DOMSubtreeModified is used instead.<br>
  2193. */
  2194. this.add = function(is_id, ie_target, io_options, if_callback, if_noMutationObserverCallback) {
  2195. var lo_observer;
  2196. if(!!ie_target) {
  2197. // If the MutationObserver can be used, do so.
  2198. if(_gb_canUseObserver) {
  2199. lo_observer = new _go_MutationObserver(if_callback);
  2200. lo_observer.observe(ie_target, io_options);
  2201. if(!_go_observerList[is_id]) {
  2202. _go_observerList[is_id] = lo_observer;
  2203. } else {
  2204. go_self.con.warn('Observer.add: Id "' + is_id + '" already used for observer, please choose another one!');
  2205. }
  2206. // Otherwise use the event listener.
  2207. } else {
  2208. if(io_options.attributes) {
  2209. ie_target.addEventListener('DOMAttrModified', if_noMutationObserverCallback, false);
  2210. }
  2211. if(io_options.characterData || io_options.childList || io_options.subtree) {
  2212. ie_target.addEventListener('DOMSubtreeModified', if_noMutationObserverCallback, false);
  2213. }
  2214. }
  2215. } else {
  2216. go_self.con.warn('Observer.add: Observer target not defined! id: ' + is_id);
  2217. }
  2218. };
  2219. /**
  2220. * Removes the observer given by the id. If the use of MutationObserver is not possible, this function can not be used.
  2221. *
  2222. * @instance
  2223. *
  2224. * @param {String} is_id
  2225. * The id of the observer to remove.
  2226. */
  2227. this.remove = function(is_id) {
  2228. // If the observer is set.
  2229. if(_gb_canUseObserver && _go_observerList[is_id]) {
  2230. var lo_observer = _go_observerList[is_id];
  2231. lo_observer.disconnect();
  2232. delete _go_observerList[is_id];
  2233. } else if(!_gb_canUseObserver) {
  2234. go_self.con.warn('Observer.remove: It is not possible to use MutationObservers so Observer.remove can not be used.');
  2235. }
  2236. };
  2237. /*---------------------------------------------------------------------*
  2238. * Types for documentation purposes (e.g. callback functions, objects) *
  2239. *---------------------------------------------------------------------*/
  2240. /**
  2241. * The callback for the mutation observer.
  2242. *
  2243. * @callback IkariamCore~Observer~MutationCallback
  2244. *
  2245. * @param {MutationRecord} mutations
  2246. * The mutations which occurred.
  2247. */
  2248. /**
  2249. * The callback if no mutation observer could be used.
  2250. *
  2251. * @callback IkariamCore~Observer~NoMutationCallback
  2252. */
  2253. }
  2254. /**
  2255. * Handler for callbacks after modification of DOM elements.
  2256. *
  2257. * @instance
  2258. *
  2259. * @type IkariamCore~Observer
  2260. */
  2261. this.Observer = new Observer();
  2262. this.con.timeStamp('IkariamCore.Observer created');
  2263. /**
  2264. * Instantiate a new set of refresh functions.
  2265. *
  2266. * @inner
  2267. *
  2268. * @class
  2269. * @classdesc Handles functions that should run on Ikariam popups and after actualizations of the page data.
  2270. */
  2271. function RefreshHandler() {
  2272. /*--------------------------------------------*
  2273. * Private variables, functions and settings. *
  2274. *--------------------------------------------*/
  2275. /**
  2276. * Storage for the actualization callbacks.<br>
  2277. * Architecture:<br>
  2278. * <pre>_go_callbacks = {
  2279. * popupId: {
  2280. * callbackId: callback
  2281. * }
  2282. * }</pre>
  2283. *
  2284. * @private
  2285. * @inner
  2286. *
  2287. * @type Object.<String, Object.<String, function>>
  2288. */
  2289. var _go_callbacks = {};
  2290. /**
  2291. * Handles the call of the callback functions for the actualization.
  2292. *
  2293. * @private
  2294. * @inner
  2295. */
  2296. var _handleActualisation = function() {
  2297. // Run the callbacks for every reload.
  2298. if(_go_callbacks['*']) {
  2299. go_self.myGM.forEach(_go_callbacks['*'], function(is_key, if_callback) {
  2300. if_callback();
  2301. });
  2302. }
  2303. // If the script was already executed on this popup.
  2304. var lb_isAlreadyExecutedPopup = !!go_self.myGM.$('#' + go_self.myGM.prefix + 'alreadyExecutedPopup');
  2305. var le_popup = go_self.myGM.$('.templateView');
  2306. if(le_popup && !lb_isAlreadyExecutedPopup) {
  2307. // Run the callbacks for every popup opening.
  2308. if(_go_callbacks['%']) {
  2309. go_self.myGM.forEach(_go_callbacks['%'], function(is_key, if_callback) {
  2310. if_callback();
  2311. });
  2312. }
  2313. go_self.myGM.addElement('input', go_self.myGM.$('.mainContent', le_popup), { 'id': 'alreadyExecutedPopup', 'type': 'hidden' });
  2314. var ls_popupId = le_popup ? le_popup.id.replace('_c', '') : '';
  2315. if(_go_callbacks[ls_popupId]) {
  2316. go_self.myGM.forEach(_go_callbacks[ls_popupId], function(is_key, if_callback) {
  2317. if_callback();
  2318. });
  2319. }
  2320. }
  2321. };
  2322. /**
  2323. * Callback for MutationObserver for calling the popup handler.
  2324. *
  2325. * @private
  2326. * @inner
  2327. *
  2328. * @param {MutationRecord} la_mutations
  2329. * All recorded mutations.
  2330. */
  2331. var _callback = function(la_mutations) {
  2332. la_mutations.forEach(function(io_mutation) {
  2333. if(io_mutation.target.getAttribute('style').search(/display: none/i) != -1) {
  2334. // Timeout to have access to GM_ funtions.
  2335. setTimeout(_handleActualisation, 0);
  2336. }
  2337. });
  2338. };
  2339. /**
  2340. * Callback for calling the popup handler if the MutationObserver could not be used.
  2341. *
  2342. * @private
  2343. * @inner
  2344. *
  2345. * @param {Event} io_event
  2346. * The called event.
  2347. */
  2348. var _callbackNoMutationObserver = function(io_event) {
  2349. if(io_event.attrChange == MutationEvent.MODIFICATION) {
  2350. if(io_event.attrName.IC.trim() == 'style' && io_event.newValue.search(/display: none/i) != -1) {
  2351. // Timeout to have access to GM_ funtions.
  2352. setTimeout(_handleActualisation, 0);
  2353. }
  2354. }
  2355. };
  2356. /*-------------------------------------------*
  2357. * Public variables, functions and settings. *
  2358. *-------------------------------------------*/
  2359. /**
  2360. * Add a new popup handler.
  2361. *
  2362. * @instance
  2363. *
  2364. * @param {(String|Array.<String>)} im_popupId
  2365. * The id(s) of the popup(s) where the callback should be called (without '_c' at the end).<br>
  2366. * Set to '*' for calling at every actualization, not just popups. Set to '%' for calling on every popup.
  2367. * @param {String} is_callbackId
  2368. * The id of the callback. This must be unique for a popup.
  2369. * @param {function} if_callback
  2370. * The callback which should be called.</code>
  2371. */
  2372. this.add = function(im_popupId, is_callbackId, if_callback) {
  2373. if(Array.isArray(im_popupId) === true) {
  2374. for(var i = 0; i < im_popupId.length; i++) {
  2375. this.add(im_popupId[i], is_callbackId, if_callback);
  2376. }
  2377. return;
  2378. }
  2379. if(!_go_callbacks[im_popupId]) {
  2380. _go_callbacks[im_popupId] = {};
  2381. }
  2382. if(!_go_callbacks[im_popupId][is_callbackId]) {
  2383. _go_callbacks[im_popupId][is_callbackId] = if_callback;
  2384. } else {
  2385. go_self.con.warn('RefreshHandler.add: Id set "' + im_popupId + '|' + is_callbackId + '" already used for observer, please choose another one!');
  2386. }
  2387. };
  2388. /**
  2389. * Removes a popup handler.
  2390. *
  2391. * @instance
  2392. *
  2393. * @param {(String|Array.<String>)} im_popupId
  2394. * The id(s) of the popup(s) where the callback was called (without '_c' at the end).
  2395. * Set to '*' for callbacks on every actualisation, not just popups. Set to '%' for callbacks on every popup.
  2396. * @param {String} is_callbackId
  2397. * The id of the callback. This must be unique for a popup.
  2398. */
  2399. this.remove = function(im_popupId, is_callbackId) {
  2400. if(Array.isArray(im_popupId) === true) {
  2401. for(var i = 0; i < im_popupId.length; i++) {
  2402. this.remove(im_popupId[i], is_callbackId);
  2403. }
  2404. return;
  2405. }
  2406. if(_go_callbacks[im_popupId] && _go_callbacks[im_popupId][is_callbackId]) {
  2407. delete _go_callbacks[im_popupId][is_callbackId];
  2408. }
  2409. };
  2410. /*----------------------------------------------------*
  2411. * Register the observer and handle popups on startup *
  2412. *----------------------------------------------------*/
  2413. // Add the observer for the popups.
  2414. go_self.Observer.add('actualisationHandler', go_self.myGM.$('#loadingPreview'), { attributes: true, attributeFilter: ['style'] }, _callback, _callbackNoMutationObserver);
  2415. // Execute the handler on popups which are shown on startup.
  2416. setTimeout(_handleActualisation, 1000);
  2417. }
  2418. /**
  2419. * Handler for functions that should run on Ikariam popups.
  2420. *
  2421. * @instance
  2422. *
  2423. * @type IkariamCore~RefreshHandler
  2424. */
  2425. this.RefreshHandler = new RefreshHandler();
  2426. this.con.timeStamp('IkariamCore.RefreshHandler created');
  2427. /**
  2428. * Instantiate a new set of options / settings functions.
  2429. *
  2430. * @inner
  2431. *
  2432. * @class
  2433. * @classdesc Handles options the user can set, provides a "panel" for them to change them.
  2434. */
  2435. function Options() {
  2436. /*--------------------------------------------*
  2437. * Private variables, functions and settings. *
  2438. *--------------------------------------------*/
  2439. /**
  2440. * Storage for option wrapper visibility.
  2441. *
  2442. * @private
  2443. * @inner
  2444. *
  2445. * @type Array.<boolean>
  2446. */
  2447. var _go_optionWrapperVisibility = {};
  2448. /**
  2449. * Storage for option wrappers.
  2450. *
  2451. * @private
  2452. * @inner
  2453. *
  2454. * @type Object
  2455. */
  2456. var _go_wrapper = {};
  2457. /**
  2458. * Storage for option wrapper order. (Order in which the wrappers are shown)
  2459. *
  2460. * @private
  2461. * @inner
  2462. *
  2463. * @type Array.<String>
  2464. */
  2465. var _ga_wrapperOrder = new Array();
  2466. /**
  2467. * Storage for the saved options. Gets filled on startup.
  2468. *
  2469. * @private
  2470. * @inner
  2471. *
  2472. * @type Object
  2473. */
  2474. var _go_savedOptions = go_self.myGM.getValue('optionPanel_options', {});
  2475. /**
  2476. * Storage for the options.
  2477. *
  2478. * @private
  2479. * @inner
  2480. *
  2481. * @type Object
  2482. */
  2483. var _go_options = {};
  2484. /**
  2485. * Storage for the id of the next <code>&lt;hr&gt;</code> element to create.
  2486. *
  2487. * @private
  2488. * @inner
  2489. *
  2490. * @type int
  2491. */
  2492. var _gi_lineId = 0;
  2493. /**
  2494. * Add a element to a wrapper. ("generic function")
  2495. *
  2496. * @private
  2497. * @inner
  2498. *
  2499. * @param {String} is_type
  2500. * The type of the element. Used for replacement - only elements with same type can be replaced.
  2501. * @param {String} is_id
  2502. * The id of the element.
  2503. * @param {String} is_wrapperId
  2504. * The id of the wrapper for the element.
  2505. * @param {(String|int)} im_table
  2506. * The id of the table in the wrapper where the element should be added.
  2507. * @param {IkariamCore~Options~CreateCallback} if_create
  2508. * Callback to create the element.
  2509. * @param {IkariamCore~Options~AddElementOptions} io_options
  2510. * Options for the element.
  2511. */
  2512. var _addElement = function(is_type, is_id, is_wrapperId, im_table, if_create, io_options) {
  2513. if(_go_wrapper[is_wrapperId]) {
  2514. if(_go_wrapper[is_wrapperId].elements[is_id] && io_options.replace !== true) {
  2515. go_self.con.warn('Options.addElement: Element with id "' + is_id + '" already defined. Wrapper id: ' + is_wrapperId);
  2516. } else if(io_options.replace === true && _go_wrapper[is_wrapperId].elements[is_id] && _go_wrapper[is_wrapperId].elements[is_id].type === is_type) {
  2517. go_self.con.warn('Options.addElement: Element with id "' + is_id + '" not replaced. ' +
  2518. 'Different type (old: ' + _go_wrapper[is_wrapperId].elements[is_id].type + ', new: ' + is_type + '). Wrapper id: ' + is_wrapperId);
  2519. } else {
  2520. var lo_options = io_options;
  2521. if(lo_options.replace === true && !_go_wrapper[is_wrapperId].elements[is_id]) {
  2522. delete lo_options.replace;
  2523. go_self.con.info('Options.addElement: Element with id "' + is_id + '" not existant. Element was created instead of replaced. Wrapper id: ' + is_wrapperId);
  2524. }
  2525. var lo_newElement = { table: im_table + '', create: if_create, serverSpecific: !!lo_options.serverSpecific };
  2526. if(lo_options.replace === true)
  2527. lo_newElement.serverSpecific = _go_wrapper[is_wrapperId].elements[is_id].serverSpecific;
  2528. var ls_serverCode = lo_newElement.serverSpecific === true ? go_self.Ikariam.serverCode : '';
  2529. if(!!lo_options.createOptions === true)
  2530. lo_newElement.options = lo_options.createOptions;
  2531. if(lo_options.defaultValue !== undefined) {
  2532. lo_newElement.defaultValue = lo_options.defaultValue;
  2533. if(_go_savedOptions[is_wrapperId] && (_go_savedOptions[is_wrapperId][is_id] || _go_savedOptions[is_wrapperId][is_id] === false)) {
  2534. _go_options[is_wrapperId][is_id] = _go_savedOptions[is_wrapperId][is_id];
  2535. if(ls_serverCode.length > 0 && !_go_options[is_wrapperId][is_id][ls_serverCode] && _go_options[is_wrapperId][is_id][ls_serverCode] !== false) {
  2536. _go_options[is_wrapperId][is_id][ls_serverCode] = lo_options.defaultValue;
  2537. }
  2538. } else {
  2539. if(ls_serverCode.length > 0) {
  2540. _go_options[is_wrapperId][is_id] = {};
  2541. _go_options[is_wrapperId][is_id][ls_serverCode] = lo_options.defaultValue;
  2542. } else {
  2543. _go_options[is_wrapperId][is_id] = lo_options.defaultValue;
  2544. }
  2545. }
  2546. }
  2547. if(!!lo_options.saveCallback === true)
  2548. lo_newElement.save = lo_options.saveCallback;
  2549. if(!!lo_options.changeCallback === true) {
  2550. lo_newElement.changeCallback = lo_options.changeCallback;
  2551. // Run the callback also when registering.
  2552. setTimeout(function() {
  2553. var lm_value = _go_options[is_wrapperId][is_id];
  2554. if(ls_serverCode.length > 0)
  2555. lm_value = lm_value[ls_serverCode];
  2556. lo_options.changeCallback(lm_value, lm_value);
  2557. }, 0);
  2558. }
  2559. _go_wrapper[is_wrapperId].elements[is_id] = lo_newElement;
  2560. if(lo_options.replace !== true) {
  2561. _go_wrapper[is_wrapperId].elementOrder.IC.insert(is_id, lo_options.position);
  2562. }
  2563. }
  2564. } else {
  2565. go_self.con.warn('Options.addElement: Wrapper with id "' + is_wrapperId + '" not defined. Element id: ' + is_id);
  2566. }
  2567. };
  2568. /**
  2569. * Save the content of <code>_go_options</code>.
  2570. *
  2571. * @private
  2572. * @inner
  2573. *
  2574. * @param {boolean} showNoSuccessHint
  2575. * If the success hint should not be shown.
  2576. */
  2577. var _saveOptions = function(ib_showNoSuccessHint) {
  2578. _go_savedOptions = _go_options;
  2579. go_self.myGM.setValue('optionPanel_options', _go_options);
  2580. if(!ib_showNoSuccessHint === true) {
  2581. go_self.Ikariam.showTooltip('cityAdvisor', 'confirm', go_self.Language.$('general.successful'));
  2582. }
  2583. };
  2584. /**
  2585. * Store the actual value of each option to <code>_option</code> and call <code>_saveOptions</code>.
  2586. *
  2587. * @private
  2588. * @inner
  2589. */
  2590. var _savePanelOptions = function() {
  2591. // Store the value of each option element.
  2592. go_self.myGM.forEach(_go_wrapper, function(is_wrapperId, io_wrapper) {
  2593. go_self.myGM.forEach(io_wrapper.elements, function(is_elementId, io_element) {
  2594. if(io_element.save) {
  2595. var ls_serverCode = io_element.serverSpecific === true ? go_self.Ikariam.serverCode : '';
  2596. var lm_oldValue = _go_options[is_wrapperId][is_elementId];
  2597. var lm_newValue = io_element.save(is_wrapperId + is_elementId);
  2598. if(ls_serverCode.length > 0) {
  2599. lm_oldValue = lm_oldValue[ls_serverCode];
  2600. _go_options[is_wrapperId][is_elementId][ls_serverCode] = lm_newValue;
  2601. } else {
  2602. _go_options[is_wrapperId][is_elementId] = lm_newValue;
  2603. }
  2604. if(lm_newValue != lm_oldValue && io_element.changeCallback) {
  2605. setTimeout(function() { io_element.changeCallback(lm_newValue, lm_oldValue); }, 0);
  2606. }
  2607. }
  2608. });
  2609. });
  2610. _saveOptions();
  2611. };
  2612. /**
  2613. * Initializes the options tab for the script and adds the scroll function to the tab menu.
  2614. *
  2615. * @private
  2616. * @inner
  2617. *
  2618. * @return {Element}
  2619. * The options tab for the script.
  2620. */
  2621. var _initializeOptionsTab = function() {
  2622. var re_tabScriptOptions = go_self.myGM.$('#tab_options' + go_self.myGM.prefix);
  2623. if(!re_tabScriptOptions) {
  2624. go_self.myGM.addStyle(
  2625. "#tab_options" + go_self.myGM.prefix + " hr { margin: 0; } \
  2626. #tab_options" + go_self.myGM.prefix + " .scriptTextArea { resize: none; width: calc(100% - 2px); height: 75px; } \
  2627. #tab_options" + go_self.myGM.prefix + " .scriptTextField { width: 173px; } \
  2628. #tab_options" + go_self.myGM.prefix + " .cbWrapper { margin: 0 0 0 10px; } \
  2629. #tab_options" + go_self.myGM.prefix + " .radioWrapper:not(:last-child) { margin-bottom: 2px; }",
  2630. 'scriptOptionsTab', true);
  2631. var le_tabmenu = go_self.myGM.$('#tabMenu');
  2632. var le_nextPageLink = go_self.myGM.$('.tabNextPage', le_tabmenu);
  2633. var la_pagerInformation = le_nextPageLink.getAttribute('onclick').match(/switchPage\(([0-9]*), this, ([0-9]*)\)/);
  2634. var li_pageNumber = go_self.Ikariam.getInt(la_pagerInformation[1]);
  2635. var li_offset = go_self.Ikariam.getInt(la_pagerInformation[2]);
  2636. var li_newIndex = go_self.myGM.$$('.tab[index]', le_tabmenu).length + 1;
  2637. var li_newPageNumber = li_newIndex / li_offset;
  2638. if(li_pageNumber < li_newPageNumber)
  2639. le_nextPageLink.classList.remove('invisible');
  2640. var la_tabClasses = ['tab'];
  2641. if(li_pageNumber !== li_newPageNumber)
  2642. la_tabClasses.push('invisible');
  2643. go_self.myGM.addElement('li', le_tabmenu, {
  2644. 'id': 'js_tab_options' + go_self.myGM.prefix,
  2645. 'classes': la_tabClasses,
  2646. 'index': li_newIndex,
  2647. 'innerHTML': '<b class="tab_options' + go_self.myGM.prefix + '">' + go_script.name + '</b>',
  2648. 'onclick': "$('#js_tab_options" + go_self.myGM.prefix + "').removeClass('selected'); switchTab('tab_options" + go_self.myGM.prefix + "');"
  2649. }, false, le_nextPageLink);
  2650. re_tabScriptOptions = go_self.myGM.addElement('div', go_self.myGM.$('#tabMenu').parentNode, {
  2651. 'id': 'tab_options' + go_self.myGM.prefix,
  2652. 'style': [['display', 'none']]
  2653. }, false);
  2654. }
  2655. _go_optionWrapperVisibility = go_self.myGM.getValue('optionPanel_optionWrapperVisibility', _go_optionWrapperVisibility);
  2656. return re_tabScriptOptions;
  2657. };
  2658. /**
  2659. * Add a wrapper for options elements to the script option tab.
  2660. *
  2661. * @private
  2662. * @inner
  2663. *
  2664. * @param {Element} ie_tab
  2665. * The tab to add the wrapper to.
  2666. * @param {String} is_id
  2667. * The id of the wrapper.
  2668. * @param {String} is_headerText
  2669. * The text for the wrapper header.
  2670. *
  2671. * @return {Element}
  2672. * The wrapper.
  2673. */
  2674. var _createOptionsWrapper = function(ie_tab, is_id, is_headerText) {
  2675. /*
  2676. * Function to toggle the visibility of an wrapper.
  2677. */
  2678. var lf_toggle = function() {
  2679. go_self.myGM.toggleShowHideButton(this);
  2680. go_self.myGM.$('.content', this.parentNode.parentNode).classList.toggle('invisible');
  2681. var ls_optionId = this.parentNode.parentNode.id.replace(go_self.myGM.prefix, '');
  2682. _go_optionWrapperVisibility[ls_optionId] = !_go_optionWrapperVisibility[ls_optionId];
  2683. go_self.myGM.setValue('optionPanel_optionWrapperVisibility', _go_optionWrapperVisibility);
  2684. // Adjust the size of the Scrollbar.
  2685. go_self.ika.controller.adjustSizes();
  2686. };
  2687. var lb_showContent = !!_go_optionWrapperVisibility[is_id];
  2688. var le_optionsWrapper = go_self.myGM.addElement('div', ie_tab, {'id': is_id, 'class': 'contentBox01h' });
  2689. var le_optionsHeader = go_self.myGM.addElement('h3', le_optionsWrapper, { 'class': 'header', 'innerHTML': is_headerText });
  2690. go_self.myGM.addElement('div', le_optionsHeader, {
  2691. 'class': lb_showContent ? 'minimizeImg' : 'maximizeImg',
  2692. 'style': [['cssFloat', 'left']],
  2693. 'title': lb_showContent ? go_self.Language.$('general.fold') : go_self.Language.$('general.expand'),
  2694. 'click': lf_toggle
  2695. });
  2696. var re_optionsWrapperContent = go_self.myGM.addElement('div', le_optionsWrapper, { 'classes': lb_showContent ? ['content'] : ['content', 'invisible'] });
  2697. go_self.myGM.addElement('div', le_optionsWrapper, { 'class': 'footer' });
  2698. return re_optionsWrapperContent;
  2699. };
  2700. /**
  2701. * Show the option script tab.
  2702. *
  2703. * @private
  2704. * @inner
  2705. */
  2706. var _showOptionPanel = function() {
  2707. var le_tab = _initializeOptionsTab();
  2708. for(var i = 0; i < _ga_wrapperOrder.length; i++) {
  2709. var ls_wrapperId = _ga_wrapperOrder[i];
  2710. var lo_wrapperOptions = _go_wrapper[ls_wrapperId];
  2711. var le_wrapper = _createOptionsWrapper(le_tab, ls_wrapperId, lo_wrapperOptions.headerText);
  2712. var lo_tables = {};
  2713. for(var j = 0; j < lo_wrapperOptions.elementOrder.length; j++) {
  2714. var ls_elementId = lo_wrapperOptions.elementOrder[j];
  2715. var lo_elementOptions = lo_wrapperOptions.elements[ls_elementId];
  2716. if(!lo_tables[lo_elementOptions.table]) {
  2717. var le_table = go_self.myGM.addElement('table', le_wrapper, { 'classes': ['moduleContent', 'table01'] });
  2718. lo_tables[lo_elementOptions.table] = go_self.myGM.addElement('tbody', le_table);
  2719. }
  2720. var ls_serverCode = lo_elementOptions.serverSpecific === true ? go_self.Ikariam.serverCode : '';
  2721. var lo_options = lo_elementOptions.options ? lo_elementOptions.options : null;
  2722. var lm_value = (_go_options[ls_wrapperId] && (_go_options[ls_wrapperId][ls_elementId] || _go_options[ls_wrapperId][ls_elementId] == false)) ? _go_options[ls_wrapperId][ls_elementId] : null;
  2723. if(ls_serverCode.length > 0)
  2724. lm_value = lm_value[ls_serverCode];
  2725. lo_elementOptions.create(lo_tables[lo_elementOptions.table], ls_wrapperId + ls_elementId, lm_value, lo_options);
  2726. }
  2727. go_self.myGM.addButton(le_wrapper, go_self.Language.$('core.optionPanel.save'), function() { setTimeout(_savePanelOptions, 0); });
  2728. }
  2729. };
  2730. /**
  2731. * Show the notification for exporting the options.
  2732. *
  2733. * @private
  2734. * @inner
  2735. */
  2736. var _exportOptionsShowNotification = function() {
  2737. var ls_options = JSON.stringify(_go_options);
  2738. var lo_notificationText = {
  2739. header: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.exportNotification.header'),
  2740. bodyTop: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.exportNotification.explanation'),
  2741. bodyBottom: ls_options
  2742. };
  2743. go_self.myGM.notification(lo_notificationText, null, { textarea: true, readonly: true, autoselect: true });
  2744. };
  2745. /**
  2746. * Callback for importing the options.
  2747. *
  2748. * @private
  2749. * @inner
  2750. *
  2751. * @param {Element} ie_textarea
  2752. * The textarea with the options string to import.
  2753. */
  2754. var _importOptionsCallback = function(ie_textarea) {
  2755. var ls_options = ie_textarea.value;
  2756. if(ls_options) {
  2757. // Function for safer parsing.
  2758. var lf_safeParse = function(is_key, im_value) {
  2759. if(typeof im_value === 'function' || Object.prototype.toString.apply(im_value) === '[object function]') {
  2760. return im_value.toString();
  2761. }
  2762. return im_value;
  2763. };
  2764. try {
  2765. var lo_parsed = JSON.parse(ls_options, lf_safeParse);
  2766. // Store the values in the script.
  2767. go_self.myGM.forEach(lo_parsed, function(is_wrapperKey, io_elements) {
  2768. go_self.myGM.forEach(io_elements, function(is_elementKey, im_setting) {
  2769. if(_go_options[is_wrapperKey] && (_go_options[is_wrapperKey][is_elementKey] || _go_options[is_wrapperKey][is_elementKey] == false) && Array.isArray(im_setting) === false) {
  2770. if(typeof im_setting !== 'object') {
  2771. _go_options[is_wrapperKey][is_elementKey] = im_setting;
  2772. } else if(_go_wrapper[is_wrapperKey].elements[is_elementKey].serverSpecific === true) {
  2773. go_self.myGM.forEach(im_setting, function(is_serverKey, im_serverSetting) {
  2774. if(Array.isArray(im_serverSetting) === false && typeof im_serverSetting !== 'object')
  2775. _go_options[is_wrapperKey][is_elementKey] = im_setting;
  2776. });
  2777. }
  2778. }
  2779. });
  2780. });
  2781. _saveOptions();
  2782. } catch(lo_error) {
  2783. var lo_notificationText = {
  2784. header: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.importError.header'),
  2785. body: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.importError.explanation')
  2786. };
  2787. go_self.con.error(lo_error);
  2788. go_self.myGM.notification(lo_notificationText);
  2789. }
  2790. }
  2791. };
  2792. /**
  2793. * Show the notification for importing the options.
  2794. *
  2795. * @private
  2796. * @inner
  2797. */
  2798. var _importOptionsShowNotification = function() {
  2799. var lo_notificationText = {
  2800. header: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.importNotification.header'),
  2801. bodyTop: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.importNotification.explanation')
  2802. };
  2803. var lo_notificationCallback = {
  2804. confirm: _importOptionsCallback
  2805. };
  2806. go_self.myGM.notification(lo_notificationText, lo_notificationCallback, { textarea: true });
  2807. };
  2808. /**
  2809. * Callback for resetting the options.
  2810. *
  2811. * @private
  2812. * @inner
  2813. */
  2814. var _resetOptionsCallback = function() {
  2815. _go_options = {};
  2816. // Store the default values.
  2817. go_self.myGM.forEach(_go_wrapper, function(is_wrapperKey, io_wrapper) {
  2818. _go_options[is_wrapperKey] = {};
  2819. go_self.myGM.forEach(io_wrapper.elements, function(is_elementKey, io_element) {
  2820. if(io_element.defaultValue || io_element.defaultValue == false) {
  2821. var ls_serverCode = io_element.serverSpecific === true ? go_self.Ikariam.serverCode : '';
  2822. if(ls_serverCode.length > 0) {
  2823. _go_options[is_wrapperKey][is_elementKey] = {};
  2824. _go_options[is_wrapperKey][is_elementKey][ls_serverCode] = io_element.defaultValue;
  2825. } else {
  2826. _go_options[is_wrapperKey][is_elementKey] = io_element.defaultValue;
  2827. }
  2828. }
  2829. });
  2830. });
  2831. _saveOptions();
  2832. };
  2833. /**
  2834. * Show the notification for resetting the options.
  2835. *
  2836. * @private
  2837. * @inner
  2838. */
  2839. var _resetOptionsShowNotification = function() {
  2840. var lo_notificationText = {
  2841. header: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.resetNotification.header'),
  2842. body: go_self.Language.$('core.optionPanel.section.optionPanelOptions.label.resetNotification.explanation')
  2843. };
  2844. var lo_notificationCallback = {
  2845. confirm: _resetOptionsCallback,
  2846. abort: function() { return; }
  2847. };
  2848. go_self.myGM.notification(lo_notificationText, lo_notificationCallback);
  2849. };
  2850. /**
  2851. * Create the export options link.
  2852. *
  2853. * @private
  2854. * @inner
  2855. *
  2856. * @param {Element} ie_parent
  2857. * Parent element for the link.
  2858. */
  2859. var _exportOptions = function(ie_parent) {
  2860. this.myGM.addElement('a', ie_parent, {
  2861. 'href': 'javascript:;',
  2862. 'innerHTML': this.Language.$('core.optionPanel.section.optionPanelOptions.label.export'),
  2863. 'click': _exportOptionsShowNotification
  2864. });
  2865. };
  2866. /**
  2867. * Create the import options link.
  2868. *
  2869. * @private
  2870. * @inner
  2871. *
  2872. * @param {Element} ie_parent
  2873. * Parent element for the link.
  2874. */
  2875. var _importOptions = function(ie_parent) {
  2876. this.myGM.addElement('a', ie_parent, {
  2877. 'href': 'javascript:;',
  2878. 'innerHTML': this.Language.$('core.optionPanel.section.optionPanelOptions.label.import'),
  2879. 'click': _importOptionsShowNotification
  2880. });
  2881. };
  2882. /**
  2883. * Create the reset options link.
  2884. *
  2885. * @private
  2886. * @inner
  2887. *
  2888. * @param {Element} ie_parent
  2889. * Parent element for the link.
  2890. */
  2891. var _resetOptions = function(ie_parent) {
  2892. this.myGM.addElement('a', ie_parent, {
  2893. 'href': 'javascript:;',
  2894. 'innerHTML': this.Language.$('core.optionPanel.section.optionPanelOptions.label.reset'),
  2895. 'click': _resetOptionsShowNotification
  2896. });
  2897. };
  2898. /*-------------------------------------------*
  2899. * Public variables, functions and settings. *
  2900. *-------------------------------------------*/
  2901. /**
  2902. * Add a wrapper to the list.
  2903. *
  2904. * @instance
  2905. *
  2906. * @param {String} is_id
  2907. * The id of the wrapper.
  2908. * @param {String} is_headerText
  2909. * The text for the wrapper header.
  2910. * @param {int} ii_position
  2911. * The position of the wrapper on the options tab. (optional)
  2912. */
  2913. this.addWrapper = function(is_id, is_headerText, ii_position) {
  2914. if(_go_wrapper[is_id]) {
  2915. go_self.con.warn('Options.addWrapper: Wrapper with id "' + is_id + '" defined two times.');
  2916. } else {
  2917. _go_wrapper[is_id] = { headerText: is_headerText, elements: {}, elementOrder: new Array() };
  2918. _go_options[is_id] = {};
  2919. _ga_wrapperOrder.IC.insert(is_id, ii_position);
  2920. }
  2921. };
  2922. /**
  2923. * Add a new checkbox to the options tab.
  2924. *
  2925. * @instance
  2926. *
  2927. * @param {String} is_id
  2928. * The id of the checkbox.
  2929. * @param {String} is_wrapperId
  2930. * The id of the wrapper.
  2931. * @param {(String|int)} im_block
  2932. * The block of the wrapper, the checkbox belongs to.
  2933. * @param {boolean} ib_defaultChecked
  2934. * If the checkbox is checked by default.
  2935. * @param {String} im_label
  2936. * The text for the label.
  2937. * @param {IkariamCore~Options~DefaultElementOptions} io_options
  2938. * Options for the checkbox.
  2939. */
  2940. this.addCheckbox = function(is_id, is_wrapperId, im_block, ib_defaultChecked, is_label, io_options) {
  2941. /*
  2942. * Function to save the checkbox value.
  2943. */
  2944. var lf_save = function(is_elementId) {
  2945. return go_self.myGM.$('#' + go_self.myGM.prefix + is_elementId + 'Cb').checked;
  2946. };
  2947. /*
  2948. * Function to create the checkbox.
  2949. */
  2950. var lf_create = function(ie_parentTable, is_elementId, ib_value, io_createOptions) {
  2951. var le_row = go_self.myGM.addElement('tr', ie_parentTable);
  2952. var le_parent = go_self.myGM.addElement('td', le_row, { 'colSpan': '2', 'class': 'left' });
  2953. go_self.Ikariam.addCheckboxes(le_parent, [{ id: is_elementId, label: io_createOptions.label, checked: ib_value }]);
  2954. };
  2955. var lo_options = {
  2956. createOptions: { label: is_label },
  2957. defaultValue: ib_defaultChecked,
  2958. serverSpecific: io_options.serverSpecific,
  2959. saveCallback: lf_save,
  2960. changeCallback: io_options.changeCallback,
  2961. position: io_options.position,
  2962. replace: io_options.replace
  2963. };
  2964. _addElement('checkbox', is_id, is_wrapperId, im_block, lf_create, lo_options);
  2965. };
  2966. /**
  2967. * Add a new set of radio buttons to the options tab.
  2968. *
  2969. * @instance
  2970. *
  2971. * @param {String} is_id
  2972. * The id of the checkbox.
  2973. * @param {String} is_wrapperId
  2974. * The id of the wrapper.
  2975. * @param {(String|int)} im_block
  2976. * The block of the wrapper, the checkbox belongs to.
  2977. * @param {(String|int)} im_defaultChecked
  2978. * The value selected by default.
  2979. * @param {String} is_label
  2980. * The text for the label.<br>
  2981. * @param {IkariamCore~myGM~ValueAndLabel} im_radioValues
  2982. * An array with the names an values of the options.
  2983. * @param {IkariamCore~Options~DefaultElementOptions} io_options
  2984. * Options for the radio buttons.
  2985. */
  2986. this.addRadios = function(is_id, is_wrapperId, im_block, im_defaultChecked, is_label, im_radioValues, io_options) {
  2987. /*
  2988. * Function to save the radiobutton value.
  2989. */
  2990. var lf_save = function(is_elementId) {
  2991. return go_self.myGM.getRadioValue(is_elementId);
  2992. };
  2993. /*
  2994. * Function to create the radiobuttons.
  2995. */
  2996. var lf_create = function(ie_parentTable, is_elementId, im_value, io_createOptions) {
  2997. go_self.Ikariam.addRadios(ie_parentTable, is_elementId, im_value, io_createOptions.options, io_createOptions.label);
  2998. };
  2999. var lo_options = {
  3000. createOptions: { label: is_label, options: im_radioValues },
  3001. defaultValue: im_defaultChecked,
  3002. serverSpecific: io_options.serverSpecific,
  3003. saveCallback: lf_save,
  3004. changeCallback: io_options.changeCallback,
  3005. position: io_options.position,
  3006. replace: io_options.replace
  3007. };
  3008. _addElement('radio', is_id, is_wrapperId, im_block, lf_create, lo_options);
  3009. };
  3010. /**
  3011. * Add a new select field to the options tab.
  3012. *
  3013. * @instance
  3014. *
  3015. * @param {String} is_id
  3016. * The id of the select field.
  3017. * @param {String} is_wrapperId
  3018. * The id of the wrapper.
  3019. * @param {(String|int)} im_block
  3020. * The block of the wrapper, the select field belongs to.
  3021. * @param {(String|int)} im_defaultSelected
  3022. * The value of the option selected by default.
  3023. * @param {String} is_label
  3024. * The text for the label.
  3025. * @param {IkariamCore~myGM~ValueAndLabel} im_selectOptions
  3026. * An array with the labels and values of the options.
  3027. * @param {IkariamCore~Options~DefaultElementOptions} io_options
  3028. * Options for the select field.
  3029. */
  3030. this.addSelect = function(is_id, is_wrapperId, im_block, im_defaultSelected, is_label, im_selectOptions, io_options) {
  3031. /*
  3032. * Function to save the select value.
  3033. */
  3034. var lf_save = function(is_elementId) {
  3035. return go_self.myGM.getSelectValue(is_elementId);
  3036. };
  3037. /*
  3038. * Function to create the select.
  3039. */
  3040. var lf_create = function(ie_parentTable, is_elementId, im_value, io_createOptions) {
  3041. go_self.Ikariam.addSelect(ie_parentTable, is_elementId, im_value, io_createOptions.options, io_createOptions.label);
  3042. };
  3043. var lo_options = {
  3044. createOptions: { label: is_label, options: im_selectOptions },
  3045. defaultValue: im_defaultSelected,
  3046. serverSpecific: io_options.serverSpecific,
  3047. saveCallback: lf_save,
  3048. changeCallback: io_options.changeCallback,
  3049. position: io_options.position,
  3050. replace: io_options.replace
  3051. };
  3052. _addElement('select', is_id, is_wrapperId, im_block, lf_create, lo_options);
  3053. };
  3054. /**
  3055. * Add a new textfield to the options tab.
  3056. *
  3057. * @instance
  3058. *
  3059. * @param {String} is_id
  3060. * The id of the textfield.
  3061. * @param {String} is_wrapperId
  3062. * The id of the wrapper.
  3063. * @param {(String|int)} im_block
  3064. * The block of the wrapper, the textfield belongs to.
  3065. * @param {String} is_defaultValue
  3066. * Default value of the textfield.
  3067. * @param {String} is_label
  3068. * The text for the label.
  3069. * @param {IkariamCore~Options~TextFieldOptions} io_options
  3070. * Options for the textfield.
  3071. */
  3072. this.addTextField = function(is_id, is_wrapperId, im_block, is_defaultValue, is_label, io_options) {
  3073. /*
  3074. * Function to save the textfield value.
  3075. */
  3076. var lf_save = function(is_elementId) {
  3077. return go_self.myGM.$('#' + go_self.myGM.prefix + is_elementId + 'TextField').value;
  3078. };
  3079. /*
  3080. * Function to create the textfield.
  3081. */
  3082. var lf_create = function(ie_parentTable, is_elementId, is_value, io_createOptions) {
  3083. var le_row = go_self.myGM.addElement('tr', ie_parentTable);
  3084. var le_labelCell = go_self.myGM.addElement('td', le_row);
  3085. var le_textFieldCell = go_self.myGM.addElement('td', le_row, { 'class': 'left' });
  3086.  
  3087. go_self.myGM.addElement('span', le_labelCell, { 'innerHTML': io_createOptions.label });
  3088. var lo_options = {
  3089. 'id': is_elementId + 'TextField',
  3090. 'classes': ['textfield', 'scriptTextField'],
  3091. 'type': 'text',
  3092. 'value': is_value
  3093. };
  3094. if(!!io_createOptions.maxlength === true)
  3095. lo_options['maxLength'] = io_createOptions.maxLength + '';
  3096. if(!!io_createOptions.style === true)
  3097. lo_options['style'] = io_createOptions.style;
  3098. go_self.myGM.addElement('input', le_textFieldCell, lo_options);
  3099. };
  3100. var lo_options = {
  3101. createOptions: { label: is_label, maxLength: io_options.maxLength, style: io_options.style },
  3102. defaultValue: is_defaultValue,
  3103. serverSpecific: io_options.serverSpecific,
  3104. saveCallback: lf_save,
  3105. changeCallback: io_options.changeCallback,
  3106. position: io_options.position,
  3107. replace: io_options.replace
  3108. };
  3109. _addElement('textfield', is_id, is_wrapperId, im_block, lf_create, lo_options);
  3110. };
  3111. /**
  3112. * Add a new textarea to the options tab.
  3113. *
  3114. * @instance
  3115. *
  3116. * @param {String} is_id
  3117. * The id of the textarea.
  3118. * @param {String} is_wrapperId
  3119. * The id of the wrapper.
  3120. * @param {(String|int)} im_block
  3121. * The block of the wrapper, the textarea belongs to.
  3122. * @param {String} is_defaultValue
  3123. * Default value of the textarea.
  3124. * @param {String} is_label
  3125. * The text for the label.
  3126. * @param {IkariamCore~Options~TextAreaOptions} io_options
  3127. * Options for the textarea.
  3128. */
  3129. this.addTextArea = function(is_id, is_wrapperId, im_block, is_defaultValue, is_label, io_options) {
  3130. /*
  3131. * Function to save the textarea value.
  3132. */
  3133. var lf_save = function(ls_elementId) {
  3134. return go_self.myGM.$('#' + go_self.myGM.prefix + ls_elementId + 'TextArea').value;
  3135. };
  3136. /*
  3137. * Function to create the textarea.
  3138. */
  3139. var lf_create = function(ie_parentTable, is_elementId, is_value, io_createOptions) {
  3140. var le_labelRow = go_self.myGM.addElement('tr', ie_parentTable);
  3141. var le_labelCell = go_self.myGM.addElement('td', le_labelRow, { 'colSpan': '2', 'class': 'left' });
  3142. go_self.myGM.addElement('p', le_labelCell, { 'innerHTML': io_createOptions.label });
  3143. var le_textAreaRow = go_self.myGM.addElement('tr', ie_parentTable);
  3144. var le_textAreaCell = go_self.myGM.addElement('td', le_textAreaRow, { 'colSpan': '2', 'class': 'left' });
  3145. var lo_options = {
  3146. 'id': is_elementId + 'TextArea',
  3147. 'classes': ['textfield', 'scriptTextArea'],
  3148. 'value': is_value
  3149. };
  3150. if(!!io_createOptions.style === true)
  3151. lo_options['style'] = io_createOptions.style;
  3152. go_self.myGM.addElement('textarea', le_textAreaCell, lo_options);
  3153. };
  3154. var lo_options = {
  3155. createOptions: { label: is_label, style: io_options.style },
  3156. defaultValue: is_defaultValue,
  3157. serverSpecific: io_options.serverSpecific,
  3158. saveCallback: lf_save,
  3159. changeCallback: io_options.changeCallback,
  3160. position: io_options.position,
  3161. replace: io_options.replace
  3162. };
  3163. _addElement('textarea', is_id, is_wrapperId, im_block, lf_create, lo_options);
  3164. };
  3165. /**
  3166. * Add HTML content to the options tab.
  3167. *
  3168. * @instance
  3169. *
  3170. * @param {String} is_id
  3171. * The id of the HTML content.
  3172. * @param {String} is_wrapperId
  3173. * The id of the wrapper.
  3174. * @param {(String|int)} im_block
  3175. * The block of the wrapper, the HTML content belongs to.
  3176. * @param {IkariamCore~Options~HtmlOptions} io_options
  3177. * Options for the html code.
  3178. */
  3179. this.addHTML = function(is_id, is_wrapperId, im_block, io_options) {
  3180. /*
  3181. * Function to create the html.
  3182. */
  3183. var lf_create = function(ie_parentTable, is_elementId, im_value, io_createOptions) {
  3184. var le_htmlRow = go_self.myGM.addElement('tr', ie_parentTable);
  3185. var lo_options = {
  3186. 'colSpan': '2',
  3187. 'class': 'center'
  3188. };
  3189. if(!!io_createOptions.html === true)
  3190. lo_options['innerHTML'] = io_createOptions.html;
  3191. var le_htmlCell = go_self.myGM.addElement('td', le_htmlRow, lo_options);
  3192. if(!!io_createOptions.callback === true)
  3193. io_createOptions.callback.call(io_createOptions.thisReference, le_htmlCell);
  3194. };
  3195. var lo_options = {
  3196. createOptions: { html: io_options.html, callback: io_options.callback, thisReference: io_options.thisReference },
  3197. position: io_options.position,
  3198. replace: io_options.replace
  3199. };
  3200. _addElement('html', is_id, is_wrapperId, im_block, lf_create, lo_options);
  3201. };
  3202. /**
  3203. * Add a new horizontal line to the options tab.
  3204. *
  3205. * @instance
  3206. *
  3207. * @param {String} is_wrapperId
  3208. * The id of the wrapper.
  3209. * @param {(String|int)} im_block
  3210. * The block of the wrapper, the horizontal line belongs to.
  3211. * @param {int} ii_position
  3212. * The position of the horizontal line in the wrapper. (optional)
  3213. *
  3214. * @return {String}
  3215. * The id of the horizontal line.
  3216. */
  3217. this.addLine = function(is_wrapperId, im_block, ii_position) {
  3218. /*
  3219. * Function to create the horizontal line.
  3220. */
  3221. var lf_create = function(ie_parentTable, is_elementId, im_value, io_options) {
  3222. var le_row = go_self.myGM.addElement('tr', ie_parentTable);
  3223. var le_lineCell = go_self.myGM.addElement('td', le_row, { 'colSpan': '2', 'class': 'left' });
  3224. go_self.myGM.addElement('hr', le_lineCell);
  3225. };
  3226. var rs_id = 'hr' + _gi_lineId;
  3227. var lo_options = {
  3228. position: ii_position
  3229. };
  3230. _addElement('line', rs_id, is_wrapperId, im_block, lf_create, lo_options);
  3231. _gi_lineId++;
  3232. return rs_id;
  3233. };
  3234. /**
  3235. * Deletes an wrapper with all option elements contained in it.
  3236. *
  3237. * @instance
  3238. *
  3239. * @param {String} is_id
  3240. * Id of the wrapper to delete.
  3241. */
  3242. this.deleteWrapper = function(is_id) {
  3243. if(!_go_wrapper[is_id]) {
  3244. go_self.con.info('Options.deleteWrapper: Wrapper with id "' + is_id + '" does not exist.');
  3245. } else {
  3246. delete _go_wrapper[is_id];
  3247. delete _go_options[is_id];
  3248. var li_position = -1;
  3249. for(var i = 0; i < _ga_wrapperOrder.length; i++) {
  3250. if(_ga_wrapperOrder[i] == is_id) {
  3251. li_position = i;
  3252. break;
  3253. }
  3254. }
  3255. _ga_wrapperOrder.IC.remove(li_position);
  3256. }
  3257. };
  3258. /**
  3259. * Deletes an option element.
  3260. *
  3261. * @instance
  3262. *
  3263. * @param {String} is_wrapperId
  3264. * The id of the wrapper containing the element.
  3265. * @param {String} is_elementId
  3266. * The id of the element to delete.
  3267. */
  3268. this.deleteElement = function(is_wrapperId, is_elementId) {
  3269. if(!(_go_wrapper[is_wrapperId] && _go_wrapper[is_wrapperId].elements[is_elementId])) {
  3270. go_self.con.info('Options.deleteElement: Element with id "' + is_wrapperId + '_' + is_elementId + '" does not exist.');
  3271. } else {
  3272. delete _go_wrapper[is_wrapperId].elements[is_elementId];
  3273. delete _go_options[is_wrapperId][is_elementId];
  3274. var li_position = -1;
  3275. for(var i = 0; i < _go_wrapper[is_wrapperId].elementOrder.length; i++) {
  3276. if(_go_wrapper[is_wrapperId].elementOrder[i] == is_elementId) {
  3277. li_position = i;
  3278. break;
  3279. }
  3280. }
  3281. _go_wrapper[is_wrapperId].elementOrder.IC.remove(li_position);
  3282. }
  3283. };
  3284. /**
  3285. * Get the stored value of an option.
  3286. *
  3287. * @instance
  3288. *
  3289. * @param {String} is_wrapperId
  3290. * Id of the wrapper of the option element.
  3291. * @param {String} is_optionId
  3292. * Id of the option element.
  3293. *
  3294. * @return {(String|int|boolean)}
  3295. * The stored value.
  3296. */
  3297. this.getOption = function(is_wrapperId, is_optionId) {
  3298. var ls_serverCode = '';
  3299. if(_go_wrapper[is_wrapperId] && _go_wrapper[is_wrapperId].elements[is_optionId] && _go_wrapper[is_wrapperId].elements[is_optionId].serverSpecific === true)
  3300. ls_serverCode = go_self.Ikariam.serverCode;
  3301. if(_go_options[is_wrapperId] && (_go_options[is_wrapperId][is_optionId] || _go_options[is_wrapperId][is_optionId] == false)) {
  3302. if(ls_serverCode.length > 0)
  3303. return _go_options[is_wrapperId][is_optionId][ls_serverCode];
  3304. return _go_options[is_wrapperId][is_optionId];
  3305. }
  3306. go_self.con.warn('Options.getOption: Option with id "' + is_wrapperId + '_' + is_optionId + '" not defined.');
  3307. return null;
  3308. };
  3309. /**
  3310. * Set the stored value of an option.
  3311. *
  3312. * @instance
  3313. *
  3314. * @param {String} is_wrapperId
  3315. * Id of the wrapper of the option element.
  3316. * @param {String} is_optionId
  3317. * Id of the option element.
  3318. * @param {(String|int|boolean)} im_value
  3319. * The value to store.
  3320. */
  3321. this.setOption = function(is_wrapperId, is_optionId, im_value) {
  3322. var ls_serverCode = _go_wrapper[is_wrapperId].elements[is_optionId].serverSpecific === true ? go_self.Ikariam.serverCode : '';
  3323. if(_go_options[is_wrapperId] && (_go_options[is_wrapperId][is_optionId] || _go_options[is_wrapperId][is_optionId] == false)) {
  3324. if(ls_serverCode.length > 0)
  3325. _go_options[is_wrapperId][is_optionId][ls_serverCode] = im_value;
  3326. else
  3327. _go_options[is_wrapperId][is_optionId] = im_value;
  3328. } else {
  3329. go_self.con.warn('Options.setOption: Option with id "' + is_wrapperId + '_' + is_optionId + '" not yet defined. Value "' + im_value + '" not stored.');
  3330. }
  3331. _saveOptions(true);
  3332. };
  3333. /*----------------------------------------*
  3334. * Register the show option panel handler *
  3335. *----------------------------------------*/
  3336. // Register the option handler to show the options in the option panel.
  3337. go_self.RefreshHandler.add(['options', 'optionsAccount', 'optionsNotification', 'optionsFacebook'], 'showOptionPanel', _showOptionPanel);
  3338. /*-------------------------------*
  3339. * Add the option panel options. *
  3340. *-------------------------------*/
  3341. this.addWrapper('optionPanelOptions', go_self.Language.$('core.optionPanel.section.optionPanelOptions.title'));
  3342. this.addHTML('exportOptions', 'optionPanelOptions', 'links', { thisReference: go_self, callback: _exportOptions });
  3343. this.addHTML('importOptions', 'optionPanelOptions', 'links', { thisReference: go_self, callback: _importOptions });
  3344. this.addHTML('resetOptions', 'optionPanelOptions', 'links', { thisReference: go_self, callback: _resetOptions });
  3345. /*---------------------------------------------------------------------*
  3346. * Types for documentation purposes (e.g. callback functions, objects) *
  3347. *---------------------------------------------------------------------*/
  3348. /**
  3349. * Callback to get the value of an option from the element.
  3350. *
  3351. * @callback IkariamCore~Options~GetOption
  3352. *
  3353. * @private
  3354. * @inner
  3355. *
  3356. * @param {String} elementId
  3357. * The id of the element which option should be retrieved.
  3358. *
  3359. * @return {(String|int|boolean)}
  3360. * The value of the option.
  3361. */
  3362. /**
  3363. * Options for the generic <code>_addElement</code> function.
  3364. *
  3365. * @typedef IkariamCore~Options~AddElementOptions
  3366. *
  3367. * @private
  3368. * @inner
  3369. *
  3370. * @mixes IkariamCore~Options~DefaultElementOptions
  3371. *
  3372. * @property {?*} [createOptions] - Options to pass to the create element function.
  3373. * @property {?(String|int|boolean)} [defaultValue] - Default value of the option. Needed to enable loading of stored option!
  3374. * @property {?IkariamCore~Options~GetOption} [saveCallback] - Callback to get the value of an option from the element.
  3375. */
  3376. /**
  3377. * Callback to create an option element.
  3378. *
  3379. * @callback IkariamCore~Options~CreateCallback
  3380. *
  3381. * @private
  3382. * @inner
  3383. *
  3384. * @param {Element} parentTable
  3385. * The parent table of the option element.
  3386. * @param {String} elementId
  3387. * The id of the option element.
  3388. * @param {(String|int|boolean)} value
  3389. * The value of the option element.
  3390. * @param {*} options
  3391. * Options needed to create this option element.
  3392. */
  3393. /**
  3394. * Callback if the value of an option is changed.
  3395. *
  3396. * @callback IkariamCore~Options~ChangeCallback
  3397. *
  3398. * @param {(String|int|boolean)} newValue
  3399. * The new value of the option.
  3400. * @param {(String|int|boolean)} oldValue
  3401. * The old value of the option.
  3402. */
  3403. /**
  3404. * Default options for elements.
  3405. *
  3406. * @typedef {Object} IkariamCore~Options~DefaultElementOptions
  3407. *
  3408. * @property {?boolean} [serverSpecific=false] - If the option should be stored for each server specific and not global for all servers. Not changable during replacement!
  3409. * @property {?IkariamCore~Options~ChangeCallback} [changeCallback] - Callback if the value of an option is changed.
  3410. * @property {?int} [position=array.length] - Position of the element in the element array. Not changable during replacement!
  3411. * @property {?boolean} [replace=false] - Replace the element with the same name if it has the same type.
  3412. */
  3413. /**
  3414. * Options for text fields.
  3415. *
  3416. * @typedef {Object} IkariamCore~Options~TextFieldOptions
  3417. *
  3418. * @mixes IkariamCore~Options~DefaultElementOptions
  3419. *
  3420. * @property {?int} [maxLength] - The maximum length of the input text.
  3421. * @property {?IkariamCore~myGM~CssStyles} [style] - Special styles to be applied to the element.
  3422. */
  3423. /**
  3424. * Options for text areas.
  3425. *
  3426. * @typedef {Object} IkariamCore~Options~TextAreaOptions
  3427. *
  3428. * @mixes IkariamCore~Options~DefaultElementOptions
  3429. *
  3430. * @property {?IkariamCore~myGM~CssStyles} [style] - Special styles to be applied to the element.
  3431. */
  3432. /**
  3433. * Callback to run after setting the HTML string. Can also be used to create the HTML content.
  3434. *
  3435. * @callback IkariamCore~Options~HtmlCreateCallback
  3436. *
  3437. * @param {Element} parent
  3438. * The parent element of the custom html.
  3439. */
  3440. /**
  3441. * Options for custom html.
  3442. *
  3443. * @typedef {Object} IkariamCore~Options~HtmlOptions
  3444. *
  3445. * @property {?String} [html] - HTML string to add to the wrapper.
  3446. * @property {?IkariamCore~Options~HtmlCreateCallback} [callback] - Callback to run after setting the HTML string. Can also be used to create the HTML content.
  3447. * @property {?*} [thisReference] - Reference to an object which should be referenced by <code>this</code> in the callback as it is not possible to use some objects. (e.g. go_self)
  3448. * @property {?int} [position=array.length] - Position of the element in the element array. Not changable during replacement!
  3449. * @property {?boolean} [replace=false] - Replace the element with the same name if it has the same type.
  3450. */
  3451. }
  3452. /**
  3453. * Handler for options the user can set / change.
  3454. *
  3455. * @instance
  3456. *
  3457. * @type IkariamCore~Options
  3458. */
  3459. this.Options = new Options();
  3460. this.con.timeStamp('IkariamCore.Options created');
  3461. /**
  3462. * Instantiate a new set of updating functions and start an initial update check.
  3463. *
  3464. * @inner
  3465. *
  3466. * @class
  3467. * @classdesc Functions for checking for updates for the script.
  3468. */
  3469. function Updater() {
  3470. /*--------------------------------------------*
  3471. * Private variables, functions and settings. *
  3472. *--------------------------------------------*/
  3473. /**
  3474. * Stores if the update check was started by the user.
  3475. *
  3476. * @private
  3477. * @inner
  3478. *
  3479. * @default false
  3480. *
  3481. * @type boolean
  3482. */
  3483. var _gb_manualUpdate = false;
  3484. /**
  3485. * Types for entries in update history. Translations have to be provided as translation
  3486. * in <code>core.update.possible.type.typeName</code><br>
  3487. * Default values which are always set:<br>
  3488. * "release" => release date<br>
  3489. * "other" => entries which type is unknown
  3490. *
  3491. * @private
  3492. * @inner
  3493. *
  3494. * @default ['feature', 'change', 'bugfix', 'language', 'core']
  3495. *
  3496. * @type Array.<String>
  3497. */
  3498. var _ga_updateHistoryEntryTypes = ['feature', 'change', 'bugfix', 'language', 'core'];
  3499. /**
  3500. * Compares two versions and returns if there is a new version.
  3501. *
  3502. * @private
  3503. * @inner
  3504. *
  3505. * @param {String} is_versionOld
  3506. * The old version number.
  3507. * @param {String} is_versionNew
  3508. * The new version number.
  3509. * @param {?int} [ii_maxPartsToCompare=infinite]
  3510. * The number of parts to compare at most.
  3511. *
  3512. * @return {boolean}
  3513. * If a new version is available.
  3514. */
  3515. var _newerVersion = function(is_versionOld, is_versionNew, ii_maxPartsToCompare) {
  3516. var rb_newVersion = false;
  3517. is_versionOld += '';
  3518. is_versionNew += '';
  3519. var la_versionOldParts = is_versionOld.split('.');
  3520. var la_versionNewParts = is_versionNew.split('.');
  3521. var li_biggerNumberOfParts = la_versionOldParts.length > la_versionNewParts.length ? la_versionOldParts.length : la_versionNewParts.length;
  3522. if(!ii_maxPartsToCompare || ii_maxPartsToCompare < 1) {
  3523. ii_maxPartsToCompare = li_biggerNumberOfParts + 1;
  3524. }
  3525. for(var i = 0; i < li_biggerNumberOfParts; i++) {
  3526. var li_versionPartOld = parseInt(la_versionOldParts[i] || 0);
  3527. var li_versionPartNew = parseInt(la_versionNewParts[i] || 0);
  3528. if(li_versionPartOld < li_versionPartNew) {
  3529. rb_newVersion = true;
  3530. break;
  3531. } else if(li_versionPartOld > li_versionPartNew || i == ii_maxPartsToCompare - 1) {
  3532. rb_newVersion = false;
  3533. break;
  3534. }
  3535. }
  3536. return rb_newVersion;
  3537. };
  3538. /**
  3539. * Extract the update history from the metadata.
  3540. *
  3541. * @private
  3542. * @inner
  3543. *
  3544. * @param {Object.<String, Array.<String>>} io_metadata
  3545. * Array with the formatted metadata.
  3546. *
  3547. * @return {Object.<String, Object.<String, Array.<String>>>}
  3548. * The extracted update history.<br>
  3549. * Structure: <code>{ &lt;version&gt;: { &lt;type&gt;: [ &lt;notes&gt; ] }}</code>
  3550. */
  3551. var _extractUpdateHistory = function(io_metadata) {
  3552. var ro_updateHistory = {};
  3553. for(var i = 0; i < io_metadata['history'].length; i++) {
  3554. // Get the information from the update history data.
  3555. var la_history_entry = io_metadata['history'][i].match(/^(\S+)\s+(\S+)\s+(.*)$/);
  3556. var ls_version = la_history_entry[1];
  3557. var ls_type = la_history_entry[2];
  3558. var ls_type_trimmed = ls_type.IC.trim(':').toLowerCase();
  3559. var ls_info = la_history_entry[3];
  3560. if(!ro_updateHistory[ls_version]) {
  3561. ro_updateHistory[ls_version] = {};
  3562. }
  3563. if(ls_type_trimmed == 'release') {
  3564. ro_updateHistory[ls_version]['release'] = ls_info;
  3565. } else if(_ga_updateHistoryEntryTypes.indexOf(ls_type_trimmed) > -1) {
  3566. if(!ro_updateHistory[ls_version][ls_type_trimmed]) {
  3567. ro_updateHistory[ls_version][ls_type_trimmed] = new Array(ls_info);
  3568. } else {
  3569. ro_updateHistory[ls_version][ls_type_trimmed].push(ls_info);
  3570. }
  3571. } else {
  3572. if(!ro_updateHistory[ls_version]['other']) {
  3573. ro_updateHistory[ls_version]['other'] = new Array(ls_type + " " + ls_info);
  3574. } else {
  3575. ro_updateHistory[ls_version]['other'].push(ls_type + " " + ls_info);
  3576. }
  3577. }
  3578. }
  3579. // Return the update history.
  3580. return ro_updateHistory;
  3581. };
  3582. /**
  3583. * Format the update history using some HTML codes.
  3584. *
  3585. * @private
  3586. * @inner
  3587. *
  3588. * @param {Object.<String, Object.<String, Array.<String>>>} io_updateHistory
  3589. * The update history.<br>
  3590. * Structure: <code>{ &lt;version&gt;: { &lt;type&gt;: [ &lt;notes&gt; ] }}</code>
  3591. *
  3592. * @return {String}
  3593. * The formated update history.
  3594. */
  3595. var _formatUpdateHistory = function(io_updateHistory) {
  3596. var rs_formattedUpdateHistory = '';
  3597. for(var ls_version in io_updateHistory) {
  3598. if(Object.prototype.hasOwnProperty.call(io_updateHistory, ls_version)) {
  3599. rs_formattedUpdateHistory += '<h2>v' + ls_version + '</h2><span class="smallFont">' + io_updateHistory[ls_version]['release'] + '</span></small><br><table class="' + go_self.myGM.prefix + 'updateTable"><tbody>';
  3600. for(var ls_type in io_updateHistory[ls_version]) {
  3601. if(Object.prototype.hasOwnProperty.call(io_updateHistory[ls_version], ls_type) && ls_type != 'release') {
  3602. rs_formattedUpdateHistory += '<tr><td class="' + go_self.myGM.prefix + 'updateDataType">' + go_self.Language.$('core.update.possible.type.' + ls_type) + '</td><td class="' + go_self.myGM.prefix + 'updateDataInfo"><ul>';
  3603. for(var i = 0 ; i < io_updateHistory[ls_version][ls_type].length; i++) {
  3604. rs_formattedUpdateHistory += '<li>' + io_updateHistory[ls_version][ls_type][i] + '</li>';
  3605. }
  3606. rs_formattedUpdateHistory += '</ul></td></tr>';
  3607. }
  3608. }
  3609. rs_formattedUpdateHistory += '</tbody></table><br>';
  3610. }
  3611. }
  3612. return rs_formattedUpdateHistory;
  3613. };
  3614. /**
  3615. * Show the update information panel.
  3616. *
  3617. * @private
  3618. * @inner
  3619. *
  3620. * @param {Object.<String, Array.<String>>} io_metadata
  3621. * Array with formatted metadata.
  3622. */
  3623. var _showUpdateInfo = function(io_metadata) {
  3624. var lo_updateHistory = _extractUpdateHistory(io_metadata);
  3625. var lo_notificationText = {
  3626. header: go_self.Language.$('core.update.possible.header'),
  3627. bodyTop: go_self.Language.$('core.update.possible.text', ['<a href="https://greasyfork.org/scripts/' + go_script.id + '" target="_blank" >' + go_script.name + '</a>', go_script.version, io_metadata.version]) + '<br>&nbsp;&nbsp;<b><u>' + go_self.Language.$('core.update.possible.history') + '</u></b>',
  3628. bodyBottom: _formatUpdateHistory(lo_updateHistory),
  3629. confirm: go_self.Language.$('core.update.possible.button.install'),
  3630. abort: go_self.Language.$('core.update.possible.button.hide')
  3631. };
  3632. var lo_notificationCallback = {
  3633. confirm: function() { go_self.win.top.location.href = 'https://greasyfork.org/scripts/' + go_script.id + '/code/' + go_script.id + '.user.js'; },
  3634. abort: function() { go_self.myGM.setValue('updater_hideUpdate', io_metadata.version + ''); }
  3635. };
  3636. go_self.myGM.notification(lo_notificationText, lo_notificationCallback);
  3637. };
  3638. /**
  3639. * Format the given metadata.
  3640. *
  3641. * @private
  3642. * @inner
  3643. *
  3644. * @param {String} is_metadata
  3645. * The metadata to format.
  3646. *
  3647. * @return {Object.<String, Array.<String>>}
  3648. * The formatted metadata.
  3649. */
  3650. var _formatMetadata = function(is_metadata) {
  3651. var rs_metadata = new Array();
  3652. // Extract the tags from the metadata.
  3653. var ls_innerMetadata = is_metadata.match(/\/\/ ==UserScript==((.|\n|\r)*?)\/\/ ==\/UserScript==/)[0];
  3654. if(ls_innerMetadata) {
  3655. // Extract all tags.
  3656. var la_metadata_entries = ls_innerMetadata.match(/\/\/ @(.*?)(\n|\r)/g);
  3657. for(var i = 0; i < la_metadata_entries.length; i++) {
  3658. // Extract the data from the tag.
  3659. var la_metadata_entry = la_metadata_entries[i].match(/\/\/ @(.*?)\s+(.*)/);
  3660. if(!rs_metadata[la_metadata_entry[1]]) {
  3661. rs_metadata[la_metadata_entry[1]] = new Array(la_metadata_entry[2]);
  3662. } else {
  3663. rs_metadata[la_metadata_entry[1]].push(la_metadata_entry[2]);
  3664. }
  3665. }
  3666. }
  3667. return rs_metadata;
  3668. };
  3669. /*-------------------------------------------*
  3670. * Public variables, functions and settings. *
  3671. *-------------------------------------------*/
  3672. /**
  3673. * Check for updates for the script. Automatically done on every instantiation of {@link IkariamCore}
  3674. * if the period from the last update is bigger than the check interval.
  3675. *
  3676. * @instance
  3677. */
  3678. this.checkForUpdates = function() {
  3679. // Send a request to the script hosting server to get the metadata of the script to check if there is a new update.
  3680. var lb_notPossible = go_self.myGM.xhr({
  3681. method: 'GET',
  3682. url: 'https://greasyfork.org/scripts/' + go_script.id + '/code.meta.js',
  3683. headers: {'User-agent': 'Mozilla/5.0', 'Accept': 'text/html'},
  3684. onload: function(io_response) {
  3685. var lo_metadata = _formatMetadata(io_response.responseText);
  3686. if(_newerVersion(go_script.version, lo_metadata.version, go_self.Options.getOption('updateOptions', 'updateNotifyLevel')) && (go_self.myGM.getValue('updater_hideUpdate', go_script.version) != lo_metadata.version || _gb_manualUpdate)) {
  3687. _showUpdateInfo(lo_metadata);
  3688. } else if(_gb_manualUpdate) {
  3689. var lo_notificationText = {
  3690. header: go_self.Language.$('core.update.noNewExists.header'),
  3691. body: go_self.Language.$('core.update.noNewExists.text', ['<a href="https://greasyfork.org/scripts/' + go_script.id + '" target="_blank" >' + go_script.name + '</a>', go_script.version])
  3692. };
  3693. go_self.myGM.notification(lo_notificationText);
  3694. }
  3695. }
  3696. });
  3697. if(lb_notPossible && lb_notPossible == true) {
  3698. go_self.Options.setOption('updateOptions', 'updateInterval', 2419200);
  3699.  
  3700. var lo_notificationText = {
  3701. header: go_self.Language.$('core.update.notPossible.header'),
  3702. body: go_self.Language.$('core.update.notPossible.text', ['<a href="https://greasyfork.org/scripts/' + go_script.id + '" target="_blank" >' + go_script.name + '</a>', go_script.version])
  3703. };
  3704.  
  3705. go_self.myGM.notification(lo_notificationText);
  3706. }
  3707. };
  3708. /**
  3709. * Search manually for updates. Forces to search for updates. Even shows a popup if no new update is available.
  3710. *
  3711. * @instance
  3712. */
  3713. this.doManualUpdate = function() {
  3714. _gb_manualUpdate = true;
  3715. go_self.Updater.checkForUpdates();
  3716. go_self.myGM.setValue('updater_lastUpdateCheck', (new Date()).getTime() + '');
  3717. };
  3718. /**
  3719. * Set the possible entries for update history entries. "release" for the release date and "other"
  3720. * for all entries which are not known will be stipped as they are default. Translations have to be
  3721. * provided as translation in <code>core.update.possible.type.typeName</code>
  3722. *
  3723. * @instance
  3724. *
  3725. * @param {Array.<String>} ia_updateHistoryEntryTypes
  3726. * The array with the update history entries to set.
  3727. */
  3728. this.setUpdateHistoryEntryTypes = function(ia_updateHistoryEntryTypes) {
  3729. ['release', 'other'].forEach(function(is_toStrip) {
  3730. var li_index = ia_updateHistoryEntryTypes.indexOf('release');
  3731. if(li_index !== -1)
  3732. ia_updateHistoryEntryTypes.IC.remove(li_index);
  3733. });
  3734. _ga_updateHistoryEntryTypes = ia_updateHistoryEntryTypes;
  3735. };
  3736. /*------------------------*
  3737. * Add the updater styles *
  3738. *------------------------*/
  3739. go_self.myGM.addStyle(
  3740. "." + go_self.myGM.prefix + "updateTable { border-collapse: separate; border-spacing: 2px; } \
  3741. ." + go_self.myGM.prefix + "updateDataType { width: 100px; padding: 5px 0px 5px 5px; border: 1px solid #D2A860; } \
  3742. ." + go_self.myGM.prefix + "updateDataInfo { width: 300px; padding: 5px 5px 5px 20px; border: 1px solid #D2A860; } \
  3743. ." + go_self.myGM.prefix + "updateDataInfo ul li { list-style: disc outside none; }",
  3744. 'updater', true
  3745. );
  3746. /*----------------------*
  3747. * Register the options *
  3748. *----------------------*/
  3749. var _ga_updateIntervalOpts = new Array(
  3750. { value: -1, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.never') },
  3751. { value: 3600, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.hour') },
  3752. { value: 43200, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.hour12') },
  3753. { value: 86400, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.day') },
  3754. { value: 259200, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.day3') },
  3755. { value: 604800, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.week') },
  3756. { value: 1209600, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.week2') },
  3757. { value: 2419200, label: go_self.Language.$('core.optionPanel.section.update.label.interval.option.week4') }
  3758. );
  3759. var _ga_updateNotifyLevelOpts = new Array(
  3760. { value: 0, label: go_self.Language.$('core.optionPanel.section.update.label.notifyLevel.option.all') },
  3761. { value: 1, label: go_self.Language.$('core.optionPanel.section.update.label.notifyLevel.option.major') },
  3762. { value: 2, label: go_self.Language.$('core.optionPanel.section.update.label.notifyLevel.option.minor') },
  3763. { value: 3, label: go_self.Language.$('core.optionPanel.section.update.label.notifyLevel.option.patch') }
  3764. );
  3765. var _searchUpdates = function(ie_parent) {
  3766. var ls_updateLink = this.Language.$('core.optionPanel.section.update.label.manual', [go_script.name]);
  3767. this.myGM.addElement('a', ie_parent, { 'href': 'javascript:;', 'innerHTML': ls_updateLink, 'click': go_self.Updater.doManualUpdate });
  3768. };
  3769. go_self.Options.addWrapper('updateOptions', go_self.Language.$('core.optionPanel.section.update.title'), 1);
  3770. go_self.Options.addSelect('updateInterval', 'updateOptions', 'generalOptions', 3600, go_self.Language.$('core.optionPanel.section.update.label.interval.description'), _ga_updateIntervalOpts, {});
  3771. go_self.Options.addSelect('updateNotifyLevel', 'updateOptions', 'generalOptions', 0, go_self.Language.$('core.optionPanel.section.update.label.notifyLevel.description'), _ga_updateNotifyLevelOpts, {});
  3772. go_self.Options.addHTML('manualUpdateLink', 'updateOptions', 'manualUpdate', { thisReference: go_self, callback: _searchUpdates });
  3773. /*-------------------------------------*
  3774. * Check automatically for new updates *
  3775. *-------------------------------------*/
  3776. setTimeout(function() {
  3777. var li_lastCheck = go_self.myGM.getValue('updater_lastUpdateCheck', 0);
  3778. var li_millis = (new Date()).getTime();
  3779. var li_diff = li_millis - li_lastCheck;
  3780. var li_interval = go_self.Options.getOption('updateOptions', 'updateInterval') * 1000;
  3781. if(li_interval > 0 && li_diff > li_interval) {
  3782. _gb_manualUpdate = false;
  3783. go_self.Updater.checkForUpdates();
  3784.  
  3785. go_self.myGM.setValue('updater_lastUpdateCheck', li_millis + '');
  3786. }
  3787. }, 0);
  3788. }
  3789. /**
  3790. * Updater to check for updates.
  3791. *
  3792. * @instance
  3793. *
  3794. * @type IkariamCore~Updater
  3795. */
  3796. this.Updater = new Updater();
  3797. this.con.timeStamp('IkariamCore.Updater created');
  3798. this.con.groupEnd();
  3799. }