CacheTour

Collect Geocaches from geocaching.com and download them as single GPX file.

当前为 2016-09-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name CacheTour
  3. // @namespace de.rocka84.cachetour
  4. // @version 0.1.7
  5. // @author Rocka84 <f.dillmeier@gmail.com>
  6. // @description Collect Geocaches from geocaching.com and download them as single GPX file.
  7. // @run-at document-end
  8. // @icon https://github.com/Rocka84/CacheTour/raw/master/dist/icon.png
  9. // @include http*://www.geocaching.com/*
  10. // @exclude https://www.geocaching.com/articles
  11. // @exclude https://www.geocaching.com/about
  12. // @exclude https://www.geocaching.com/login
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_deleteValue
  16. // @grant GM_addStyle
  17. // @require https://greasyfork.org/scripts/23570-filesaver-js/code/FileSaverjs.js?version=149663
  18. // @require https://code.jquery.com/jquery-1.8.3.min.js
  19. // ==/UserScript==
  20.  
  21. var console = unsafeWindow.console; //for greasemonkey
  22.  
  23. (function(){
  24. "use strict";
  25. var modules = [],
  26. settings,
  27. styles = [],
  28. tours = [],
  29. current_tour = 0,
  30. locales = [],
  31. locale;
  32.  
  33. function initDependencies() {
  34. $('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">').appendTo(document.body);
  35. $('<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/master/FileSaver.min.js">').appendTo(document.body);
  36. }
  37.  
  38. function createStyles() {
  39. GM_addStyle(styles.join("\n"));
  40. }
  41. function loadSettings() {
  42. settings = JSON.parse(GM_getValue("settings") || "{}");
  43. current_tour = Math.min(settings.current_tour || 0, tours.length - 1);
  44. locale = settings.locale || 'en';
  45. return CacheTour;
  46. }
  47.  
  48. function saveTours() {
  49. var tour_data = [];
  50. for (var i = 0, c = tours.length; i < c; i++) {
  51. tour_data.push(tours[i].toJSON());
  52. }
  53. GM_setValue("tours", JSON.stringify(tour_data));
  54. return CacheTour;
  55. }
  56.  
  57. function loadTours() {
  58. var tour_data = JSON.parse(GM_getValue("tours") || "[]");
  59. for (var i = 0, c = tour_data.length; i < c; i++) {
  60. CacheTour.addTour(CacheTour.Tour.fromJSON(tour_data[i]));
  61. }
  62. if (tours.length === 0) {
  63. CacheTour.addTour(new CacheTour.Tour());
  64. }
  65. console.log('tours', tours);
  66. return CacheTour;
  67. }
  68.  
  69. function initModules() {
  70. for (var i = 0, c = modules.length; i < c; i++) {
  71. if (modules[i].shouldRun()) {
  72. console.info("init Module " + modules[i].getName());
  73. try {
  74. modules[i].init();
  75. } catch (exception) {
  76. console.error('Exception while initializing module', modules[i].getName());
  77. console.error(exception);
  78. }
  79. }
  80. }
  81. }
  82.  
  83. function runModules() {
  84. for (var i = 0, c = modules.length; i < c; i++) {
  85. if (modules[i].shouldRun()) {
  86. console.info("run Module " + modules[i].getName());
  87. try {
  88. modules[i].run();
  89. } catch (exception) {
  90. console.error('Exception in module', modules[i].getName());
  91. console.error(exception);
  92. }
  93. }
  94. }
  95. }
  96.  
  97. function tourChanged() {
  98. saveTours();
  99. CacheTour.Gui.updateCacheList();
  100. }
  101.  
  102. var CacheTour = unsafeWindow.CacheTour = window.CacheTour = {
  103. initialize: function() {
  104. initDependencies();
  105. loadTours();
  106. loadSettings();
  107.  
  108. initModules();
  109. createStyles();
  110. runModules();
  111. },
  112. registerModule: function(module) {
  113. modules.push(module);
  114. return CacheTour;
  115. },
  116. addStyle: function(style) {
  117. styles.push(style);
  118. return CacheTour;
  119. },
  120. addTour: function(Tour) {
  121. Tour.onChange(tourChanged);
  122. current_tour = tours.length;
  123. tours.push(Tour);
  124. // tourChanged();
  125. return CacheTour;
  126. },
  127. setTourIndex: function(index) {
  128. current_tour = index;
  129. CacheTour.Gui.setTour(CacheTour.getCurrentTour());
  130. CacheTour.saveSettings();
  131. return CacheTour;
  132. },
  133. getCurrentTour: function() {
  134. return tours[current_tour];
  135. },
  136. getTours: function() {
  137. return tours;
  138. },
  139. getTour: function(id) {
  140. return this.tours[id];
  141. },
  142. useTemplate: function(template, data) {
  143. var out = template;
  144. for (var key in data) {
  145. if (data.hasOwnProperty(key)) {
  146. out = out.replace(new RegExp('<% ' + key + ' %>','g'), data[key]);
  147. }
  148. }
  149. return out.replace(/<%.+?%>/g,'');
  150. },
  151. getSetting: function(setting) {
  152. return settings[setting];
  153. },
  154. setSetting: function(setting, value, dont_save) {
  155. if (typeof setting === 'string') {
  156. settings[setting] = value;
  157. }
  158. if (typeof setting === 'object') {
  159. for(var _setting in setting) {
  160. if (setting.hasOwnProperty(_setting)) {
  161. CacheTour.setSetting(_setting, setting[_setting], true);
  162. }
  163. }
  164. }
  165. return dont_save ? CacheTour : CacheTour.saveSettings();
  166. },
  167. saveSettings: function() {
  168. settings.current_tour = current_tour;
  169. GM_setValue("settings", JSON.stringify(settings));
  170. return CacheTour;
  171. },
  172. saveFile: function(filepath, content, type) {
  173. saveAs(new Blob([content], {type: type || 'text/plain;charset=utf-8'}), filepath);
  174. return CacheTour;
  175. },
  176. escapeHTML: function(html) {
  177. return $('<div>').text(html).html();
  178. },
  179. registerLocale: function(language, strings) {
  180. locales[language] = strings;
  181. },
  182. setLocale: function(_locale) {
  183. locale = _locale;
  184. },
  185. l10n: function(key) {
  186. if (locale !== 'en' && !locales[locale][key]) {
  187. return locales.en[key];
  188. }
  189. return locales[locale][key];
  190. },
  191. resetSettings: function(){
  192. if (confirm('This deletes all tours and settings for CacheTour and cannot be undone! Are you sure about that?')) {
  193. GM_deleteValue("settings");
  194. GM_deleteValue("tours");
  195. document.location.reload();
  196. }
  197. }
  198. };
  199. })();
  200.  
  201.  
  202. (function(){
  203. "use strict";
  204.  
  205. var attribute_names = [
  206. 'unknown', // thankfully index 0 is unused
  207. 'dogs',
  208. 'fee',
  209. 'rappelling',
  210. 'boat',
  211. 'scuba',
  212. 'kids',
  213. 'onehour',
  214. 'scenic',
  215. 'hiking',
  216. 'climbing',
  217. 'wading',
  218. 'swimming',
  219. 'available',
  220. 'night',
  221. 'winter',
  222. '16', // to make indexes match attribute-ids
  223. 'poisonoak',
  224. 'snakes',
  225. 'ticks',
  226. 'mine',
  227. 'cliff',
  228. 'hunting',
  229. 'danger',
  230. 'wheelchair',
  231. 'parking',
  232. 'public',
  233. 'water',
  234. 'restrooms',
  235. 'phone',
  236. 'picnic',
  237. 'camping',
  238. 'bicycles',
  239. 'motorcycles',
  240. 'quads',
  241. 'jeeps',
  242. 'snowmobiles',
  243. 'horses',
  244. 'campfires',
  245. 'thorn',
  246. 'stealth',
  247. 'stroller',
  248. 'firstaid',
  249. 'cow',
  250. 'flashlight',
  251. 'landf',
  252. '46', // to make indexes match attribute-ids
  253. 'field_puzzle',
  254. 'UV',
  255. 'snowshoes',
  256. 'skiis',
  257. 's-tools',
  258. 'nightcache',
  259. 'parkngrab',
  260. 'abandonedbuilding',
  261. 'hike_short',
  262. 'hike_med',
  263. 'hike_long',
  264. 'fuel',
  265. 'food',
  266. 'wirelessbeacon',
  267. 'partnership',
  268. 'seasonal',
  269. 'touristOK',
  270. 'treeclimbing',
  271. 'frontyard',
  272. 'teamwork',
  273. 'geotour'
  274. ];
  275.  
  276. //temporary!
  277. function ucFirst(str) {
  278. var letters = str.split(''),
  279. first = letters.shift();
  280. return first.toUpperCase() + letters.join('');
  281. }
  282.  
  283. var Attribute = CacheTour.Attribute = function(type, invert) {
  284. this.type = type;
  285. this.invert = !!invert;
  286. };
  287.  
  288. Attribute.createByName = function(name, invert) {
  289. return new Attribute(Math.max(attribute_names.indexOf(name), 0), invert);
  290. };
  291.  
  292. Attribute.prototype.getName = function() {
  293. return attribute_names[this.type];
  294. };
  295.  
  296. Attribute.prototype.getDescription = function(no_prefix) {
  297. //@todo use internationalized descriptions instead of the attributes name
  298. return (!no_prefix && this.invert ? 'Not ' : '') + ucFirst(this.getName());
  299. };
  300.  
  301. Attribute.prototype.toGPX = function() {
  302. return Promise.resolve('<groundspeak:attribute id="' + this.type + '" inc="' + (this.invert ? '0' : '1') + '">' + this.getDescription(true) + '</groundspeak:attribute>');
  303. };
  304.  
  305. })();
  306.  
  307.  
  308. (function(){
  309. "use strict";
  310.  
  311. var template_gpx =
  312. '<wpt lat="<% lat %>" lon="<% lon %>">\n'+
  313. '<time><% date %></time>\n'+
  314. '<name><% gc_code %></name>\n'+
  315. '<desc><% name %> by <% owner %>, <% type %> (<% difficulty %>/<% terrain %>)</desc>\n'+
  316. '<url><% link %></url>\n'+
  317. '<urlname><% name %></urlname>\n'+
  318. '<sym><% symbol %></sym>\n'+
  319. '<type>geocache|<% type %></type>\n'+
  320. '<groundspeak:cache id="<% id %>" available="<% available %>" archived="<% archived %>" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">\n'+
  321. '<groundspeak:name><% name %></groundspeak:name>\n'+
  322. '<groundspeak:placed_by><% owner %></groundspeak:placed_by>\n'+
  323. '<groundspeak:owner><% owner %></groundspeak:owner>\n'+
  324. '<groundspeak:type><% type %></groundspeak:type>\n'+
  325. '<groundspeak:container><% size %></groundspeak:container>\n'+
  326. '<groundspeak:difficulty><% difficulty %></groundspeak:difficulty>\n'+
  327. '<groundspeak:terrain><% terrain %></groundspeak:terrain>\n'+
  328. '<groundspeak:country><% country %></groundspeak:country>\n'+
  329. '<groundspeak:state><% state %></groundspeak:state>\n'+
  330. '<groundspeak:attributes>\n<% attributes %>\n</groundspeak:attributes>\n'+
  331. '<groundspeak:short_description html="true"><% short_description %></groundspeak:short_description>\n'+
  332. '<groundspeak:long_description html="true"><% long_description %></groundspeak:long_description>\n'+
  333. '<groundspeak:encoded_hints><% hint %></groundspeak:encoded_hints>\n'+
  334. '<groundspeak:logs>\n<% logs %>\n</groundspeak:logs>\n'+
  335. '</groundspeak:cache>\n'+
  336. '</wpt>';
  337.  
  338. var types = {
  339. 2: {
  340. name: 'traditional',
  341. icon: "/images/WptTypes/2.gif"
  342. },
  343. 3: {
  344. name: 'multi',
  345. icon: "/images/WptTypes/3.gif"
  346. },
  347. 4: {
  348. name: 'virtual',
  349. icon: "/images/WptTypes/4.gif"
  350. },
  351. 5: {
  352. name: 'letterbox',
  353. icon: "/images/WptTypes/5.gif"
  354. },
  355. 6: {
  356. name: 'event',
  357. icon: "/images/WptTypes/6.gif"
  358. },
  359. 8: {
  360. name: 'mystery',
  361. icon: "/images/WptTypes/8.gif"
  362. },
  363. 11: {
  364. name: 'webcam',
  365. icon: "/images/WptTypes/11.gif"
  366. },
  367. 13: {
  368. name: 'cito',
  369. icon: "/images/WptTypes/13.gif"
  370. },
  371. 137: {
  372. name: 'earthcache',
  373. icon: "/images/WptTypes/137.gif"
  374. },
  375. 1858: {
  376. name: 'wherigo',
  377. icon: "/images/WptTypes/1858.gif"
  378. }
  379. };
  380.  
  381. var Cache = window.CacheTour.Cache = function(gc_code) {
  382. this.gc_code = gc_code ? gc_code.toUpperCase() : null;
  383. this.logs = [];
  384. this.attributes = [];
  385. this.type = 'traditional';
  386. };
  387.  
  388. Cache.fromJSON = function(data) {
  389. var NewCache = new Cache(data.gc_code);
  390. if (data.name) NewCache.setName(data.name);
  391. if (data.type) NewCache.setType(data.type);
  392. if (data.difficulty) NewCache.setDifficulty(data.difficulty);
  393. if (data.terrain) NewCache.setTerrain(data.terrain);
  394. if (data.size) NewCache.setSize(data.size);
  395. return NewCache;
  396. };
  397.  
  398. Cache.prototype.getGcCode = function() {
  399. return this.gc_code;
  400. };
  401. Cache.prototype.setGcCode = function(gc_code) {
  402. this.gc_code = gc_code.toUpperCase();
  403. return this;
  404. };
  405.  
  406. Cache.prototype.getId = function() {
  407. return this.id;
  408. };
  409. Cache.prototype.setId = function(id) {
  410. this.id = id;
  411. return this;
  412. };
  413.  
  414. Cache.prototype.setName = function(name) {
  415. this.name = name;
  416. return this;
  417. };
  418. Cache.prototype.getName = function() {
  419. return this.name;
  420. };
  421. Cache.prototype.setType = function(type) {
  422. this.type = type;
  423. return this;
  424. };
  425. Cache.prototype.getType = function() {
  426. return this.type;
  427. };
  428. Cache.prototype.setDifficulty = function(difficulty) {
  429. this.difficulty = difficulty;
  430. return this;
  431. };
  432. Cache.prototype.getDifficulty = function() {
  433. return this.difficulty;
  434. };
  435.  
  436. Cache.prototype.setTerrain = function(terrain) {
  437. this.terrain = terrain;
  438. return this;
  439. };
  440. Cache.prototype.getTerrain = function() {
  441. return this.terrain;
  442. };
  443.  
  444. Cache.prototype.setDate = function(date) {
  445. this.date = date;
  446. return this;
  447. };
  448. Cache.prototype.getDate = function() {
  449. return this.date;
  450. };
  451.  
  452. Cache.prototype.setOwner = function(owner) {
  453. this.owner = owner;
  454. return this;
  455. };
  456. Cache.prototype.getOwner = function() {
  457. return this.owner;
  458. };
  459.  
  460. Cache.prototype.setSize = function(size) {
  461. this.size = size;
  462. return this;
  463. };
  464. Cache.prototype.getSize = function() {
  465. return this.size;
  466. };
  467.  
  468. Cache.prototype.setTour = function(Tour) {
  469. this.Tour = Tour;
  470. return this;
  471. };
  472. Cache.prototype.matches = function(Cache) {
  473. return this.gc_code === Cache.getGcCode();
  474. };
  475.  
  476. Cache.prototype.getLink = function() {
  477. // return "https://coord.info/" + this.gc_code;
  478. return "https://www.geocaching.com/seek/cache_details.aspx?wp=" + this.gc_code;
  479. };
  480.  
  481. Cache.prototype.addAttribute = function(attribute) {
  482. this.attributes.push(attribute);
  483. return this;
  484. };
  485.  
  486. Cache.prototype.clearAttributes = function() {
  487. this.attributes = [];
  488. return this;
  489. };
  490.  
  491. Cache.prototype.addLog = function(Log) {
  492. this.logs.push(Log);
  493. return this;
  494. };
  495.  
  496. Cache.prototype.clearLogs = function() {
  497. this.logs = [];
  498. return this;
  499. };
  500.  
  501. Cache.prototype.setLongDescription = function(description) {
  502. this.long_description = description;
  503. };
  504.  
  505. Cache.prototype.setShortDescription = function(description) {
  506. this.short_description = description;
  507. };
  508.  
  509. Cache.prototype.getCoordinates = function() {
  510. if (!this.coordinates) {
  511. this.coordinates = new CacheTour.Coordinates();
  512. }
  513. return this.coordinates;
  514. };
  515.  
  516. Cache.prototype.setCoordinates = function(coordinates) {
  517. this.coordinates = coordinates;
  518. };
  519.  
  520. Cache.prototype.retrieveDetails = function(){
  521. return new Promise(function(resolve, reject) {
  522. $.get(this.getLink(), function(result) {
  523. (new CacheTour.CacheParser(result, this)).parseAllExtras();
  524. resolve();
  525. }.bind(this)).fail(reject);
  526. }.bind(this));
  527. };
  528.  
  529. Cache.getTypeName = function(type) {
  530. return types[type].name;
  531. };
  532.  
  533. Cache.getTypeText = function(type) {
  534. return CacheTour.l10n("cachetype_" + type);
  535. };
  536.  
  537. Cache.getTypeIcon = function(type) {
  538. return types[type] ? types[type].icon : null;
  539. };
  540.  
  541. Cache.getTypeIdByName = function(name) {
  542. for(var id in types) {
  543. if (types[id].name === name) {
  544. return id;
  545. }
  546. }
  547. return null;
  548. };
  549.  
  550. Cache.prototype.toGPX = function() {
  551. var logs;
  552. return this.retrieveDetails().then(function() {
  553. var log_promises = [];
  554. for (var i = 0, c = this.logs.length; i < c; i++) {
  555. log_promises.push(this.logs[i].toGPX());
  556. }
  557. return Promise.all(log_promises);
  558. }.bind(this)).then(function(_logs) {
  559. logs = _logs;
  560. var attrib_promises = [];
  561. for (var i = 0, c = this.attributes.length; i < c; i++) {
  562. attrib_promises.push(this.attributes[i].toGPX());
  563. }
  564. return Promise.all(attrib_promises);
  565. }.bind(this)).then(function(attributes) {
  566. var data = this.toJSON();
  567. data.logs = logs.join('\n');
  568. data.attributes = attributes.join('\n');
  569. data.short_description = CacheTour.escapeHTML(this.short_description);
  570. data.long_description = CacheTour.escapeHTML(this.long_description);
  571. data.lat = this.getCoordinates().getLatitude();
  572. data.lon = this.getCoordinates().getLongitude();
  573. data.available = this.available !== false ? 'TRUE' : 'FALSE';
  574. data.archived = this.archived ? 'TRUE' : 'FALSE';
  575. data.link = this.getLink();
  576.  
  577. return CacheTour.useTemplate(template_gpx, data);
  578. }.bind(this));
  579. };
  580. Cache.prototype.toElement = function() {
  581. var element = $('<div class="cachetour_cache">');
  582. element.append($('<img class="cachetour_cache_icon" src="' + Cache.getTypeIcon(this.type) + '">'));
  583. element.append($('<div class="cachetour_cache_name"><a href="' + this.getLink() + '">' + this.name + '</a></div>'));
  584. element.append($('<div class="cachetour_cache_code">' + this.gc_code + '</div>'));
  585. element.append($('<div class="cachetour_cache_difficulty">' + this.difficulty + '</div>'));
  586. element.append($('<div class="cachetour_cache_terrain">' + this.terrain + '</div>'));
  587. element.append($('<div class="cachetour_cache_size">' + this.size + '</div>'));
  588.  
  589. element.append($('<div class="fa fa-trash-o cachetour_cache_delete">').click(function() {
  590. this.Tour.removeCache(this);
  591. }.bind(this)));
  592.  
  593. var updown = $('<div class="cachetour_cache_order">');
  594. updown.append($('<div class="cachetour_cache_up fa fa-chevron-up"></div>').click(function(){
  595. this.Tour.moveCacheUp(this);
  596. }.bind(this)));
  597.  
  598. updown.append($('<div class="cachetour_cache_down fa fa-chevron-down"></div>').click(function(){
  599. this.Tour.moveCacheDown(this);
  600. }.bind(this)));
  601.  
  602. element.append(updown);
  603.  
  604. return element;
  605. };
  606. Cache.prototype.toJSON = function() {
  607. return {
  608. gc_code: this.gc_code,
  609. id: this.id,
  610. type: this.type,
  611. name: this.name,
  612. owner: this.owner,
  613. difficulty: this.difficulty,
  614. terrain: this.terrain,
  615. available: this.available,
  616. archived: this.archived,
  617. size: this.size,
  618. date: this.date
  619. };
  620. };
  621. Cache.prototype.toString = function() {
  622. return this.gc_code + " " + this.name;
  623. };
  624. })();
  625.  
  626. (function(){
  627. "use strict";
  628.  
  629. var Geo = unsafeWindow.Geo;
  630.  
  631. var CacheParser = CacheTour.CacheParser = function(source, Cache) {
  632. this.source_raw = source;
  633. this.source = $(source);
  634. this.Cache = Cache ? Cache : new CacheTour.Cache();
  635. this.parseBaseData();
  636. };
  637.  
  638. CacheParser.prototype.getCache = function() {
  639. return this.Cache;
  640. };
  641. CacheParser.prototype.parseBaseData = function() {
  642. var gc_code = this.source.find('#ctl00_ContentBody_CoordInfoLinkControl1_uxCoordInfoCode').first().html();
  643.  
  644. this.Cache.setId(this.source.find('.LogVisit').attr('href').match(/ID=(\d+)/)[1]);
  645. this.Cache.setGcCode(gc_code);
  646. this.Cache.setType(this.source.find('.cacheImage img').attr('src').replace(/^.*\/(\d+)\.gif.*$/,'$1'));
  647. this.Cache.setName(this.source.find('#ctl00_ContentBody_CacheName').first().text());
  648. this.Cache.setOwner(this.source.find('#ctl00_ContentBody_mcd1 a').first().text());
  649. this.Cache.setDate(this.source.find('#ctl00_ContentBody_mcd2').text().split('\n')[3].replace(/^ */,''));
  650.  
  651. if (this.source.find('span.minorCacheDetails').first().text().match(/\((.+)\)/)) {
  652. this.Cache.setSize(RegExp.$1);
  653. }
  654.  
  655. if (this.source.find('#ctl00_ContentBody_Localize12').first().html().match(/stars([\d_]+)\./)) {
  656. this.Cache.setTerrain(parseFloat(RegExp.$1.replace('_','.')));
  657. }
  658.  
  659. if (this.source.find('#ctl00_ContentBody_uxLegendScale').first().html().match(/stars([\d_]+)\./)) {
  660. this.Cache.setDifficulty(parseFloat(RegExp.$1.replace('_','.')));
  661. }
  662. return this;
  663. };
  664.  
  665. CacheParser.prototype.parseAllExtras = function() {
  666. return this
  667. .parseCoordinates()
  668. .parseAttributes()
  669. .parseDescription()
  670. .parseLogs();
  671. };
  672.  
  673. CacheParser.prototype.parseAttributes = function() {
  674. this.Cache.clearAttributes();
  675. this.source.find('#ctl00_ContentBody_detailWidget img').each(function(key,el) {
  676. if ($(el).attr('src').match(/([^\/]*)-(yes|no)\./)) {
  677. this.Cache.addAttribute(CacheTour.Attribute.createByName(RegExp.$1, RegExp.$2 === 'no'));
  678. }
  679. }.bind(this));
  680. return this;
  681. };
  682.  
  683. CacheParser.prototype.parseDescription = function() {
  684. this.Cache.setShortDescription(this.source.find('#ctl00_ContentBody_ShortDescription').first().html());
  685. this.Cache.setLongDescription(this.source.find('#ctl00_ContentBody_LongDescription').first().html());
  686. return this;
  687. };
  688.  
  689. CacheParser.prototype.parseCoordinates = function() {
  690. var parts = this.source.find('#uxLatLon').text().match(/[NS] (.+) [EW] (.+)/);
  691. this.Cache.setCoordinates(new CacheTour.Coordinates(Geo.parseDMS(parts[1]), Geo.parseDMS(parts[2])));
  692. return this;
  693. };
  694.  
  695. CacheParser.prototype.parseLogs = function(limit) {
  696. this.Cache.clearLogs();
  697. limit = limit || 20;
  698. if (this.source_raw.match(/initalLogs\s*=\s*(\{.*\});/)) {
  699. var initialLogs = JSON.parse(RegExp.$1).data;
  700. for (var i = 0, c = Math.min(limit, initialLogs.length); i < c; i++) {
  701. var Log = new CacheTour.Log();
  702. Log.setId(initialLogs[i].LogID)
  703. .setFinder(initialLogs[i].UserName)
  704. .setType(initialLogs[i].LogType)
  705. .setDate(initialLogs[i].Visited)
  706. .setText(CacheTour.escapeHTML(initialLogs[i].LogText));
  707. this.Cache.addLog(Log);
  708. }
  709. }
  710. return this;
  711. };
  712. })();
  713.  
  714. (function() {
  715. "use strict";
  716.  
  717. var Coordinates = CacheTour.Coordinates = function(latitude, longitude) {
  718. this.latitude = latitude || 0;
  719. this.longitude = longitude || 0;
  720. };
  721.  
  722. Coordinates.prototype.setLatitude = function(latitude) {
  723. this.latitude = latitude;
  724. return this;
  725. };
  726.  
  727. Coordinates.prototype.getLatitude = function() {
  728. return this.latitude;
  729. };
  730.  
  731. Coordinates.prototype.setLongitude = function(longitude) {
  732. this.longitude = longitude;
  733. return this;
  734. };
  735.  
  736. Coordinates.prototype.getLongitude = function() {
  737. return this.longitude;
  738. };
  739.  
  740. Coordinates.prototype.getDistance = function(OtherCoordinates) {
  741. //@todo this method isn't entirely correct for geo-coordinates
  742. return Math.sqrt(Math.pow(this.latitude - OtherCoordinates.getLatitude(), 2) + Math.pow(this.longitude - OtherCoordinates.getLongitude(), 2));
  743. };
  744.  
  745. Coordinates.prototype.toString = function() {
  746. return (this.latitude > 0 ? "N " : "S ") + this.latitude + (this.longitude > 0 ? " E " : " W ") + this.longitude;
  747. };
  748. })();
  749.  
  750. (function(){
  751. "use strict";
  752.  
  753. var template_gpx =
  754. '<groundspeak:log id="<% id %>">\n' +
  755. '<groundspeak:date><% date %></groundspeak:date>\n' +
  756. '<groundspeak:type><% type %></groundspeak:type>\n' +
  757. '<groundspeak:finder><% finder %></groundspeak:finder>\n' +
  758. '<groundspeak:text encoded="false"><% text %></groundspeak:text>\n' +
  759. '</groundspeak:log>';
  760.  
  761. var Log = window.CacheTour.Log = function() {
  762. };
  763.  
  764. Log.prototype.getId = function() {
  765. return this.id;
  766. };
  767. Log.prototype.setId = function(id) {
  768. this.id = id;
  769. return this;
  770. };
  771.  
  772. Log.prototype.getDate = function() {
  773. return this.date;
  774. };
  775. Log.prototype.setDate = function(date) {
  776. this.date = date;
  777. return this;
  778. };
  779.  
  780. Log.prototype.getType = function() {
  781. return this.type;
  782. };
  783. Log.prototype.setType = function(type) {
  784. this.type = type;
  785. return this;
  786. };
  787.  
  788. Log.prototype.getFinder = function() {
  789. return this.finder;
  790. };
  791. Log.prototype.setFinder = function(finder) {
  792. this.finder = finder;
  793. return this;
  794. };
  795.  
  796. Log.prototype.getText = function() {
  797. return this.finder;
  798. };
  799. Log.prototype.setText = function(text) {
  800. this.text = text;
  801. return this;
  802. };
  803.  
  804. Log.prototype.toGPX = function() {
  805. return new Promise(function(resolve, reject) {
  806. resolve(CacheTour.useTemplate(template_gpx, {
  807. id: this.id,
  808. date: this.date,
  809. type: this.type,
  810. finder: CacheTour.escapeHTML(this.finder),
  811. text: this.text
  812. }));
  813. }.bind(this));
  814. };
  815. })();
  816.  
  817.  
  818. (function(){
  819. "use strict";
  820.  
  821. var Module = CacheTour.Module = function(options) {
  822. options = options || {};
  823. this.name = options.name || 'UNKNOWN';
  824. this.requirements = options.requirements || {};
  825. if (typeof options.run === 'function') {
  826. this.run_function = options.run;
  827. }
  828. if (typeof options.init === 'function') {
  829. this.init_function = options.init;
  830. }
  831. };
  832.  
  833. Module.prototype.shouldRun = function() {
  834. return (
  835. !this.requirements.setting || CacheTour.getSetting(this.requirements.setting)
  836. ) && (
  837. !this.requirements.url || this.requirements.url.test(document.location.href)
  838. ) && (
  839. !this.requirements.element || (
  840. !!(this.element = $(this.requirements.element).first()) &&
  841. this.element.length > 0
  842. )
  843. );
  844. };
  845.  
  846. Module.prototype.getName = function() {
  847. return this.name;
  848. };
  849.  
  850. Module.prototype.init = function() {
  851. if (this.init_function) {
  852. this.init_function();
  853. }
  854. return this;
  855. };
  856. Module.prototype.run = function() {
  857. if (this.run_function) {
  858. this.run_function(this.element);
  859. }
  860. return this;
  861. };
  862.  
  863. })();
  864.  
  865.  
  866. (function(){
  867. "use strict";
  868.  
  869. var template_gpx =
  870. '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' +
  871. '<gpx xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:xsd="http://www.w3.org/2001/xmlschema" version="1.0" creator="cachetour" xsi:schemalocation="http://www.topografix.com/gpx/1/0 http://www.topografix.com/gpx/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd" xmlns="http://www.topografix.com/gpx/1/0">\n' +
  872. '<name><% name %></name>\n' +
  873. '<desc><% description %></desc>\n' +
  874. '<author>CacheTour</author>\n' +
  875. '<url>http://www.geocaching.com</url>\n' +
  876. '<urlname>geocaching - high tech treasure hunting</urlname>\n' +
  877. // '<time>' + xsddatetime(new date()) + '</time>\n' +
  878. '<keywords>cache, geocache</keywords>\n' +
  879. '<bounds minlat="<% minlat %>" minlon="<% minlon %>" maxlat="<% maxlat %>" maxlon="<% maxlon %>" />\n' +
  880. '<% caches %>\n' +
  881. '<% waypoints %>\n' +
  882. '</gpx>';
  883.  
  884. var Tour = window.CacheTour.Tour = function(name){
  885. this.name = name || 'Tour ' + (new Date()).toISOString().substr(0, 10);
  886. this.caches = [];
  887. return this;
  888. };
  889.  
  890. Tour.fromJSON = function(data) {
  891. var tour = new Tour(data.name);
  892. if (data.caches) {
  893. for (var i = 0, c = data.caches.length; i < c; i++) {
  894. tour.addCache(CacheTour.Cache.fromJSON(data.caches[i]).setTour(this));
  895. }
  896. }
  897. return tour;
  898. };
  899.  
  900. Tour.prototype.addCache = function(Cache) {
  901. if (!this.hasCache(Cache)) {
  902. this.caches.push(Cache.setTour(this));
  903. this.fireChange();
  904. }
  905. return this;
  906. };
  907. Tour.prototype.hasCache = function(Cache) {
  908. for (var i = 0, c = this.caches.length; i < c; i++) {
  909. if (this.caches[i].matches(Cache)) return true;
  910. }
  911. return false;
  912. };
  913. Tour.prototype.moveCacheDown = function(Cache) {
  914. for (var i = 0, c = this.caches.length - 1; i < c; i++) {
  915. if (this.caches[i].matches(Cache)) {
  916. this.caches[i] = this.caches[i + 1];
  917. this.caches[i + 1] = Cache;
  918. this.fireChange();
  919. return true;
  920. }
  921. }
  922. return false;
  923. };
  924. Tour.prototype.moveCacheUp = function(Cache) {
  925. for (var i = 1, c = this.caches.length; i < c; i++) {
  926. if (this.caches[i].matches(Cache)) {
  927. this.caches[i] = this.caches[i - 1];
  928. this.caches[i - 1] = Cache;
  929. this.fireChange();
  930. return true;
  931. }
  932. }
  933. return false;
  934. };
  935. Tour.prototype.removeCache = function(Cache) {
  936. for (var i = 0, c = this.caches.length; i < c; i++) {
  937. if (this.caches[i].matches(Cache)) {
  938. this.caches.splice(i,1);
  939. this.fireChange();
  940. return true;
  941. }
  942. }
  943. return false;
  944. };
  945. Tour.prototype.getName = function() {
  946. return this.name;
  947. };
  948. Tour.prototype.setName = function(name) {
  949. this.name = name;
  950. this.fireChange();
  951. };
  952.  
  953. Tour.prototype.getDescription = function() {
  954. return this.description;
  955. };
  956. Tour.prototype.setDescription = function(description) {
  957. this.description = description;
  958. this.fireChange();
  959. };
  960.  
  961. Tour.prototype.onChange = function(callback) {
  962. this.change_callback = callback;
  963. };
  964. Tour.prototype.fireChange = function() {
  965. if (this.change_callback) {
  966. this.change_callback();
  967. }
  968. return CacheTour;
  969. };
  970.  
  971. Tour.prototype.getCaches = function() {
  972. return this.caches;
  973. };
  974.  
  975. Tour.prototype.getBounds = function() {
  976. var min = new CacheTour.Coordinates(999,999),
  977. max = new CacheTour.Coordinates();
  978. for (var i = 0, c = this.caches.length; i < c; i++) {
  979. if (this.caches[i].getCoordinates().getLatitude() < min.getLatitude()) {
  980. min.setLatitude(this.caches[i].getCoordinates().getLatitude());
  981. }
  982. if (this.caches[i].getCoordinates().getLatitude() > max.getLatitude()) {
  983. max.setLatitude(this.caches[i].getCoordinates().getLatitude());
  984. }
  985.  
  986. if (this.caches[i].getCoordinates().getLongitude() < min.getLongitude()) {
  987. min.setLongitude(this.caches[i].getCoordinates().getLongitude());
  988. }
  989. if (this.caches[i].getCoordinates().getLongitude() > max.getLongitude()) {
  990. max.setLongitude(this.caches[i].getCoordinates().getLongitude());
  991. }
  992. }
  993. return {
  994. min: min,
  995. max: max
  996. };
  997. };
  998.  
  999. Tour.prototype.toGPX = function(on_progress) {
  1000. var cache_promises = [],
  1001. cache_done = function(gpx){
  1002. on_progress('cache', 'done', i);
  1003. return gpx;
  1004. };
  1005. on_progress = on_progress || function(){};
  1006. on_progress('tour', 'start');
  1007. for (var i = 0, c = this.caches.length; i < c; i++) {
  1008. on_progress('cache', 'start', i);
  1009. cache_promises.push(this.caches[i].toGPX().then(cache_done));
  1010. }
  1011. return Promise.all(cache_promises).then(function(caches) {
  1012. on_progress('tour', 'caches_done');
  1013. var bounds = this.getBounds();
  1014.  
  1015. on_progress('tour', 'done');
  1016. return CacheTour.useTemplate(template_gpx, {
  1017. name: this.name,
  1018. caches: caches.join(''),
  1019. minlat: bounds.min.getLatitude(),
  1020. minlon: bounds.min.getLongitude(),
  1021. maxlat: bounds.max.getLatitude(),
  1022. maxlon: bounds.max.getLongitude()
  1023. });
  1024. }.bind(this));
  1025. };
  1026. Tour.prototype.toElement = function() {
  1027. var element = $('<div class="cachetour_tour">'),
  1028. header = $('<div class="cachetour_tour_header">' + this.name + '</div>');
  1029. header.append($('<div class="cachetour_tour_rename fa fa-pencil" title="' + CacheTour.l10n('rename_tour') + '">').click(function() {
  1030. var new_name = prompt(CacheTour.l10n('choose_name'), this.name);
  1031. if (new_name) {
  1032. this.setName(new_name);
  1033. CacheTour.saveSettings();
  1034. }
  1035. }.bind(this)));
  1036. element.append(header);
  1037. for (var i = 0, c = this.caches.length; i < c; i++) {
  1038. element.append(this.caches[i].toElement());
  1039. }
  1040. return element;
  1041. };
  1042. Tour.prototype.toJSON = function() {
  1043. var data = {
  1044. name: this.name,
  1045. description: this.description,
  1046. caches: []
  1047. };
  1048. for (var i = 0, c = this.caches.length; i < c; i++) {
  1049. data.caches.push(this.caches[i].toJSON());
  1050. }
  1051. return data;
  1052. };
  1053. Tour.prototype.toString = function() {
  1054. var out = "";
  1055. for (var i = 0, c = this.caches.length; i < c; i++) {
  1056. out = out + this.caches[i].toString() + "\n";
  1057. }
  1058. return out;
  1059. };
  1060.  
  1061. })();
  1062.  
  1063. (function() {
  1064. "use strict";
  1065.  
  1066. var gui,
  1067. tour_wrapper,
  1068. tour_select_wrapper,
  1069. tour_select,
  1070. tour,
  1071. mask,
  1072. mask_message;
  1073.  
  1074. var Gui = CacheTour.Gui = new CacheTour.Module({name: 'Gui'});
  1075.  
  1076. Gui.shouldRun = function(){
  1077. return true;
  1078. };
  1079.  
  1080. Gui.init = function() {
  1081. tour = CacheTour.getCurrentTour();
  1082. };
  1083.  
  1084. Gui.run = function() {
  1085. gui = $('<div id="cachetour_gui">').appendTo(document.body);
  1086. var header = $('<div id="cachetour_header">').appendTo(gui);
  1087. $('<i class="fa fa-archive main-icon">').appendTo(header);
  1088. $('<span id="cachetour_header">CacheTour</span>').appendTo(header);
  1089.  
  1090. var pin = $('<div id="cachetour_pin" class="fa-stack" title="' + CacheTour.l10n('keep_expanded') + '">');
  1091. gui.append(pin);
  1092. pin.append($('<div class="fa fa-thumb-tack fa-stack-1x">'));
  1093. pin.append($('<div class="fa fa-ban fa-stack-2x">'));
  1094. pin.on("click",function(){
  1095. gui.toggleClass("cachetour_pinned");
  1096. CacheTour.setSetting("pinned", gui.hasClass("cachetour_pinned"));
  1097. });
  1098. if (CacheTour.getSetting("pinned")) {
  1099. gui.addClass("cachetour_pinned");
  1100. }
  1101.  
  1102. var buttonbar = $('<div id="cachetour_buttonbar">').appendTo(gui);
  1103. $('<div class="fa fa-download" title="' + CacheTour.l10n('download_gpx_file') + '">').appendTo(buttonbar).click(downloadGPX);
  1104.  
  1105. $('<div class="fa fa-plus" title="' + CacheTour.l10n('add_new_tour') + '">').appendTo(buttonbar).click(function(){
  1106. var tour = new CacheTour.Tour(),
  1107. new_name = prompt(CacheTour.l10n('choose_name'), tour.getName());
  1108. if (new_name) {
  1109. tour.setName();
  1110. CacheTour.addTour(tour);
  1111. }
  1112. });
  1113. $('<div class="fa fa-cog" title="' + CacheTour.l10n('settings') + '">').appendTo(buttonbar).click(showSettingsDialog);
  1114.  
  1115. initTourSelect();
  1116. Gui.updateCacheList();
  1117.  
  1118. return Gui;
  1119. };
  1120.  
  1121. function initTourSelect() {
  1122. tour_wrapper = $('<div id="cachetour_tour_wrapper">').appendTo(gui);
  1123. tour_select_wrapper = $('<div id="cachetour_select_wrapper">');
  1124. $('<div class="fa fa-caret-square-o-down" id="cachetour_tour_select_btn" title="' + CacheTour.l10n('select_tour') + '">').appendTo(tour_select_wrapper);
  1125. gui.delegate('#cachetour_tour_select_btn', 'click', toggleTourSelect);
  1126. tour_select = $('<div id="cachetour_select">').appendTo(tour_select_wrapper);
  1127. }
  1128. function downloadGPX() {
  1129. var count = CacheTour.getCurrentTour().getCaches().length,
  1130. caches_done = 0;
  1131.  
  1132. gui.addClass('cachetour_working');
  1133. Gui.showWaitMessage(CacheTour.l10n('download_gpx_progress').replace('%done%', '0').replace('%count%', count));
  1134.  
  1135. CacheTour.getCurrentTour().toGPX(function(phase, state, index){
  1136. if (phase === 'cache' && state === 'done') {
  1137. caches_done++;
  1138. $('.cachetour_cache').eq(index).addClass('cachetour_done');
  1139. Gui.showWaitMessage(CacheTour.l10n('download_gpx_progress').replace('%done%', caches_done).replace('%count%', count));
  1140. }
  1141. }).then(function(content) {
  1142. CacheTour.saveFile(CacheTour.getCurrentTour().getName() + ".gpx", content);
  1143. }).then(function(){
  1144. Gui.hideWaitMessage();
  1145. gui.removeClass('cachetour_working');
  1146. });
  1147. }
  1148.  
  1149. Gui.showWaitMessage = function(message) {
  1150. Gui.showMask();
  1151. if (!mask_message) {
  1152. mask_message = $('<div id="cachetour_mask_message">').appendTo(mask);
  1153. }
  1154. mask.empty()
  1155. .append($('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw">'))
  1156. .append(mask_message);
  1157. mask_message.html(message || CacheTour.l10n('please_wait'));
  1158. };
  1159.  
  1160. Gui.showMask = function(message) {
  1161. if (!mask) {
  1162. mask = $('<div id="cachetour_mask">').appendTo(document.body);
  1163. }
  1164. mask.removeClass('hidden');
  1165. return Gui;
  1166. };
  1167.  
  1168. Gui.hideMask = Gui.hideWaitMessage = function () {
  1169. if (mask) {
  1170. mask.addClass('hidden');
  1171. }
  1172. return Gui;
  1173. };
  1174.  
  1175. Gui.setTour = function(_tour) {
  1176. tour = _tour;
  1177. Gui.updateCacheList();
  1178. };
  1179.  
  1180. Gui.updateCacheList = function() {
  1181. tour_wrapper.empty()
  1182. .append(tour.toElement())
  1183. .append(tour_select_wrapper);
  1184. return Gui;
  1185. };
  1186. function showTourSelect() {
  1187. tour_select.empty();
  1188. var tours = CacheTour.getTours();
  1189. for (var i = 0, c = tours.length; i < c; i++) {
  1190. $('<div class="cachetour_select_item" data-index="' + i + '">' + tours[i].getName() + '</div>').appendTo(tour_select);
  1191. }
  1192. tour_select.delegate('.cachetour_select_item', 'click', function(target) {
  1193. CacheTour.setTourIndex($(this).attr('data-index'));
  1194. hideTourSelect();
  1195. });
  1196. tour_select_wrapper.addClass('cachetour_show');
  1197. return Gui;
  1198. }
  1199.  
  1200. function hideTourSelect() {
  1201. tour_select_wrapper.removeClass('cachetour_show');
  1202. return Gui;
  1203. }
  1204.  
  1205. function toggleTourSelect() {
  1206. if (tour_select_wrapper.hasClass('cachetour_show')) {
  1207. hideTourSelect();
  1208. } else {
  1209. showTourSelect();
  1210. }
  1211. }
  1212.  
  1213. var settings_dialog;
  1214.  
  1215. function createSettingRow(label, element) {
  1216. return $('<tr><td>' + label + '</td></tr>').append($('<td>').append(element));
  1217. }
  1218.  
  1219. function initSettingsDialog() {
  1220. if (settings_dialog) return settings_dialog;
  1221.  
  1222. settings_dialog = $('<div id="cachetour_settings_dialog"><div>CacheTour - ' + CacheTour.l10n('settings') + '</div></div>');
  1223. $('<div class="fa fa-times" id="cachetour_settings_close">').appendTo(settings_dialog);
  1224. var table = $('<table>').appendTo(settings_dialog);
  1225.  
  1226. var language_select = $('<select id="cachetour_settings_language">');
  1227. // @todo: automate creation of language <option>s
  1228. $('<option value="en">English</option>').appendTo(language_select);
  1229. $('<option value="de">Deutsch</option>').appendTo(language_select);
  1230.  
  1231. createSettingRow(CacheTour.l10n('language') + ':', language_select).appendTo(table);
  1232. createSettingRow('', $('<input type="button" id="cachetour_settings_save" value="' + CacheTour.l10n('save') + '">')).appendTo(table);
  1233.  
  1234. return settings_dialog;
  1235. }
  1236.  
  1237. function showSettingsDialog() {
  1238. initSettingsDialog();
  1239.  
  1240. Gui.showMask();
  1241. mask.empty().append(settings_dialog);
  1242. //(re)add event after the element is in the DOM
  1243. $('#cachetour_settings_save').click(saveSettings);
  1244. $('#cachetour_settings_close').click(hideSetttingsDialog);
  1245.  
  1246. $('#cachetour_settings_language').attr('value', CacheTour.getSetting('locale'));
  1247. }
  1248.  
  1249. function hideSetttingsDialog() {
  1250. Gui.hideMask();
  1251. }
  1252.  
  1253. function saveSettings() {
  1254. hideSetttingsDialog();
  1255. CacheTour.setSetting('locale', $('#cachetour_settings_language').attr('value'));
  1256. // document.location.reload();
  1257. }
  1258.  
  1259. CacheTour.registerModule(Gui);
  1260. })();
  1261.  
  1262.  
  1263. (function(){
  1264. "use strict";
  1265.  
  1266. CacheTour.registerModule(
  1267. new CacheTour.Module({
  1268. name: 'Add latlng.js',
  1269. run: function() {
  1270. if ($('script[src*="latlng.js"]').length===0) {
  1271. $('<script src="https://www.geocaching.com/js/latlng.js">').appendTo(document.body);
  1272. }
  1273. },
  1274. shouldRun: function() {
  1275. return true;
  1276. }
  1277. })
  1278. );
  1279.  
  1280. })();
  1281.  
  1282. /* jshint multistr: true */
  1283. (function(){
  1284. "use strict";
  1285. var StylesJS = new CacheTour.Module({name: "main styles"});
  1286. StylesJS.shouldRun = function() {
  1287. return true;
  1288. };
  1289. StylesJS.run = function() {};
  1290. window.CacheTour.registerModule(StylesJS);
  1291.  
  1292.  
  1293. StylesJS.init = function(){
  1294. CacheTour.addStyle(
  1295. '#cachetour_gui {\
  1296. position: fixed;\
  1297. right: 0;\
  1298. top: 80px;\
  1299. width:24px;\
  1300. height:24px;\
  1301. border-style: solid;\
  1302. border-width: 2px 0 2px 2px;\
  1303. border-color: #f4f3ed;\
  1304. border-radius: 5px 0 0 5px;\
  1305. background: white;\
  1306. padding: 6px 10px;\
  1307. z-index:1100;\
  1308. overflow: hidden;\
  1309. }\
  1310. #cachetour_gui, #cachetour_gui * {\
  1311. box-sizing: content-box;\
  1312. }\
  1313. #cachetour_gui:hover, #cachetour_gui.cachetour_pinned {\
  1314. width: 280px;\
  1315. height: 85%;\
  1316. }\
  1317. \
  1318. #cachetour_header {\
  1319. font-weight:bold;\
  1320. font-size:x-large;\
  1321. line-height: 24px;\
  1322. }\
  1323. #cachetour_header span {\
  1324. visibility:hidden;\
  1325. display:inline-block;\
  1326. margin-left:4px;\
  1327. }\
  1328. #cachetour_header i {\
  1329. color: #2d4f15;\
  1330. font-size: x-large;\
  1331. }\
  1332. #cachetour_gui.cachetour_pinned #cachetour_header span, #cachetour_gui:hover #cachetour_header span {\
  1333. visibility:visible;\
  1334. }\
  1335. \
  1336. #cachetour_tour_wrapper {\
  1337. border-radius:2px;\
  1338. position:relative;\
  1339. }\
  1340. #cachetour_tour_wrapper:empty {\
  1341. text-align: center;\
  1342. font-style: italic;\
  1343. }\
  1344. #cachetour_tour_wrapper:empty:before {\
  1345. content: "- No entries -";\
  1346. }\
  1347. \
  1348. #cachetour_pin {\
  1349. position: absolute;\
  1350. right: 5px;\
  1351. top: 4px;\
  1352. cursor: pointer;\
  1353. visibility:hidden;\
  1354. }\
  1355. \
  1356. #cachetour_gui #cachetour_pin .fa-ban {\
  1357. display: none;\
  1358. color: gray;\
  1359. }\
  1360. \
  1361. #cachetour_gui.cachetour_pinned #cachetour_pin, #cachetour_gui:hover #cachetour_pin {\
  1362. visibility:visible;\
  1363. }\
  1364. \
  1365. #cachetour_gui.cachetour_pinned #cachetour_pin .fa-ban {\
  1366. display:block;\
  1367. }\
  1368. \
  1369. .cachetour_clear {\
  1370. clear:both;\
  1371. }\
  1372. \
  1373. .cachetour_tour {\
  1374. counter-reset: tour;\
  1375. }\
  1376. \
  1377. #cachetour_select_wrapper {\
  1378. position:absolute;\
  1379. top:0;\
  1380. left:0;\
  1381. width: 100%;\
  1382. }\
  1383. #cachetour_tour_select_btn {\
  1384. font-size: large;\
  1385. line-height: inherit;\
  1386. cursor:pointer;\
  1387. }\
  1388. .cachetour_tour_header {\
  1389. font-size: larger;\
  1390. border-bottom: 1px solid black;\
  1391. text-overflow: ellipsis;\
  1392. white-space: nowrap;\
  1393. overflow: hidden;\
  1394. position:relative;\
  1395. padding: 0 1.5em;\
  1396. }\
  1397. .cachetour_tour_rename {\
  1398. position: absolute;\
  1399. right: 0.3em;\
  1400. top: 0.3em;\
  1401. cursor: pointer;\
  1402. z-index:1100;\
  1403. }\
  1404. \
  1405. .cachetour_cache {\
  1406. border: 1px dashed #DCDCDC;\
  1407. border-radius: 6px;\
  1408. margin-top: 6px;\
  1409. padding: 3px 6px 3px 36px;\
  1410. position:relative;\
  1411. }\
  1412. .cachetour_cache_icon {\
  1413. width: 16px;\
  1414. height: 16px;\
  1415. position: absolute;\
  1416. top: 4px;\
  1417. }\
  1418. .cachetour_working .cachetour_cache {\
  1419. background-color: #DCDCDC;\
  1420. }\
  1421. .cachetour_working .cachetour_cache.cachetour_done {\
  1422. background-color: #D7FFD7;\
  1423. }\
  1424. .cachetour_cache_name {\
  1425. overflow: hidden;\
  1426. margin-left: 20px;\
  1427. }\
  1428. .cachetour_cache_code {\
  1429. font-family: monospace;\
  1430. display: inline-block;\
  1431. width:25%;\
  1432. }\
  1433. .cachetour_cache .cachetour_cache_delete {\
  1434. position: absolute;\
  1435. top: 1px;\
  1436. right: 1px;\
  1437. color: gray;\
  1438. background-color: white;\
  1439. padding: 3px;\
  1440. cursor: pointer;\
  1441. display: none;\
  1442. }\
  1443. .cachetour_cache:hover .cachetour_cache_delete {\
  1444. display: block;\
  1445. }\
  1446. .cachetour_cache::before {\
  1447. counter-increment: tour;\
  1448. content: counters(tour, ".") ".";\
  1449. position: absolute;\
  1450. left: 6px;\
  1451. font-size: x-large;\
  1452. color: gray;\
  1453. overflow: hidden;\
  1454. top: -4px;\
  1455. }\
  1456. .cachetour_cache_difficulty, .cachetour_cache_terrain, .cachetour_cache_size {\
  1457. display: inline-block;\
  1458. position: relative;\
  1459. width: calc(25% - .7em);\
  1460. padding-left:.2em;\
  1461. margin-left:.5em;\
  1462. }\
  1463. \
  1464. .cachetour_cache_difficulty::before, .cachetour_cache_terrain::before, .cachetour_cache_size::before {\
  1465. font-style: italic;\
  1466. font-size: smaller;\
  1467. position: absolute;\
  1468. left: -1.2em;\
  1469. top: 0.2em;\
  1470. }\
  1471. .cachetour_cache:hover .cachetour_cache_difficulty::before {\
  1472. content: "D: ";\
  1473. }\
  1474. .cachetour_cache:hover .cachetour_cache_terrain::before {\
  1475. content: "T: ";\
  1476. }\
  1477. .cachetour_cache:hover .cachetour_cache_size::before {\
  1478. content: "S: ";\
  1479. }\
  1480. \
  1481. .cachetour_cache_order {\
  1482. position: absolute;\
  1483. left: 4px;\
  1484. bottom: 2px;\
  1485. display: none;\
  1486. color: gray;\
  1487. }\
  1488. .cachetour_cache_order > * {\
  1489. cursor: pointer;\
  1490. }\
  1491. .cachetour_cache:hover .cachetour_cache_order {\
  1492. display: block;\
  1493. }\
  1494. .cachetour_tour_header + .cachetour_cache .cachetour_cache_up {\
  1495. visibility: hidden;\
  1496. }\
  1497. .cachetour_cache:last-of-type .cachetour_cache_down {\
  1498. visibility: hidden;\
  1499. }\
  1500. #cachetour_buttonbar {\
  1501. border-bottom: 1px dashed lightgray;\
  1502. margin: 4px 0 8px 0;\
  1503. }\
  1504. #cachetour_buttonbar > div {\
  1505. margin: 0 3px;\
  1506. font-size:larger;\
  1507. cursor: pointer;\
  1508. }\
  1509. \
  1510. #cachetour_mask {\
  1511. position: fixed;\
  1512. top:0;\
  1513. bottom:0;\
  1514. left:0;\
  1515. right:0;\
  1516. background-color: rgba(68, 68, 68, .8);\
  1517. z-index: 1101;\
  1518. }\
  1519. \
  1520. .hidden {\
  1521. display:none !important;\
  1522. }\
  1523. #cachetour_mask > .fa {\
  1524. position: absolute;\
  1525. top: 50%;\
  1526. width:100%;\
  1527. color: white;\
  1528. }\
  1529. #cachetour_mask > .fa::before {\
  1530. position: absolute;\
  1531. transform: translate(-50%, -50%);\
  1532. }\
  1533. #cachetour_mask_message {\
  1534. position: absolute;\
  1535. left:50%;\
  1536. top:50%;\
  1537. transform: translate(-50%, 32px);\
  1538. color:white;\
  1539. font-size: x-large;\
  1540. background: rgba(68, 68, 68, 1);\
  1541. padding: 12px 14px;\
  1542. border-radius: 12px;\
  1543. text-align: center;\
  1544. }\
  1545. #cachetour_select {\
  1546. background: white;\
  1547. font-size: larger;\
  1548. padding: 6px;\
  1549. border: 1px solid black;\
  1550. border-radius: 6px;\
  1551. display:none;\
  1552. }\
  1553. #cachetour_select_wrapper.cachetour_show #cachetour_select {\
  1554. display:block;\
  1555. }\
  1556. .cachetour_select_item {\
  1557. border: 1px dashed #DCDCDC;\
  1558. border-radius: 4px;\
  1559. padding: 2px 4px;\
  1560. cursor: pointer;\
  1561. }\
  1562. .cachetour_select_item + .cachetour_select_item {\
  1563. margin-top:4px;\
  1564. }\
  1565. .cachetour_select_item:hover {\
  1566. border-color: black;\
  1567. }\
  1568. #cachetour_settings_dialog {\
  1569. position:relative;\
  1570. background: white;\
  1571. border: 1px solid black;\
  1572. width: 600px;\
  1573. margin: 65px auto 0;\
  1574. padding: 12px;\
  1575. border-radius: 6px;\
  1576. }\
  1577. #cachetour_settings_dialog table {\
  1578. width:100%;\
  1579. margin-bottom: 0;\
  1580. }\
  1581. \
  1582. #cachetour_settings_dialog tr td:first-of-type {\
  1583. width: 100px;\
  1584. }\
  1585. #cachetour_settings_dialog div:first-of-type {\
  1586. font-size: large;\
  1587. font-weight: bold;\
  1588. margin-bottom: 12px;\
  1589. }\
  1590. #cachetour_settings_dialog select, #cachetour_settings_dialog input {\
  1591. min-width: 120px;\
  1592. padding: 3px;\
  1593. }\
  1594. #cachetour_settings_save {\
  1595. font-weight: bold;\
  1596. float: right;\
  1597. }\
  1598. #cachetour_settings_close {\
  1599. position:absolute;\
  1600. top:6px;\
  1601. right:6px;\
  1602. cursor:pointer;\
  1603. }\
  1604. #cachetour_settings_close:hover {\
  1605. color: red;\
  1606. }\
  1607. \
  1608. '
  1609. );
  1610. };
  1611. })();
  1612.  
  1613.  
  1614. (function(){
  1615. "use strict";
  1616.  
  1617. CacheTour.registerModule(
  1618. new CacheTour.Module({
  1619. name: "Cache details page",
  1620. requirements: {url: /\/geocache\/|\/cache_details.aspx/},
  1621. run: function() {
  1622. var gpx_button = $('#ctl00_ContentBody_btnSendToGPS').first(),
  1623. add_to_tour_button = gpx_button.clone();
  1624.  
  1625. add_to_tour_button
  1626. .attr("id","cachetour_add_to_tour")
  1627. .attr("onclick","return false")
  1628. .attr("value","Add to Tour");
  1629.  
  1630. add_to_tour_button.click(function(event) {
  1631. event.preventDefault();
  1632.  
  1633. var Parser = new CacheTour.CacheParser(document.body);
  1634. CacheTour.getCurrentTour().addCache(Parser.getCache());
  1635. return false;
  1636. });
  1637.  
  1638. gpx_button.parent().append(add_to_tour_button);
  1639. }
  1640. })
  1641. );
  1642. })();
  1643.  
  1644. (function(){
  1645. "use strict";
  1646.  
  1647. CacheTour.registerModule(
  1648. new CacheTour.Module({
  1649. name: 'Map',
  1650. requirements: {
  1651. url: /\/map\//
  1652. },
  1653. init: function() {
  1654. CacheTour.addStyle('#cachetour_gui { top:20%; }');
  1655. CacheTour.addStyle('#cachetour_gui:hover, #cachetour_gui.cachetour_pinned { height:75%; }');
  1656.  
  1657. var template = $("#cacheDetailsTemplate");
  1658. template.html(template.html().replace(
  1659. /<span>Log Visit<\/span>/,
  1660. "<span>Log Visit</span></a>\n" +
  1661. "<a class=\"lnk cachetour_add_to_tour\" href=\"#\" " +
  1662. "onclick=\"$('#cachetour_add_to_tour').click(); return false;\" " +
  1663. "data-gc-code=\"{{=gc}}\" " +
  1664. "data-type=\"{{=type.value}}\" " +
  1665. "data-name=\"{{=name}}\" " +
  1666. "data-size=\"{{=container.text}}\" " +
  1667. "data-difficulty=\"{{=difficulty.text}}\" " +
  1668. "data-terrain=\"{{=terrain.text}}\"" +
  1669. ">\n" +
  1670. "<img src=\"/images/icons/16/write_log.png\"><span>Add to Tour</span>"
  1671. ));
  1672. $(unsafeWindow.document.body).delegate('.cachetour_add_to_tour', 'click', function() {
  1673. console.log($(this));
  1674. });
  1675.  
  1676. /*
  1677. * In Firefox, the sandbox is quite restrictive, so you can't add event handlers
  1678. * to elements you didn't create yourself. But you may trigger events on elements
  1679. * you created from outside of the sandbox.
  1680. * So here I create one static element, whose click event is triggered by the
  1681. * links that are created from the template above.
  1682. * Maybe someday I'll find a better solution for this, but this works for now.
  1683. */
  1684. CacheTour.addStyle('#cachetour_add_to_tour { display:none; }');
  1685. var add_btn = $('<button id="cachetour_add_to_tour">Add Cache</button>').appendTo(document.body).click(function(){
  1686. var cache_link = $('.cachetour_add_to_tour');
  1687. console.log(cache_link);
  1688. if (cache_link && cache_link.attr('data-gc-code')) {
  1689. CacheTour.getCurrentTour().addCache(
  1690. (new CacheTour.Cache(cache_link.attr('data-gc-code')))
  1691. .setName(cache_link.attr('data-name'))
  1692. .setType(cache_link.attr('data-type'))
  1693. .setSize(cache_link.attr('data-size').toLowerCase())
  1694. .setDifficulty(cache_link.attr('data-difficulty'))
  1695. .setTerrain(cache_link.attr('data-terrain'))
  1696. );
  1697. }
  1698. });
  1699. }
  1700. })
  1701. );
  1702. })();
  1703.  
  1704. (function() {
  1705. "use strict";
  1706.  
  1707. CacheTour.registerLocale('de', {
  1708. download_gpx_file: "GPX-Datei herunterladen",
  1709. download_gpx_progress: "Erstelle GPX<br />%done% von %count% Caches erledigt",
  1710. add_new_tour: "Tour hinzufügen",
  1711. choose_name: "Name wählen",
  1712. please_wait: "Bitte warten...",
  1713. select_tour: "Tour auswählen",
  1714. rename_tour: "Tour umbenennen",
  1715. keep_expanded: "Immer ausgeklappt",
  1716. settings: "Einstellungen",
  1717. save: "Speichern",
  1718. language: "Sprache"
  1719. });
  1720. })();
  1721.  
  1722.  
  1723. (function() {
  1724. "use strict";
  1725.  
  1726. CacheTour.registerLocale('en', {
  1727. download_gpx_file: "Download GPX file",
  1728. download_gpx_progress: "Creating GPX<br />%done% of %count% Caches done",
  1729. add_new_tour: "Add new tour",
  1730. choose_name: "Choose a name",
  1731. please_wait: "Please wait...",
  1732. select_tour: "Select tour",
  1733. rename_tour: "Rename tour",
  1734. keep_expanded: "Keep expanded",
  1735. cachetype_2: "Traditional Cache",
  1736. cachetype_3: "Multi-Cache",
  1737. cachetype_4: "Virtual Cache",
  1738. cachetype_5: "Letterbox Hybrid",
  1739. cachetype_6: "Event Cache",
  1740. cachetype_8: "Mystery Cache",
  1741. cachetype_11: "Webcam Cache",
  1742. cachetype_13: "Cache In Trash Out Event",
  1743. cachetype_earthcache: "EarthCache",
  1744. settings: "Settings",
  1745. save: "Save",
  1746. language: "Language"
  1747. });
  1748. })();
  1749.  
  1750.  
  1751. CacheTour.initialize();