diff --git a/jumpapp/.jump-version b/jumpapp/.jump-version
index e89e45a..8ec18c5 100644
--- a/jumpapp/.jump-version
+++ b/jumpapp/.jump-version
@@ -1 +1 @@
-v1.2.3 (1657207353)
\ No newline at end of file
+v1.2.3 (1658150865)
\ No newline at end of file
diff --git a/jumpapp/api/icon.php b/jumpapp/api/icon.php
deleted file mode 100644
index b8e9ba8..0000000
--- a/jumpapp/api/icon.php
+++ /dev/null
@@ -1,24 +0,0 @@
-
- * @license MIT
- */
-
-// Provided by composer for psr-4 style autoloading.
-require __DIR__ .'/../vendor/autoload.php';
-
-$config = new Jump\Config();
-$cache = new Jump\Cache($config);
-$sites = new Jump\Sites($config, $cache);
-
-$siteurl = isset($_GET['siteurl']) ? filter_var($_GET['siteurl'], FILTER_SANITIZE_URL) : (throw new Exception('siteurl param not provided'));
-
-$site = $sites->get_site_by_url($siteurl);
-
-$imagedata = $site->get_favicon_image_data();
-
-// We made it here so output the API response as json.
-header('Content-Type: '.$imagedata->mimetype);
-echo $imagedata->data;
diff --git a/jumpapp/api/unsplashdata.php b/jumpapp/api/unsplashdata.php
deleted file mode 100644
index f146e70..0000000
--- a/jumpapp/api/unsplashdata.php
+++ /dev/null
@@ -1,90 +0,0 @@
-
- * @license MIT
- */
-
-// Provided by composer for psr-4 style autoloading.
-require __DIR__ .'/../vendor/autoload.php';
-
-$config = new Jump\Config();
-$cache = new Jump\Cache($config);
-
-// If this script is run via CLI then clear the cache and repopulate it,
-// otherwise if run via web then get image data from cache and run this
-// script asynchronously to refresh the cache for next time.
-if (http_response_code() === false) {
- $unsplashdata = load_cache_unsplash_data($config);
- $cache->save(cachename: 'unsplash', data: $unsplashdata);
- die('Cached data from Unsplash');
-}
-
-// Output header here so we can return early with a json response if there is a curl error.
-header('Content-Type: application/json; charset=utf-8');
-
-// Initialise a new session using the request object.
-$session = new Nette\Http\Session((new Nette\Http\RequestFactory)->fromGlobals(), new Nette\Http\Response);
-$session->setName($config->get('sessionname'));
-$session->setExpiration($config->get('sessiontimeout'));
-
-// Get a Nette session section for CSRF data.
-$csrfsection = $session->getSection('csrf');
-
-// Has a CSRF token been set up for the session yet?
-if (!$csrfsection->offsetExists('token')){
- http_response_code(401);
- die(json_encode(['error' => 'Session not fully set up']));
-}
-
-// Check CSRF token saved in session against token provided via request.
-$token = isset($_GET['token']) ? $_GET['token'] : false;
-if (!$token || !hash_equals($csrfsection->get('token'), $token)) {
- http_response_code(401);
- die(json_encode(['error' => 'API token is incorrect or missing']));
-}
-
-$unsplashdata = $cache->load(cachename: 'unsplash');
-if ($unsplashdata == null) {
- $unsplashdata = load_cache_unsplash_data($config);
- $cache->save(cachename: 'unsplash', data: $unsplashdata);
-}
-
-echo json_encode($unsplashdata);
-
-shell_exec('/usr/bin/nohup /usr/bin/php -f unsplashdata.php >/dev/null 2>&1 &');
-
-function load_cache_unsplash_data($config) {
- Crew\Unsplash\HttpClient::init([
- 'utmSource' => 'jump_startpage',
- 'applicationId' => $config->get('unsplashapikey'),
- ]);
- // Try to get a random image via the API.
- try {
- $photo = Crew\Unsplash\Photo::random([
- 'collections' => $config->get('unsplashcollections', false),
- ]);
- } catch (Exception $e) {
- http_response_code(500);
- die(json_encode(['error' => json_decode($e->getMessage())]));
- }
- // Download the image data from Unsplash.
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_URL, $photo->urls['raw'].'&auto=compress&w=1920');
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_FAILONERROR, true);
- $response = curl_exec($ch);
- // Create the response and return it.
- $description = 'Photo';
- if ($photo->description !== null &&
- strlen($photo->description) <= 45) {
- $description = $photo->description;
- }
- $unsplashdata = new stdClass();
- $unsplashdata->color = $photo->color;
- $unsplashdata->attribution = ''.$description.' by '.$photo->user['name'].'';
- $unsplashdata->imagedatauri = 'data: '.(new finfo(FILEINFO_MIME_TYPE))->buffer($response).';base64,'.base64_encode($response);
- return $unsplashdata;
-}
diff --git a/jumpapp/api/weatherdata.php b/jumpapp/api/weatherdata.php
deleted file mode 100644
index d5b2d73..0000000
--- a/jumpapp/api/weatherdata.php
+++ /dev/null
@@ -1,88 +0,0 @@
-
- * @license MIT
- */
-
-// Provided by composer for psr-4 style autoloading.
-require __DIR__ .'/../vendor/autoload.php';
-
-$config = new Jump\Config();
-$cache = new Jump\Cache($config);
-
-// Output header here so we can return early with a json response if there is a curl error.
-header('Content-Type: application/json; charset=utf-8');
-
-// Initialise a new session using the request object.
-$session = new \Nette\Http\Session((new \Nette\Http\RequestFactory)->fromGlobals(), new \Nette\Http\Response);
-$session->setName($config->get('sessionname'));
-$session->setExpiration($config->get('sessiontimeout'));
-
-// Get a Nette session section for CSRF data.
-$csrfsection = $session->getSection('csrf');
-
-// Has a CSRF token been set up for the session yet?
-if (!$csrfsection->offsetExists('token')){
- http_response_code(401);
- die(json_encode(['error' => 'Session not fully set up']));
-}
-
-// Check CSRF token saved in session against token provided via request.
-$token = isset($_GET['token']) ? $_GET['token'] : false;
-if (!$token || !hash_equals($csrfsection->get('token'), $token)) {
- http_response_code(401);
- die(json_encode(['error' => 'API token is incorrect or missing']));
-}
-
-// Start of variables we want to use.
-$owmapiurlbase = 'https://api.openweathermap.org/data/2.5/weather';
-$units = $config->parse_bool($config->get('metrictemp')) ? 'metric' : 'imperial';
-
-// If we have either lat or lon query params then cast them to a float, if not then
-// set the values to zero.
-$lat = isset($_GET['lat']) ? (float) $_GET['lat'] : 0;
-$lon = isset($_GET['lon']) ? (float) $_GET['lon'] : 0;
-
-// Use the lat and lon values provided unless they are zero, this might mean that
-// either they werent provided as query params or they couldn't be cast to a float.
-// If they are zero then use the default latlong from config.
-$latlong = [$lat, $lon];
-if ($lat === 0 || $lon === 0) {
- $latlong = explode(',', $config->get('latlong', false));
-}
-
-// This is the API endpoint and params we are using for the query,
-$url = $owmapiurlbase
- .'?units=' . $units
- .'&lat=' . $latlong[0]
- .'&lon=' . $latlong[1]
- .'&appid=' . $config->get('owmapikey', false);
-
-// Use the cache to store/retrieve data, make an md5 hash of latlong so it is not possible
-// to track location history form the stored cache.
-$weatherdata = $cache->load(cachename: 'weatherdata', key: md5(json_encode($latlong)), callback: function() use ($url) {
- // Ask the API for some data.
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_FAILONERROR, true);
- $response = curl_exec($ch);
-
- // Just in case something went wrong with the request we'll capture the error.
- if (curl_errno($ch)) {
- $curlerror = curl_error($ch);
- }
- curl_close($ch);
- // If we had an error then return the error message and exit, otherwise return the API response.
- if (isset($curlerror)) {
- http_response_code(400);
- die(json_encode(['error' => $curlerror]));
- }
- return $response;
-});
-
-// We made it here so output the API response as json.
-echo $weatherdata;
diff --git a/jumpapp/assets/js/index.01967507a63ce4e80097.min.js b/jumpapp/assets/js/index.068ef33aa2ef2fcb48cf.min.js
similarity index 78%
rename from jumpapp/assets/js/index.01967507a63ce4e80097.min.js
rename to jumpapp/assets/js/index.068ef33aa2ef2fcb48cf.min.js
index 37ed3ad..b767903 100644
--- a/jumpapp/assets/js/index.01967507a63ce4e80097.min.js
+++ b/jumpapp/assets/js/index.068ef33aa2ef2fcb48cf.min.js
@@ -1 +1 @@
-(()=>{"use strict";var e={729:e=>{var t=Object.prototype.hasOwnProperty,s="~";function n(){}function i(e,t,s){this.fn=e,this.context=t,this.once=s||!1}function r(e,t,n,r,o){if("function"!=typeof n)throw new TypeError("The listener must be a function");var c=new i(n,r||e,o),h=s?s+t:t;return e._events[h]?e._events[h].fn?e._events[h]=[e._events[h],c]:e._events[h].push(c):(e._events[h]=c,e._eventsCount++),e}function o(e,t){0==--e._eventsCount?e._events=new n:delete e._events[t]}function c(){this._events=new n,this._eventsCount=0}Object.create&&(n.prototype=Object.create(null),(new n).__proto__||(s=!1)),c.prototype.eventNames=function(){var e,n,i=[];if(0===this._eventsCount)return i;for(n in e=this._events)t.call(e,n)&&i.push(s?n.slice(1):n);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},c.prototype.listeners=function(e){var t=s?s+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,r=n.length,o=new Array(r);i{e.exports="v1.2.3 (1657207353)"}},t={};function s(n){var i=t[n];if(void 0!==i)return i.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,s),r.exports}s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},s.d=(e,t)=>{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{class e{constructor(e,t=!1,s=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=e,this.ampm=t,this.forcelocaltime=s}set_utc_shift(e=0){this.utcshift=e,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let e=this.shifteddate.getUTCHours(),t=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(this.forcelocaltime&&(e=(new Date).getHours(),t=String((new Date).getMinutes()).padStart(2,"0")),!this.ampm)return String(e).padStart(2,"0")+":"+t;const s=e<=12?"AM":"PM";return e=(e+11)%12+1,e+":"+t+""+s+""}get_hour(){return this.forcelocaltime?(new Date).getHours():this.shifteddate.getUTCHours()}update_time(){this.set_utc_shift(this.utcshift),this.eventemitter.emit("clock-updated",{formatted_time:this.get_formatted_time(),hour:this.get_hour(),utcshift:this.utcshift})}run(e){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),e)}}var t=s(729),n=s.n(t);function i(e){return Array.isArray?Array.isArray(e):"[object Array]"===u(e)}function r(e){return"string"==typeof e}function o(e){return"number"==typeof e}function c(e){return!0===e||!1===e||function(e){return h(e)&&null!==e}(e)&&"[object Boolean]"==u(e)}function h(e){return"object"==typeof e}function a(e){return null!=e}function l(e){return!e.trim().length}function u(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const d=Object.prototype.hasOwnProperty;class g{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let s=f(e);t+=s.weight,this._keys.push(s),this._keyMap[s.id]=s,t+=s.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function f(e){let t=null,s=null,n=null,o=1,c=null;if(r(e)||i(e))n=e,t=m(e),s=p(e);else{if(!d.call(e,"name"))throw new Error((e=>`Missing ${e} property in key`)("name"));const i=e.name;if(n=i,d.call(e,"weight")&&(o=e.weight,o<=0))throw new Error((e=>`Property 'weight' in key '${e}' must be a positive integer`)(i));t=m(i),s=p(i),c=e.getFn}return{path:t,id:s,weight:o,src:n,getFn:c}}function m(e){return i(e)?e:e.split(".")}function p(e){return i(e)?e.join("."):e}var v={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(a(e))if(t[l]){const u=e[t[l]];if(!a(u))return;if(l===t.length-1&&(r(u)||o(u)||c(u)))s.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(u));else if(i(u)){n=!0;for(let e=0,s=u.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,r(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const t=this.size();r(e)?this._addString(e,t):this._addObject(e,t)}removeAt(e){this.records.splice(e,1);for(let t=e,s=this.size();t{let o=t.getFn?t.getFn(e):this.getFn(e,t.path);if(a(o))if(i(o)){let e=[];const t=[{nestedArrIndex:-1,value:o}];for(;t.length;){const{nestedArrIndex:s,value:n}=t.pop();if(a(n))if(r(n)&&!l(n)){let t={v:n,i:s,n:this.norm.get(n)};e.push(t)}else i(n)&&n.forEach(((e,s)=>{t.push({nestedArrIndex:s,value:e})}))}s.$[n]=e}else if(r(o)&&!l(o)){let e={v:o,n:this.norm.get(o)};s.$[n]=e}})),this.records.push(s)}toJSON(){return{keys:this.keys,records:this.records}}}function M(e,t,{getFn:s=v.getFn,fieldNormWeight:n=v.fieldNormWeight}={}){const i=new w({getFn:s,fieldNormWeight:n});return i.setKeys(e.map(f)),i.setSources(t),i.create(),i}function _(e,{errors:t=0,currentLocation:s=0,expectedLocation:n=0,distance:i=v.distance,ignoreLocation:r=v.ignoreLocation}={}){const o=t/e.length;if(r)return o;const c=Math.abs(n-s);return i?o+c/i:c?1:o}const x=32;function L(e,t,s,{location:n=v.location,distance:i=v.distance,threshold:r=v.threshold,findAllMatches:o=v.findAllMatches,minMatchCharLength:c=v.minMatchCharLength,includeMatches:h=v.includeMatches,ignoreLocation:a=v.ignoreLocation}={}){if(t.length>x)throw new Error(`Pattern length exceeds max of ${x}.`);const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let g=r,f=d;const m=c>1||h,p=m?Array(u):[];let y;for(;(y=e.indexOf(t,f))>-1;){let e=_(t,{currentLocation:y,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),f=y+l,m){let e=0;for(;e=h;r-=1){let o=r-1,c=s[e.charAt(o)];if(m&&(p[o]=+!!c),y[r]=(y[r+1]<<1|1)&c,n&&(y[r]|=(w[r+1]|w[r])<<1|1|w[r+1]),y[r]&b&&(M=_(t,{errors:n,currentLocation:o,expectedLocation:d,distance:i,ignoreLocation:a}),M<=g)){if(g=M,f=o,f<=d)break;h=Math.max(1,2*d-f)}}if(_(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;w=y}const k={isMatch:f>=0,score:Math.max(.001,M)};if(m){const e=function(e=[],t=v.minMatchCharLength){let s=[],n=-1,i=-1,r=0;for(let o=e.length;r=t&&s.push([n,i]),n=-1)}return e[r-1]&&r-n>=t&&s.push([n,r-1]),s}(p,c);e.length?h&&(k.indices=e):k.isMatch=!1}return k}function b(e){let t={};for(let s=0,n=e.length;s{this.chunks.push({pattern:e,alphabet:b(e),startIndex:t})},l=this.pattern.length;if(l>x){let e=0;const t=l%x,s=l-t;for(;e{const{isMatch:f,score:m,indices:p}=L(e,t,d,{location:n+g,distance:i,threshold:r,findAllMatches:o,minMatchCharLength:c,includeMatches:s,ignoreLocation:h});f&&(u=!0),l+=m,f&&p&&(a=[...a,...p])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&s&&(d.indices=a),d}}class S{constructor(e){this.pattern=e}static isMultiMatch(e){return C(e,this.multiRegex)}static isSingleMatch(e){return C(e,this.singleRegex)}search(){}}function C(e,t){const s=e.match(t);return s?s[1]:null}class E extends S{constructor(e,{location:t=v.location,threshold:s=v.threshold,distance:n=v.distance,includeMatches:i=v.includeMatches,findAllMatches:r=v.findAllMatches,minMatchCharLength:o=v.minMatchCharLength,isCaseSensitive:c=v.isCaseSensitive,ignoreLocation:h=v.ignoreLocation}={}){super(e),this._bitapSearch=new k(e,{location:t,threshold:s,distance:n,includeMatches:i,findAllMatches:r,minMatchCharLength:o,isCaseSensitive:c,ignoreLocation:h})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class A extends S{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,s=0;const n=[],i=this.pattern.length;for(;(t=e.indexOf(this.pattern,s))>-1;)s=t+i,n.push([t,s-1]);const r=!!n.length;return{isMatch:r,score:r?0:1,indices:n}}}const I=[class extends S{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},A,class extends S{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},E],N=I.length,O=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;const q=new Set([E.type,A.type]);class ${constructor(e,{isCaseSensitive:t=v.isCaseSensitive,includeMatches:s=v.includeMatches,minMatchCharLength:n=v.minMatchCharLength,ignoreLocation:i=v.ignoreLocation,findAllMatches:r=v.findAllMatches,location:o=v.location,threshold:c=v.threshold,distance:h=v.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:s,minMatchCharLength:n,findAllMatches:r,ignoreLocation:i,location:o,threshold:c,distance:h},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let s=e.trim().split(O).filter((e=>e&&!!e.trim())),n=[];for(let e=0,i=s.length;e!(!e[F]&&!e[j]),W=e=>({[F]:Object.keys(e).map((t=>({[t]:e[t]})))});function H(e,t,{auto:s=!0}={}){const n=e=>{let o=Object.keys(e);const c=(e=>!!e[J])(e);if(!c&&o.length>1&&!T(e))return n(W(e));if((e=>!i(e)&&h(e)&&!T(e))(e)){const n=c?e[J]:o[0],i=c?e[U]:e[n];if(!r(i))throw new Error((e=>`Invalid value for key ${e}`)(n));const h={keyId:p(n),pattern:i};return s&&(h.searcher=R(i,t)),h}let a={children:[],operator:o[0]};return o.forEach((t=>{const s=e[t];i(s)&&s.forEach((e=>{a.children.push(n(e))}))})),a};return T(e)||(e=W(e)),n(e)}function z(e,t){const s=e.matches;t.matches=[],a(s)&&s.forEach((e=>{if(!a(e.indices)||!e.indices.length)return;const{indices:s,value:n}=e;let i={indices:s,value:n};e.key&&(i.key=e.key.src),e.idx>-1&&(i.refIndex=e.idx),t.matches.push(i)}))}function D(e,t){t.score=e.score}class K{constructor(e,t={},s){this.options={...v,...t},this.options.useExtendedSearch,this._keyStore=new g(this.options.keys),this.setCollection(e,s)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof w))throw new Error("Incorrect 'index' type");this._myIndex=t||M(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){a(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=(()=>!1)){const t=[];for(let s=0,n=this._docs.length;s{let s=1;e.matches.forEach((({key:e,norm:n,score:i})=>{const r=e?e.weight:null;s*=Math.pow(0===i&&r?Number.EPSILON:i,(r||1)*(t?1:n))})),e.score=s}))}(a,{ignoreFieldNorm:h}),i&&a.sort(c),o(t)&&t>-1&&(a=a.slice(0,t)),function(e,t,{includeMatches:s=v.includeMatches,includeScore:n=v.includeScore}={}){const i=[];return s&&i.push(z),n&&i.push(D),e.map((e=>{const{idx:s}=e,n={item:t[s],refIndex:s};return i.length&&i.forEach((t=>{t(e,n)})),n}))}(a,this._docs,{includeMatches:s,includeScore:n})}_searchStringList(e){const t=R(e,this.options),{records:s}=this._myIndex,n=[];return s.forEach((({v:e,i:s,n:i})=>{if(!a(e))return;const{isMatch:r,score:o,indices:c}=t.searchIn(e);r&&n.push({item:e,idx:s,matches:[{score:o,value:e,norm:i,indices:c}]})})),n}_searchLogical(e){const t=H(e,this.options),s=(e,t,n)=>{if(!e.children){const{keyId:s,searcher:i}=e,r=this._findMatches({key:this._keyStore.get(s),value:this._myIndex.getValueForItemAtKeyId(t,s),searcher:i});return r&&r.length?[{idx:n,item:t,matches:r}]:[]}const i=[];for(let r=0,o=e.children.length;r{if(a(e)){let o=s(t,e,n);o.length&&(i[n]||(i[n]={idx:n,item:e,matches:[]},r.push(i[n])),o.forEach((({matches:e})=>{i[n].matches.push(...e)})))}})),r}_searchObjectList(e){const t=R(e,this.options),{keys:s,records:n}=this._myIndex,i=[];return n.forEach((({$:e,i:n})=>{if(!a(e))return;let r=[];s.forEach(((s,n)=>{r.push(...this._findMatches({key:s,value:e[n],searcher:t}))})),r.length&&i.push({idx:n,item:e,matches:r})})),i}_findMatches({key:e,value:t,searcher:s}){if(!a(t))return[];let n=[];if(i(t))t.forEach((({v:t,i,n:r})=>{if(!a(t))return;const{isMatch:o,score:c,indices:h}=s.searchIn(t);o&&n.push({score:c,key:e,value:t,idx:i,norm:r,indices:h})}));else{const{v:i,n:r}=t,{isMatch:o,score:c,indices:h}=s.searchIn(i);o&&n.push({score:c,key:e,value:i,norm:r,indices:h})}return n}}K.version="6.6.2",K.createIndex=M,K.parseIndex=function(e,{getFn:t=v.getFn,fieldNormWeight:s=v.fieldNormWeight}={}){const{keys:n,records:i}=e,r=new w({getFn:t,fieldNormWeight:s});return r.setKeys(n),r.setIndexRecords(i),r},K.config=v,K.parseQuery=H,function(...e){P.push(...e)}($);class V{constructor(e){this.hour=e,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let e=Object.keys(this.greetings).reverse();for(let t of e)if(this.hour>=t)return this.greetings[t]}}class B{constructor(e,t,s,n){this.containerelm=s,this.eventemitter=n,this.inputelm=t,this.suggestionslistelm=s.querySelector(".suggestion-list"),this.searchproviderlist=null,this.searchengines=e}build_searchprovider_list_elm(e){const t=document.createElement("ul");return t.classList.add("searchproviders"),t.setAttribute("tabindex",-1),this.searchengines.forEach((s=>{const n=document.createElement("li");n.setAttribute("tabindex",-1),n.innerHTML='Search on '+s.name+"",t.appendChild(n)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.inputelm.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){const t=document.querySelector(".suggestionholder .suggestions");t?t.firstChild.firstChild.focus():e.target.parentNode.parentNode.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}build_suggestion_list_elm(e){const t=document.createElement("ul");return t.classList.add("suggestions"),t.setAttribute("tabindex",-1),e.forEach((e=>{const s=document.createElement("li");s.setAttribute("tabindex",-1),s.innerHTML='
'+e.name+"",t.appendChild(s)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.searchproviderlist.lastChild.firstChild.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){this.searchproviderlist.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}replace(e){const t=this.build_suggestion_list_elm(e),s=document.createElement("span");if(s.classList.add("suggestionholder"),""!==this.inputelm.value){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Search",s.appendChild(e),this.searchproviderlist=this.build_searchprovider_list_elm(this.inputelm.value),s.appendChild(this.searchproviderlist)}if(t.childNodes.length>0){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Sites",s.appendChild(e),s.appendChild(t)}if(s.childNodes.length>0)this.containerelm.classList.add("suggestions"),this.suggestionslistelm.replaceChildren(s);else{this.containerelm.classList.remove("suggestions");let e=this.containerelm.querySelector(".suggestionholder");e&&e.remove()}}}class Q{constructor(e){this.eventemitter=e}fetch_owm_data(e){let t=JUMP.wwwurl+"/api/weatherdata.php?token="+JUMP.token;e.length&&(t+="&lat="+e[0]+"&lon="+e[1]),fetch(t).then((e=>e.json())).then((e=>{if(e.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error);else if(401!==e.cod){var t="night";e.dt>e.sys.sunrise&&e.dte.json())).then((t=>{t.error?console.error("JUMP ERROR: There was an issue with the Unsplash API... "+t.error):(e.style.backgroundImage='url("'+t.imagedatauri+'")',document.querySelector(".unsplash").innerHTML=t.attribution)}))}this.add_event_listeners(),JUMP.owmapikey?(this.weather.fetch_owm_data(this.latlong),setInterval((()=>{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){if(this.eventemitter.on("weather-loaded",(e=>{this.timezoneshift=e.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+e.locationcode,this.weathericonelm.classList.add(e.iconclass),this.clientlocationelm.innerHTML=e.locationname,this.tempelm.innerHTML=e.temp,this.weatherdescelm.innerHTML=e.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(e=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=e.formatted_time),null!=this.greetingelm){let t=new V(e.hour);this.greetingelm.innerHTML=t.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(e=>{navigator.geolocation.getCurrentPosition((e=>{this.latlong=[e.coords.latitude,e.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(e=>{console.error(e.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.add("enable"),e.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.remove("enable")})),this.showsearchbuttonelm){const e=document.querySelector(".search-form input");this.searchsuggestions=new B(JSON.parse(JUMP.searchengines),e,this.showsearchbuttonelm,this.eventemitter),this.showsearchbuttonelm.addEventListener("click",(t=>{t.target.classList.contains("open")||(this.showsearchbuttonelm.classList.add("open"),e.focus())})),document.addEventListener("keyup",(t=>{t.ctrlKey&&t.shiftKey&&"Slash"==t.code&&(this.showsearchbuttonelm.classList.contains("open")?this.search_close():(this.showsearchbuttonelm.classList.add("open"),e.focus()))})),this.searchclosebuttonelm.addEventListener("click",(e=>{e.stopPropagation(),this.search_close()})),e.addEventListener("keyup",(t=>{let s=document.querySelector(".suggestion-list .searchproviders");if("ArrowDown"===t.code)return void(s&&s.childNodes.length&&s.firstChild.firstChild.focus());let n=[],i=this.fuse.search(e.value);i.length=8,i.length>0&&i.forEach((e=>{n.push(e.item)})),this.searchsuggestions.replace(n)})),document.querySelector(".search-form").addEventListener("submit",(t=>{t.preventDefault(),""!=e.value&&document.querySelector(".searchproviders li a").click()}))}}search_close(){let e=this.showsearchbuttonelm.querySelector(".suggestionholder");e&&e.remove(),this.showsearchbuttonelm.classList.remove("suggestions"),document.querySelector(".search").classList.remove("open"),document.querySelector(".search-form input").value=""}show_content(){document.querySelectorAll(".hidden").forEach((function(e){e.classList.remove("hidden")}))}set_clock(){this.clock.set_utc_shift(this.timezoneshift),this.clock.run(this.clockfrequency)}}).init()})()})();
\ No newline at end of file
+(()=>{"use strict";var e={729:e=>{var t=Object.prototype.hasOwnProperty,s="~";function n(){}function i(e,t,s){this.fn=e,this.context=t,this.once=s||!1}function r(e,t,n,r,o){if("function"!=typeof n)throw new TypeError("The listener must be a function");var c=new i(n,r||e,o),h=s?s+t:t;return e._events[h]?e._events[h].fn?e._events[h]=[e._events[h],c]:e._events[h].push(c):(e._events[h]=c,e._eventsCount++),e}function o(e,t){0==--e._eventsCount?e._events=new n:delete e._events[t]}function c(){this._events=new n,this._eventsCount=0}Object.create&&(n.prototype=Object.create(null),(new n).__proto__||(s=!1)),c.prototype.eventNames=function(){var e,n,i=[];if(0===this._eventsCount)return i;for(n in e=this._events)t.call(e,n)&&i.push(s?n.slice(1):n);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},c.prototype.listeners=function(e){var t=s?s+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,r=n.length,o=new Array(r);i{e.exports="v1.2.3 (1658150865)"}},t={};function s(n){var i=t[n];if(void 0!==i)return i.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,s),r.exports}s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},s.d=(e,t)=>{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{class e{constructor(e,t=!1,s=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=e,this.ampm=t,this.forcelocaltime=s}set_utc_shift(e=0){this.utcshift=e,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let e=this.shifteddate.getUTCHours(),t=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(this.forcelocaltime&&(e=(new Date).getHours(),t=String((new Date).getMinutes()).padStart(2,"0")),!this.ampm)return String(e).padStart(2,"0")+":"+t;const s=e<=12?"AM":"PM";return e=(e+11)%12+1,e+":"+t+""+s+""}get_hour(){return this.forcelocaltime?(new Date).getHours():this.shifteddate.getUTCHours()}update_time(){this.set_utc_shift(this.utcshift),this.eventemitter.emit("clock-updated",{formatted_time:this.get_formatted_time(),hour:this.get_hour(),utcshift:this.utcshift})}run(e){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),e)}}var t=s(729),n=s.n(t);function i(e){return Array.isArray?Array.isArray(e):"[object Array]"===u(e)}function r(e){return"string"==typeof e}function o(e){return"number"==typeof e}function c(e){return!0===e||!1===e||function(e){return h(e)&&null!==e}(e)&&"[object Boolean]"==u(e)}function h(e){return"object"==typeof e}function a(e){return null!=e}function l(e){return!e.trim().length}function u(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const d=Object.prototype.hasOwnProperty;class g{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let s=f(e);t+=s.weight,this._keys.push(s),this._keyMap[s.id]=s,t+=s.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function f(e){let t=null,s=null,n=null,o=1,c=null;if(r(e)||i(e))n=e,t=m(e),s=p(e);else{if(!d.call(e,"name"))throw new Error((e=>`Missing ${e} property in key`)("name"));const i=e.name;if(n=i,d.call(e,"weight")&&(o=e.weight,o<=0))throw new Error((e=>`Property 'weight' in key '${e}' must be a positive integer`)(i));t=m(i),s=p(i),c=e.getFn}return{path:t,id:s,weight:o,src:n,getFn:c}}function m(e){return i(e)?e:e.split(".")}function p(e){return i(e)?e.join("."):e}var v={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(a(e))if(t[l]){const u=e[t[l]];if(!a(u))return;if(l===t.length-1&&(r(u)||o(u)||c(u)))s.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(u));else if(i(u)){n=!0;for(let e=0,s=u.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,r(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const t=this.size();r(e)?this._addString(e,t):this._addObject(e,t)}removeAt(e){this.records.splice(e,1);for(let t=e,s=this.size();t{let o=t.getFn?t.getFn(e):this.getFn(e,t.path);if(a(o))if(i(o)){let e=[];const t=[{nestedArrIndex:-1,value:o}];for(;t.length;){const{nestedArrIndex:s,value:n}=t.pop();if(a(n))if(r(n)&&!l(n)){let t={v:n,i:s,n:this.norm.get(n)};e.push(t)}else i(n)&&n.forEach(((e,s)=>{t.push({nestedArrIndex:s,value:e})}))}s.$[n]=e}else if(r(o)&&!l(o)){let e={v:o,n:this.norm.get(o)};s.$[n]=e}})),this.records.push(s)}toJSON(){return{keys:this.keys,records:this.records}}}function M(e,t,{getFn:s=v.getFn,fieldNormWeight:n=v.fieldNormWeight}={}){const i=new w({getFn:s,fieldNormWeight:n});return i.setKeys(e.map(f)),i.setSources(t),i.create(),i}function _(e,{errors:t=0,currentLocation:s=0,expectedLocation:n=0,distance:i=v.distance,ignoreLocation:r=v.ignoreLocation}={}){const o=t/e.length;if(r)return o;const c=Math.abs(n-s);return i?o+c/i:c?1:o}const x=32;function L(e,t,s,{location:n=v.location,distance:i=v.distance,threshold:r=v.threshold,findAllMatches:o=v.findAllMatches,minMatchCharLength:c=v.minMatchCharLength,includeMatches:h=v.includeMatches,ignoreLocation:a=v.ignoreLocation}={}){if(t.length>x)throw new Error(`Pattern length exceeds max of ${x}.`);const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let g=r,f=d;const m=c>1||h,p=m?Array(u):[];let y;for(;(y=e.indexOf(t,f))>-1;){let e=_(t,{currentLocation:y,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),f=y+l,m){let e=0;for(;e=h;r-=1){let o=r-1,c=s[e.charAt(o)];if(m&&(p[o]=+!!c),y[r]=(y[r+1]<<1|1)&c,n&&(y[r]|=(w[r+1]|w[r])<<1|1|w[r+1]),y[r]&b&&(M=_(t,{errors:n,currentLocation:o,expectedLocation:d,distance:i,ignoreLocation:a}),M<=g)){if(g=M,f=o,f<=d)break;h=Math.max(1,2*d-f)}}if(_(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;w=y}const k={isMatch:f>=0,score:Math.max(.001,M)};if(m){const e=function(e=[],t=v.minMatchCharLength){let s=[],n=-1,i=-1,r=0;for(let o=e.length;r=t&&s.push([n,i]),n=-1)}return e[r-1]&&r-n>=t&&s.push([n,r-1]),s}(p,c);e.length?h&&(k.indices=e):k.isMatch=!1}return k}function b(e){let t={};for(let s=0,n=e.length;s{this.chunks.push({pattern:e,alphabet:b(e),startIndex:t})},l=this.pattern.length;if(l>x){let e=0;const t=l%x,s=l-t;for(;e{const{isMatch:f,score:m,indices:p}=L(e,t,d,{location:n+g,distance:i,threshold:r,findAllMatches:o,minMatchCharLength:c,includeMatches:s,ignoreLocation:h});f&&(u=!0),l+=m,f&&p&&(a=[...a,...p])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&s&&(d.indices=a),d}}class S{constructor(e){this.pattern=e}static isMultiMatch(e){return C(e,this.multiRegex)}static isSingleMatch(e){return C(e,this.singleRegex)}search(){}}function C(e,t){const s=e.match(t);return s?s[1]:null}class E extends S{constructor(e,{location:t=v.location,threshold:s=v.threshold,distance:n=v.distance,includeMatches:i=v.includeMatches,findAllMatches:r=v.findAllMatches,minMatchCharLength:o=v.minMatchCharLength,isCaseSensitive:c=v.isCaseSensitive,ignoreLocation:h=v.ignoreLocation}={}){super(e),this._bitapSearch=new k(e,{location:t,threshold:s,distance:n,includeMatches:i,findAllMatches:r,minMatchCharLength:o,isCaseSensitive:c,ignoreLocation:h})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class A extends S{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,s=0;const n=[],i=this.pattern.length;for(;(t=e.indexOf(this.pattern,s))>-1;)s=t+i,n.push([t,s-1]);const r=!!n.length;return{isMatch:r,score:r?0:1,indices:n}}}const I=[class extends S{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},A,class extends S{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},E],N=I.length,O=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;const q=new Set([E.type,A.type]);class ${constructor(e,{isCaseSensitive:t=v.isCaseSensitive,includeMatches:s=v.includeMatches,minMatchCharLength:n=v.minMatchCharLength,ignoreLocation:i=v.ignoreLocation,findAllMatches:r=v.findAllMatches,location:o=v.location,threshold:c=v.threshold,distance:h=v.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:s,minMatchCharLength:n,findAllMatches:r,ignoreLocation:i,location:o,threshold:c,distance:h},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let s=e.trim().split(O).filter((e=>e&&!!e.trim())),n=[];for(let e=0,i=s.length;e!(!e[F]&&!e[j]),W=e=>({[F]:Object.keys(e).map((t=>({[t]:e[t]})))});function H(e,t,{auto:s=!0}={}){const n=e=>{let o=Object.keys(e);const c=(e=>!!e[J])(e);if(!c&&o.length>1&&!T(e))return n(W(e));if((e=>!i(e)&&h(e)&&!T(e))(e)){const n=c?e[J]:o[0],i=c?e[U]:e[n];if(!r(i))throw new Error((e=>`Invalid value for key ${e}`)(n));const h={keyId:p(n),pattern:i};return s&&(h.searcher=R(i,t)),h}let a={children:[],operator:o[0]};return o.forEach((t=>{const s=e[t];i(s)&&s.forEach((e=>{a.children.push(n(e))}))})),a};return T(e)||(e=W(e)),n(e)}function z(e,t){const s=e.matches;t.matches=[],a(s)&&s.forEach((e=>{if(!a(e.indices)||!e.indices.length)return;const{indices:s,value:n}=e;let i={indices:s,value:n};e.key&&(i.key=e.key.src),e.idx>-1&&(i.refIndex=e.idx),t.matches.push(i)}))}function D(e,t){t.score=e.score}class K{constructor(e,t={},s){this.options={...v,...t},this.options.useExtendedSearch,this._keyStore=new g(this.options.keys),this.setCollection(e,s)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof w))throw new Error("Incorrect 'index' type");this._myIndex=t||M(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){a(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=(()=>!1)){const t=[];for(let s=0,n=this._docs.length;s{let s=1;e.matches.forEach((({key:e,norm:n,score:i})=>{const r=e?e.weight:null;s*=Math.pow(0===i&&r?Number.EPSILON:i,(r||1)*(t?1:n))})),e.score=s}))}(a,{ignoreFieldNorm:h}),i&&a.sort(c),o(t)&&t>-1&&(a=a.slice(0,t)),function(e,t,{includeMatches:s=v.includeMatches,includeScore:n=v.includeScore}={}){const i=[];return s&&i.push(z),n&&i.push(D),e.map((e=>{const{idx:s}=e,n={item:t[s],refIndex:s};return i.length&&i.forEach((t=>{t(e,n)})),n}))}(a,this._docs,{includeMatches:s,includeScore:n})}_searchStringList(e){const t=R(e,this.options),{records:s}=this._myIndex,n=[];return s.forEach((({v:e,i:s,n:i})=>{if(!a(e))return;const{isMatch:r,score:o,indices:c}=t.searchIn(e);r&&n.push({item:e,idx:s,matches:[{score:o,value:e,norm:i,indices:c}]})})),n}_searchLogical(e){const t=H(e,this.options),s=(e,t,n)=>{if(!e.children){const{keyId:s,searcher:i}=e,r=this._findMatches({key:this._keyStore.get(s),value:this._myIndex.getValueForItemAtKeyId(t,s),searcher:i});return r&&r.length?[{idx:n,item:t,matches:r}]:[]}const i=[];for(let r=0,o=e.children.length;r{if(a(e)){let o=s(t,e,n);o.length&&(i[n]||(i[n]={idx:n,item:e,matches:[]},r.push(i[n])),o.forEach((({matches:e})=>{i[n].matches.push(...e)})))}})),r}_searchObjectList(e){const t=R(e,this.options),{keys:s,records:n}=this._myIndex,i=[];return n.forEach((({$:e,i:n})=>{if(!a(e))return;let r=[];s.forEach(((s,n)=>{r.push(...this._findMatches({key:s,value:e[n],searcher:t}))})),r.length&&i.push({idx:n,item:e,matches:r})})),i}_findMatches({key:e,value:t,searcher:s}){if(!a(t))return[];let n=[];if(i(t))t.forEach((({v:t,i,n:r})=>{if(!a(t))return;const{isMatch:o,score:c,indices:h}=s.searchIn(t);o&&n.push({score:c,key:e,value:t,idx:i,norm:r,indices:h})}));else{const{v:i,n:r}=t,{isMatch:o,score:c,indices:h}=s.searchIn(i);o&&n.push({score:c,key:e,value:i,norm:r,indices:h})}return n}}K.version="6.6.2",K.createIndex=M,K.parseIndex=function(e,{getFn:t=v.getFn,fieldNormWeight:s=v.fieldNormWeight}={}){const{keys:n,records:i}=e,r=new w({getFn:t,fieldNormWeight:s});return r.setKeys(n),r.setIndexRecords(i),r},K.config=v,K.parseQuery=H,function(...e){P.push(...e)}($);class V{constructor(e){this.hour=e,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let e=Object.keys(this.greetings).reverse();for(let t of e)if(this.hour>=t)return this.greetings[t]}}class B{constructor(e,t,s,n){this.containerelm=s,this.eventemitter=n,this.inputelm=t,this.suggestionslistelm=s.querySelector(".suggestion-list"),this.searchproviderlist=null,this.searchengines=e}build_searchprovider_list_elm(e){const t=document.createElement("ul");return t.classList.add("searchproviders"),t.setAttribute("tabindex",-1),this.searchengines.forEach((s=>{const n=document.createElement("li");n.setAttribute("tabindex",-1),n.innerHTML='Search on '+s.name+"",t.appendChild(n)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.inputelm.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){const t=document.querySelector(".suggestionholder .suggestions");t?t.firstChild.firstChild.focus():e.target.parentNode.parentNode.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}build_suggestion_list_elm(e){const t=document.createElement("ul");return t.classList.add("suggestions"),t.setAttribute("tabindex",-1),e.forEach((e=>{const s=document.createElement("li");s.setAttribute("tabindex",-1),s.innerHTML='
'+e.name+"",t.appendChild(s)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.searchproviderlist.lastChild.firstChild.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){this.searchproviderlist.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}replace(e){const t=this.build_suggestion_list_elm(e),s=document.createElement("span");if(s.classList.add("suggestionholder"),""!==this.inputelm.value){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Search",s.appendChild(e),this.searchproviderlist=this.build_searchprovider_list_elm(this.inputelm.value),s.appendChild(this.searchproviderlist)}if(t.childNodes.length>0){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Sites",s.appendChild(e),s.appendChild(t)}if(s.childNodes.length>0)this.containerelm.classList.add("suggestions"),this.suggestionslistelm.replaceChildren(s);else{this.containerelm.classList.remove("suggestions");let e=this.containerelm.querySelector(".suggestionholder");e&&e.remove()}}}class Q{constructor(e){this.eventemitter=e}fetch_owm_data(e){let t=JUMP.wwwurl+"/api/weather/"+JUMP.token+"/";e.length&&(t+=e[0]+"/"+e[1]+"/"),fetch(t).then((e=>e.json())).then((e=>{if(e.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error);else if(401!==e.cod){var t="night";e.dt>e.sys.sunrise&&e.dte.json())).then((t=>{t.error?console.error("JUMP ERROR: There was an issue with the Unsplash API... "+t.error):(e.style.backgroundImage='url("'+t.imagedatauri+'")',document.querySelector(".unsplash").innerHTML=t.attribution)}))}this.add_event_listeners(),JUMP.owmapikey?(this.weather.fetch_owm_data(this.latlong),setInterval((()=>{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){if(this.eventemitter.on("weather-loaded",(e=>{this.timezoneshift=e.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+e.locationcode,this.weathericonelm.classList.add(e.iconclass),this.clientlocationelm.innerHTML=e.locationname,this.tempelm.innerHTML=e.temp,this.weatherdescelm.innerHTML=e.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(e=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=e.formatted_time),null!=this.greetingelm){let t=new V(e.hour);this.greetingelm.innerHTML=t.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(e=>{navigator.geolocation.getCurrentPosition((e=>{this.latlong=[e.coords.latitude,e.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(e=>{console.error(e.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.add("enable"),e.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.remove("enable")})),this.showsearchbuttonelm){const e=document.querySelector(".search-form input");this.searchsuggestions=new B(JSON.parse(JUMP.searchengines),e,this.showsearchbuttonelm,this.eventemitter),this.showsearchbuttonelm.addEventListener("click",(t=>{t.target.classList.contains("open")||(this.showsearchbuttonelm.classList.add("open"),e.focus())})),document.addEventListener("keyup",(t=>{t.ctrlKey&&t.shiftKey&&"Slash"==t.code&&(this.showsearchbuttonelm.classList.contains("open")?this.search_close():(this.showsearchbuttonelm.classList.add("open"),e.focus()))})),this.searchclosebuttonelm.addEventListener("click",(e=>{e.stopPropagation(),this.search_close()})),e.addEventListener("keyup",(t=>{let s=document.querySelector(".suggestion-list .searchproviders");if("ArrowDown"===t.code)return void(s&&s.childNodes.length&&s.firstChild.firstChild.focus());let n=[],i=this.fuse.search(e.value);i.length=8,i.length>0&&i.forEach((e=>{n.push(e.item)})),this.searchsuggestions.replace(n)})),document.querySelector(".search-form").addEventListener("submit",(t=>{t.preventDefault(),""!=e.value&&document.querySelector(".searchproviders li a").click()}))}}search_close(){let e=this.showsearchbuttonelm.querySelector(".suggestionholder");e&&e.remove(),this.showsearchbuttonelm.classList.remove("suggestions"),document.querySelector(".search").classList.remove("open"),document.querySelector(".search-form input").value=""}show_content(){document.querySelectorAll(".hidden").forEach((function(e){e.classList.remove("hidden")}))}set_clock(){this.clock.set_utc_shift(this.timezoneshift),this.clock.run(this.clockfrequency)}}).init()})()})();
\ No newline at end of file
diff --git a/jumpapp/assets/js/src/classes/Main.js b/jumpapp/assets/js/src/classes/Main.js
index b968993..72c3d6a 100644
--- a/jumpapp/assets/js/src/classes/Main.js
+++ b/jumpapp/assets/js/src/classes/Main.js
@@ -55,7 +55,7 @@ export default class Main {
if (JUMP.unsplashcolor) {
backgroundelm.style.backgroundColor = JUMP.unsplashcolor;
}
- fetch(JUMP.wwwurl + '/api/unsplashdata.php?token=' + JUMP.token)
+ fetch(JUMP.wwwurl + '/api/unsplash/' + JUMP.token + '/')
.then(response => response.json())
.then(data => {
if (data.error) {
diff --git a/jumpapp/assets/js/src/classes/Weather.js b/jumpapp/assets/js/src/classes/Weather.js
index c477d67..db456a0 100644
--- a/jumpapp/assets/js/src/classes/Weather.js
+++ b/jumpapp/assets/js/src/classes/Weather.js
@@ -16,9 +16,9 @@ export default class Weather {
fetch_owm_data(latlong) {
// If we are provided with a latlong then the user must have cliecked on the location
// button at some point, so let's use this in the api url...
- let apiurl = JUMP.wwwurl + '/api/weatherdata.php?token=' + JUMP.token;
+ let apiurl = JUMP.wwwurl + '/api/weather/' + JUMP.token + '/';
if (latlong.length) {
- apiurl += ('&lat=' + latlong[0] + '&lon=' + latlong[1]);
+ apiurl += (latlong[0] + '/' + latlong[1] + '/');
}
// Get some data from the weather api...
fetch(apiurl)
diff --git a/jumpapp/classes/API/AbstractAPI.php b/jumpapp/classes/API/AbstractAPI.php
new file mode 100644
index 0000000..a5b57b1
--- /dev/null
+++ b/jumpapp/classes/API/AbstractAPI.php
@@ -0,0 +1,39 @@
+send_json_header();
+
+ // Get a Nette session section for CSRF data.
+ $csrfsection = $this->session->getSection('csrf');
+
+ // Has a CSRF token been set up for the session yet?
+ if (!$csrfsection->offsetExists('token')){
+ http_response_code(401);
+ die(json_encode(['error' => 'Session not fully set up']));
+ }
+
+ // Check CSRF token saved in session against token provided via request.
+ if (!isset($this->routeparams['token']) || !hash_equals($csrfsection->get('token'), $this->routeparams['token'])) {
+ http_response_code(401);
+ die(json_encode(['error' => 'API token is incorrect or missing']));
+ }
+ }
+
+ abstract protected function get_output(): string;
+
+}
diff --git a/jumpapp/classes/API/Icon.php b/jumpapp/classes/API/Icon.php
new file mode 100644
index 0000000..9fc2ff3
--- /dev/null
+++ b/jumpapp/classes/API/Icon.php
@@ -0,0 +1,24 @@
+routeparams['siteurl']) || empty($this->routeparams['siteurl'])) {
+ throw new \Exception('The siteurl query parameter is not provided or empty');
+ }
+
+ $sites = new \Jump\Sites($this->config, $this->cache);
+
+ $siteurl = filter_var($this->routeparams['siteurl'], FILTER_SANITIZE_URL);
+ $site = $sites->get_site_by_url($siteurl);
+
+ $imagedata = $site->get_favicon_image_data();
+
+ // We made it here so output the API response as json.
+ header('Content-Type: '.$imagedata->mimetype);
+ return $imagedata->data;
+ }
+
+}
diff --git a/jumpapp/classes/API/Unsplash.php b/jumpapp/classes/API/Unsplash.php
new file mode 100644
index 0000000..7eca71d
--- /dev/null
+++ b/jumpapp/classes/API/Unsplash.php
@@ -0,0 +1,23 @@
+validate_token();
+
+ $unsplashdata = $this->cache->load(cachename: 'unsplash');
+
+ if ($unsplashdata == null) {
+ $unsplashdata = \Jump\Unsplash::load_cache_unsplash_data($this->config);
+ $this->cache->save(cachename: 'unsplash', data: $unsplashdata);
+ }
+
+ $toexec = '/usr/bin/nohup /usr/bin/php -f ' . $this->config->get('wwwroot') . '/cli/cacheunsplash.php >/dev/null 2>&1 &';
+ shell_exec($toexec);
+
+ return json_encode($unsplashdata);
+ }
+}
diff --git a/jumpapp/classes/API/Weather.php b/jumpapp/classes/API/Weather.php
new file mode 100644
index 0000000..aa32d54
--- /dev/null
+++ b/jumpapp/classes/API/Weather.php
@@ -0,0 +1,63 @@
+validate_token();
+
+ // Start of variables we want to use.
+ $owmapiurlbase = 'https://api.openweathermap.org/data/2.5/weather';
+ $units = $this->config->parse_bool($this->config->get('metrictemp')) ? 'metric' : 'imperial';
+
+ // If we have either lat or lon query params then cast them to a float, if not then
+ // set the values to zero.
+ $lat = isset($this->routeparams['lat']) ? (float) $this->routeparams['lat'] : 0;
+ $lon = isset($this->routeparams['lat']) ? (float) $this->routeparams['lat'] : 0;
+
+ // Use the lat and lon values provided unless they are zero, this might mean that
+ // either they werent provided as query params or they couldn't be cast to a float.
+ // If they are zero then use the default latlong from config.
+ $latlong = [$lat, $lon];
+ if ($lat === 0 || $lon === 0) {
+ $latlong = explode(',', $this->config->get('latlong', false));
+ }
+
+ // This is the API endpoint and params we are using for the query,
+ $url = $owmapiurlbase
+ .'?units=' . $units
+ .'&lat=' . $latlong[0]
+ .'&lon=' . $latlong[1]
+ .'&appid=' . $this->config->get('owmapikey', false);
+
+ // Use the cache to store/retrieve data, make an md5 hash of latlong so it is not possible
+ // to track location history form the stored cache.
+ $weatherdata = $this->cache->load(cachename: 'weatherdata', key: md5(json_encode($latlong)), callback: function() use ($url) {
+ // Ask the API for some data.
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_FAILONERROR, true);
+ $response = curl_exec($ch);
+
+ // Just in case something went wrong with the request we'll capture the error.
+ if (curl_errno($ch)) {
+ $curlerror = curl_error($ch);
+ }
+ curl_close($ch);
+ // If we had an error then return the error message and exit, otherwise return the API response.
+ if (isset($curlerror)) {
+ http_response_code(400);
+ die(json_encode(['error' => $curlerror]));
+ }
+ return $response;
+ });
+
+ // We made it here so return the API response as a json string.
+ return $weatherdata;
+ }
+
+}
diff --git a/jumpapp/classes/Main.php b/jumpapp/classes/Main.php
index 0cfe3c9..42b8e34 100644
--- a/jumpapp/classes/Main.php
+++ b/jumpapp/classes/Main.php
@@ -26,6 +26,15 @@ class Main {
$this->router->addRoute('/tag/', [
'class' => 'Jump\Pages\TagPage'
]);
+ $this->router->addRoute('/api/icon?siteurl=', [
+ 'class' => 'Jump\API\Icon'
+ ]);
+ $this->router->addRoute('/api/unsplash[/]', [
+ 'class' => 'Jump\API\Unsplash'
+ ]);
+ $this->router->addRoute('/api/weather[/[/[/]]]', [
+ 'class' => 'Jump\API\Weather'
+ ]);
}
function init() {
diff --git a/jumpapp/classes/Unsplash.php b/jumpapp/classes/Unsplash.php
new file mode 100644
index 0000000..1725802
--- /dev/null
+++ b/jumpapp/classes/Unsplash.php
@@ -0,0 +1,40 @@
+ 'jump_startpage',
+ 'applicationId' => $config->get('unsplashapikey'),
+ ]);
+ // Try to get a random image via the API.
+ try {
+ $photo = \Crew\Unsplash\Photo::random([
+ 'collections' => $config->get('unsplashcollections', false),
+ ]);
+ } catch (\Exception $e) {
+ http_response_code(500);
+ die(json_encode(['error' => json_decode($e->getMessage())]));
+ }
+ // Download the image data from Unsplash.
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_URL, $photo->urls['raw'].'&auto=compress&w=1920');
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_FAILONERROR, true);
+ $response = curl_exec($ch);
+ // Create the response and return it.
+ $description = 'Photo';
+ if ($photo->description !== null &&
+ strlen($photo->description) <= 45) {
+ $description = $photo->description;
+ }
+ $unsplashdata = new \stdClass();
+ $unsplashdata->color = $photo->color;
+ $unsplashdata->attribution = ''.$description.' by '.$photo->user['name'].'';
+ $unsplashdata->imagedatauri = 'data: '.(new \finfo(FILEINFO_MIME_TYPE))->buffer($response).';base64,'.base64_encode($response);
+ return $unsplashdata;
+ }
+}
diff --git a/jumpapp/cli/cacheunsplash.php b/jumpapp/cli/cacheunsplash.php
new file mode 100644
index 0000000..e94736c
--- /dev/null
+++ b/jumpapp/cli/cacheunsplash.php
@@ -0,0 +1,22 @@
+
+ * @license MIT
+ */
+
+// Provided by composer for psr-4 style autoloading.
+require __DIR__ .'/../vendor/autoload.php';
+
+$config = new Jump\Config();
+$cache = new Jump\Cache($config);
+
+// If this script is run via CLI then clear the cache and repopulate it,
+// otherwise if run via web then get image data from cache and run this
+// script asynchronously to refresh the cache for next time.
+if (http_response_code() === false) {
+ $unsplashdata = Jump\Unsplash::load_cache_unsplash_data($config);
+ $cache->save(cachename: 'unsplash', data: $unsplashdata);
+ die('Cached data from Unsplash');
+}
diff --git a/jumpapp/templates/footer.mustache b/jumpapp/templates/footer.mustache
index 0eed59d..3c36885 100644
--- a/jumpapp/templates/footer.mustache
+++ b/jumpapp/templates/footer.mustache
@@ -33,6 +33,6 @@
{{/ hastags}}
-
+