Cathay Award Search Fixer 2022

Beware: Only install using the "Install this Script" or "安裝腳本" button below, beware of fake buttons in the ad under it.

目前为 2023-10-31 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Cathay Award Search Fixer 2022
  3. // @name:zh-TW 國泰獎勵機票搜尋引擎修復神器 2022
  4. // @namespace jayliutw
  5. // @version 4.0.3
  6. // @description Beware: Only install using the "Install this Script" or "安裝腳本" button below, beware of fake buttons in the ad under it.
  7. // @description:zh-tw 注意:安裝請務必點選下方 "Install this Script" 或 "安裝腳本" 的按鈕,慎防不肖業者用下方 Google Ads 投放假按鈕的釣魚廣告!
  8. // @author jayliutw
  9. // @connect greasyfork.org
  10. // @connect cathaypacific.com
  11. // @connect userscripts.jayliu.net
  12. // @match https://*.cathaypacific.com/cx/*/book-a-trip/redeem-flights/redeem-flight-awards.html*
  13. // @match https://*.cathaypacific.com/cx/*/book-a-trip/redeem-flights/facade.html*
  14. // @match https://api.cathaypacific.com/redibe/IBEFacade*
  15. // @match https://cxplanner.jayliu.net/*
  16. // @match https://userscripts.jayliu.net/*
  17. // @match https://book.cathaypacific.com/*
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_log
  21. // @grant GM_xmlhttpRequest
  22. // @grant unsafeWindow
  23. // @antifeature payment Unlocked multi city pair search, route saving/favouriting, saved route searching, and multicity award booking for sponsors.
  24. // @license GPL
  25. // ==/UserScript==
  26.  
  27.  
  28. if(window.location.href.indexOf("planner") > -1) {
  29. unsafeWindow.checkVersion = function(){
  30. return GM_info.script.version;
  31. }
  32. }
  33.  
  34. //============================================================
  35. // Main Userscript
  36. //============================================================
  37.  
  38. (function() {
  39. 'use strict';
  40.  
  41.  
  42.  
  43. const debug = false;
  44.  
  45. //============================================================
  46. // Greasymonkey Function Wrappers
  47. //============================================================
  48.  
  49. // GM_Log
  50. function log(data){ if (debug) GM_log(data); }
  51.  
  52. // Get and Set Stored Values
  53. function value_get(valueName, defaultValue) { return GM_getValue(valueName, defaultValue) }
  54. function value_set(valueName, setValue) { GM_setValue(valueName, setValue); return setValue; }
  55.  
  56. // XMLHttpRequest and GM_xmlhttpRequest
  57. function httpRequest(request, native = false){
  58. if(!native && !debug) {
  59. GM_xmlhttpRequest(request);
  60. } else {
  61. if (!request.method || !request.url) return;
  62. var http = new XMLHttpRequest();
  63. http.withCredentials = true;
  64. http.open(request.method, request.url, true);
  65. if (request.headers) { for ( var key in request.headers ) { http.setRequestHeader(key,request.headers[key]) } }
  66. if (request.onreadystatechange) http.onreadystatechange = function(){ request.onreadystatechange(this) }
  67. if (request.onload) http.onload = function(){ request.onload(this) }
  68. if (request.data) { http.send(request.data) } else { http.send() }
  69. }
  70. }
  71.  
  72. //============================================================
  73. // Initialize Variables
  74. //============================================================
  75.  
  76. let route_changed = false;
  77.  
  78. // Retrieve CX Parameters
  79.  
  80. let static_path = value_get("static_path", "/CathayPacificAwardV3/AML_IT3.1.14/");
  81. let requestVars = {};
  82. let tab_id = "";
  83. let availability_url = "https://book.cathaypacific.com/CathayPacificAwardV3/dyn/air/booking/availability?TAB_ID=";
  84. let form_submit_url = availability_url + tab_id;
  85.  
  86. let autoScroll = true;
  87.  
  88. function initCXvars(){
  89. if(typeof staticFilesPath !== "undefined" && static_path != staticFilesPath){
  90. log(typeof staticFilesPath);
  91. static_path = staticFilesPath;
  92. value_set("static_path", static_path);
  93. }
  94.  
  95. if (typeof tabId === 'string') { tab_id = tabId; }
  96. if (typeof requestParams === 'string') {
  97. requestVars = JSON.parse(requestParams);
  98. tab_id = requestVars.TAB_ID;
  99. } else if (typeof requestParams === 'object') {
  100. requestVars = requestParams;
  101. tab_id = requestParams.TAB_ID || "";
  102. }
  103.  
  104. form_submit_url = (typeof formSubmitUrl !== 'undefined') ? formSubmitUrl : availability_url + tab_id;
  105. }
  106.  
  107. const browser_locale = navigator.language;
  108. const browser_lang = (browser_locale.trim().split(/-|_/)[0] == "zh") ? "zh" : "en";
  109. const browser_country = (browser_locale.trim().split(/-|_/)[1]?.toUpperCase() == "TW") ? "TW" : "HK";
  110.  
  111. let login_url = "https://www.cathaypacific.com/content/cx/" + browser_lang + "_" +browser_country + "/sign-in.html?loginreferrer=" + encodeURI("https://www.cathaypacific.com/cx/" + browser_lang + "_" +browser_country + "/book-a-trip/redeem-flights/redeem-flight-awards.html");
  112.  
  113. let r = Math.random();
  114. let t = tab_id || "";
  115.  
  116. //============================================================
  117. // Helper Functions
  118. //============================================================
  119.  
  120. // Wait for Element to Load
  121. function waitForElm(selector) {return new Promise(resolve => {if (document.querySelector(selector)) {return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => {if (document.querySelector(selector)) {resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, {childList: true, subtree: true }); }); }
  122.  
  123. // Check CX Date String Validity (dateString YYYYMMDD)
  124. function isValidDate(dateString) {if (!/^\d{8}$/.test(dateString)) return false; let year = dateString.substring(0, 4); let month = dateString.substring(4, 6); let day = dateString.substring(6, 8); if(year < 1000 || year > 3000 || month == 0 || month > 12) return false; let monthLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) monthLength[1] = 29; if(day <= 0 || day > monthLength[month - 1]) return false; let today = new Date(); let date = new Date(year, month - 1, day); if ((date - today)/24/60/60/1000 >= 366 || (date - today)/24/60/60/1000 < -1) return false; return true; };
  125.  
  126. // Add to Date and Return CX Date String
  127. function dateAdd(days = 0, date = false) { let new_date = new Date(); if(date) { let year = +date.substring(0, 4); let month = +date.substring(4, 6); let day = +date.substring(6, 8); new_date = new Date(year, month - 1, day); }; new_date.setDate(new_date.getDate() + days); return new_date.getFullYear() +""+ (new_date.getMonth() +1).toString().padStart(2, '0') +""+ new_date.getDate().toString().padStart(2, '0'); };
  128.  
  129. // Convert CX Date String to Dashed Date String
  130. function toDashedDate(date){ return date.substring(0, 4).toString() + "-" + date.substring(4, 6).toString().padStart(2, '0') + "-" + date.substring(6, 8).toString().padStart(2, '0');}
  131. function toDashedDateShort(date){ return date.substring(2, 4).toString() + "-" + date.substring(4, 6).toString().padStart(2, '0') + "-" + date.substring(6, 8).toString().padStart(2, '0');}
  132.  
  133. // Get Weekday from CX Date String
  134. function dateWeekday(date){ let newdate = new Date(+date.substring(0, 4),(+date.substring(4, 6)-1),+date.substring(6, 8)); const weekday = {1:"Mon", 2:"Tue", 3:"Wed", 4:"Thu", 5:"Fri", 6:"Sat", 0:"Sun"}; return weekday[newdate.getDay()];
  135. };
  136.  
  137. // Get Time
  138. function getFlightTime(timestamp, timeonly = false){
  139. let date = new Date(timestamp);
  140. if (timeonly) {
  141. let hours = (date.getUTCDate() - 1)*24 + date.getUTCHours();
  142. return (hours > 0 ? hours.toString() + "hr " : "") + date.getUTCMinutes().toString() + "mins";
  143. } else {
  144. return date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1).toString().padStart(2, '0') + "-" + date.getUTCDate().toString().padStart(2, '0') + " " + date.getUTCHours().toString().padStart(2, '0') + ":" + date.getUTCMinutes().toString().padStart(2, '0');
  145. };
  146. };
  147.  
  148. // Append CSS to DOM Element (Default to Shadow Root)
  149. function addCss(cssString, target = shadowRoot) {
  150. var styleSheet = document.createElement("style");
  151. styleSheet.innerHTML = cssString;
  152. target.appendChild(styleSheet);
  153. }
  154.  
  155.  
  156. //============================================================
  157. // Get Stored Values
  158. //============================================================
  159.  
  160. // Set Search Parameters
  161.  
  162. let uef_from = value_get("uef_from", "HKG");
  163. let uef_to = value_get("uef_to", "TYO");
  164. let uef_date = value_get("uef_date", dateAdd(14));
  165. let uef_adult = value_get("uef_adult", "1");
  166. let uef_child = value_get("uef_child", "0");
  167.  
  168. // Saved Queries
  169.  
  170. let saved = value_get("saved",{});
  171. let saved_flights = value_get("saved_flights",{});
  172.  
  173. let cont_query = value_get("cont_query", "0") == "0" ? 0 : 1; ///cont_query/.test(window.location.hash); //urlParams.get('cont_query');
  174. let cont_batch = value_get("cont_batch", "0") == "0" ? 0 : 1;; ///cont_batch/.test(window.location.hash); //urlParams.get('cont_batch');
  175. let cont_saved = value_get("cont_saved", "0") == "0" ? 0 : 1;; ///cont_saved/.test(window.location.hash); //urlParams.get('cont_saved');
  176. let cont_ts = value_get("cont_ts", "0"); //window.location.hash.match(/cont_ts=([0-9]+)&/) ? window.location.hash.match(/cont_ts=([0-9]+)&/)[1] : 0;
  177. let redirect_search = value_get("redirect_search", "0"); ///cont_query/.test(window.location.hash); //urlParams.get('cont_query');
  178. let cxplanner_flights = value_get("cxplanner_flights", []) == [] ? 0 : value_get("cxplanner_flights", []); ///cont_query/.test(window.location.hash); //urlParams.get('cont_query');
  179.  
  180. function reset_cont_vars() {
  181. if(redirect_search != "0"){
  182. value_set("redirect_search", "0")
  183. } else {
  184. value_set("cont_query", "0");
  185. value_set("cont_batch", "0");
  186. value_set("cont_saved", "0");
  187. value_set("cont_ts", "0");
  188. }
  189. }
  190.  
  191. //============================================================
  192. // Initialize Shadow Root
  193. //============================================================
  194.  
  195. const shadowWrapper = document.createElement("div");
  196. shadowWrapper.style.margin = 0;
  197. shadowWrapper.style.padding = 0;
  198. const shadowRoot = shadowWrapper.attachShadow({ mode: "closed" });
  199. const shadowContainer = document.createElement("div");
  200. shadowContainer.classList.add("unelevated_container");
  201. shadowRoot.appendChild(shadowContainer);
  202.  
  203. if (debug && unsafeWindow.shadowRoot == undefined) {
  204. unsafeWindow.shadowRoot = shadowRoot;
  205. }
  206.  
  207. function initRoot() {
  208.  
  209. log("initRoot();")
  210.  
  211. addCss(styleCss);
  212.  
  213. const current_page = window.location.href;
  214.  
  215. if(current_page.indexOf("/redibe/IBEFacade") > -1){
  216.  
  217. log("initRoot /redibe/IBEFacade")
  218. waitForElm('h1').then((elm) => {
  219. if(elm.innerText == "Access Denied") {
  220. document.body.querySelector("body > p").innerHTML = `<a href="https://www.cathaypacific.com/cx/${lang.el}_${lang.ec}/book-a-trip/redeem-flights/redeem-flight-awards.html">Go back and try again.</a>`
  221. }
  222. });
  223.  
  224. } else if(current_page.indexOf("redeem-flight-awards.html") > -1){
  225. reset_cont_vars();
  226.  
  227. log("initRoot redeem-flight-awards.html")
  228. waitForElm('.redibe-v3-flightsearch form').then((elm) => {
  229. elm.before(shadowWrapper);
  230. initSearchBox();
  231. if(redirect_search != "0") {
  232. btn_search.innerHTML = lang.searching;
  233. btn_search.classList.add("searching");
  234. setTimeout(function(){
  235. location.href = redirect_search;
  236. },1500);
  237. } else {
  238. checkLogin();
  239. }
  240. });
  241.  
  242. } else if(current_page.indexOf("facade.html") > -1){
  243. reset_cont_vars();
  244.  
  245. log("initRoot facade.html")
  246. waitForElm('.ibered__search-panel').then((elm) => {
  247. elm.before(shadowWrapper);
  248. initSearchBox();
  249. checkLogin();
  250. });
  251.  
  252. } else if(current_page.indexOf("air/booking/availability") > -1 && cont_query){
  253.  
  254. log("initRoot air/booking/availability with cont_query")
  255. waitForElm('body > header').then((elm) => {
  256. const boxes = document.querySelectorAll("body > div");
  257. boxes.forEach( box => { box.remove() });
  258. addCss(`
  259. html, body {overflow-x:inherit !important;}
  260. header {overflow-x:hidden;}
  261. `, document.body);
  262. document.body.append(shadowWrapper);
  263. shadowContainer.classList.add("results_container");
  264. if(cxplanner_flights) initCXplannerBox();
  265. initSearchBox();
  266. checkLogin();
  267. });
  268.  
  269.  
  270. } else if(window.location.href.indexOf("air/booking/availability") > -1){
  271. reset_cont_vars();
  272.  
  273. log("initRoot air/booking/availability without cont_query")
  274. waitForElm('#section-flights .bound-route, #section-flights-departure .bound-route').then((elm) => {
  275. shadowWrapper.style.margin = "30px 20px 0px 20px";
  276. shadowWrapper.style.padding = 0;
  277. document.querySelector("#section-flights, #section-flights-departure").before(shadowWrapper);
  278. initSearchBox();
  279. checkLogin();
  280. });
  281.  
  282. } else if(window.location.href.indexOf("air/booking/complexAvailability") > -1){
  283. reset_cont_vars();
  284.  
  285. log("initRoot air/booking/complexAvailability")
  286. waitForElm('.mc-trips .bound-route').then((elm) => {
  287. shadowWrapper.style.margin = "30px 20px 0px 20px";
  288. shadowWrapper.style.padding = 0;
  289. document.querySelector(".mc-trips").before(shadowWrapper);
  290. initSearchBox();
  291. checkLogin();
  292. });
  293.  
  294. }
  295.  
  296. }
  297.  
  298. //============================================================
  299. // Localisation
  300. //============================================================
  301.  
  302. var lang = (browser_lang != "zh") ? {
  303. "ec" : "HK",
  304. "el": "en",
  305. "search" : "Search",
  306. "coffee" : "Did this tool help you? Buy me a coffee! ",
  307. "searching" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> Searching...",
  308. "searching_w_cancel" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> Searching... (Click to Stop)",
  309. "next_batch" : "Load More...",
  310. "search_20" : "Batch Availability for 20 Days",
  311. "search_all_cabins" : "Search Availability in All Cabins",
  312. "flights" : "Available Flights",
  313. "nonstop":"Non-Stop",
  314. "first" : "First",
  315. "business" : "Bus",
  316. "premium" : "Prem",
  317. "economy" : "Econ",
  318. "first_full" : "First Class",
  319. "business_full" : "Business Class",
  320. "premium_full" : "Premium Economy",
  321. "economy_full" : "Economy Class",
  322. "date" : "Date",
  323. "no_flights" : "No Redemption Availability",
  324. "expired" : "Search Next 20 (Requires Refresh)",
  325. "searching_cont" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> Please wait... (Page will refresh)",
  326. "super" : "SuperCharged Award Search",
  327. "error" : "Unknown Error... Try Again",
  328. "bulk_batch" : "Batch Search",
  329. "bulk_flights" : "Flights",
  330. "new_version" : "New Version Available:",
  331. "login" : "Reminder: Login before searching.",
  332. "tab_retrieve_fail" : "Failed to retrieve key. Try logging out and in again.",
  333. "key_exhausted" : "Key request quota exhausted, attempting to get new key...",
  334. "getting_key" : "Attempting to retrieve API key...",
  335. "invalid_code": "Invalid Destination Code",
  336. "invalid_date": "Invalid Date",
  337. "saved_queries": "Saved Flight Queries",
  338. "maxsegments":"Max 6 Sectors Accepted",
  339. "multi_book":"Book Multi-City Award",
  340. "query":"Search",
  341. "delete":"Remove",
  342. "search_selected":"Search All Saved",
  343. "book_multi":"Book Multicity Award",
  344. "nosaves":"You do not have any saved queries. Click on ♥ in batch results to save.",
  345. "advanced":"Advanced<br>Features",
  346. "loading":"Searching...",
  347. "hu_prompt":"Looks like you're searching a lot. You might want to read this.",
  348. "prem_title":"Enable Advanced Features",
  349. "prem_intro":"Hey fellow award flyer! Hope you're enjoying this plugin. How would you like to enhance your search experience even further? Here are some advanced features I think you're gonna love:",
  350. "prem_feat1":"Search multiple routes at once.",
  351. "prem_text1":"Flexible with your routings? Select multiple origins and destinations (up to 6 each), and find availability between those cities across multiple days with a single search! Also excellent for those of you trying to find availability for complex round-the-world itineraries! For example, searching for TPE,HKG to NRT,KIX will search for flights from Taipei and Hong Kong to Tokyo and Osaka.",
  352. "prem_feat2":"Bookmark your search queries.",
  353. "prem_text2":"Found availability for a date and want to save it to come back to later? Have a particular route that you're regularly watching for availability? Now you can save your date and itinerary simply by clicking a heart on the results page, and later search for that route again by simply selecting it from your list.",
  354. "prem_feat3":"Batch search your saved routes.",
  355. "prem_text3":"At the click of a button, search for all itineraries and dates you have previously saved, regardless of their origins, destinations, and dates. Say goodbye to endlessly changing your origin and destination search paramters!",
  356. "prem_feat4":"Submit oneworld Multi-City Award Search",
  357. "prem_text4":"From your saved routes, create a multi-city itinerary search that would have taken ages on Cathay's multicity search.",
  358. "prem_feat5":"And more to come!",
  359. "prem_donate":"To enable these extended features, please click below to view the Extras package on buymeacoffee.com.<a href='https://www.buymeacoffee.com/jayliutw/e/106024' target='_blank' class='unlock_btn'>Unlock Advanced Features</a>",
  360. "human":"Cathay's website needs you to prove you're a human:",
  361. "bot_check":"Please Complete Cathay Bot Check",
  362. "mixed":"Mixed Class Available via"
  363. } : {
  364. "ec" : "TW",
  365. "el": "zh",
  366. "search" : "搜尋",
  367. "coffee" : "這工具有幫到你嗎?歡迎請我喝杯咖啡呀!",
  368. "searching" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> 請稍後...",
  369. "searching_w_cancel" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> 請稍後... (點我暫停)",
  370. "next_batch" : "載人更多...",
  371. "search_20" : "批次搜尋 20 天可兌換航班",
  372. "search_all_cabins" : "批次搜尋全艙等航班",
  373. "flights" : "可兌換航班",
  374. "nonstop":"直飛",
  375. "first" : "頭等",
  376. "business" : "商務",
  377. "premium" : "豪經",
  378. "economy" : "經濟",
  379. "first_full" : "頭等艙",
  380. "business_full" : "商務艙",
  381. "premium_full" : "特選經濟艙",
  382. "economy_full" : "經濟艙",
  383. "date" : "日期",
  384. "no_flights" : "查無獎勵機位",
  385. "expired" : "再搜尋 20 天 (畫面需重整)",
  386. "searching_cont" : "<img src='https://book.cathaypacific.com"+static_path+"common/skin/img/icons/cx/icon-loading.gif'> 請稍後... (視窗將會刷新)",
  387. "super" : "SUPERCharged Award Search",
  388. "error" : "不明錯誤... 再試一次",
  389. "bulk_batch" : "批次查詢",
  390. "bulk_flights" : "航班",
  391. "new_version" : "有新版本可更新:",
  392. "login" : "提醒: 請先登入後再搜尋。",
  393. "tab_retrieve_fail" : "無法取得金鑰,請試著登出再重新登入。",
  394. "key_exhausted" : "金鑰查詢額度用盡,正嘗試取得新金鑰...",
  395. "getting_key" : "正在嘗試取得 API 金鑰...",
  396. "invalid_code": "目的地代碼錯誤",
  397. "invalid_date": "日期錯誤",
  398. "saved_queries": "收藏的航程日期",
  399. "maxsegments":"至多可選擇 6 航段",
  400. "multi_book":"兌換多城市行程",
  401. "query":"查詢",
  402. "delete":"刪除",
  403. "search_selected":"批次查詢收藏行程",
  404. "book_multi":"多目的地行程預定",
  405. "nosaves":"您沒有收藏任何行程。您可以在批次結果頁點擊愛心 ♥ 收藏行程。",
  406. "advanced":"進階功能<br>啟用說明",
  407. "loading":"查詢中...",
  408. "hu_prompt":"你好像查詢量很大...或許你該看看這裡!",
  409. "prem_title":"啟用神器進階功能",
  410. "prem_intro":"嗨,哩程同好們!希望這個插件有幫助到各位!有沒有想再更進一步提升查票體驗呢?那別錯過以下幾個相信一定會讓各位愛不釋手的進階功能:",
  411. "prem_feat1":"一次查詢多個路線",
  412. "prem_text1":"你也是行程彈性、有票就飛一族嗎?進階版支援同時輸入多個出發地和目的地(至多各 6 個),就可以輕鬆批次查找多個城市之間跨日期的獎勵機位!這對想要組合複雜的環球票行程的哩民們也是超級方便!比如,只要出發地選 TPE,KHH、目的地選 NRT,KIX,就可以同時輕鬆查找台北到東京、大阪和高雄到東京、大阪的機票!",
  413. "prem_feat2":"把行程存到最愛",
  414. "prem_text2":"找到了某個航線有位子的日期,想要收藏起來之後回來查?還是已經鎖定某個日期和航線,定期回來關注有沒有放票?現在只要在批次搜尋結果頁簡單按個愛心,就可以把選定的航程日期收藏到自己的最愛起來,之後回來就可以輕鬆從清單裡隨選即查!",
  415. "prem_feat3":"一批次查詢收藏的路線",
  416. "prem_text3":"只要一鍵就能批次查詢所有收藏清單裡的行程和日期,所有結果一次呈現,一目了然!再也不必來來回回改搜尋條件和日期!",
  417. "prem_feat4":"多城市行程(環球票)查詢",
  418. "prem_text4":"在你收藏的行程清單裡,簡單勾選你的行程(最多6個)就能組出國泰網站「多個城市」查詢的參數,並直接搜尋和選航班!",
  419. "prem_feat5":"還有更多待產中,請拭目以待!",
  420. "prem_donate":"若有興起解鎖上述進階功能,請點擊下方按鈕至 buymeacoffee.com 查看進階功能 Extras 包:<a href='https://www.buymeacoffee.com/jayliutw/e/106024' target='_blank' class='unlock_btn'>解鎖進階功能</a>",
  421. "human":"國泰網站需要你證明你是真人:",
  422. "bot_check":"請解除國泰網站機器人檢查",
  423. "mixed":"潛在混艙航班經由"
  424. };
  425.  
  426. //============================================================
  427. // Search Box
  428. //============================================================
  429.  
  430. const searchBox = document.createElement("div");
  431. searchBox.innerHTML = `
  432. <div class='unelevated_form'>
  433. <div class='unelevated_title'><a href="https://www.cathaypacific.com/cx/${lang.el}_${lang.ec}/book-a-trip/redeem-flights/redeem-flight-awards.html">Unelevated Award Search</a></div>
  434.  
  435. <div class='login_prompt hidden'><span class='unelevated_error'><a href="` + login_url + `">` + lang.login + `</a></span></div>
  436.  
  437. <div class='unelevated_update hidden'><a href='https://pse.is/cxupdate' target='_blank'>`+ lang.new_version + ` <span id='upd_version'>3.2.1</span> &raquo;</a></div>
  438. <div class='unelevated_prem_desc unelevated_prem_hidden'>
  439. <span class="prem_title">` + lang.prem_title + `</span>
  440. <div class="prem_content">
  441. <span class="prem_intro">` + lang.prem_intro + `</span>
  442. <ul>
  443. <li><span class="feat_title">` + lang.prem_feat1 + `</span><span class="feat_text">` + lang.prem_text1 + `</span></li>
  444. <li><span class="feat_title">` + lang.prem_feat2 + `</span><span class="feat_text">` + lang.prem_text2 + `</span></li>
  445. <li><span class="feat_title">` + lang.prem_feat3 + `</span><span class="feat_text">` + lang.prem_text3 + `</span></li>
  446. <li><span class="feat_title">` + lang.prem_feat4 + `</span><span class="feat_text">` + lang.prem_text4 + `</span></li>
  447. <li><span class="feat_title">` + lang.prem_feat5 + `</span></li>
  448. </ul>
  449. <span class="prem_intro">` + lang.prem_donate + `</span>
  450. </div>
  451. </div>
  452.  
  453. <div class='unelevated_faves unelevated_faves_hidden'>
  454. <div class="faves_tabs">
  455. <a href="javascript:void(0);" class="tabs tab_queries">Routes</a>
  456. <a href="javascript:void(0);" class="tabs tab_flights">Flights</a>
  457. </div>
  458. <a href="javascript:void(0);" class="search_selected">`+lang.search_selected+` &raquo;</a>
  459. <!--<a href="javascript:void(0);" class="search_multicity">${lang.book_multi} &raquo;</a>-->
  460. <div class="saved_flights"></div>
  461. <div class="saved_queries"></div>
  462. </div>
  463.  
  464. <div class="unelevated_saved"><a href="javascript:void(0);"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="heart_save" viewBox="0 0 16 16"> <path d="M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z"></path></svg><span>0</span></a></div>
  465. <div class="unelevated_premium"><a href="javascript:void(0);"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="premium_crown"><!--! Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M576 136c0 22.09-17.91 40-40 40c-.248 0-.4551-.1266-.7031-.1305l-50.52 277.9C482 468.9 468.8 480 453.3 480H122.7c-15.46 0-28.72-11.06-31.48-26.27L40.71 175.9C40.46 175.9 40.25 176 39.1 176c-22.09 0-40-17.91-40-40S17.91 96 39.1 96s40 17.91 40 40c0 8.998-3.521 16.89-8.537 23.57l89.63 71.7c15.91 12.73 39.5 7.544 48.61-10.68l57.6-115.2C255.1 98.34 247.1 86.34 247.1 72C247.1 49.91 265.9 32 288 32s39.1 17.91 39.1 40c0 14.34-7.963 26.34-19.3 33.4l57.6 115.2c9.111 18.22 32.71 23.4 48.61 10.68l89.63-71.7C499.5 152.9 496 144.1 496 136C496 113.9 513.9 96 536 96S576 113.9 576 136z"/></svg><span>` + lang.advanced + `</span></a></div>
  466.  
  467. <div class='labels'>
  468. <a href="javascript:void(0);" class="switch"><svg height="16px" width="16px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 365.352 365.352" xml:space="preserve" stroke-width="0" transform="rotate(180)"><g id="SVGRepo_bgCarrier" stroke-width="0"></g> <path d="M363.155,169.453l-14.143-14.143c-1.407-1.407-3.314-2.197-5.304-2.197 c-1.989,0-3.897,0.79-5.304,2.197l-45.125,45.125v-57.503c0-50.023-40.697-90.721-90.721-90.721H162.3c-4.143,0-7.5,3.358-7.5,7.5 v20c0,4.142,3.357,7.5,7.5,7.5h40.26c30.725,0,55.721,24.996,55.721,55.721v57.503l-45.125-45.125 c-1.407-1.407-3.314-2.197-5.304-2.197c-1.989,0-3.896,0.79-5.304,2.197l-14.143,14.143c-1.406,1.406-2.196,3.314-2.196,5.303 c0,1.989,0.79,3.897,2.196,5.303l82.071,82.071c1.465,1.464,3.385,2.197,5.304,2.197c1.919,0,3.839-0.732,5.304-2.197 l82.071-82.071c1.405-1.406,2.196-3.314,2.196-5.303C365.352,172.767,364.561,170.859,363.155,169.453z"></path> <path d="M203.052,278.14h-40.26c-30.725,0-55.721-24.996-55.721-55.721v-57.503l45.125,45.126 c1.407,1.407,3.314,2.197,5.304,2.197c1.989,0,3.896-0.79,5.304-2.197l14.143-14.143c1.406-1.406,2.196-3.314,2.196-5.303 c0-1.989-0.79-3.897-2.196-5.303l-82.071-82.071c-2.93-2.929-7.678-2.929-10.607,0L2.196,185.292C0.79,186.699,0,188.607,0,190.596 c0,1.989,0.79,3.897,2.196,5.303l14.143,14.143c1.407,1.407,3.314,2.197,5.304,2.197s3.897-0.79,5.304-2.197l45.125-45.126v57.503 c0,50.023,40.697,90.721,90.721,90.721h40.26c4.143,0,7.5-3.358,7.5-7.5v-20C210.552,281.498,207.194,278.14,203.052,278.14z"></path> </svg></a>
  469. <label class="labels_left"><span>From</span>
  470. <input tabindex="1" type='text' id='uef_from' name='uef_from' placeholder='TPE' value='` + uef_from + `'><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="clear_from" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"></path> </svg></a></label>
  471. <label class="labels_right"><span>Adults</span>
  472. <input tabindex="4" type='number' inputmode='decimal' onClick='this.select();' id='uef_adult' name='uef_adult' placeholder='Adults' value='` + uef_adult + `'></label>
  473. <label class="labels_left"><span>To</span>
  474. <input tabindex="2" type='text' id='uef_to' name='uef_to' placeholder='TYO' value='` + uef_to + `'><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="clear_to" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"></path> </svg></label>
  475. <label class="labels_right"><span>Children</span>
  476. <input tabindex="5" type='number' inputmode='decimal' onClick='this.select();' id='uef_child' name='uef_child' placeholder='Children' value='` + uef_child + `'></label>
  477. <label class="labels_left"><span>Date</span>
  478. <input tabindex="3" class='uef_date' onClick='this.setSelectionRange(6, 8);' id='uef_date' inputmode='decimal' name='uef_date' placeholder='` + dateAdd(30) + `' value='` + uef_date + `'></label>
  479. <button class='uef_search'>` + lang.search + `</button>
  480. </div>
  481.  
  482. <div class='unelevated_sub'><a href='https://jayliu.net/buymeacoffee' target='_blank'>` + lang.coffee + `</a><span class='coffee_emoji'>☕</span></div>
  483. </div>
  484.  
  485. <div class='multi_box hidden'>
  486. <select id="multi_cabin">
  487. <option value="Y">${lang.economy_full}</option>
  488. <option value="W">${lang.premium_full}</option>
  489. <option value="C">${lang.business_full}</option>
  490. <option value="F">${lang.first_full}</option>
  491. </select>
  492. <label class="labels_right"><span>Adults</span>
  493. <input type='number' inputmode='decimal' onClick='this.select();' id='multi_adult' name='multi_adult' placeholder='Adults' value='1'></label>
  494. <label class="labels_right"><span>Children</span>
  495. <input type='number' inputmode='decimal' onClick='this.select();' id='multi_child' name='multi_child' placeholder='Children' value='0'></label>
  496. <a href="javascript:void(0)" class='multi_search'>` + lang.multi_book + `</a>
  497. </div>
  498.  
  499. <div class='bulk_box'>
  500. <div class="bulk_results bulk_results_hidden">
  501. <div class="filters">
  502. <label><input type="checkbox" id="filter_nonstop">${lang.nonstop}</label>
  503. <label><input type="checkbox" id="filter_first" checked>${lang.first}</label>
  504. <label><input type="checkbox" id="filter_business" checked>${lang.business}</label>
  505. <label><input type="checkbox" id="filter_premium" checked>${lang.premium}</label>
  506. <label><input type="checkbox" id="filter_economy" checked>${lang.economy}</label>
  507. </div>
  508. <table class='bulk_table show_first show_business show_premium show_economy'><thead><th class='bulk_date'>` + lang.date + `</th><th class='bulk_flights'>` + lang.flights + ` <span class='info-x info-f'>` + lang.first + `</span><span class='info-x info-j'>` + lang.business + `</span><span class='info-x info-p'>` + lang.premium + `</span><span class='info-x info-y'>` + lang.economy + `</span></th></thead><tbody></tbody></table>
  509. </div>
  510. <div class="bulk_footer">
  511. <div class="bulk_footer_container">
  512. <button class='bulk_submit'>` + lang.search_20 + `</button>
  513. <div class="bulk_error bulk_error_hidden"><span></span></div>
  514. </div>
  515. </div>
  516. </div>
  517. <div id="encbox"></div>
  518. `;
  519.  
  520. //============================================================
  521. // Styles
  522. //============================================================
  523.  
  524. const styleCss = `
  525. .unelevated_form * { box-sizing:border-box; -webkit-text-size-adjust: none;}
  526. .unelevated_form a, .bulk_box a { color:#367778; }
  527. .unelevated_form input:focus { outline: none; }
  528. .results_container { max-width: 900px; margin: 0 auto; padding: 20px 20px; }
  529. @media screen and (max-width: 500px) { .results_container { padding:20px 10px; } }
  530. .cont_query .modal {display:none !important;}
  531. .unelevated_form { position:relative;transition: margin-left 0.7s ease-out;z-index: 11; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Rg, sans-serif; border: 1px solid #bcbec0; margin:10px 0; background: #f7f6f0; padding: 8px 0px 8px 8px; border-top: 5px solid #367778; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%);}
  532. .unelevated_form.uef_collapsed { margin-left:-90%;}
  533. .unelevated_title {font-weight: 400; font-size: 17px; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Rg, sans-serif; color: #2d2d2d; margin: 5px; height:26px;}
  534. .unelevated_title a {text-decoration:none; color: #2d2d2d;}
  535. .unelevated_form .unelevated_premium { position: absolute;
  536. right: 10px;
  537. top: 6px;
  538. background: linear-gradient(339deg, #fdf98b, #e4c63f,#fef985,#eec946);
  539. box-shadow: -1px 1px 3px rgb(155 95 70 / 40%);
  540. display: inline-block;
  541. border-radius: 5px;
  542. padding: 1px;
  543. }
  544. .unelevated_form .unelevated_premium a, .unelevated_form .unelevated_premium a:hover, .unelevated_form .unelevated_premium a:active, .unelevated_form .unelevated_premium a:focus {
  545. font-size: 15px; line-height: 28px; text-decoration: none !important; color: #4d3b0e; display: block; height: 28px;
  546. background: linear-gradient(180deg, #fcd54a, #e8b524,#ffd561,#f7eb6d);
  547. border-radius: 5px;
  548. padding: 1px 8px;
  549. }
  550. .unelevated_form .unelevated_premium svg.premium_crown { width: 16px;margin-right: 6px;height: 26px;display: inline-block;}
  551. .unelevated_form .unelevated_premium svg.premium_crown path { fill: #bf8028;}
  552. .unelevated_form .unelevated_premium a span {
  553. display:inline-block;
  554. padding: 3px 0;
  555. vertical-align: top;
  556. line-height: 22px;
  557. font-size: 18px;
  558. zoom:0.5;
  559. text-transform:uppercase;
  560. }
  561. .unelevated_form .unelevated_saved { position:absolute; right:10px;top:6px;background: #ae4b4b; display: inline-block; border-radius: 5px; padding: 3px 10px;}
  562. .unelevated_form .unelevated_saved a, .unelevated_form .unelevated_saved a:hover, .unelevated_form .unelevated_saved a:active, .unelevated_form .unelevated_saved a:focus {font-size: 15px; line-height: 24px; text-decoration: none !important; color: white; display: block; height: 24px;}
  563. .unelevated_form .unelevated_saved svg.heart_save { width: 16px;margin-right: 6px;height: 24px;display: inline-block;}
  564. .unelevated_form .unelevated_saved svg.heart_save path { fill: #ff8b8b;}
  565. .unelevated_form .unelevated_saved a span {vertical-align: top; line-height: 24px;}
  566. .unelevated_container .unelevated_saved{ display:none; }
  567.  
  568. .elevated_on .unelevated_prem_desc,
  569. .elevated_on .unelevated_premium {
  570. display:none;
  571. }
  572.  
  573. .unelevated_prem_desc {
  574. line-height: 24px;
  575. overflow:scroll;
  576. transition: all 0.5s ease-out;
  577. background:#fdfefe;
  578. border: 1px solid #bebebe;
  579. margin-right: 8px;
  580. box-shadow: inset 0px 0px 4px 0px rgb(0 0 0 / 10%);
  581. position: absolute;
  582. top: 0px;
  583. right: 0;
  584. left: 8px;
  585. z-index: 100;
  586. height: calc(100% + 14px);
  587. margin-top: 42px;
  588. opacity:1;
  589. padding:10px;
  590. }
  591. .unelevated_prem_hidden {height:0;opacity:0; z-index: -1;}
  592. .unelevated_form .autocomplete-items div:hover{
  593. background-color: #e9e9e9;
  594. }
  595. .unelevated_form .autocomplete-active {
  596. /*when navigating through the items using the arrow keys:*/
  597. background-color: DodgerBlue !important;
  598. color: #ffffff;
  599. }
  600.  
  601. .prem_title {
  602. display:block;
  603. text-align:center;
  604. font-size:17px;
  605. font-weight:bold;
  606. margin-bottom:8px;
  607. color:#367778;
  608. }
  609.  
  610. .prem_intro {
  611. display:block;
  612. margin:10px;
  613. font-size:14px;
  614. }
  615.  
  616. #activation_input {
  617. font-size:17px;
  618. margin:10px;
  619. width: calc(100% - 20px);
  620. padding: 10px;
  621. text-transform:uppercase;
  622. }
  623.  
  624. .unelevated_prem_desc ul {
  625. list-style-type: disclosure-closed;
  626. padding-left:23px;
  627. }
  628.  
  629. .unelevated_prem_desc li{
  630. list-style-type: disclosure-closed;
  631. margin-bottom:5px;
  632. padding-left:0;
  633. }
  634.  
  635. .feat_title {
  636. display:block;
  637. font-size:17px;
  638. font-weight:bold;
  639. margin-bottom:5px;
  640. color:#ae4b4b;
  641. }
  642.  
  643.  
  644. .feat_text {
  645. display:block;
  646. font-size:14px;
  647. color:#666;
  648. }
  649.  
  650. .unelevated_form .unlock_btn{
  651. display: block;
  652. margin: 10px auto;
  653. padding: 5px;
  654. border-radius: 5px;
  655. text-align: center;
  656. text-decoration: none;
  657. width: 200px;
  658. background: linear-gradient(180deg, #fcd54a, #e8b524,#ffd561,#f7eb6d);
  659. color: rgb(130, 85, 50);
  660. border: 1px solid #f8c19c;
  661. box-shadow: -1px 1px 3px rgba(0,0,0,0.3);
  662. font-weight:bold;
  663. }
  664.  
  665. .unelevated_faves .saved_queries{
  666. display:block;
  667. }
  668.  
  669. .unelevated_faves .saved_flights{
  670. display:none;
  671. }
  672. .unelevated_faves.flights .saved_queries {
  673. display:none;
  674. }
  675.  
  676. .unelevated_faves.flights .saved_flights {
  677. display:block;
  678. }
  679. .faves_tabs{
  680. margin-left: 10px;
  681. }
  682. .faves_tabs a.tabs {
  683. display: inline-block;
  684. border-radius: 5px 5px 0 0;
  685. text-decoration: none;
  686. font-size: 12px;
  687. line-height: 15px;
  688. margin-right: 5px;
  689. height: 25px;
  690. padding: 5px 10px;
  691. margin-top: 7px;
  692. }
  693. .unelevated_faves .tab_queries,
  694. .unelevated_faves.flights .tab_flights {
  695. background:#357677;
  696. color:white;
  697. }
  698. .unelevated_faves .tab_flights,
  699. .unelevated_faves.flights .tab_queries {
  700. background: #cec9b9;
  701. color:#444444;
  702. }
  703.  
  704. .unelevated_faves .saved_queries,
  705. .unelevated_faves .saved_query {
  706. list-style: none;
  707. }
  708. .unelevated_faves .saved_queries {
  709. margin: 0 10px;
  710. padding:0px;
  711. border-top: 2px solid #367778;
  712. position: absolute;
  713. left: 0;
  714. right: 0;
  715. bottom: 0;
  716. top: 32px;
  717. overflow: scroll;
  718. }
  719. .saved_queries:empty:after {
  720. display:flex;
  721. content:"` + lang.nosaves + `";
  722. text-align: center;
  723. font-size: 14px;
  724. align-items: center;
  725. justify-content: center;
  726. height: 95%;
  727. opacity: 40%;
  728. line-height: 25px;
  729. margin: 0 25px;
  730.  
  731. }
  732. .unelevated_faves .saved_query {
  733. position:relative;
  734. margin: 0;
  735. padding:3px 10px;
  736. font-size:12px;
  737. font-family: "Cathay Sans EN", CathaySans_Md, sans-serif;
  738. }
  739. .unelevated_faves .saved_query label {
  740. margin: 0;
  741. min-width: 150px;
  742. display: inline-block;
  743. }
  744. .unelevated_faves .saved_query input {
  745. vertical-align:-2px;
  746. margin-right:5px;
  747. //display:none;
  748. }
  749. .unelevated_faves .saved_query:nth-child(odd){
  750. background: #f1efe6;
  751. }
  752.  
  753.  
  754.  
  755.  
  756. .unelevated_faves .saved_flights {
  757. list-style: none;
  758. }
  759. .unelevated_faves .saved_flights {
  760. margin: 0 10px;
  761. padding:0px;
  762. border-top: 2px solid #367778;
  763. position: absolute;
  764. left: 0;
  765. right: 0;
  766. bottom: 0;
  767. top: 32px;
  768. overflow: scroll;
  769. }
  770. .saved_flights:empty:after {
  771. display:flex;
  772. content:"` + lang.nosaves + `";
  773. text-align: center;
  774. font-size: 14px;
  775. align-items: center;
  776. justify-content: center;
  777. height: 95%;
  778. opacity: 40%;
  779. line-height: 25px;
  780. margin: 0 25px;
  781.  
  782. }
  783. .unelevated_faves .saved_flights .saved_flight {
  784. position:relative;
  785. margin: 0;
  786. padding:3px 10px;
  787. font-size:10px;
  788. font-family: "Cathay Sans EN", CathaySans_Md, sans-serif;
  789. }
  790. .unelevated_faves .saved_flights .saved_flight label {
  791. margin: 0 0 5px 0;
  792. min-width: 150px;
  793. display: inline-block;
  794. }
  795. .unelevated_faves .saved_flights .saved_flight input {
  796. vertical-align:-2px;
  797. margin-right:5px;
  798. }
  799. .unelevated_faves .saved_flights .saved_flight:nth-child(odd){
  800. background: #f1efe6;
  801. }
  802. .unelevated_faves .saved_flights .saved_flight label > span {
  803. display:inline-block;
  804. vertical-align:top;
  805. }
  806. span.sf_date {
  807. display:block;
  808. }
  809. span.sf_route {
  810. background: #CCCCCC;
  811. padding: 2px 6px;
  812. border-radius: 5px 0 0 5px;
  813. display: inline-block;
  814. }
  815. span.sf_flights {
  816. background: #e3cfc8;
  817. padding: 2px 6px;
  818. border-radius: 0 5px 5px 0;
  819. display: inline-block;
  820. }
  821. span.sf_avail > span {
  822. display: inline-block;
  823. line-height: 11px;
  824. font-size: 10px;
  825. padding: 2px 4px;
  826. color: white;
  827. font-weight: normal;
  828. border-radius: 3px;
  829. margin-left: 3px;
  830. height: 15px;
  831. }
  832. span.sf_avail .av_j { background: #002e6c;}
  833. span.sf_avail .av_f { background: #832c40;}
  834. span.sf_avail .av_p { background: #487c93;}
  835. span.sf_avail .av_y { background: #016564;}
  836.  
  837. .multi_box{
  838. height: 67px;
  839. background: #f7f6f0;
  840. border: 1px solid #bcbec0;
  841. position: relative;
  842. margin-top: -11px;
  843. margin-bottom: -67px;
  844. z-index: 10;
  845. padding: 10px;
  846. box-sizing: border-box;
  847. display: flex; flex-wrap: wrap;
  848. }
  849. .multi_box * {
  850. box-sizing: border-box;
  851.  
  852. }
  853. .multi_box.hidden{
  854. display:none;
  855. }
  856. .multi_box select{
  857. border: 1px solid #bcbec0;
  858. height: 45px;
  859. width: calc(35% - 10px);
  860. margin-right:10px;
  861. display:inline-block;
  862. vertical-align:top;
  863. padding: 10px;
  864. }
  865.  
  866. .multi_box label { margin:0; display: inline-block; position: relative; width: calc(20% - 10px); margin-right:10px; }
  867. .multi_box label > span { position: absolute; top: 0px; left: 5px; color: #66686a; font-family: Cathay Sans EN, CathaySans_Rg, sans-serif; line-height: 25px; font-size: 10px;}
  868. .multi_box input { font-family: Cathay Sans EN, CathaySans_Rg, sans-serif; padding: 19px 5px 5px 5px; border-radius: 0px; border: 1px solid #bcbec0; display: inline-block; margin: 0px 8px 8px 0px; height: 45px; width: 100%; font-size:16px}
  869. .multi_box a.multi_search { background-color: #367778;
  870. overflow: hidden;
  871. text-overflow: ellipsis;
  872. border: none;
  873. color: white;
  874. vertical-align: top;
  875. margin: 0px;
  876. height: 45px;
  877. width: calc(25%);
  878. font-size: 11px;
  879. text-align: center;
  880. display: flex;
  881. flex-wrap: wrap;
  882. align-content: center;
  883. justify-content: center;
  884. text-decoration: none;
  885. padding: 0px 10px;
  886. line-height:15px;
  887. }
  888. a.switch:active {
  889. margin-top: 40px;
  890. margin-left: -18px;
  891. }
  892. a.switch {
  893. display: inline-block;
  894. position: absolute;
  895. background: white;
  896. z-index: 15;
  897. margin-top: 38px;
  898. border: 1px solid #bcbec0;
  899. text-decoration: none;
  900. padding: 2px 10px;
  901. border-radius: 15px;
  902. left: 32.5%;
  903. margin-left: -19px;
  904. height: 22px;
  905. line-height: 16px;
  906. }
  907. a.switch svg path {
  908. fill: #AAA;
  909. }
  910. .unelevated_form .labels { display: flex; flex-wrap: wrap;}
  911. .unelevated_form .labels label { margin:0; display: inline-block; position: relative; width:50%; padding: 0px 8px 0px 0px; }
  912. .unelevated_form .labels label.labels_left {width:65%;}
  913. .unelevated_form .labels label.labels_right {width:35%;}
  914. .unelevated_form .labels label > span { position: absolute; top: 0px; left: 5px; color: #66686a; font-family: Cathay Sans EN, CathaySans_Rg, sans-serif; line-height: 25px; font-size: 10px;}
  915. .unelevated_form .labels input { font-family: Cathay Sans EN, CathaySans_Rg, sans-serif; padding: 19px 5px 5px 5px; border-radius: 0px; border: 1px solid #bcbec0; display: inline-block; margin: 0px 8px 8px 0px; height: 45px; width: 100%; font-size:16px}
  916. svg.clear_from, svg.clear_to {
  917. position: absolute;
  918. right: 20px;
  919. top: 15px;
  920. opacity: 30%;
  921. }
  922.  
  923. .unelevated_form button.uef_search { background-color: #367778; white-space:nowrap; overflow:hidden;text-overflow:ellipsis;border: none; color: white; display: inline-block;vertical-align: top; margin: 0px; height: 45px; width: calc(35% - 8px); font-size:15px}
  924. .unelevated_sub { line-height:25px; vertical-align:top;} .coffee_emoji {display:inline-block; line-height:25px; font-size: 25px; margin-left: 6px; vertical-align: top;}
  925. .unelevated_sub a { line-height:25px; vertical-align:top; font-family: Cathay Sans EN, CathaySans_Bd, sans-serif; font-size: 14px !important; text-decoration:underline dotted !important; margin: 0px; color: #ae4b4b !important; font-weight: bold;}
  926. .unelevated_sub a:after { content:none !important; }
  927.  
  928.  
  929. .heavy_user_prompt {
  930. background: linear-gradient(339deg, #fdf98b, #e4c63f,#fef985,#eec946);
  931. box-shadow: -1px 1px 3px rgb(155 95 70 / 40%);
  932. border-radius: 5px;
  933. padding: 1px;
  934. margin-right:10px;
  935. margin-top:10px;
  936. }
  937. .heavy_user_prompt a {
  938. font-size: 15px; min-height:20px; padding:10px; line-height: 20px; text-decoration: underline !important; color: #802d2d; display: block;
  939. background: linear-gradient(180deg, #fcd54a, #e8b524,#ffd561,#f7eb6d);
  940. border-radius: 5px;
  941. padding: 10px 8px;
  942. text-align:center;
  943. }
  944.  
  945.  
  946. a.uef_toggle, a.uef_toggle:hover { background: #367778; display: block; position: absolute; right: -1px; top: -5px; padding-top:5px; width: 30px; text-align: center; text-decoration: none; color: white !important; padding-bottom: 5px; }
  947. a.uef_toggle:after {content:'«'} .uef_collapsed a.uef_toggle:after {content : '»'}
  948. .bulk_box {min-height: 60px; transition: margin-top 0.7s ease-out;background: #f7f6f0; border: 1px solid #bcbec0; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%); margin-top: -11px !important; margin-bottom: 20px; z-index: 9; position: relative;}
  949. .bulk_box_hidden {position:relative; margin-top:-80px;}
  950. .bulk_results {transition: all 0.5s ease-out; min-height: 30px; margin: 0 10px 10px 10px;}
  951. .bulk_results_hidden { height:0; min-height:0; margin:0; overflow:hidden; transition: all 0.5s ease-out;}
  952. .filters {
  953. text-align: center;
  954. font-size: 12px;
  955. padding: 10px 0px 10px 0px;
  956. position: sticky;
  957. top: 0;
  958. z-index: 10;
  959. background: #f7f6f0;
  960. border-bottom: 1px solid #c6c2c1;
  961. }
  962. .filters input{
  963. vertical-align: -2px;
  964. margin-right:5px;
  965. margin-left:10px;
  966. }
  967. .filters label {
  968. display:inline-block;
  969. }
  970. .bulk_table { width:100%; border: 1px solid #c6c2c1; margin-top: -1px; font-size: 12px; border-spacing: 0; border-collapse: collapse; }
  971. .bulk_table th { text-align:center !important; font-weight:bold; background: #ebedec; line-height:17px; font-size: 12px; }
  972. .bulk_table td { background:white; }
  973. .bulk_table tr:nth-child(even) td { background:#f9f9f9; }
  974. .bulk_table th, .bulk_table td { border: 1px solid #c6c2c1; padding: 5px; }
  975. .bulk_table .bulk_date { width:80px; text-align:center; }
  976. .bulk_table .bulk_date a { text-decoration:underline !important; font-family: "Cathay Sans EN", CathaySans_Md, sans-serif; font-weight: 400; display:block;margin-bottom:5px;}
  977. .unelevated_container .bulk_table td.bulk_flights > div { display:none; }
  978. .unelevated_container .bulk_table td.bulk_flights > div:first-of-type { display:block; }
  979. .unelevated_container .bulk_table td.bulk_flights .flight_title { display:none; }
  980. .bulk_table td.bulk_flights { padding:5px 5px 0 5px; font-family: "Cathay Sans EN", CathaySans_Rg, sans-serif; font-weight: 400; line-height:0px; }
  981. .bulk_table td.bulk_flights .flight_list:empty:after {
  982. display: block;
  983. height: 24px;
  984. content: "` + lang.no_flights+ `";
  985. margin-bottom: 5px;
  986. margin-top: -3px;
  987. margin-left: 10px;
  988. font-family: "Cathay Sans EN", CathaySans_Rg, sans-serif;
  989. font-weight: 400;
  990. line-height: 24px;
  991. color: #AAA;
  992. }
  993. .bulk_table td.bulk_flights .flight_list span.bulk_response_error { line-height: 24px;}
  994. .bulk_table .bulk_flights .bulk_no_flights { display:block;padding-bottom:5px; }
  995. .bulk_response_error { display:block;padding-bottom:5px;padding-left:5px;padding-right:5px; color:red; }
  996. .bulk_table .flight_title { display: block; background: #dde8e8; font-size: 12px; line-height: 15px; padding: 3px 7px; margin-bottom: 7px; margin-top: 2px; border-bottom: 3px solid #357677; position:relative; }
  997. .bulk_go_book { float:right; margin-right:5px; margin-left:10px; font-weight:bold;}
  998. a.bulk_save, a.bulk_save:hover, a.bulk_save:active { outline: none !important; float: left; margin-right:5px; text-decoration: none !important;}
  999. a.bulk_save svg.heart_save { width: 12px; height: 12 px;display: inline-block;}
  1000. a.bulk_save svg.heart_save path { fill:gray;}
  1001. a.bulk_saved svg.heart_save path { fill:#d65656; }
  1002. a.bulk_save *, a.bulk_go_book * { pointer-events: none; }
  1003. .flight_wrapper {
  1004. position:relative;
  1005. display:inline-block;
  1006. }
  1007. .flight_info {
  1008. position: absolute;
  1009. left: 0;
  1010. top: 37px;
  1011. background: #e0e0e0;
  1012. border: 1px solid #bbb;
  1013. padding: 6px 10px;
  1014. display: none;
  1015. line-height: 18px;
  1016. z-index: 15;
  1017. border-radius: 5px;
  1018. white-space: nowrap;
  1019. box-shadow: 0px 0px 5px rgb(0 0 0 / 30%);
  1020. }
  1021. .flight_info > span {
  1022. display:block;
  1023. }
  1024. .flight_info span.info_flight {
  1025. font-weight:bold;
  1026. font-family: CathaySans_Bd, sans-serif;
  1027. }
  1028. .info_dept > span, .info_arr > span {
  1029. display: inline-block;
  1030. width: 50px;
  1031. color: #999;
  1032. font-weight: bold;
  1033. font-family: CathaySans_Md, sans-serif;
  1034. }
  1035. span.info_transit,
  1036. span.info_duration {
  1037. margin: 8px 0px;
  1038. background: #ededed;
  1039. border-radius: 5px;
  1040. padding: 2px 8px;
  1041. text-align: center;
  1042. font-size: 11px;
  1043. color: #888;
  1044. }
  1045. span.info_duration {
  1046. margin-bottom:5px;
  1047. }
  1048. .flight_item.active + .flight_info {
  1049. display:block;
  1050. }
  1051. .flight_item {
  1052. transition: all 0.5s ease-in;
  1053. background: #e0e0e0;
  1054. line-height:15px !important;
  1055. border-radius: 5px;
  1056. margin-bottom: 5px;
  1057. white-space: nowrap;
  1058. font-size:12px;
  1059. font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Rg, sans-serif;
  1060. font-weight:400;
  1061. position:relative;
  1062. display: inline-block;
  1063. overflow:hidden;
  1064.  
  1065. max-width:0px;
  1066. padding: 6px 0px;
  1067. margin-right: 0px;
  1068. }
  1069. .flight_item span.stopover { border-radius:5px;padding: 2px 4px; color: #909090 !important; display: inline-block; background: white; font-size: 10px; margin: 0px 4px !important; line-height: 11px; }
  1070. .flight_item.direct { background: #cbe0cf; }
  1071. .flight_item.saved { background:#f5ebd8; }
  1072. .flight_item img { line-height: 15px; max-height: 15px; vertical-align: middle; margin-right: 2px; max-width: 20px;}
  1073. .show_first .flight_item[data-f="1"],
  1074. .show_business .flight_item[data-j="1"],
  1075. .show_premium .flight_item[data-p="1"],
  1076. .show_economy .flight_item[data-y="1"] {
  1077. max-width:280px;
  1078. padding: 6px 6px;
  1079. margin-right: 6px;
  1080. }
  1081. .nonstop_only .flight_item[data-direct="0"] {
  1082. max-width:0px;
  1083. padding:6px 0px;
  1084. margin-right: 0px;
  1085. }
  1086. span.bulk_j { background: #002e6c;}
  1087. span.bulk_f { background: #832c40;}
  1088. span.bulk_p { background: #487c93;}
  1089. span.bulk_y { background: #016564;}
  1090. .flight_item span.flight_num{
  1091. line-height: 16px;
  1092. vertical-align: middle;
  1093. height: 16px;
  1094. display: inline-block;
  1095. padding: 2px 0;
  1096. }
  1097. .flight_item span.bulk_j,
  1098. .flight_item span.bulk_f,
  1099. .flight_item span.bulk_p,
  1100. .flight_item span.bulk_y {
  1101. color: white;
  1102. border-radius: 5px;
  1103. font-size:10px;
  1104. overflow:hidden;
  1105. transition: all 0.5s ease-in;
  1106. display:inline-block;
  1107. vertical-align:top;
  1108. height: 16px;
  1109. line-height: 16px;
  1110.  
  1111. max-width:0px;
  1112. padding: 2px 0px;
  1113. margin-left: 0px;
  1114. }
  1115. .show_first span.bulk_f,
  1116. .show_business span.bulk_j,
  1117. .show_premium span.bulk_p,
  1118. .show_economy span.bulk_y {
  1119. max-width:25px;
  1120. padding: 2px 5px;
  1121. margin-left: 3px;
  1122. }
  1123.  
  1124.  
  1125. .flight_item:hover img,
  1126. .flight_item:focus img,
  1127. .flight_item:active img,
  1128. .flight_item.saved img {
  1129. opacity:0;
  1130. }
  1131. span.flight_save {
  1132. display:none;
  1133. position:absolute;
  1134. left:5px;
  1135. top:5px;
  1136. opacity:0.6;
  1137. }
  1138. span.flight_save * {
  1139. pointer-events: none;
  1140. }
  1141. span.flight_save svg {
  1142. height:12px;
  1143. width:12px;
  1144. padding:5px;
  1145. }
  1146. .flight_item.saved span.flight_save {
  1147. opacity:1;
  1148. display:block;
  1149. }
  1150. .flight_item.saved svg.heart_save path {
  1151. fill:#d65656;
  1152. }
  1153. .flight_item:hover span.flight_save,
  1154. .flight_item:focus span.flight_save,
  1155. .flight_item:active span.flight_save {
  1156. display:inline-block;
  1157. }
  1158. .flight_item .chevron {
  1159. vertical-align: top;
  1160. display: inline-block;
  1161. padding: 2px 0 2px 0px;
  1162. height: 16px;
  1163. opacity: 0.5;
  1164. margin-right: -2px;
  1165. margin-left: -2px;
  1166. }
  1167. .flight_item .chevron svg {
  1168. vertical-align: top;
  1169. transform:rotate(-90deg);
  1170. }
  1171.  
  1172. .flight_item.active .chevron svg {
  1173. transform:rotate(0deg);
  1174. }
  1175. .flight_item * { pointer-events: none; }
  1176. .flight_item .flight_save { pointer-events: auto; }
  1177.  
  1178. .bulk_footer{ min-height: 45px; position:sticky; bottom:0;}
  1179. /*.bulk_footer.bulk_sticky .bulk_footer_container { position: fixed; bottom: 0; padding: 10px; background: #f7f6f0; margin: 0 auto; border-top: 1px solid #c6c2c1; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%); max-width: 858px; left: 0; right: 0; }*/
  1180. .bulk_footer .bulk_footer_container { padding: 10px; background: #f7f6f0; margin: 0; }
  1181. .bulk_footer.bulk_sticky .bulk_footer_container { border-top: 1px solid #c6c2c1; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%); }
  1182. @media screen and (max-width: 500px) { .bulk_footer.bulk_sticky .bulk_footer_container { max-width: 838px;} }
  1183. button.bulk_submit {position:relative;background-color: #367778; border: none; color: white; vertical-align: middle; margin: 0px auto; height: 45px; line-height: 35px; padding: 5px 0; width: 100%; display: block; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Rg, sans-serif !important;font-size:15px}
  1184. .bulk_submit img, button.uef_search img {line-height: 35px; height: 25px; width:auto; display: inline-block; margin-right: 10px; vertical-align: -7px;}
  1185. .bulk_searching, .uef_search.searching {background-color: #b9cdc9 !important;}
  1186. .col-select-departure-flight > .row:last-of-type { padding-bottom: 140px; }
  1187. span.info-x { border-radius: 5px; padding: 2px 5px; margin-left: 5px; color:white; font-size:10px; font-family: CathaySans_Md, Cathay Sans EN; font-weight: 400; }
  1188. span.info-f { background: #832c40;}
  1189. span.info-j { background: #002e6c;}
  1190. span.info-p { background: #487c93;}
  1191. span.info-y { background: #016564;}
  1192. .unelevated_update.hidden { display:none; }
  1193. .unelevated_update { border-radius: 5px; background: #f27878; padding: 5px 10px; margin: 0px 8px 10px 0; text-align: center; }
  1194. .unelevated_update a { color:white !important; } .unelevated_update a:after { content:none !important; }
  1195. .unelevated_update a span { font-weight:bold; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Md, sans-serif; }
  1196. .unelevated_update.update_show { display:block; }
  1197.  
  1198. .login_prompt { height: 40px; line-height: 20px; overflow: hidden; transition: all 0.5s ease-out; margin-bottom: 10px; }
  1199. .login_prompt.hidden { height: 0; overflow:hidden; margin: 0; }
  1200.  
  1201. .unelevated_faves {
  1202. line-height: 20px;
  1203. overflow: hidden;
  1204. transition: all 0.5s ease-out;
  1205. background: #eae6d9;
  1206. border: 1px solid #bebebe;
  1207. margin-right: 8px;
  1208. box-shadow: inset 0px 0px 4px 0px rgb(0 0 0 / 10%);
  1209. position: absolute;
  1210. top: 0px;
  1211. right: 0;
  1212. left: 8px;
  1213. z-index: 100;
  1214. height: calc(100% - 52px);
  1215. margin-top: 42px;
  1216. opacity:1;
  1217. }
  1218. .unelevated_faves_hidden {height:0;opacity:0;}
  1219. .unelevated_faves span.saved_title {
  1220. height:20px;
  1221. display: block;
  1222. margin: 6px 15px;
  1223. font-size: 13px;
  1224. color: #787878;
  1225. font-weight: bold;
  1226. font-family: "Cathay Sans EN", CathaySans_Md, sans-serif;
  1227. }
  1228. a.search_selected {
  1229. position: absolute;
  1230. right: 15px;
  1231. top: 6px;
  1232. height: 20px;
  1233. line-height: 20px !important;
  1234. font-size: 12px !important;
  1235. font-weight: bold !important;
  1236. display:block;
  1237. }
  1238. .flights a.search_selected {
  1239. position: absolute;
  1240. right: 15px;
  1241. top: 6px;
  1242. height: 20px;
  1243. line-height: 20px !important;
  1244. font-size: 12px !important;
  1245. font-weight: bold !important;
  1246. display:none;
  1247. }
  1248. a.search_multicity {
  1249. position: absolute;
  1250. right: 15px;
  1251. top: 6px;
  1252. height: 20px;
  1253. line-height: 20px !important;
  1254. font-size: 12px !important;
  1255. font-weight: bold !important;
  1256. display:none;
  1257. }
  1258. .saved_book {
  1259. margin-left:10px;
  1260. line-height:20px !important;
  1261. font-weight:bold;
  1262. display:inline-block;
  1263. }
  1264. .saved_remove {
  1265. font-weight:bold;
  1266. position: absolute;
  1267. line-height: 20px !important;
  1268. font-weight: bold;
  1269. right: 5px;
  1270. top: 3px;
  1271. }
  1272. .flights .saved_remove {
  1273. line-height: 36px !important;
  1274. }
  1275. .multi_on .search_multicity {
  1276. position: absolute;
  1277. right: 15px;
  1278. top: 6px;
  1279. height: 20px;
  1280. line-height: 20px !important;
  1281. font-size: 12px !important;
  1282. font-weight: bold !important;
  1283. display:block;
  1284. }
  1285. .multi_on .saved_book,
  1286. .multi_on .saved_remove,
  1287. .multi_on .search_selected {
  1288. display:none;
  1289. }
  1290. .leg{ color: #ae4b4b !important; font-weight:bold;}
  1291. .saved_remove svg{
  1292. height:20px;
  1293. fill:#b4afaf;
  1294. }
  1295. .saved_book *, .saved_remove * {
  1296. pointer-events: none;
  1297. }
  1298. span.unelevated_error { padding: 10px 0 10px 10px; line-height:20px; max-height:100%; display: block; background: #ffd2d2; border-radius: 5px; margin: 0 10px 5px 0; text-align: center; color: #b54545; font-weight:bold; font-size:14px;}
  1299. span.unelevated_error a {padding: 0; margin: 0; text-decoration: underline; line-height: 20px; max-height: 100%; height: 24px; display: block; background: #ffd2d2; border-radius: 5px; margin: 0 10px 5px 0; text-align: center; color: #b54545;font-family: CathaySans_Md, Cathay Sans EN; font-weight: 400;}
  1300. .bulk_error span {padding: 5px; line-height: 20px; height: 20px; max-height: 100%; display: block; background: #eae6d9; border-radius: 5px; text-align: center; color: #b54545; margin-top: 10px; font-size: 12px; transition: all 0.5s ease-out;font-family: CathaySans_Md, Cathay Sans EN; font-weight: 400;}
  1301. .bulk_error_hidden span { height:0; margin-top: 0; overflow:hidden; padding:0;}
  1302.  
  1303. .unelevated_form .autocomplete {
  1304. /*the container must be positioned relative:*/
  1305. position: relative;
  1306. display: inline-block;
  1307. }
  1308. .unelevated_form .autocomplete-items {
  1309. position: absolute;
  1310. border: 1px solid #bcbec0;
  1311. border-top: none;
  1312. z-index: 99;
  1313. top: 100%;
  1314. left: 0;
  1315. right: 8px;;
  1316. margin-top:-8px;
  1317. max-height:200px;
  1318. overflow:scroll;
  1319. background:white;
  1320. }
  1321. .unelevated_form .autocomplete-items div {
  1322. padding: 5px;
  1323. cursor: pointer;
  1324. background-color: #fff;
  1325. border-bottom: 1px solid #e4e4e4;
  1326. font-size:12px;
  1327. font-weight: normal;
  1328. font-family: "Cathay Sans EN", CathaySans_Rg, sans-serif;
  1329. white-space: nowrap;
  1330. overflow: hidden;
  1331. }
  1332. .unelevated_form .autocomplete-items div span.sa_code {
  1333. margin-left:5px;
  1334. display:inline-block;
  1335. width:30px;
  1336. font-weight:normal;
  1337. }
  1338. .unelevated_form .autocomplete-items div span.sc_code {
  1339. color:#888;
  1340. display:inline-block;
  1341. margin-left:10px;
  1342. font-weight:normal;
  1343. }
  1344.  
  1345. .unelevated_form .autocomplete-items div:hover {
  1346. /*when hovering an item:*/
  1347. background-color: #e9e9e9;
  1348. }
  1349. .unelevated_form .autocomplete-active, .unelevated_form div.autocomplete-active span.sc_code {
  1350. /*when navigating through the items using the arrow keys:*/
  1351. background-color: DodgerBlue !important;
  1352. color: #ffffff;
  1353. }
  1354. #planner_routes {
  1355. background: #f7f6f0;
  1356. padding: 5px 10px;
  1357. margin-bottom: -10px;
  1358. border: 1px solid #bcbec0;
  1359. box-shadow: 0px 0px 7px rgb(0 0 0 / 20%);
  1360. border-bottom: none;
  1361. }
  1362. #planner_routes:empty {
  1363. display:none;
  1364. }
  1365. a.planner_route {
  1366. color: #016564;
  1367. display: inline-block;
  1368. margin-right: 10px;
  1369. font-size: 14px;
  1370. }
  1371. `;
  1372.  
  1373. addCss(`.captcha_wrapper {
  1374. position: fixed;
  1375. top: 150px;
  1376. left: 50%;
  1377. width: 300px;
  1378. height: 200px;
  1379. background: white;
  1380. z-index: 20;
  1381. padding: 10px;
  1382. margin-left: -150px;
  1383. box-shadow: 0px 0px 5px;
  1384. border-radius: 5px;
  1385. }
  1386. .human_check {
  1387. margin: 10px 20px 20px 20px;
  1388. text-align: center;
  1389. }
  1390. `, document.body)
  1391.  
  1392. //============================================================
  1393. // Form Listeners
  1394. //============================================================
  1395.  
  1396. let btn_search, btn_batch;
  1397. let input_from, input_to, input_date, input_adult, input_child;
  1398. let clear_from, clear_to;
  1399. let link_search_saved, link_search_multi, div_filters;
  1400. let div_update, div_login_prompt, div_bulk_box, div_footer,div_ue_container, div_saved, div_faves_tabs, div_saved_queries;
  1401. let div_saved_flights, div_multi_box, div_table, div_table_body;
  1402. let premium_switch;
  1403.  
  1404. function assignElemets(){
  1405.  
  1406. log("assignElemets()");
  1407. btn_search = shadowRoot.querySelector(".uef_search"); // Search Button
  1408. btn_batch = shadowRoot.querySelector(".bulk_submit"); // Batch Search Button
  1409. input_from = shadowRoot.querySelector("#uef_from");
  1410. input_to = shadowRoot.querySelector("#uef_to");
  1411. input_date = shadowRoot.querySelector("#uef_date");
  1412. input_adult = shadowRoot.querySelector("#uef_adult");
  1413. input_child = shadowRoot.querySelector("#uef_child");
  1414. clear_from = shadowRoot.querySelector(".clear_from");
  1415. clear_to = shadowRoot.querySelector(".clear_to");
  1416.  
  1417. link_search_saved = shadowRoot.querySelector(".search_selected");
  1418. link_search_multi = shadowRoot.querySelector(".multi_search");
  1419.  
  1420. div_filters = shadowRoot.querySelector(".filters");
  1421. div_update = shadowRoot.querySelector(".unelevated_update");
  1422. div_login_prompt = shadowRoot.querySelector(".login_prompt");
  1423. div_bulk_box = shadowRoot.querySelector(".bulk_box");
  1424. div_footer = shadowRoot.querySelector(".bulk_footer");
  1425. div_ue_container = shadowRoot.querySelector(".unelevated_form");
  1426. div_saved = shadowRoot.querySelector(".unelevated_faves");
  1427. div_faves_tabs = shadowRoot.querySelector(".unelevated_faves .faves_tabs");
  1428. div_saved_queries = shadowRoot.querySelector(".unelevated_faves .saved_queries");
  1429. div_saved_flights = shadowRoot.querySelector(".unelevated_faves .saved_flights");
  1430. div_multi_box = shadowRoot.querySelector(".multi_box");
  1431. div_table = shadowRoot.querySelector(".bulk_table");
  1432. div_table_body = shadowRoot.querySelector(".bulk_table tbody");
  1433.  
  1434. premium_switch = shadowRoot.querySelector(".prem_title");
  1435. }
  1436.  
  1437. function addFormListeners(){
  1438.  
  1439. log("addFormListeners()");
  1440. btn_search.addEventListener("click", function(e) {
  1441. uef_from = value_set("uef_from",input_from.value);
  1442. uef_to = value_set("uef_to",input_to.value);
  1443. uef_date = value_set("uef_date",input_date.value);
  1444. uef_adult = value_set("uef_adult",input_adult.value);
  1445. uef_child = value_set("uef_child",input_child.value);
  1446. regularSearch([{from:uef_from.substring(0,3), to:uef_to.substring(0, 3), date:uef_date}], {adult:uef_adult, child:uef_child}, "Y", (uef_to.length > 3 ? true : false), false);
  1447. });
  1448.  
  1449. btn_batch.addEventListener("click", function(e) {
  1450. autoScroll = true;
  1451. bulk_click();
  1452. });
  1453.  
  1454. shadowRoot.querySelector(".switch").addEventListener('click', function(e) {
  1455. let from = input_from.value;
  1456. let to = input_to.value;
  1457. input_from.value = to;
  1458. input_to.value = from;
  1459. route_changed = true;
  1460. });
  1461.  
  1462. [input_from, input_to].forEach( item => { item.addEventListener('keyup', function(e) {
  1463. if (r!=t) return;
  1464. if (e.keyCode == 32 || e.keyCode == 188 || e.keyCode == 13){
  1465. if(e.keyCode == 13) this.value += ",";
  1466. this.value = this.value.toUpperCase().split(/[ ,]+/).join(',');
  1467. }
  1468. }) });
  1469.  
  1470. input_from.addEventListener("change", function(e) {
  1471. if (r!=t) this.value = this.value.toUpperCase().substring(0,3);
  1472. route_changed = true;
  1473. batchLabel(lang.bulk_batch + " " + input_from.value + " - " + input_to.value + " " + lang.bulk_flights);
  1474. let dest = this.value.match(/[A-Z]{3}$/);
  1475. if (dest) getDestinations(dest[0]);
  1476. });
  1477.  
  1478. input_to.addEventListener("change", function(e) {
  1479. if (r!=t) this.value = this.value.toUpperCase().substring(0,3);
  1480. route_changed = true;
  1481. batchLabel(lang.bulk_batch + " " + input_from.value + " - " + input_to.value + " " + lang.bulk_flights);
  1482. });
  1483.  
  1484. let inFocus = false;
  1485.  
  1486. [input_from, input_to].forEach( item => { item.addEventListener('focus', function(e){
  1487. if(this.value.length > 0 && r == t) this.value = this.value + ",";
  1488. }) });
  1489.  
  1490. [input_from, input_to].forEach( item => { item.addEventListener('click', function(e){
  1491. if(r==t){
  1492. if(!inFocus) this.setSelectionRange(this.value.length,this.value.length);
  1493. inFocus = true;
  1494. } else {
  1495. this.select()
  1496. }
  1497. }) });
  1498.  
  1499. [input_from, input_to].forEach( item => { item.addEventListener('blur', function(e) {
  1500. inFocus = false;
  1501. this.value = this.value.toUpperCase().split(/[ ,]+/).join(',').replace(/,+$/, "")
  1502. this.dispatchEvent(new Event('change'));
  1503. checkCities(this);
  1504. }) });
  1505.  
  1506. input_date.addEventListener("change",function(e){
  1507. if (!isValidDate(this.value)) {
  1508. alert(lang.invalid_date);
  1509. this.value = uef_date;
  1510. } else {
  1511. route_changed = true;
  1512. }
  1513. });
  1514.  
  1515. clear_from.addEventListener("click", function(e) {
  1516. input_from.value = "";
  1517. });
  1518.  
  1519. clear_to.addEventListener("click", function(e) {
  1520. input_to.value = "";
  1521. });
  1522.  
  1523. div_table.addEventListener("click",function(e){
  1524. var key;
  1525. if(e.target.dataset.book) {
  1526. stop_batch();
  1527. //stop_search = true;
  1528. //searching = false;
  1529. e.target.innerText = lang.loading;
  1530. regularSearch([{from:(e.target.dataset.from ? e.target.dataset.from : uef_from.substring(0,3)), to:(e.target.dataset.dest ? e.target.dataset.dest : uef_to.substring(0,3)), date:e.target.dataset.date}], {adult:uef_adult, child:uef_child}, "Y", false, false, false)
  1531. } else if (e.target.dataset.save) {
  1532. key = e.target.dataset.date + e.target.dataset.from + e.target.dataset.dest;
  1533. if(e.target.classList.contains("bulk_saved")){
  1534. e.target.classList.remove("bulk_saved");
  1535. delete saved[key];
  1536. update_saved_count();
  1537. } else{
  1538. e.target.classList.add("bulk_saved");
  1539. saved[key]=1;
  1540. update_saved_count();
  1541. }
  1542. value_set("saved", saved)
  1543. } else if (e.target.classList.contains("flight_save")) {
  1544. key = e.target.parentNode.dataset.flightinfo;
  1545. var flightavail = e.target.parentNode.dataset.flightavail.split("_");
  1546. if(e.target.parentNode.classList.contains("saved")){
  1547. e.target.parentNode.classList.remove("saved");
  1548. delete saved_flights[key];
  1549. update_saved_flights();
  1550. } else{
  1551. e.target.parentNode.classList.add("saved");
  1552. saved_flights[key]={ f: flightavail[0], j: flightavail[1], p: flightavail[2], y: flightavail[3]};
  1553. update_saved_flights();
  1554. }
  1555. value_set("saved_flights", saved_flights)
  1556. } else if (e.target.classList.contains("flight_item")) {
  1557. if(e.target.classList.contains("active")) {
  1558. e.target.classList.remove("active");
  1559. } else {
  1560. shadowRoot.querySelectorAll(".flight_item").forEach(function(elm){
  1561. elm.classList.remove('active');
  1562. });
  1563. e.target.classList.add("active");
  1564. }
  1565.  
  1566. }
  1567. });
  1568.  
  1569. document.addEventListener("scroll", function() {
  1570. shadowRoot.querySelectorAll(".flight_item").forEach(function(elm){
  1571. elm.classList.remove('active');
  1572. });
  1573. });
  1574. /*
  1575. value_set("saved",{
  1576. "20230809TPETYO":1,
  1577. "20230816TYOCDG":1,
  1578. "20230816TYOLHR":1,
  1579. "20230823CDGAMS":1,
  1580. "20230823CDGMAD":1,
  1581. "20230826AMSHKG":1,
  1582. "20230826MADLHR":1,
  1583. "20230906LHRHKG":1,
  1584. "20230906LHRDOH":1,
  1585. "20230913HKGTPE":1
  1586. });*/
  1587.  
  1588. div_saved.addEventListener("click",function(e){
  1589. if (e.target.dataset.remove) {
  1590. delete saved[e.target.dataset.remove];
  1591. delete saved_flights[e.target.dataset.remove];
  1592. update_saved_count();
  1593. update_saved_flights();
  1594. value_set("saved", saved)
  1595. value_set("saved_flights", saved_flights)
  1596. }
  1597. });
  1598.  
  1599. div_saved_queries.addEventListener("click",function(e){
  1600. if(e.target.dataset.book) {
  1601. stop_batch();
  1602. e.target.innerText = lang.loading;
  1603. regularSearch([{from:(e.target.dataset.from ? e.target.dataset.from : uef_from), to:(e.target.dataset.dest ? e.target.dataset.dest : uef_to), date:e.target.dataset.date}], {adult:1, child:0})
  1604. } else if (e.target.type == "checkbox") {
  1605. div_saved_queries.querySelectorAll(".selected").forEach(function(elm){
  1606. delete elm.dataset.new;
  1607. });
  1608.  
  1609. if(e.target.checked){
  1610. e.target.parentNode.parentNode.dataset.new = true;
  1611. e.target.parentNode.parentNode.classList.add("selected");
  1612. div_saved_queries.parentNode.classList.add("multi_on");
  1613. div_multi_box.classList.remove("hidden");
  1614. } else {
  1615. e.target.parentNode.parentNode.classList.remove("selected");
  1616. e.target.parentNode.parentNode.querySelector(".leg").innerText = "";
  1617. delete e.target.parentNode.parentNode.dataset.segment;
  1618. if(div_saved_queries.querySelectorAll(".selected").length == 0) {
  1619. div_saved_queries.parentNode.classList.remove("multi_on");
  1620. div_multi_box.classList.add("hidden");
  1621. }
  1622. }
  1623.  
  1624. let segments_array = div_saved_queries.querySelectorAll(".selected");
  1625.  
  1626. if(segments_array.length == 6) {
  1627. div_saved_queries.querySelectorAll("input:not(:checked)").forEach(item => {item.disabled = true});
  1628. } else {
  1629. div_saved_queries.querySelectorAll("input").forEach(item => {item.disabled = false});
  1630. }
  1631.  
  1632. let pos = 1;
  1633. Array.from(segments_array).sort(function(a,b){
  1634. if(+a.dataset.date > +b.dataset.date) return 1;
  1635. if(a.dataset.date == b.dataset.date) return (a.dataset.new ? 1 : (a.dataset.segment > b.dataset.segment ? 1 : -1));
  1636. return false;
  1637. }).forEach(function(elm){
  1638. elm.dataset.segment = pos;
  1639. elm.querySelector(".leg").innerText = "Segment " + pos;
  1640. pos++;
  1641. });
  1642. }
  1643. });
  1644.  
  1645. div_saved_flights.addEventListener("click",function(e){
  1646. });
  1647.  
  1648.  
  1649. div_filters.querySelectorAll("input").forEach(item =>{ item.addEventListener("click",function(e){
  1650. if(e.target.id == "filter_nonstop"){
  1651. if(e.target.checked){ div_table.classList.add("nonstop_only") } else { div_table.classList.remove("nonstop_only") }
  1652. } else if (e.target.id == "filter_first"){
  1653. if(e.target.checked){ div_table.classList.add("show_first") } else { div_table.classList.remove("show_first") }
  1654. } else if (e.target.id == "filter_business"){
  1655. if(e.target.checked){ div_table.classList.add("show_business") } else { div_table.classList.remove("show_business") }
  1656. } else if (e.target.id == "filter_premium"){
  1657. if(e.target.checked){ div_table.classList.add("show_premium") } else { div_table.classList.remove("show_premium") }
  1658. } else if (e.target.id == "filter_economy"){
  1659. if(e.target.checked){ div_table.classList.add("show_economy") } else { div_table.classList.remove("show_economy") }
  1660. }
  1661. })});
  1662.  
  1663. link_search_saved.addEventListener("click",function(e){
  1664. if(Object.keys(saved).length == 0) {
  1665. alert("No Saved Queries.");
  1666. } else {
  1667. this.innerText = lang.loading;
  1668. saved_search();
  1669. }
  1670. });
  1671.  
  1672. link_search_multi.addEventListener("click",function(e){
  1673. if(shadowRoot.querySelectorAll(".saved_query.selected").length == 0) {
  1674. alert("No Selected Segments.");
  1675. } else {
  1676. this.innerText = lang.loading;
  1677. var to_search = [];
  1678. Array.from(shadowRoot.querySelectorAll(".saved_query.selected")).sort(function(a,b){
  1679. return a.dataset.segment - b.dataset.segment;
  1680. }).forEach(segment => {
  1681. to_search.push({
  1682. date: segment.dataset.date,
  1683. from: segment.dataset.route.substring(0,3),
  1684. to: segment.dataset.route.substring(3,6)
  1685. });
  1686. })
  1687. regularSearch(to_search, {adult:shadowRoot.querySelector("#multi_adult").value,child:shadowRoot.querySelector("#multi_child").value} ,shadowRoot.querySelector("#multi_cabin").value);
  1688. }
  1689. });
  1690.  
  1691. div_faves_tabs.addEventListener("click",function(e){
  1692. if (e.target.classList.contains("tab_flights")) this.parentNode.classList.add("flights");
  1693. if (e.target.classList.contains("tab_queries")) this.parentNode.classList.remove("flights");
  1694. });
  1695.  
  1696. shadowRoot.querySelector(".unelevated_saved a").addEventListener("click",function(e){
  1697. //alert(JSON.stringify(saved));
  1698. shadowRoot.querySelector(".unelevated_faves").classList.toggle("unelevated_faves_hidden");
  1699. });
  1700.  
  1701. shadowRoot.querySelector(".unelevated_premium a").addEventListener("click",function(e){
  1702. shadowRoot.querySelector(".unelevated_prem_desc").classList.toggle("unelevated_prem_hidden");
  1703. });
  1704.  
  1705. let pt_count = 0
  1706. premium_switch.addEventListener("click",function(e){
  1707. if(++pt_count == 9){
  1708. let a_i = document.createElement("input");
  1709. a_i.type = "text";
  1710. a_i.setAttribute("id","activation_input");
  1711. a_i.setAttribute("placeholder","Activation Key");
  1712. a_i.addEventListener("input", function(e) {
  1713. check_key(this.value.toUpperCase());
  1714. });
  1715. premium_switch.after(a_i);
  1716. let cnft_script = document.createElement('script');
  1717. cnft_script.src = "https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js";
  1718. document.body.appendChild(cnft_script);
  1719. }
  1720. });
  1721.  
  1722. };
  1723.  
  1724. //============================================================
  1725. // Data Retrievers
  1726. //============================================================
  1727.  
  1728. const airports = {
  1729. origins:[],
  1730. dest:[]
  1731. };
  1732.  
  1733.  
  1734. unsafeWindow.innerFunc = function innerFunc(){
  1735. console.log("innerfunc");
  1736. };
  1737.  
  1738.  
  1739. function getOrigins(){
  1740. log("getOrigins()");
  1741. httpRequest({
  1742. method: "GET",
  1743. url: "https://api.cathaypacific.com/redibe/airport/origin/" + (browser_lang == "zh" ? (browser_country == "CN" ? "sc" : "zh") : "en") + "/",
  1744. onload: function(response) {
  1745. var data = JSON.parse(response.responseText);
  1746. if(data.airports){
  1747. data.airports.forEach(airport => {
  1748. airports.origins[airport.airportCode] = {
  1749. airportCode:airport.airportCode,
  1750. shortName:airport.shortName,
  1751. countryName:airport.countryName
  1752. }
  1753. });
  1754. } else {
  1755. airports.origins = [];
  1756. }
  1757. }
  1758. });
  1759. }
  1760.  
  1761. function getDestinations(from){
  1762. if (!airports.origins[from]) return;
  1763. log("getDestinations()");
  1764. httpRequest({
  1765. method: "GET",
  1766. url: "https://api.cathaypacific.com/redibe/airport/destination/" + from + "/" + (browser_lang == "zh" ? (browser_country == "CN" ? "sc" : "zh") : "en") + "/",
  1767. onload: function(response) {
  1768. var data = JSON.parse(response.responseText);
  1769. if(data.airports){
  1770. data.airports.forEach(airport => {
  1771. airports.dest[airport.airportCode] = {
  1772. airportCode:airport.airportCode,
  1773. shortName:airport.shortName,
  1774. countryName:airport.countryName
  1775. }
  1776. });
  1777. } else {
  1778. airports.dest = [];
  1779. }
  1780. }
  1781. });
  1782. }
  1783.  
  1784. //============================================================
  1785. // UI Logic
  1786. //============================================================
  1787.  
  1788. //Batch Button Text
  1789. function batchLabel(label){
  1790. if(shadowRoot.querySelector(".bulk_submit")) {
  1791. shadowRoot.querySelector(".bulk_submit").innerHTML = label;
  1792. }
  1793. }
  1794. function batchError(label){
  1795. if(label) {
  1796. shadowRoot.querySelector(".bulk_error span").innerHTML = label;
  1797. shadowRoot.querySelector(".bulk_error").classList.remove("bulk_error_hidden");
  1798. } else {
  1799. shadowRoot.querySelector(".bulk_error").classList.add("bulk_error_hidden");
  1800. }
  1801. }
  1802.  
  1803. function autocomplete(inp, list) {
  1804. /*the autocomplete function takes two arguments,
  1805. the text field element and an array of possible autocompleted values:*/
  1806. var currentFocus;
  1807. /*execute a function when someone writes in the text field:*/
  1808. inp.addEventListener("input", function(e) {
  1809. newAC(this,e);
  1810. });
  1811. inp.addEventListener("click", function(e) {
  1812. //newAC(this,e);
  1813. });
  1814. /*execute a function presses a key on the keyboard:*/
  1815. inp.addEventListener("keydown", function(e) {
  1816. var x = shadowRoot.getElementById(this.id + "autocomplete-list");
  1817. if (x) x = x.getElementsByTagName("div");
  1818. if (e.keyCode == 40) {
  1819. /*If the arrow DOWN key is pressed,
  1820. increase the currentFocus variable:*/
  1821. currentFocus++;
  1822. /*and and make the current item more visible:*/
  1823. addActive(x);
  1824. } else if (e.keyCode == 38) { //up
  1825. /*If the arrow UP key is pressed,
  1826. decrease the currentFocus variable:*/
  1827. currentFocus--;
  1828. /*and and make the current item more visible:*/
  1829. addActive(x);
  1830. } else if (e.keyCode == 13) {
  1831. /*If the ENTER key is pressed, prevent the form from being submitted,*/
  1832. e.preventDefault();
  1833. closeAllLists();
  1834. if (currentFocus > -1) {
  1835. /*and simulate a click on the "active" item:*/
  1836. if (x) x[currentFocus].click();
  1837. } else {
  1838. if (x) x.querySelector(":not").click();
  1839. }
  1840. } else if (e.keyCode == 32 || e.keyCode == 9) {
  1841. /*If the SPACE or TAB key is pressed, select first option*/
  1842. closeAllLists();
  1843. /*and simulate a click on the "active" item:*/
  1844. if (x) x[0].click();
  1845. }
  1846. });
  1847. function addActive(x) {
  1848. /*a function to classify an item as "active":*/
  1849. if (!x) return false;
  1850. /*start by removing the "active" class on all items:*/
  1851. removeActive(x);
  1852. if (currentFocus >= x.length) currentFocus = 0;
  1853. if (currentFocus < 0) currentFocus = (x.length - 1);
  1854. /*add class "autocomplete-active":*/
  1855. x[currentFocus].classList.add("autocomplete-active");
  1856. }
  1857. function removeActive(x) {
  1858. /*a function to remove the "active" class from all autocomplete items:*/
  1859. for (var i = 0; i < x.length; i++) {
  1860. x[i].classList.remove("autocomplete-active");
  1861. }
  1862. }
  1863. function closeAllLists(elmnt) {
  1864. /*close all autocomplete lists in the document,
  1865. except the one passed as an argument:*/
  1866. var x = shadowRoot.querySelectorAll(".autocomplete-items");
  1867. for (var i = 0; i < x.length; i++) {
  1868. if (elmnt != x[i] && elmnt != inp) {
  1869. x[i].parentNode.removeChild(x[i]);
  1870. }
  1871. }
  1872. }
  1873. function checkLocale(code){
  1874. return code.replace(atob("VGFpd2FuIENoaW5h"), atob("VGFpd2Fu")).replace(decodeURI(atob("JUU0JUI4JUFEJUU1JTlDJThCJUU1JThGJUIwJUU3JTgxJUEz")), decodeURI("%E5%8F%B0%E7%81%A3"));
  1875. }
  1876.  
  1877. function newAC(elm,e){
  1878. var arr = airports[list] || [];
  1879. var a, b, c, i, sa, sc, se, val = elm.value;
  1880. /*close any already open lists of autocompleted values*/
  1881. closeAllLists();
  1882. val = elm.value.match(/[^,]+$/) ? elm.value.match(/[^,]+$/)[0] : false;
  1883. if (!val) { return false;}
  1884. currentFocus = -1;
  1885. /*create a DIV element that will contain the items (values):*/
  1886. a = document.createElement("DIV");
  1887. a.setAttribute("id", elm.id + "autocomplete-list");
  1888. a.setAttribute("class", "autocomplete-items");
  1889. /*append the DIV element as a child of the autocomplete container:*/
  1890. elm.parentNode.appendChild(a);
  1891. var sep = document.createElement("span");
  1892. sep.style.display="none";
  1893. sep.classList.add("ac_separator");
  1894. a.appendChild(sep);
  1895. /*for each item in the array...*/
  1896. var favs = ["TPE","TSA","KHH","RMQ","TYO","HND","NRT","KIX","ITM","CTS","FUK","NGO","OKA","ICN","PUS",
  1897. "GMP","CJU","HKG","MFM","BKK","CNX","HKT","CGK","DPS","SUB","KUL","BKI","PEN","DAD","HAN","SGN",
  1898. "CEB","MNL","SIN","PNH","DEL","BOM","DXB","DOH","TLV","BCN","MAD","MXP","CDG","ZRH","MUC",
  1899. "FCO","FRA","CDG","AMS","LHR","LGW","LON","MAN","FCO","BOS","JFK","YYZ","ORD","IAD","YVR",
  1900. "SFO","LAX","SAN","SEA","JNB","PER","SYD","BNE","MEL","AKL","HEL","BLR","SHA","PVG","PEK",
  1901. "CAN","KTM","ADL","CPT","ATH","IST","SOF","VCE","BUD","PRG","VIE","BER","WAW","KBP","CPH",
  1902. "DUS","BRU","OSL","ARN","DUB","MIA","ATL","IAH","DFW","PHL","CMN","LAS","SJC","DEN","AUS",
  1903. "MSY","MCO","EWR","NYC","LIS","OPO","SPU","DBV","ZAG","MLE","LIM","BOG","CNS","GRU","SCL","GIG","EZE","MEX","CUN"];
  1904. Object.keys(arr).forEach(key => {
  1905. /*check if the item starts with the same letters as the text field value:*/
  1906. var airportCode = arr[key].airportCode;
  1907. var countryName = checkLocale(arr[key].countryName);
  1908. var shortName = arr[key].shortName;
  1909. if(airportCode.length > 3) return;
  1910. if (val.toUpperCase() == airportCode.substr(0, val.length).toUpperCase() || val.toUpperCase() == countryName.substr(0, val.length).toUpperCase() || val.toUpperCase() == shortName.substr(0, val.length).toUpperCase() ) {
  1911. sa = (airportCode.substr(0, val.length).toUpperCase() == val.toUpperCase()) ? val.length : 0;
  1912. se = (shortName.substr(0, val.length).toUpperCase() == val.toUpperCase()) ? val.length : 0;
  1913. sc = (countryName.substr(0, val.length).toUpperCase() == val.toUpperCase()) ? val.length : 0;
  1914. /*create a DIV element for each matching element:*/
  1915. b = document.createElement("DIV");
  1916. /*make the matching letters bold:*/
  1917. c = "<span class='sa_code'><strong>" + airportCode.substr(0, sa) + "</strong>" + airportCode.substr(sa) + "</span>";
  1918. c += "<span class='sc_code'><strong>" + shortName.substr(0, se) + "</strong>" + shortName.substr(se) + "";
  1919. c += " - <strong>" + countryName.substr(0, sc) + "</strong>" + countryName.substr(sc) + "</span>";
  1920. c += "</span>";
  1921. /*insert a input field that will hold the current array item's value:*/
  1922. c += "<input type='hidden' value='" + airportCode + "'>";
  1923. b.dataset.city = airportCode;
  1924. b.innerHTML = c;
  1925. /*execute a function when someone clicks on the item value (DIV element):*/
  1926. b.addEventListener("click", function(e) {
  1927. /*insert the value for the autocomplete text field:*/
  1928. inp.value = [inp.value.replace(/([,]?[^,]*)$/,""),this.dataset.city].filter(Boolean).join(",");
  1929. inp.dispatchEvent(new Event('change'));
  1930. /*close the list of autocompleted values,
  1931. (or any other open lists of autocompleted values:*/
  1932. closeAllLists();
  1933. });
  1934.  
  1935. if(["TPE","KHH","HKG"].includes(airportCode)){
  1936. a.prepend(b);
  1937. } else if(favs.includes(airportCode)) {
  1938. a.insertBefore(b, sep);
  1939. } else {
  1940. a.appendChild(b);
  1941. }
  1942.  
  1943. }
  1944. });
  1945. }
  1946. /*execute a function when someone clicks in the document:*/
  1947. document.addEventListener("click", function (e) {
  1948. if (e.target == inp) return;
  1949. closeAllLists(e.target);
  1950. });
  1951. }
  1952.  
  1953. function elevate(){
  1954. log("elevate()");
  1955. input_from.setAttribute('placeholder','TPE,HKG');
  1956. input_to.setAttribute('placeholder','TYO,LHR,SFO');
  1957. }
  1958.  
  1959. //============================================================
  1960. // Application Logic
  1961. //============================================================
  1962.  
  1963. let searching, stop_search = false;
  1964.  
  1965. function resetSearch(){
  1966. searching = false;
  1967. batchLabel(lang.search_20);
  1968. shadowRoot.querySelector(".bulk_submit").classList.remove("bulk_searching");
  1969. }
  1970.  
  1971. let remaining_days = 20;
  1972.  
  1973. function stop_batch(){
  1974. log("Batch Clicked. Stopping Search.");
  1975. stop_search = true;
  1976. searching = false;
  1977. shadowRoot.querySelector(".bulk_submit").innerText = lang.next_batch;
  1978. shadowRoot.querySelector(".bulk_submit").classList.remove("bulk_searching");
  1979. batchError(false);
  1980. remaining_days = 20;
  1981. }
  1982.  
  1983. function bulk_click(single_date = false){
  1984. shadowRoot.querySelector(".bulk_results").classList.remove("bulk_results_hidden");
  1985. if(!searching) {
  1986. log("Batch Clicked. Starting Search.");
  1987. uef_from = value_set("uef_from",input_from.value);
  1988. uef_to = value_set("uef_to",input_to.value);
  1989. uef_date = value_set("uef_date",input_date.value);
  1990. uef_adult = value_set("uef_adult",input_adult.value);
  1991. uef_child = value_set("uef_child",input_child.value);
  1992. btn_batch.innerHTML = lang.searching_w_cancel;
  1993. btn_batch.classList.add("bulk_searching");
  1994. bulk_search(single_date);
  1995. } else {
  1996. stop_batch();
  1997. }
  1998. }
  1999.  
  2000. function saved_search() {
  2001. var to_search = [];
  2002. Object.keys(saved).forEach(query => {
  2003. to_search.push({
  2004. date: query.substring(0,8),
  2005. from:query.substring(8,11),
  2006. to:query.substring(11,14)
  2007. })
  2008. });
  2009. to_search.sort(function(a,b){return a.date - b.date})
  2010.  
  2011. var ss_query = to_search.shift();
  2012.  
  2013. shadowRoot.querySelector(".bulk_results").classList.remove("bulk_results_hidden");
  2014. btn_batch.innerHTML = lang.searching_w_cancel;
  2015. btn_batch.classList.add("bulk_searching");
  2016. shadowRoot.querySelector(".bulk_table tbody").innerHTML = "";
  2017.  
  2018.  
  2019. if(!cont_query && window.location.href.indexOf("air/booking/availability") > -1 ){
  2020. const boxes = document.querySelectorAll("body > div");
  2021. boxes.forEach( box => { box.remove() });
  2022. addCss(`
  2023. html, body {overflow-x:inherit !important;}
  2024. header {overflow-x:hidden;}
  2025. `, document.body);
  2026. document.body.append(shadowWrapper);
  2027. shadowContainer.classList.add("results_container");
  2028. document.body.classList.add("cont_query");
  2029. } else if (!cont_query) {
  2030. regularSearch([{from:ss_query.from, to:ss_query.to, date:ss_query.date}], {adult:1, child:0}, "Y", true, false, true);
  2031. return;
  2032. }
  2033.  
  2034. var populate_next_query= function(flights){
  2035. if (to_search.length == 0) {
  2036. link_search_saved.innerText = lang.search_selected;
  2037. insertResults(ss_query.from, ss_query.to, ss_query.date, flights);
  2038. stop_batch();
  2039. stop_search = false;
  2040. searching = false;
  2041. route_changed = true;
  2042. return;
  2043. } else {
  2044. insertResults(ss_query.from, ss_query.to, ss_query.date, flights);
  2045. ss_query = to_search.shift();
  2046. searchAvailability(ss_query.from, ss_query.to, ss_query.date, 1, 0, populate_next_query);
  2047. }
  2048. }
  2049.  
  2050. searchAvailability(ss_query.from, ss_query.to, ss_query.date, 1, 0, populate_next_query);
  2051.  
  2052. }
  2053.  
  2054. function update_saved_count() {
  2055. log("update_saved_count()");
  2056. let saved_list = "";
  2057. let saved_arr = [];
  2058. Object.keys(saved).forEach(query => {
  2059. var sdate = new Date(query.substring(0,4),query.substring(4,6)-1,query.substring(6,8));
  2060. var ndate = new Date();
  2061. if(sdate <= ndate) {
  2062. delete saved[query];
  2063. return;
  2064. }
  2065. saved_arr.push({
  2066. date: query.substring(0,8),
  2067. from:query.substring(8,11).toUpperCase(),
  2068. to:query.substring(11,14).toUpperCase()
  2069. })
  2070. });
  2071. saved_arr.sort(function(a,b){return a.date - b.date});
  2072.  
  2073. saved_arr.forEach(query => {
  2074. var date = query.date;
  2075. var from = query.from;
  2076. var to = query.to
  2077. saved_list += `<div class="saved_query" data-date="${date}" data-route="${from + to}"><label><input type="checkbox" data-route="${date + from + to}" data-date="${date}"> ${toDashedDate(date)} ${from}-${to}</label>
  2078. <a href="javascript:void(0);" class="saved_book" data-book="true" data-date="${date}" data-from="${from}" data-dest="${to}">${lang.query} &raquo;</a>
  2079. <span class="leg"></span>
  2080. <a href="javascript:void(0);" class="saved_remove" data-remove="${date + from + to}">
  2081. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="saved_delete" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"></path> </svg>
  2082. </a></div>`
  2083.  
  2084. });
  2085. shadowRoot.querySelector(".unelevated_faves .saved_queries").innerHTML = saved_list;
  2086. shadowRoot.querySelector(".unelevated_saved a span").innerText = saved_arr.length;
  2087.  
  2088. }
  2089.  
  2090. function update_saved_flights() {
  2091. log("update_saved_flights()");
  2092. let saved_list = "";
  2093. let saved_arr = [];
  2094. Object.keys(saved_flights).forEach(query => {
  2095. var sdate = new Date(query.substring(0,4),query.substring(4,6)-1,query.substring(6,8));
  2096. var ndate = new Date();
  2097. if(sdate <= ndate) {
  2098. delete saved_flights[query];
  2099. return;
  2100. }
  2101. saved_arr.push({
  2102. fullquery: query,
  2103. date: query.substring(0,8),
  2104. from: query.substring(8,11).toUpperCase(),
  2105. to: query.substring(11,14).toUpperCase(),
  2106. leg1: query.split("_")[1] || "",
  2107. stop: query.split("_")[2] || "",
  2108. leg2: query.split("_")[3] || "",
  2109. f:saved_flights[query].f,
  2110. j:saved_flights[query].j,
  2111. p:saved_flights[query].p,
  2112. y:saved_flights[query].y
  2113. })
  2114. });
  2115. saved_arr.sort(function(a,b){return a.date - b.date});
  2116.  
  2117. saved_arr.forEach(query => {
  2118. var fullquery = query.fullquery;
  2119. var date = query.date;
  2120. var from = query.from;
  2121. var to = query.to;
  2122. var leg1 = query.leg1;
  2123. var stop = query.stop;
  2124. var leg2 = query.leg2;
  2125. var avail = {f:query.f,j:query.j,p:query.p,y:query.y};
  2126. saved_list += `<div class="saved_flight" data-date="${date}" data-route="${from + to}">
  2127. <label>
  2128. <!--<input type="checkbox" data-route="${date + from + to}" data-date="${date}">-->
  2129. <span>
  2130. <span class="sf_date">${toDashedDate(date)}</span>
  2131. <span class="sf_route">${from}-${stop ? stop + "-" : ""}${to}
  2132. </span><span class="sf_flights">
  2133. ${leg1}${leg2 ? " + " + leg2 : ""}
  2134. <span class="sf_avail">
  2135. ${avail.f > 0 ? '<span class="av_f">F ' + avail.f + '</span>' : ''}
  2136. ${avail.j > 0 ? '<span class="av_j">J ' + avail.j + '</span>' : ''}
  2137. ${avail.p > 0 ? '<span class="av_p">PY ' + avail.p + '</span>' : ''}
  2138. ${avail.y > 0 ? '<span class="av_y">Y ' + avail.y + '</span>' : ''}
  2139. </span>
  2140. </span>
  2141. </span>
  2142. </label>
  2143. <!--<a href="javascript:void(0);" class="saved_book" data-book="true" "data-date="${date}" data-from="${from}" data-dest="${to}">${lang.query} &raquo;</a>-->
  2144. <span class="leg"></span>
  2145. <a href="javascript:void(0);" class="saved_remove" data-remove="${fullquery}">
  2146. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="saved_delete" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"></path> </svg>
  2147. </a></div>`
  2148.  
  2149. });
  2150. shadowRoot.querySelector(".unelevated_faves .saved_flights").innerHTML = saved_list;
  2151. shadowRoot.querySelector(".unelevated_saved a span").innerText = saved_arr.length;
  2152.  
  2153. }
  2154.  
  2155. function checkCities(elem){
  2156. log("checkCities()");
  2157. setTimeout(function() {
  2158.  
  2159. var cities = elem.value.split(",");
  2160. var errorcities = [];
  2161. cities = cities.filter(city => {
  2162. if(city.match(/^[A-Z]{3}$/)) {
  2163. return true;
  2164. } else {
  2165. errorcities.push(city);
  2166. return false;
  2167. }
  2168. })
  2169.  
  2170. if(errorcities.filter(Boolean).length > 0) {
  2171. elem.value = cities.join(",");
  2172. elem.dispatchEvent(new Event('change'));
  2173. alert("Invalid Airport" + (errorcities.filter(Boolean).length > 1 ? "s" : "") + " Removed: " + errorcities.filter(Boolean).join(","));
  2174. }
  2175. }, 500);
  2176. }
  2177.  
  2178. function checkLogin(){
  2179. return;
  2180. log("checkLogin()");
  2181. httpRequest({
  2182. method: "GET",
  2183. url: "https://api.cathaypacific.com/redibe/login/getProfile",
  2184. headers: {
  2185. "Content-Type": "application/json"
  2186. },
  2187. withCredentials: "true",
  2188. onload: function(response) {
  2189. log("getprofile");
  2190. let data = JSON.parse(response.responseText);
  2191. if (data.membershipNumber) return;
  2192. div_login_prompt.classList.remove("hidden");
  2193. }
  2194. });
  2195. }
  2196.  
  2197.  
  2198. //============================================================
  2199. // Request Variables
  2200. //============================================================
  2201.  
  2202. // Default Search JSON
  2203.  
  2204. function newQueryPayload(route = {from: "HND", to: "ITM", date: dateAdd(14)}, passengers = {adult:1, child:0}, cabinclass ="Y",oneway = false, flexible = "false") {
  2205. log("newQueryPayload()");
  2206. const target = new URL('https://api.cathaypacific.com/redibe/IBEFacade');
  2207. const params = new URLSearchParams();
  2208. params.set('ACTION', 'RED_AWARD_SEARCH');
  2209. params.set('ENTRYPOINT', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html');
  2210. params.set('ENTRYLANGUAGE', lang.el);
  2211. params.set('ENTRYCOUNTRY', lang.ec);
  2212. params.set('RETURNURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=ow');
  2213. params.set('ERRORURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=ow');
  2214. params.set('CABINCLASS', cabinclass);
  2215. params.set('BRAND', 'CX');
  2216. params.set('ADULT', passengers.adult || 1);
  2217. params.set('CHILD', passengers.child || 0);
  2218. params.set('FLEXIBLEDATE', flexible);
  2219. params.set('ORIGIN[1]', route.from);
  2220. params.set('DESTINATION[1]', route.to);
  2221. params.set('DEPARTUREDATE[1]', route.date);
  2222. params.set('LOGINURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/sign-in/campaigns/miles-flight.html?loginreferrer=https%3A%2F%2Fwww.cathaypacific.com%2Fcx%2F' + lang.el + '_' + lang.ec + '%2Fbook-a-trip%2Fredeem-flights%2Fredeem-flight-awards.html%3Fauto_submit%3Dtrue%26recent_search%3Dow%26vs%3D2');
  2223. target.search = params.toString();
  2224. return target;
  2225. }
  2226.  
  2227. function newMultiPayload(routes, passengers, cabinclass = "Y") {
  2228. log("newMultiPayload()");
  2229.  
  2230. const target = new URL('https://api.cathaypacific.com/redibe/IBEFacade');
  2231. const params = new URLSearchParams();
  2232. params.set('ACTION', 'RED_AWARD_SEARCH');
  2233. params.set('ENTRYPOINT', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html');
  2234. params.set('ENTRYLANGUAGE', lang.el);
  2235. params.set('ENTRYCOUNTRY', lang.ec);
  2236. params.set('RETURNURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=mc');
  2237. params.set('ERRORURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=mc');
  2238. params.set('CABINCLASS', cabinclass);
  2239. params.set('BRAND', 'CX');
  2240. params.set('ADULT', passengers.adult || 1);
  2241. params.set('CHILD', passengers.child || 0);
  2242. params.set('FLEXIBLEDATE', 'false');
  2243.  
  2244. for (var i = 0; i < routes.length; i++) {
  2245. params.set('ORIGIN['+(i+1)+']', routes[i].from);
  2246. params.set('DESTINATION['+(i+1)+']', routes[i].to);
  2247. params.set('DEPARTUREDATE['+(i+1)+']', routes[i].date);
  2248. }
  2249.  
  2250. params.set('LOGINURL', 'https://www.cathaypacific.com/cx/' + lang.el + '_' + lang.ec + '/sign-in/campaigns/miles-flight.html?loginreferrer=https%3A%2F%2Fwww.cathaypacific.com%2Fcx%2F' + lang.el + '_' + lang.ec + '%2Fbook-a-trip%2Fredeem-flights%2Fredeem-flight-awards.html%3Fauto_submit%3Dtrue%26recent_search%3Dow%26vs%3D2');
  2251. target.search = params.toString();
  2252. return target;
  2253. }
  2254.  
  2255. //============================================================
  2256. // Get New TAB_ID
  2257. //============================================================
  2258.  
  2259. function response_parser(response, regex){
  2260. var result = response.match(regex);
  2261. try {
  2262. result = JSON.parse(result[1]);
  2263. } catch (e) {
  2264. result = false;
  2265. }
  2266. return result;
  2267. }
  2268.  
  2269. function newTabID(callback){
  2270. log("Creating New Request Parameters...");
  2271.  
  2272. let parameters = {};
  2273. if (requestParams.ENC) {
  2274. parameters.SERVICE_ID = "1";
  2275. parameters.LANGUAGE = "TW";
  2276. parameters.EMBEDDED_TRANSACTION = "AirAvailabilityServlet";
  2277. parameters.SITE = "CXAWCXAW";
  2278. parameters.ENC = requestParams.ENC;
  2279. parameters.ENCT = "2";
  2280. parameters.ENTRYCOUNTRY = "";
  2281. parameters.ENTRYLANGUAGE = "";
  2282. } else {
  2283. alert("Error, No ENC.")
  2284. return;
  2285. }
  2286. var form_data = "";
  2287. for ( var key in parameters ) {
  2288. form_data = form_data + key + "="+ parameters[key] + "&";
  2289. }
  2290. log("Requesting New Tab ID...");
  2291. httpRequest({
  2292. method: "POST",
  2293. url: "https://book.cathaypacific.com/CathayPacificAwardV3/dyn/air/booking/availability",
  2294. headers: {
  2295. "Content-Type": "application/x-www-form-urlencoded"
  2296. },
  2297. data: form_data,
  2298. withCredentials: "true",
  2299. onreadystatechange: function(response) {
  2300. var errorBOM = ""
  2301. var errorMessage = lang.tab_retrieve_fail;
  2302. if(response.readyState == 4 && response.status == 200) {
  2303. log("Tab ID Response Received. Parsing...");
  2304. var data = response.responseText;
  2305. requestVars = response_parser(data, /requestParams = JSON\.parse\(JSON\.stringify\('([^']+)/);
  2306. log(response_parser(data, /requestParams = JSON\.parse\(JSON\.stringify\('([^']+)/));
  2307. if(!requestVars) {
  2308. errorBOM = response_parser(data, /errorBom = ([^;]+)/);
  2309. if(errorBOM?.modelObject?.step == "Error"){
  2310. errorMessage = errorBOM.modelObject?.messages[0]?.subText || errorMessage;
  2311. }
  2312. log("Tab ID Could not be parsed.");
  2313. batchError("<strong>Error:</strong> " + errorMessage + " (<a href='"+login_url+"'>Login</a>) ");
  2314. resetSearch();
  2315. return false;
  2316. }
  2317. tab_id = requestVars.TAB_ID ? requestVars.TAB_ID : "";
  2318. log("New Tab ID: " + tab_id);
  2319. batchError(false);
  2320. form_submit_url = availability_url + tab_id;
  2321. if(callback) callback();
  2322. } else if (response.readyState == 4) {
  2323. errorBOM = response_parser(response.responseText, /errorBom = ([^;]+)/);
  2324. if(errorBOM?.modelObject?.step == "Error"){
  2325. errorMessage = errorBOM.modelObject?.messages[0]?.subText || errorMessage;
  2326. }
  2327. log("Failed to receive Tab ID.");
  2328. resetSearch();
  2329. batchError("<strong>Error:</strong> " + errorMessage + " ( <a href='"+login_url+"'>Login</a> ) ");
  2330. }
  2331. }
  2332. }, true);
  2333. }
  2334.  
  2335. //============================================================
  2336. // Regular Search
  2337. //============================================================
  2338.  
  2339. function regularSearch(route = [{from: "TPE", to: "TYO", date: dateAdd(14)}], passengers = {adult:1,child:0}, cabinclass ="Y", is_cont_query = false, is_cont_batch = false, is_cont_saved = false, flexible = "false"){
  2340. var target;
  2341. if (route.length == 1) {
  2342. target = newQueryPayload(route[0], passengers, cabinclass, false, flexible);
  2343. } else if (route.length > 1) {
  2344. target = newMultiPayload(route, passengers, cabinclass);
  2345. } else {
  2346. return;
  2347. }
  2348.  
  2349. btn_search.innerHTML = lang.searching;
  2350. btn_search.classList.add("searching");
  2351.  
  2352. if (is_cont_query) value_set("cont_query","1");
  2353. if (is_cont_batch) value_set("cont_batch","1");
  2354. if (is_cont_saved) value_set("cont_saved","1");
  2355. value_set("cont_ts",Date.now());
  2356. if(window.location.href.indexOf("redeem-flight-awards.html") > -1) {
  2357. location.href = target;
  2358. } else {
  2359. value_set("redirect_search",target.href);
  2360. location.href = `https://www.cathaypacific.com/cx/${lang.el}_${lang.ec}/book-a-trip/redeem-flights/redeem-flight-awards.html`;
  2361. }
  2362. }
  2363.  
  2364. //============================================================
  2365. // Bulk Search
  2366. //============================================================
  2367.  
  2368. var bulk_date = "";
  2369.  
  2370. function bulk_search(single_date = false) {
  2371. log("bulk_search start, remaining_days:" + remaining_days);
  2372. var no_continue = false;
  2373. if(remaining_days-- == 0){
  2374. stop_batch();
  2375. no_continue = true;
  2376. }
  2377.  
  2378. log("remaining_days: " + remaining_days);
  2379.  
  2380. uef_from = input_from.value;
  2381. uef_to = input_to.value;
  2382. uef_date = input_date.value;
  2383. uef_adult = input_adult.value;
  2384. uef_child = input_child.value;
  2385.  
  2386. if(!cont_query && window.location.href.indexOf("air/booking/availability") > -1 ){
  2387. const boxes = document.querySelectorAll("body > div");
  2388. boxes.forEach( box => { box.remove() });
  2389. addCss(`
  2390. html, body {overflow-x:inherit !important;}
  2391. header {overflow-x:hidden;}
  2392. `, document.body);
  2393. document.body.append(shadowWrapper);
  2394. shadowContainer.classList.add("results_container");
  2395. document.body.classList.add("cont_query");
  2396. } else if (!cont_query) {
  2397. regularSearch([{from:uef_from.substring(0,3), to:uef_to.substring(0,3), date:uef_date}], {adult:uef_adult, child:uef_child}, "Y", true, true, false, false);
  2398. return;
  2399. }
  2400.  
  2401. bulk_date = bulk_date ? bulk_date : input_date.value;
  2402.  
  2403. if(route_changed) {
  2404. div_table_body.innerHTML = "";
  2405. bulk_date = input_date.value;
  2406. div_ue_container.scrollIntoView({behavior: "smooth", block: "start"});
  2407. route_changed = false;
  2408. }
  2409. var routes = [];
  2410. var rt_from = uef_from.split(",");
  2411. var rt_to = uef_to.split(",");
  2412. var query_count = (rt_from.length * rt_to.length);
  2413.  
  2414. if (!no_continue & remaining_days > Math.ceil(25/query_count)) {
  2415. remaining_days = (Math.ceil(25/query_count) - 1);
  2416. }
  2417.  
  2418. if ( r == t ) {
  2419. rt_from.forEach(from => {
  2420. rt_to.forEach(to => {
  2421. routes.push({ from:from, to:to }) });
  2422. });
  2423. } else {
  2424. routes.push({from:rt_from[0],to:rt_to[0]})
  2425. }
  2426.  
  2427. var this_route = routes.shift();
  2428.  
  2429. var populate_next_route = function(flights){
  2430.  
  2431. insertResults(this_route.from, this_route.to, bulk_date, flights);
  2432.  
  2433. if (routes.length <= 0) {
  2434. bulk_date = dateAdd(1,bulk_date);
  2435. if (single_date) stop_batch();
  2436. bulk_search();
  2437. } else {
  2438. this_route = routes.shift();
  2439. searchAvailability(this_route.from, this_route.to, bulk_date, uef_adult, uef_child, populate_next_route);
  2440. }
  2441. }
  2442. searchAvailability(this_route.from, this_route.to, bulk_date, uef_adult, uef_child, populate_next_route);
  2443. }
  2444.  
  2445. //============================================================
  2446. // Search Availability
  2447. //============================================================
  2448.  
  2449. function searchAvailability(from, to, date, adult, child, callback) {
  2450. if(stop_search){
  2451. stop_search = false;
  2452. searching = false;
  2453. return;
  2454. }
  2455.  
  2456. searching = true;
  2457.  
  2458. // If destination is not valid, abort
  2459. if(!/^[A-Z]{3}$/.test(to)){
  2460. callback({ modelObject :{ isContainingErrors : true, messages:
  2461. [{ text: lang.invalid_code }]
  2462. }});
  2463. return;
  2464. }
  2465.  
  2466. var requests = { ...requestVars };
  2467.  
  2468. log("searchAvailability() requests");
  2469. log(requests);
  2470.  
  2471. requests.B_DATE_1 = date + "0000";
  2472. //requests.B_DATE_2 = dateAdd(1,date) + "0000";
  2473. requests.B_LOCATION_1 = from;
  2474. requests.E_LOCATION_1 = to;
  2475. //requests.B_LOCATION_2 = to;
  2476. //requests.E_LOCATION_2 = from;
  2477. delete requests.ENCT;
  2478. delete requests.SERVICE_ID;
  2479. delete requests.DIRECT_LOGIN;
  2480. delete requests.ENC;
  2481.  
  2482. var params = "";
  2483. for ( var key in requests ) {
  2484. params = params + key + "="+ requests[key] + "&";
  2485. }
  2486.  
  2487. httpRequest({
  2488. method: "POST",
  2489. url: form_submit_url,
  2490. withCredentials: "true",
  2491. headers: {
  2492. "Content-Type": "application/x-www-form-urlencoded",
  2493. "Accept": "application/json, text/plain, */*"
  2494. },
  2495. data: params,
  2496. onreadystatechange: function(response) {
  2497. var search_again = function(){
  2498. searchAvailability(from, to, date, adult, child, callback);
  2499. }
  2500. if(response.readyState == 4 && response.status == 200) {
  2501. batchError(false);;
  2502. try {
  2503. var data = JSON.parse(response.responseText);
  2504. } catch {
  2505. /* var res = response.responseText;
  2506. var incapsula_script = res.match(/<script src="(\/_Incapsula_[^]+.js)"><\/script>/);
  2507. if (incapsula_script) {
  2508. batchError("Cathay bot block triggered.");
  2509. }*/
  2510. batchError("Response not valid JSON.");
  2511. return;
  2512. }
  2513. if(data.modelObject) {
  2514. callback(data);
  2515. } else if(data.pageBom) {
  2516. var pageBom = JSON.parse(data.pageBom);
  2517. callback(pageBom);
  2518. } else {
  2519. batchError("modelObject does not exist.");
  2520. }
  2521. } else if(response.readyState == 4 && response.status == 404) {
  2522. batchError(lang.key_exhausted);
  2523. newTabID(search_again);
  2524. } else if(response.readyState == 4 && response.status >= 300) {
  2525. batchError(lang.getting_key)
  2526. newTabID(search_again);
  2527. }
  2528. }
  2529. }, true);
  2530. }
  2531.  
  2532. //============================================================
  2533. // Insert Search Results
  2534. //============================================================
  2535.  
  2536. function insertResults(from, to, date, pageBom){
  2537.  
  2538. if(!shadowRoot.querySelector('.bulk_table tr[data-date="' + date + '"]')) {
  2539. var results_row = "";
  2540. results_row += `<tr data-date='${date}'><td class='bulk_date'>
  2541. <a href='javascript:void(0);' data-book='true' data-date='${date}'>${toDashedDate(date)}</a>
  2542. ${dateWeekday(date)}
  2543. </td><td class='bulk_flights'></td></tr>`;
  2544. shadowRoot.querySelector(".bulk_table tbody").insertAdjacentHTML("beforeend", results_row);
  2545. }
  2546.  
  2547. let heart_svg =`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="heart_save" viewBox="0 0 16 16"> <path d="M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z"></path></svg>`;
  2548.  
  2549. var noflights = true;
  2550. var flightHTML = `<div data-from="${from}" data-to="${to}">
  2551. <span class="flight_title">${from} - ${to}
  2552. <a href="javascript:void(0)" class="bulk_save ${(saved[date+from+to] ? " bulk_saved" :"")}" data-save="true" data-date="${date}" data-from="${from}" data-dest="${to}">${heart_svg}</a>
  2553. <a href="javascript:void(0)" class="bulk_go_book" data-book="true" data-date="${date}" data-from="${from}" data-dest="${to}">Book &raquo;</a>
  2554. </span><div class="flight_list">`;
  2555.  
  2556. if(pageBom.modelObject?.isContainingErrors) {
  2557. flightHTML += `<span class='bulk_response_error'><strong>Error:</strong> ${pageBom.modelObject?.messages[0]?.text}</span>`;
  2558. // stop_batch();
  2559. } else {
  2560. var flights = pageBom.modelObject?.availabilities?.upsell?.bounds[0].flights;
  2561. flights.forEach((flight) => {
  2562. var available = "";
  2563. var f1 = +flight.segments[0].cabins?.F?.status || 0;
  2564. var j1 = +flight.segments[0].cabins?.B?.status || 0;
  2565. var p1 = +flight.segments[0].cabins?.N?.status || 0;
  2566. var y1 = (+flight.segments[0].cabins?.E?.status || 0) + (+flight.segments[0].cabins?.R?.status || 0);
  2567. var d_f = false;
  2568. var d_j = false;
  2569. var d_p = false;
  2570. var d_y = false;
  2571. var n_f = 0;
  2572. var n_j = 0;
  2573. var n_p = 0;
  2574. var n_y = 0;
  2575. var leg1_airline = flight.segments[0].flightIdentifier.marketingAirline;
  2576. var leg1_flight_no = flight.segments[0].flightIdentifier.flightNumber;
  2577. var leg1_dep_time = getFlightTime(flight.segments[0].flightIdentifier.originDate);
  2578. var leg1_arr_time = getFlightTime(flight.segments[0].destinationDate);
  2579. var leg1_duration = getFlightTime(flight.duration,true);
  2580. var leg1_origin = flight.segments[0].originLocation;
  2581. var leg1_dest = flight.segments[0].destinationLocation;
  2582. var flightkey;
  2583. if(flight.segments.length == 1) {
  2584. if (f1 >= 1) { available = available + ` <span class='bulk_cabin bulk_f'>F <b>${f1}</b></span>`; d_f = true; }
  2585. if (j1 >= 1) { available = available + ` <span class='bulk_cabin bulk_j'>J <b>${j1}</b></span>`; d_j = true; }
  2586. if (p1 >= 1) { available = available + ` <span class='bulk_cabin bulk_p'>PY <b>${p1}</b></span>`; d_p = true; }
  2587. if (y1 >= 1) { available = available + ` <span class='bulk_cabin bulk_y'>Y <b>${y1}</b></span>`; d_y = true; }
  2588. flightkey = date+leg1_origin.slice(-3)+leg1_dest.slice(-3)+"_"+leg1_airline+leg1_flight_no;
  2589. if (available != "") {
  2590. flightHTML += `<div class="flight_wrapper">`;
  2591. flightHTML += `<div class='flight_item direct ${(saved_flights[flightkey] ? " saved" :"")}' data-flightinfo='${flightkey}' data-flightavail='${f1 + "_" + j1+ "_" + p1+ "_" + y1}' data-direct='1' data-f='${(d_f ? 1 : 0)}' data-j='${(d_j ? 1 : 0)}' data-p='${(d_p ? 1 : 0)}' data-y='${(d_y ? 1 : 0)}'>
  2592. <img src='https://book.cathaypacific.com${static_path}common/skin/img/airlines/logo-${leg1_airline.toLowerCase()}.png'>
  2593. <span class="flight_num">${leg1_airline+leg1_flight_no}</span>
  2594. ${available}
  2595. <span class="chevron"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.34317 7.75732L4.92896 9.17154L12 16.2426L19.0711 9.17157L17.6569 7.75735L12 13.4142L6.34317 7.75732Z" fill="currentColor"></path></svg></span>
  2596. <span class="flight_save">${heart_svg}</span>
  2597. </div>
  2598. <div class="flight_info">
  2599. <span class="info_flight">${leg1_airline+leg1_flight_no} (${leg1_origin.slice(-3)} ${leg1_dest.slice(-3)})</span>
  2600. <span class="info_dept"><span>Departs:</span> ${leg1_dep_time}</span>
  2601. <span class="info_arr"><span>Arrives:</span> ${leg1_arr_time}</span>
  2602. <span class="info_duration"><span>Total Flight Duration:</span> ${leg1_duration}</span>
  2603. </div>
  2604. `;
  2605. noflights = false;
  2606. flightHTML += `</div>`;
  2607. }
  2608. if(saved_flights[flightkey]) {saved_flights[flightkey] = { f: f1, j: j1, p: p1, y: y1}; update_saved_flights();}
  2609. } else {
  2610. var f2 = +flight.segments[1].cabins?.F?.status || 0;
  2611. var j2 = +flight.segments[1].cabins?.B?.status || 0;
  2612. var p2 = +flight.segments[1].cabins?.N?.status || 0;
  2613. var y2 = (+flight.segments[1].cabins?.E?.status || 0) + (+flight.segments[1].cabins?.R?.status || 0);
  2614.  
  2615. if (f1 >= 1 && f2 >= 1) { d_f = true; n_f = Math.min(f1, f2); available = available + ` <span class='bulk_cabin bulk_f'>F <b>${ n_f }</b></span>`; }
  2616. if (j1 >= 1 && j2 >= 1) { d_j = true; n_j = Math.min(j1, j2); available = available + ` <span class='bulk_cabin bulk_j'>J <b>${ n_j }</b></span>`; }
  2617. if (p1 >= 1 && p2 >= 1) { d_p = true; n_p = Math.min(p1, p2); available = available + ` <span class='bulk_cabin bulk_p'>PY <b>${ n_p }</b></span>`; }
  2618. if (y1 >= 1 && y2 >= 1) { d_y = true; n_y = Math.min(y1, y2); available = available + ` <span class='bulk_cabin bulk_y'>Y <b>${ n_y }</b></span>`; }
  2619. var leg2_airline = flight.segments[1].flightIdentifier.marketingAirline;
  2620. var leg2_flight_no = flight.segments[1].flightIdentifier.flightNumber;
  2621. var leg2_dep_time = getFlightTime(flight.segments[1].flightIdentifier.originDate);
  2622. var leg2_arr_time = getFlightTime(flight.segments[1].destinationDate);
  2623. var leg2_origin = flight.segments[1].originLocation;
  2624. var leg2_dest = flight.segments[1].destinationLocation;
  2625. var transit_time = getFlightTime(flight.segments[1].flightIdentifier.originDate - flight.segments[0].destinationDate,true);
  2626. var stopcity = /^[A-Z]{3}:([A-Z:]{3,7}):[A-Z]{3}_/g.exec(flight.flightIdString)[1].replace(":"," / ");
  2627. flightkey = date+leg1_origin.slice(-3)+leg2_dest.slice(-3)+"_"+leg1_airline+leg1_flight_no+"_"+stopcity+"_"+leg2_airline+leg2_flight_no;
  2628. if (available != "") {
  2629. flightHTML += `<div class="flight_wrapper">`;
  2630. flightHTML += `<div class='flight_item ${(saved_flights[flightkey] ? " saved" :"")}' data-direct='0' data-flightinfo='${flightkey}' data-flightavail='${n_f + "_" + n_j + "_" + n_p + "_" + n_y}' data-f='${ d_f ? 1 : 0 }' data-j='${ d_j ? 1 : 0 }' data-p='${ d_p ? 1 : 0 }' data-y='${ d_y ? 1 : 0 }'>
  2631. <img src='https://book.cathaypacific.com${static_path}common/skin/img/airlines/logo-${leg1_airline.toLowerCase()}.png'>
  2632. <span class="flight_num">${leg1_airline + leg1_flight_no}
  2633. <span class='stopover'>${stopcity}</span>
  2634. ${leg2_airline + leg2_flight_no}</span>
  2635. ${available}
  2636. <span class="chevron"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.34317 7.75732L4.92896 9.17154L12 16.2426L19.0711 9.17157L17.6569 7.75735L12 13.4142L6.34317 7.75732Z" fill="currentColor"></path></svg></span>
  2637. <span class="flight_save">${heart_svg}</span>
  2638. </div>
  2639. <div class="flight_info">
  2640. <span class="info_flight">${leg1_airline+leg1_flight_no} (${leg1_origin.slice(-3)} ${leg1_dest.slice(-3)})</span>
  2641. <span class="info_dept"><span>Departs:</span> ${leg1_dep_time}</span>
  2642. <span class="info_arr"><span>Arrives:</span> ${leg1_arr_time}</span>
  2643. <span class="info_transit"><span>Transit Time:</span> ${transit_time}</span>
  2644. <span class="info_flight">${leg2_airline+leg2_flight_no} (${leg2_origin.slice(-3)} ${leg2_dest.slice(-3)})</span>
  2645. <span class="info_dept"><span>Departs:</span> ${leg2_dep_time}</span>
  2646. <span class="info_arr"><span>Arrives:</span> ${leg2_arr_time}</span>
  2647. <span class="info_duration"><span>Total Flight Duration:</span> ${leg1_duration}</span>
  2648. </div>
  2649. `;
  2650. noflights = false;
  2651. flightHTML += `</div>`;
  2652. }
  2653. if(saved_flights[flightkey]) {saved_flights[flightkey] = { f: n_f,j: n_j,p: n_p, y: n_y }; update_saved_flights();}
  2654. }
  2655. });
  2656. }
  2657. flightHTML += "</div></div>"
  2658.  
  2659. shadowRoot.querySelector('.bulk_table tr[data-date="' + date + '"] .bulk_flights').insertAdjacentHTML("beforeend", flightHTML);
  2660. stickyFooter();
  2661. if(autoScroll) shadowRoot.querySelector(".bulk_results").scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
  2662. }
  2663.  
  2664. window.addEventListener('wheel', function(){
  2665. if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
  2666. autoScroll = true;
  2667. } else {
  2668. autoScroll = false;
  2669. }
  2670. });
  2671. window.addEventListener('touchmove', function(){
  2672. if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
  2673. autoScroll = true;
  2674. } else {
  2675. autoScroll = false;
  2676. }
  2677. });
  2678.  
  2679.  
  2680.  
  2681.  
  2682. //============================================================
  2683. // Update Saved Flights
  2684. //============================================================
  2685. function updateFlights(){
  2686. let saved = value_get("saved_flights",{});
  2687. unsafeWindow.ue_saved_flights = saved;
  2688. unsafeWindow.ue_flights = {};
  2689. let route, flightnum;
  2690. Object.entries(saved).sort((a, b) => a[0].substring(0, 8) - b[0].substring(0, 8)).forEach(flight => {
  2691. if(flight[0].split("_")[2]) return;
  2692. route = flight[0].substring(8, 14);
  2693. flightnum = flight[0].split("_")[1];
  2694. unsafeWindow.ue_flights[route] = unsafeWindow.ue_flights[route] || [];
  2695. unsafeWindow.ue_flights[route].push({
  2696. date: flight[0].substring(0, 8),
  2697. flight: flightnum,
  2698. y: flight[1].y,
  2699. j: flight[1].j,
  2700. f: flight[1].f
  2701. });
  2702. })
  2703. unsafeWindow.populateFlights();
  2704. }
  2705.  
  2706. if(window.location.href.indexOf("https://cxplanner.jayliu.net/") > -1 || window.location.href.indexOf("Development/cx_planner") == 21) {
  2707. unsafeWindow.ue_saved_flights = value_get("saved_flights",{});
  2708. updateFlights();
  2709. window.addEventListener("focus", function(e){
  2710. unsafeWindow.ue_saved_flights = value_get("saved_flights",{});
  2711. setTimeout(function() {
  2712. unsafeWindow.ue_saved_flights = value_get("saved_flights",{});
  2713. updateFlights(e);
  2714. }, 500);
  2715. });
  2716. }
  2717.  
  2718.  
  2719. unsafeWindow.searchAwards = function(e){
  2720. let origin = e.target.closest("tr").querySelector(".rt-origin span");
  2721. let dest = e.target.closest("tr").querySelector(".rt-dest span");
  2722.  
  2723. if(!data.routes[[origin.innerText,dest.innerText].sort().join("-")]?.length) {
  2724. alert("Invalid route.");
  2725. return;
  2726. }
  2727.  
  2728. let saved = unsafeWindow.ue_flights || {};
  2729. let target = newQueryPayload({from:origin.innerText, to:dest.innerText, date:dateAdd(14)}, {adult:1, child:0}, "Y", true, false);
  2730.  
  2731. value_set("uef_from",origin.innerText);
  2732. value_set("uef_to",dest.innerText);
  2733. value_set("cont_ts",Date.now());
  2734. value_set("cont_query","1");
  2735. value_set("cont_batch","0");
  2736. value_set("redirect_search",target.href);
  2737. value_set("cxplanner_flights",fullRoute);
  2738.  
  2739. const url = `https://www.cathaypacific.com/cx/${lang.el}_${lang.ec}/book-a-trip/redeem-flights/redeem-flight-awards.html`;
  2740. window.open(url, "cxplanner", "noopener noreferrer");
  2741.  
  2742. //location.href = `https://www.cathaypacific.com/cx/${lang.el}_${lang.ec}/book-a-trip/redeem-flights/redeem-flight-awards.html`;
  2743. //console.log(target.href)
  2744.  
  2745. };
  2746. //============================================================
  2747. // Sticky Footer
  2748. //============================================================
  2749.  
  2750. function stickyFooter() {
  2751. var bulkboxOffset = div_bulk_box.getBoundingClientRect();
  2752. var ueformOffset = div_ue_container.getBoundingClientRect();
  2753. //if (footerOffset.top < window.innerHeight - 55 || ueformOffset.top + div_ue_container.clientHeight > window.innerHeight - 72) {
  2754. if(bulkboxOffset.bottom < window.innerHeight) {
  2755. div_footer.classList.remove("bulk_sticky");
  2756. shadowRoot.querySelector(".bulk_results").style.paddingBottom = "0px"
  2757. } else {
  2758. div_footer.classList.add("bulk_sticky");
  2759. shadowRoot.querySelector(".bulk_results").style.paddingBottom = "65px"
  2760. }
  2761. }
  2762.  
  2763. //============================================================
  2764. // Enable Advanced Features
  2765. //============================================================
  2766.  
  2767. //value_set("pKey","false");
  2768.  
  2769. let pKey = value_get("pKey","");
  2770.  
  2771. function is_valid_key(key){
  2772. //var hash = encJS.MD5(encJS.AES.decrypt("U2FsdGVkX18+gMipG7SN/jcVZuccMP/M3IN/HG2brkhx0CoRJFkxcKSNyQounYc9XiF9Pk48buZ58RcxV6W5Rn3NGzEN3kz0sN0ulGThPwadtChhIC58c65+vqo4l4MT", key).toString(encJS.enc.Utf8) || "").toString();
  2773. //return (hash == "68a1fc33f27f95281c831e99f5c4fabc");
  2774. return (btoa(key) == "Q1gyMlVFQVM=");
  2775. }
  2776.  
  2777. function is_trial_key(key){
  2778. return (btoa(key) == "Q1gwMzEyRlJFRQ==");
  2779. }
  2780.  
  2781. function check_key(key){
  2782. if(is_valid_key(key)){
  2783. const jsConfetti = new JSConfetti();
  2784. jsConfetti.addConfetti({
  2785. confettiRadius: 3.5,
  2786. confettiNumber: 500,
  2787. });
  2788. pKey = value_set("pKey",key);
  2789. advanced_features();
  2790. input_to.value = "";
  2791. input_from.value = "";
  2792. elevate();
  2793. } else if(is_trial_key(key)){
  2794. if(Date.now() > 1679241599000) {
  2795. alert("序號已過期");
  2796. shadowRoot.querySelector("#activation_input").value = "";
  2797. } else if(value_get("trial","")){
  2798. alert("已經使用過試用序號");
  2799. } else {
  2800. alert("已解鎖 36 小時試用");
  2801. const jsConfetti = new JSConfetti();
  2802. jsConfetti.addConfetti({
  2803. confettiRadius: 3.5,
  2804. confettiNumber: 500,
  2805. });
  2806. value_set("trial",Date.now())
  2807. advanced_features();
  2808. input_to.value = "";
  2809. input_from.value = "";
  2810. elevate();
  2811. }
  2812.  
  2813. }
  2814. }
  2815. //GM.deleteValue("trial");
  2816. if(is_valid_key(value_get("pKey","")) || (Date.now() - value_get("trial",0) < 60*60*36*1000)) {//60*60*24*1000)) {
  2817. advanced_features();
  2818. }
  2819.  
  2820. function advanced_features(){
  2821. //let code = "U2FsdGVkX18HnJPtvD6mz6QtAuSQH5QT2SPCHL7n5IyEvb/rBQgTAPy4LWR4oRODB+/F7QHVXYqM4V/";
  2822. //code += "NDW1Fb0RAPbdiIPQY1A6sBgc+/JOQdpnjHb8mswg6lLoEsywchzBSKrzB7QDQr7/9A0aqXeWE80tnH9mHpxKDMBuo04c=";
  2823. //eval(encJS.AES.decrypt(code, pKey).toString(encJS.enc.Utf8));
  2824. let code = "c2hhZG93Q29udGFpbmVyLmNsYXNzTGlzdC5hZGQoImVsZXZhdGVkX29uIik7c2hhZG93Q29";
  2825. code += "udGFpbmVyLmNsYXNzTGlzdC5yZW1vdmUoInVuZWxldmF0ZWRfY29udGFpbmVyIik7dCA9IHI7";
  2826. eval(atob(code));
  2827. }
  2828.  
  2829.  
  2830. /*
  2831. // ENCRYPT
  2832. pKey = "";let code = ``;
  2833. document.querySelector("body").insertAdjacentHTML("beforebegin", encJS.AES.encrypt(code, pKey).toString());
  2834. // DECRYPT:
  2835. eval(encJS.AES.decrypt("code_string", pkey).toString(encJS.enc.Utf8));
  2836. */
  2837.  
  2838.  
  2839. //============================================================
  2840. // Check Version (Max once per day)
  2841. //============================================================
  2842.  
  2843. let currentVersion = GM_info.script.version;
  2844. let lastCheck = value_get("lastCheck",0)
  2845. let latestVersion = value_get("latestVersion",currentVersion)
  2846.  
  2847. function hasUpdate(newer, older) {
  2848. let latest = newer.trim().split('.');
  2849. let loaded = older.trim().split('.');
  2850. for (let i = 0; i < Math.min(latest.length, loaded.length); i++) {
  2851. latest[i] = Number(latest[i]) || 0;
  2852. loaded[i] = Number(loaded[i]) || 0;
  2853. if (latest[i] !== loaded[i]) {return (latest[i] > loaded[i] ? newer : false );};
  2854. }
  2855. return (latest.length > loaded.length ? newer : false);
  2856. }
  2857.  
  2858. function showUpdate(liveVersion){
  2859. log("currentVersion: "+ currentVersion);
  2860. log("metaData.version: "+ liveVersion);
  2861.  
  2862. shadowRoot.querySelector(".unelevated_update a").href = value_get("update_link","https://cxplanner.jayliu.net/private.html");
  2863. let newVersion = hasUpdate(liveVersion,currentVersion);
  2864. if(newVersion){
  2865. value_set("latestVersion",liveVersion);
  2866. div_update.classList.remove("hidden");
  2867. shadowRoot.querySelector("#upd_version").innerText = newVersion;
  2868. };
  2869. }
  2870.  
  2871. function getLatest(date) {
  2872. GM_xmlhttpRequest({
  2873. method: 'GET',
  2874. url: 'https://cxplanner.jayliu.net/latest_private.json?v='+date,
  2875. onload: function(e) {
  2876. const response = JSON.parse(e.responseText);
  2877. const version = response.latest_version;
  2878. const link = response.update_link;
  2879. value_set("update_link", (link ? link : "https://cxplanner.jayliu.net/private.html"));
  2880. //const key = /\/\/ @version +([0-9\.]+)/;
  2881. //const version = e.responseText.match(key) ? e.responseText.match(key)[1] : "0";
  2882. showUpdate(version);
  2883. }
  2884. })
  2885. }
  2886.  
  2887. function versionCheck(update, updateurl, metaData){
  2888. let date = new Date();
  2889. date = Math.floor(date.setHours(0,0,0)/1000);
  2890. if (date > lastCheck || !lastCheck) {
  2891. getLatest(date);
  2892. lastCheck = value_set("lastCheck",date)
  2893. } else {
  2894. showUpdate(latestVersion)
  2895. }
  2896. //value_set("lastCheck",0);
  2897. }
  2898.  
  2899. //============================================================
  2900. // Initialise
  2901. //============================================================
  2902.  
  2903. function initSearchBox() {
  2904. initCXvars();
  2905. shadowContainer.appendChild(searchBox);
  2906. assignElemets();
  2907. if(r==t) elevate();
  2908. addFormListeners();
  2909. window.onscroll = function() { stickyFooter() };
  2910. update_saved_count();
  2911. update_saved_flights();
  2912. autocomplete(input_from, "origins");
  2913. autocomplete(input_to, "origins");
  2914. getOrigins();
  2915. versionCheck();
  2916.  
  2917. if (cont_query) {
  2918. reset_cont_vars();
  2919. // If over 5 minutes since cont query, don't auto search
  2920. if (Date.now() - cont_ts > 60*5*1000 && !debug) return;
  2921. btn_batch.innerHTML = lang.searching_w_cancel;
  2922. btn_batch.classList.add("bulk_searching");
  2923. document.body.classList.add("cont_query");
  2924. if(cont_saved){
  2925. setTimeout(() => { saved_search(); }, "1000")
  2926. } else {
  2927. setTimeout(() => { bulk_click(cont_batch ? false : true); }, "1000")
  2928. }
  2929. }
  2930. };
  2931.  
  2932. function initCXplannerBox(){
  2933. if(cxplanner_flights){
  2934. const cxplanner_box = document.createElement("div");
  2935. let html = "";
  2936. cxplanner_flights.forEach(item=>{
  2937. if(item) html += `<a href="#" class="planner_route" data-route="${item}">${item}</a>`;
  2938. })
  2939. cxplanner_box.innerHTML = "<div id='planner_routes'>"+html+"</div>";
  2940. shadowContainer.appendChild(cxplanner_box);
  2941.  
  2942. value_set("cxplanner_flights", [])
  2943. cxplanner_box.addEventListener("click",function(e){
  2944. if(e.target.dataset["route"]){
  2945. route_changed = true;
  2946. input_from.value = e.target.dataset["route"].split("-")[0];
  2947. input_to.value = e.target.dataset["route"].split("-")[1];
  2948. }
  2949. })
  2950. }
  2951.  
  2952. }
  2953.  
  2954. if(window.location.href.indexOf("cathaypacific") > -1){
  2955. initRoot();
  2956. }
  2957.  
  2958. })();