Ikariam Core

Framework for Ikariam userscript developers.

当前为 2017-11-14 提交的版本,查看 最新版本

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

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

QingJ © 2025

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