From 8776424b0115dc289630461f9458da6242f2dc97 Mon Sep 17 00:00:00 2001 From: quaintdev <59229571+quaintdev@users.noreply.github.com> Date: Tue, 5 Apr 2022 01:49:19 +0530 Subject: [PATCH 1/9] Added Podman install instructions --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index edacf7c..985a82a 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,21 @@ You can use the following optional environment variables to configure/customise You can map the "backgrounds" and "sites" directories to local directories as shown in the Docker Compose example above. Your local directories will be populated with Jump's default files when the container is next started unless the local directories already contain files, in which case the local files will be used by Jump instead. +### Podman +The following will start Jump and serve the page at http://localhost:8123 with a custom site name, Open Weather Map support, and volumes to map Jump's "backgrounds" and "sites" directories to local directories on your machine... + +``` +podman run -d --volume :/backgrounds:Z \ +--volume :/sites:Z -p 8123:8080 \ +--env SITENAME='Welcome' --env OWMAPIKEY='' \ +--env LATLONG='' --name jump docker.io/daledavies/jump +``` +This will start the service but you will have to run above command again in case of power cycle. To make this config more permanent do this +1. Go to Systemd user director usually located in `~/.config/systemd/user/` +2. Generate Systemd unit file using `podman generate systemd --new --name jump > jump.service` +3. Use `--user` attribute to run the service as a user service `systemd --user start containe-jump.service` + + ### Without Docker Clone this repository and copy everything within the `jumpapp` directory to your server, edit `config.php` accordingly. From 80c387166e5f62772762bde52100fda28efaee7f Mon Sep 17 00:00:00 2001 From: quaintdev <59229571+quaintdev@users.noreply.github.com> Date: Tue, 5 Apr 2022 01:53:09 +0530 Subject: [PATCH 2/9] Updated spell errors --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 985a82a..0f74aea 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ podman run -d --volume :/backgrounds:Z \ --env LATLONG='' --name jump docker.io/daledavies/jump ``` This will start the service but you will have to run above command again in case of power cycle. To make this config more permanent do this -1. Go to Systemd user director usually located in `~/.config/systemd/user/` -2. Generate Systemd unit file using `podman generate systemd --new --name jump > jump.service` -3. Use `--user` attribute to run the service as a user service `systemd --user start containe-jump.service` +1. Go to Systemd user directory usually located in `~/.config/systemd/user/` +2. Generate Systemd unit file using `podman generate systemd --new --name jump > container-jump.service` +3. Use `--user` attribute to run the service as a user service `systemd --user start container-jump.service` ### Without Docker From 4267b86c98f798c2b34e54c9d3a3a83949233fcd Mon Sep 17 00:00:00 2001 From: quaintdev <59229571+quaintdev@users.noreply.github.com> Date: Thu, 14 Apr 2022 21:20:18 +0530 Subject: [PATCH 3/9] Update README.md --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0f74aea..d0c3cdc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Jump is yet another self-hosted startpage for your server designed to be simple, ## Installation -### Docker +### Docker Compose Get the container image from Docker Hub (https://hub.docker.com/r/daledavies/jump). @@ -63,19 +63,17 @@ You can use the following optional environment variables to configure/customise You can map the "backgrounds" and "sites" directories to local directories as shown in the Docker Compose example above. Your local directories will be populated with Jump's default files when the container is next started unless the local directories already contain files, in which case the local files will be used by Jump instead. -### Podman -The following will start Jump and serve the page at http://localhost:8123 with a custom site name, Open Weather Map support, and volumes to map Jump's "backgrounds" and "sites" directories to local directories on your machine... +#### Docker + +If you prefer `docker run` you can run the following command and that's it. ``` -podman run -d --volume :/backgrounds:Z \ ---volume :/sites:Z -p 8123:8080 \ +docker run -d --volume :/backgrounds \ +--volume :/sites -p 8123:8080 \ --env SITENAME='Welcome' --env OWMAPIKEY='' \ --env LATLONG='' --name jump docker.io/daledavies/jump ``` -This will start the service but you will have to run above command again in case of power cycle. To make this config more permanent do this -1. Go to Systemd user directory usually located in `~/.config/systemd/user/` -2. Generate Systemd unit file using `podman generate systemd --new --name jump > container-jump.service` -3. Use `--user` attribute to run the service as a user service `systemd --user start container-jump.service` +**Podman Setup**: If you prefer podman over docker you can replace `docker` with `podman` in above command and it should bring up jump. ### Without Docker From 223644b8389aa31e773f0aed86cecfc6337db93b Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Thu, 31 Mar 2022 10:49:05 +0100 Subject: [PATCH 4/9] Update Dockerfile with build instructions --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index cdfb608..4fca892 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,11 @@ +# This dockerfile is intended to built using buildx to enable multi-platform +# images... +# https://docs.docker.com/desktop/multi-arch/ +# docker buildx create --name mybuilder +# docker buildx use mybuilder +# docker buildx inspect --bootstrap +# docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t daledavies/jump:v1.1.3 --push . + # Start with the official composer image, copy application files and install # dependencies. FROM --platform=$BUILDPLATFORM composer AS builder From f9bb4df720e60b505f71adb0e0302b7d69d05901 Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Thu, 31 Mar 2022 11:08:15 +0100 Subject: [PATCH 5/9] Improve handling of CURL errors in weatherdata API --- jumpapp/api/weatherdata.php | 6 ++++-- jumpapp/assets/js/src/classes/Weather.js | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/jumpapp/api/weatherdata.php b/jumpapp/api/weatherdata.php index d8d9a7e..97835bd 100644 --- a/jumpapp/api/weatherdata.php +++ b/jumpapp/api/weatherdata.php @@ -35,6 +35,9 @@ $url = $owmapiurlbase .'&lon=' . $latlong[1] .'&appid=' . $config->get('owmapikey', false); +// 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'); + // 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) { @@ -53,11 +56,10 @@ $weatherdata = $cache->load(cachename: 'weatherdata', key: md5(json_encode($latl curl_close($ch); // If we had an error then return the error message and exit, otherwise return the API response. if (isset($curlerror)) { - die($curlerror); + die(json_encode(['error' => $curlerror])); } return $response; }); // We made it here so output the API response as json. -header('Content-Type: application/json; charset=utf-8'); echo $weatherdata; \ No newline at end of file diff --git a/jumpapp/assets/js/src/classes/Weather.js b/jumpapp/assets/js/src/classes/Weather.js index 48c2b3c..c588971 100644 --- a/jumpapp/assets/js/src/classes/Weather.js +++ b/jumpapp/assets/js/src/classes/Weather.js @@ -24,8 +24,13 @@ export default class Weather { fetch(apiurl) .then(response => response.json()) .then(data => { + if (data.error) { + console.error('JUMP ERROR: There was a problem contacting the OWM API'); + return; + } if (data.cod === 401) { - alert('The OWM API key is invalid, check config.php'); + console.error('JUMP ERROR: The OWM API key is invalid, check config.php'); + return; } // Determine if we should use the day or night variant of our weather icon. var daynightvariant = 'night'; From a1b1104b5e937b728ddab8f45b3e59e6e9774c95 Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Thu, 31 Mar 2022 11:10:46 +0100 Subject: [PATCH 6/9] Use local google fonts --- jumpapp/assets/css/styles.css | 9 + .../font/quicksand-v28-latin-regular.eot | Bin 0 -> 15706 bytes .../font/quicksand-v28-latin-regular.svg | 446 ++++++++++++++++++ .../font/quicksand-v28-latin-regular.ttf | Bin 0 -> 29852 bytes .../font/quicksand-v28-latin-regular.woff | Bin 0 -> 17160 bytes .../font/quicksand-v28-latin-regular.woff2 | Bin 0 -> 13776 bytes jumpapp/templates/header.mustache | 3 - 7 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 jumpapp/assets/font/quicksand-v28-latin-regular.eot create mode 100644 jumpapp/assets/font/quicksand-v28-latin-regular.svg create mode 100644 jumpapp/assets/font/quicksand-v28-latin-regular.ttf create mode 100644 jumpapp/assets/font/quicksand-v28-latin-regular.woff create mode 100644 jumpapp/assets/font/quicksand-v28-latin-regular.woff2 diff --git a/jumpapp/assets/css/styles.css b/jumpapp/assets/css/styles.css index 2605ffd..fad4389 100644 --- a/jumpapp/assets/css/styles.css +++ b/jumpapp/assets/css/styles.css @@ -5,6 +5,15 @@ * @license MIT */ +@font-face { + font-family: 'Quicksand'; + font-style: normal; + font-weight: 400; + src: local(''), + url('../font/quicksand-v28-latin-regular.woff2') format('woff2'), + url('../font/quicksand-v28-latin-regular.woff') format('woff'); +} + * { box-sizing: border-box; } diff --git a/jumpapp/assets/font/quicksand-v28-latin-regular.eot b/jumpapp/assets/font/quicksand-v28-latin-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..319062067dc7248e05e7d005e13cef92dd061223 GIT binary patch literal 15706 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 N8Kq&o`oF)8{})37{Sg2F literal 0 HcmV?d00001 diff --git a/jumpapp/assets/font/quicksand-v28-latin-regular.svg b/jumpapp/assets/font/quicksand-v28-latin-regular.svg new file mode 100644 index 0000000..16f9b4e --- /dev/null +++ b/jumpapp/assets/font/quicksand-v28-latin-regular.svg @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jumpapp/assets/font/quicksand-v28-latin-regular.ttf b/jumpapp/assets/font/quicksand-v28-latin-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4e69cf5d4c48b0d2133fed011063f609888a171a GIT binary patch literal 29852 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

si!!atU+QQc#8&2X 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 literal 0 HcmV?d00001 diff --git a/jumpapp/assets/font/quicksand-v28-latin-regular.woff2 b/jumpapp/assets/font/quicksand-v28-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..528baa730236ba2f6cf6c9adb13c1475171c048c GIT binary patch literal 13776 zcmV;>H809{Pew8T0RR9105#A65C8xG0Ck7}05xv_0RR9100000000000000000000 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 G)Q12cF}qa& literal 0 HcmV?d00001 diff --git a/jumpapp/templates/header.mustache b/jumpapp/templates/header.mustache index c32ec7e..33a0ab4 100644 --- a/jumpapp/templates/header.mustache +++ b/jumpapp/templates/header.mustache @@ -4,9 +4,6 @@ {{# noindex}}{{/ noindex}} - - - From ae910173bf0349520c63cdfdbb48579c7cc8d2e7 Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Wed, 13 Apr 2022 16:28:12 +0100 Subject: [PATCH 7/9] Added CSRF check in weatherdata API --- Dockerfile | 1 + jumpapp/api/weatherdata.php | 31 +++++++++++++++++++++--- jumpapp/assets/js/index.bundle.js | 2 +- jumpapp/assets/js/src/classes/Main.js | 8 ++++-- jumpapp/assets/js/src/classes/Weather.js | 6 ++--- jumpapp/classes/Config.php | 14 +++++++++++ jumpapp/classes/Main.php | 24 +++++++++++++++--- jumpapp/classes/Pages/AbstractPage.php | 7 +++++- jumpapp/classes/Pages/HomePage.php | 2 ++ jumpapp/classes/Pages/TagPage.php | 2 ++ jumpapp/composer.json | 3 ++- jumpapp/composer.lock | 2 +- jumpapp/templates/header.mustache | 1 + 13 files changed, 86 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4fca892..8803505 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,6 +45,7 @@ RUN apk add --no-cache \ php8-json \ php8-opcache \ php8-openssl \ + php8-session \ php8-xml \ php8-zlib diff --git a/jumpapp/api/weatherdata.php b/jumpapp/api/weatherdata.php index 97835bd..d5b2d73 100644 --- a/jumpapp/api/weatherdata.php +++ b/jumpapp/api/weatherdata.php @@ -12,6 +12,31 @@ 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'; @@ -35,9 +60,6 @@ $url = $owmapiurlbase .'&lon=' . $latlong[1] .'&appid=' . $config->get('owmapikey', false); -// 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'); - // 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) { @@ -56,10 +78,11 @@ $weatherdata = $cache->load(cachename: 'weatherdata', key: md5(json_encode($latl 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; \ No newline at end of file +echo $weatherdata; diff --git a/jumpapp/assets/js/index.bundle.js b/jumpapp/assets/js/index.bundle.js index ab61c6f..0447efc 100644 --- a/jumpapp/assets/js/index.bundle.js +++ b/jumpapp/assets/js/index.bundle.js @@ -1 +1 @@ -(()=>{"use strict";var t={729:t=>{var e=Object.prototype.hasOwnProperty,n="~";function i(){}function s(t,e,n){this.fn=t,this.context=e,this.once=n||!1}function r(t,e,i,r,o){if("function"!=typeof i)throw new TypeError("The listener must be a function");var h=new s(i,r||t,o),c=n?n+e:e;return t._events[c]?t._events[c].fn?t._events[c]=[t._events[c],h]:t._events[c].push(h):(t._events[c]=h,t._eventsCount++),t}function o(t,e){0==--t._eventsCount?t._events=new i:delete t._events[e]}function h(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(n=!1)),h.prototype.eventNames=function(){var t,i,s=[];if(0===this._eventsCount)return s;for(i in t=this._events)e.call(t,i)&&s.push(n?i.slice(1):i);return Object.getOwnPropertySymbols?s.concat(Object.getOwnPropertySymbols(t)):s},h.prototype.listeners=function(t){var e=n?n+t:t,i=this._events[e];if(!i)return[];if(i.fn)return[i.fn];for(var s=0,r=i.length,o=new Array(r);s{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{class t{constructor(t,e=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=t,this.ampm=e}set_utc_shift(t=0){this.utcshift=t,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let t=this.shifteddate.getUTCHours();const e=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(!this.ampm)return String(t).padStart(2,"0")+":"+e;const n=t<=12?"AM":"PM";return t=(t+11)%12+1,t+":"+e+""+n+""}get_hour(){return 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(t){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),t)}}var e=n(729),i=n.n(e);class s{constructor(t){this.hour=t,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let t=Object.keys(this.greetings).reverse();for(let e of t)if(this.hour>=e)return this.greetings[e]}}class r{constructor(t){this.eventemitter=t}fetch_owm_data(t){let e="/api/weatherdata.php";t.length&&(e+="?lat="+t[0]+"&lon="+t[1]),fetch(e).then((t=>t.json())).then((t=>{401===t.cod&&alert("The OWM API key is invalid, check config.php");var e="night";t.dt>t.sys.sunrise&&t.dt{this.timezoneshift=t.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+t.locationcode,this.weathericonelm.classList.add(t.iconclass),this.clientlocationelm.innerHTML=t.locationname,this.tempelm.innerHTML=t.temp,this.weatherdescelm.innerHTML=t.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(t=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=t.formatted_time),null!=this.greetingelm){let e=new s(t.hour);this.greetingelm.innerHTML=e.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(t=>{navigator.geolocation.getCurrentPosition((t=>{this.latlong=[t.coords.latitude,t.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(t=>{console.error(t.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.add("enable"),t.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.remove("enable")}))}show_content(){document.querySelectorAll(".hidden").forEach((function(t){t.classList.remove("hidden")}))}set_clock(){this.clock.set_utc_shift(this.timezoneshift),this.clock.run(this.updatefrequency)}}).init()})()})(); \ No newline at end of file +(()=>{"use strict";var t={729:t=>{var e=Object.prototype.hasOwnProperty,n="~";function s(){}function i(t,e,n){this.fn=t,this.context=e,this.once=n||!1}function r(t,e,s,r,o){if("function"!=typeof s)throw new TypeError("The listener must be a function");var h=new i(s,r||t,o),c=n?n+e:e;return t._events[c]?t._events[c].fn?t._events[c]=[t._events[c],h]:t._events[c].push(h):(t._events[c]=h,t._eventsCount++),t}function o(t,e){0==--t._eventsCount?t._events=new s:delete t._events[e]}function h(){this._events=new s,this._eventsCount=0}Object.create&&(s.prototype=Object.create(null),(new s).__proto__||(n=!1)),h.prototype.eventNames=function(){var t,s,i=[];if(0===this._eventsCount)return i;for(s in t=this._events)e.call(t,s)&&i.push(n?s.slice(1):s);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(t)):i},h.prototype.listeners=function(t){var e=n?n+t:t,s=this._events[e];if(!s)return[];if(s.fn)return[s.fn];for(var i=0,r=s.length,o=new Array(r);i{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var s in e)n.o(e,s)&&!n.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{class t{constructor(t,e=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=t,this.ampm=e}set_utc_shift(t=0){this.utcshift=t,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let t=this.shifteddate.getUTCHours();const e=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(!this.ampm)return String(t).padStart(2,"0")+":"+e;const n=t<=12?"AM":"PM";return t=(t+11)%12+1,t+":"+e+""+n+""}get_hour(){return 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(t){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),t)}}var e=n(729),s=n.n(e);class i{constructor(t){this.hour=t,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let t=Object.keys(this.greetings).reverse();for(let e of t)if(this.hour>=e)return this.greetings[e]}}class r{constructor(t){this.eventemitter=t}fetch_owm_data(t){let e="/api/weatherdata.php?token="+JUMP.token;t.length&&(e+="&lat="+t[0]+"&lon="+t[1]),fetch(e).then((t=>t.json())).then((t=>{if(t.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+t.error);else if(401!==t.cod){var e="night";t.dt>t.sys.sunrise&&t.dt{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){this.eventemitter.on("weather-loaded",(t=>{this.timezoneshift=t.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+t.locationcode,this.weathericonelm.classList.add(t.iconclass),this.clientlocationelm.innerHTML=t.locationname,this.tempelm.innerHTML=t.temp,this.weatherdescelm.innerHTML=t.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(t=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=t.formatted_time),null!=this.greetingelm){let e=new i(t.hour);this.greetingelm.innerHTML=e.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(t=>{navigator.geolocation.getCurrentPosition((t=>{this.latlong=[t.coords.latitude,t.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(t=>{console.error(t.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.add("enable"),t.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.remove("enable")}))}show_content(){document.querySelectorAll(".hidden").forEach((function(t){t.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 8ddd0e9..3d2b4ab 100644 --- a/jumpapp/assets/js/src/classes/Main.js +++ b/jumpapp/assets/js/src/classes/Main.js @@ -8,7 +8,8 @@ export default class Main { constructor() { this.latlong = []; this.storage = window.localStorage; - this.updatefrequency = 10000; + this.clockfrequency = 10000; // 10 seconds. + this.weatherfrequency = 300000; // 5 minutes. this.timezoneshift = 0; this.metrictemp = JUMP.metrictemp; // Cache some DOM elements that we will access frequently. @@ -48,6 +49,9 @@ export default class Main { } // Retrieve weather and timezone data from Open Weather Map API. this.weather.fetch_owm_data(this.latlong); + setInterval(() => { + this.weather.fetch_owm_data(this.latlong); + }, this.weatherfrequency); } /** @@ -126,7 +130,7 @@ export default class Main { set_clock() { this.clock.set_utc_shift(this.timezoneshift); - this.clock.run(this.updatefrequency); + this.clock.run(this.clockfrequency); } } diff --git a/jumpapp/assets/js/src/classes/Weather.js b/jumpapp/assets/js/src/classes/Weather.js index c588971..5b5a70c 100644 --- a/jumpapp/assets/js/src/classes/Weather.js +++ b/jumpapp/assets/js/src/classes/Weather.js @@ -16,16 +16,16 @@ 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 = '/api/weatherdata.php'; + let apiurl = '/api/weatherdata.php?token=' + JUMP.token; if (latlong.length) { - apiurl += ('?lat=' + latlong[0] + '&lon=' + latlong[1]); + apiurl += ('&lat=' + latlong[0] + '&lon=' + latlong[1]); } // Get some data from the weather api... fetch(apiurl) .then(response => response.json()) .then(data => { if (data.error) { - console.error('JUMP ERROR: There was a problem contacting the OWM API'); + console.error('JUMP ERROR: There was an issue with the OWM API... ' + data.error); return; } if (data.cod === 401) { diff --git a/jumpapp/classes/Config.php b/jumpapp/classes/Config.php index fcf7f3b..10d9846 100644 --- a/jumpapp/classes/Config.php +++ b/jumpapp/classes/Config.php @@ -42,9 +42,18 @@ class Config { 'noindex' ]; + /** + * Session config params. + */ + private const CONFIG_SESSION = [ + 'sessionname' => 'JUMP', + 'sessiontimeout' => '10 minutes' + ]; + public function __construct() { $this->config = new \PHLAK\Config\Config(__DIR__.'/../config.php'); $this->add_wwwroot_to_base_paths(); + $this->add_session_config(); if ($this->config_params_missing()) { throw new Exception('Config.php must always contain... '.implode(', ', self::CONFIG_PARAMS)); } @@ -63,6 +72,11 @@ class Config { } } + private function add_session_config(): void { + foreach(self::CONFIG_SESSION as $key => $value) { + $this->config->set($key, $value); + } + } /** * Determine if any configuration params are missing in the list loaded * from the config.php. diff --git a/jumpapp/classes/Main.php b/jumpapp/classes/Main.php index 745ef93..2b5523b 100644 --- a/jumpapp/classes/Main.php +++ b/jumpapp/classes/Main.php @@ -14,6 +14,8 @@ class Main { private Cache $cache; private Config $config; + private \Nette\Http\Request $request; + private \Nette\Http\Session $session; public function __construct() { $this->config = new Config(); @@ -27,10 +29,24 @@ class Main { } function init() { + // Create a request object based on globals so we can utilise url rewriting etc. + $this->request = (new \Nette\Http\RequestFactory)->fromGlobals(); + + // Initialise a new session using the request object. + $this->session = new \Nette\Http\Session($this->request, new \Nette\Http\Response); + $this->session->setName($this->config->get('sessionname')); + $this->session->setExpiration($this->config->get('sessiontimeout')); + + // Get a Nette session section for CSRF data. + $csrfsection = $this->session->getSection('csrf'); + + // Create a new CSRF token within the section if one doesn't exist already. + if (!$csrfsection->offsetExists('token')){ + $csrfsection->set('token', bin2hex(random_bytes(32))); + } + // Try to match the correct route based on the HTTP request. - $matchedroute = $this->router->match( - (new \Nette\Http\RequestFactory)->fromGlobals() - ); + $matchedroute = $this->router->match($this->request); // If we do not have a matched route then just serve up the home page. $pageclass = $matchedroute['class'] ?? 'Jump\Pages\HomePage'; @@ -38,7 +54,7 @@ class Main { // Instantiate the correct class to build the requested page, get the // content and return it. - $page = new $pageclass($this->config, $this->cache, $param ?? null); + $page = new $pageclass($this->config, $this->cache, $this->session, $param ?? null); return $page->get_output(); } diff --git a/jumpapp/classes/Pages/AbstractPage.php b/jumpapp/classes/Pages/AbstractPage.php index b23305c..0194e7c 100644 --- a/jumpapp/classes/Pages/AbstractPage.php +++ b/jumpapp/classes/Pages/AbstractPage.php @@ -14,7 +14,12 @@ abstract class AbstractPage { * @param \Jump\Cache $cache * @param string|null $generic param, passed from router. */ - public function __construct(protected \Jump\Config $config, protected \Jump\Cache $cache, protected ?string $param = null) { + public function __construct( + protected \Jump\Config $config, + protected \Jump\Cache $cache, + protected \Nette\Http\Session $session, + protected ?string $param = null + ){ $this->hastags = false; $this->mustache = new \Mustache_Engine([ 'loader' => new \Mustache_Loader_FilesystemLoader($this->config->get('templatedir')), diff --git a/jumpapp/classes/Pages/HomePage.php b/jumpapp/classes/Pages/HomePage.php index dc25fa0..78e100d 100644 --- a/jumpapp/classes/Pages/HomePage.php +++ b/jumpapp/classes/Pages/HomePage.php @@ -10,7 +10,9 @@ class HomePage extends AbstractPage { if (!$this->config->parse_bool($this->config->get('showgreeting'))) { $greeting = 'home'; } + $csrfsection = $this->session->getSection('csrf'); return $template->render([ + 'csrftoken' => $csrfsection->get('token'), 'greeting' => $greeting, 'noindex' => $this->config->parse_bool($this->config->get('noindex')), 'title' => $this->config->get('sitename'), diff --git a/jumpapp/classes/Pages/TagPage.php b/jumpapp/classes/Pages/TagPage.php index 6139c29..04e8d91 100644 --- a/jumpapp/classes/Pages/TagPage.php +++ b/jumpapp/classes/Pages/TagPage.php @@ -10,7 +10,9 @@ class TagPage extends AbstractPage { $template = $this->mustache->loadTemplate('header'); $greeting = $this->param; $title = 'Tag: '.$this->param; + $csrfsection = $this->session->getSection('csrf'); return $template->render([ + 'csrftoken' => $csrfsection->get('token'), 'greeting' => $greeting, 'noindex' => $this->config->parse_bool($this->config->get('noindex')), 'title' => $title, diff --git a/jumpapp/composer.json b/jumpapp/composer.json index 81f381c..d5c748b 100644 --- a/jumpapp/composer.json +++ b/jumpapp/composer.json @@ -9,6 +9,7 @@ "arthurhoaro/favicon": "~1.0", "nette/caching": "^3.1", "nette/routing": "^3.0.2", - "phlak/config": "^7.0" + "phlak/config": "^7.0", + "nette/http": "^3.1" } } diff --git a/jumpapp/composer.lock b/jumpapp/composer.lock index 77ff91b..15d0c0e 100644 --- a/jumpapp/composer.lock +++ b/jumpapp/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "097843a2f00f12e9786893c07a3ae8e3", + "content-hash": "860d4eabaa4ccb80ee0f7d501149c83d", "packages": [ { "name": "arthurhoaro/favicon", diff --git a/jumpapp/templates/header.mustache b/jumpapp/templates/header.mustache index 33a0ab4..3f9c077 100644 --- a/jumpapp/templates/header.mustache +++ b/jumpapp/templates/header.mustache @@ -14,6 +14,7 @@ owmapikey: '{{owmapikey}}', metrictemp: '{{metrictemp}}', ampmclock: '{{ampmclock}}', + token: '{{csrftoken}}' }; From 46b393144244ffcadfca9a7f4151eec6ffbcca0e Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Wed, 20 Apr 2022 08:44:57 +0100 Subject: [PATCH 8/9] Added a dockerfile with xdebug for development --- Dockerfile-xdebug | 68 +++++++++++++++++++++++++++++++++++++++++++++++ docker/xdebug.ini | 6 +++++ 2 files changed, 74 insertions(+) create mode 100644 Dockerfile-xdebug create mode 100644 docker/xdebug.ini diff --git a/Dockerfile-xdebug b/Dockerfile-xdebug new file mode 100644 index 0000000..d9898cf --- /dev/null +++ b/Dockerfile-xdebug @@ -0,0 +1,68 @@ +# Start with the official composer image, copy application files and install +# dependencies. +FROM composer AS builder +COPY jumpapp/ /app +RUN composer install --no-dev \ + --optimize-autoloader \ + --no-interaction \ + --no-progress + +# Switch to base alpine image so we can copy application files into it. +FROM alpine:latest + +WORKDIR /var/www/html + +# Create a non-root user for running nginx and php. +RUN addgroup -S jumpapp && \ + adduser \ + --disabled-password \ + --ingroup jumpapp \ + --no-create-home \ + jumpapp + +# Copy the built files from composer, chowning as jumpapp or they will +# be owned by root. +COPY --chown=jumpapp --from=builder /app /usr/src/jumpapp + +# Install required packages. +RUN apk add --no-cache \ + bash \ + curl \ + nginx \ + php8 \ + php8-curl \ + php8-dom \ + php8-fileinfo \ + php8-fpm \ + php8-json \ + php8-opcache \ + php8-openssl \ + php8-session \ + php8-xml \ + php8-zlib \ + php8-xdebug + +# Create symlink for anything expecting to use "php". +RUN ln -s /usr/bin/php8 /usr/bin/php + +# Nginx config. +COPY docker/nginx.conf /etc/nginx/nginx.conf + +# PHP/FPM config. +COPY docker/fpm-pool.conf /etc/php8/php-fpm.d/www.conf +COPY docker/php.ini /etc/php8/conf.d/custom.ini +COPY docker/xdebug.ini /etc/php8/conf.d/50_xdebug.ini + +COPY docker/entrypoint.sh /usr/local/bin/ + +# Create the cache directories and change owner of everything we need. +RUN mkdir -p /var/www/cache/application \ + && mkdir -p /var/www/cache/icons \ + && chown -R jumpapp:jumpapp /var/www/html /var/www/cache/icons \ + /var/www/cache/application \ + && chmod +x /usr/local/bin/entrypoint.sh + +# Expose the port we configured for nginx. +EXPOSE 8080 + +ENTRYPOINT ["entrypoint.sh"] diff --git a/docker/xdebug.ini b/docker/xdebug.ini new file mode 100644 index 0000000..497ec15 --- /dev/null +++ b/docker/xdebug.ini @@ -0,0 +1,6 @@ +zend_extension=xdebug.so + +xdebug.mode=debug +xdebug.discover_client_host = true +xdebug.start_with_request=trigger +xdebug.log="/tmp/xdebug.log" \ No newline at end of file From ef21547df333887d16d7a5b7ee85038e3943c021 Mon Sep 17 00:00:00 2001 From: Dale Davies Date: Wed, 20 Apr 2022 09:26:51 +0100 Subject: [PATCH 9/9] Issue #27: Use client timezone when OWM API is not used --- jumpapp/assets/js/index.bundle.js | 2 +- jumpapp/assets/js/src/classes/Clock.js | 15 +++++++++++++-- jumpapp/assets/js/src/classes/Main.js | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/jumpapp/assets/js/index.bundle.js b/jumpapp/assets/js/index.bundle.js index 0447efc..34c8221 100644 --- a/jumpapp/assets/js/index.bundle.js +++ b/jumpapp/assets/js/index.bundle.js @@ -1 +1 @@ -(()=>{"use strict";var t={729:t=>{var e=Object.prototype.hasOwnProperty,n="~";function s(){}function i(t,e,n){this.fn=t,this.context=e,this.once=n||!1}function r(t,e,s,r,o){if("function"!=typeof s)throw new TypeError("The listener must be a function");var h=new i(s,r||t,o),c=n?n+e:e;return t._events[c]?t._events[c].fn?t._events[c]=[t._events[c],h]:t._events[c].push(h):(t._events[c]=h,t._eventsCount++),t}function o(t,e){0==--t._eventsCount?t._events=new s:delete t._events[e]}function h(){this._events=new s,this._eventsCount=0}Object.create&&(s.prototype=Object.create(null),(new s).__proto__||(n=!1)),h.prototype.eventNames=function(){var t,s,i=[];if(0===this._eventsCount)return i;for(s in t=this._events)e.call(t,s)&&i.push(n?s.slice(1):s);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(t)):i},h.prototype.listeners=function(t){var e=n?n+t:t,s=this._events[e];if(!s)return[];if(s.fn)return[s.fn];for(var i=0,r=s.length,o=new Array(r);i{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var s in e)n.o(e,s)&&!n.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{class t{constructor(t,e=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=t,this.ampm=e}set_utc_shift(t=0){this.utcshift=t,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let t=this.shifteddate.getUTCHours();const e=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(!this.ampm)return String(t).padStart(2,"0")+":"+e;const n=t<=12?"AM":"PM";return t=(t+11)%12+1,t+":"+e+""+n+""}get_hour(){return 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(t){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),t)}}var e=n(729),s=n.n(e);class i{constructor(t){this.hour=t,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let t=Object.keys(this.greetings).reverse();for(let e of t)if(this.hour>=e)return this.greetings[e]}}class r{constructor(t){this.eventemitter=t}fetch_owm_data(t){let e="/api/weatherdata.php?token="+JUMP.token;t.length&&(e+="&lat="+t[0]+"&lon="+t[1]),fetch(e).then((t=>t.json())).then((t=>{if(t.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+t.error);else if(401!==t.cod){var e="night";t.dt>t.sys.sunrise&&t.dt{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){this.eventemitter.on("weather-loaded",(t=>{this.timezoneshift=t.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+t.locationcode,this.weathericonelm.classList.add(t.iconclass),this.clientlocationelm.innerHTML=t.locationname,this.tempelm.innerHTML=t.temp,this.weatherdescelm.innerHTML=t.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(t=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=t.formatted_time),null!=this.greetingelm){let e=new i(t.hour);this.greetingelm.innerHTML=e.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(t=>{navigator.geolocation.getCurrentPosition((t=>{this.latlong=[t.coords.latitude,t.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(t=>{console.error(t.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.add("enable"),t.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(t=>{this.tagselectorelm.classList.remove("enable")}))}show_content(){document.querySelectorAll(".hidden").forEach((function(t){t.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,n="~";function i(){}function s(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function r(e,t,i,r,o){if("function"!=typeof i)throw new TypeError("The listener must be a function");var h=new s(i,r||e,o),c=n?n+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],h]:e._events[c].push(h):(e._events[c]=h,e._eventsCount++),e}function o(e,t){0==--e._eventsCount?e._events=new i:delete e._events[t]}function h(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(n=!1)),h.prototype.eventNames=function(){var e,i,s=[];if(0===this._eventsCount)return s;for(i in e=this._events)t.call(e,i)&&s.push(n?i.slice(1):i);return Object.getOwnPropertySymbols?s.concat(Object.getOwnPropertySymbols(e)):s},h.prototype.listeners=function(e){var t=n?n+e:e,i=this._events[t];if(!i)return[];if(i.fn)return[i.fn];for(var s=0,r=i.length,o=new Array(r);s{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var i in t)n.o(t,i)&&!n.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{class e{constructor(e,t=!1,n=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=e,this.ampm=t,this.forcelocaltime=n}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 n=e<=12?"AM":"PM";return e=(e+11)%12+1,e+":"+t+""+n+""}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=n(729),i=n.n(t);class s{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 r{constructor(e){this.eventemitter=e}fetch_owm_data(e){let t="/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.dt{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){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 s(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")}))}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/Clock.js b/jumpapp/assets/js/src/classes/Clock.js index f389747..22d5e5a 100644 --- a/jumpapp/assets/js/src/classes/Clock.js +++ b/jumpapp/assets/js/src/classes/Clock.js @@ -14,11 +14,12 @@ export default class Clock { * @param boolean ampm Return 12 hour format if true. * @param number utcshift Number of seconds to shift time from UTC. */ - constructor(eventemitter, ampm = false) { + constructor(eventemitter, ampm = false, forcelocaltime = false) { this.set_utc_shift(); this.contentintervalid = null; this.eventemitter = eventemitter; this.ampm = ampm; + this.forcelocaltime = forcelocaltime; } set_utc_shift(newutcshift = 0) { @@ -37,7 +38,14 @@ export default class Clock { // the Date() object adjusting the returned time relative to the // browser's local timezone. let hour = this.shifteddate.getUTCHours(); - const minutes = String(this.shifteddate.getUTCMinutes()).padStart(2, '0'); + let minutes = String(this.shifteddate.getUTCMinutes()).padStart(2, '0'); + + // Completely ignore the shifted date and just return whatever happens to be + // in the local timezone. + if (this.forcelocaltime) { + hour = new Date().getHours(); + minutes = String(new Date().getMinutes()).padStart(2, '0'); + } if (!this.ampm) { return String(hour).padStart(2, '0') + ":" + minutes; @@ -54,6 +62,9 @@ export default class Clock { * @returns number The hour. */ get_hour() { + if (this.forcelocaltime) { + return new Date().getHours(); + } return this.shifteddate.getUTCHours(); } diff --git a/jumpapp/assets/js/src/classes/Main.js b/jumpapp/assets/js/src/classes/Main.js index 3d2b4ab..7d587d5 100644 --- a/jumpapp/assets/js/src/classes/Main.js +++ b/jumpapp/assets/js/src/classes/Main.js @@ -30,7 +30,7 @@ export default class Main { } // Finally create instances of the classes we'll be using. this.eventemitter = new EventEmitter(); - this.clock = new Clock(this.eventemitter, !!JUMP.ampmclock); + this.clock = new Clock(this.eventemitter, !!JUMP.ampmclock, !JUMP.owmapikey); this.weather = new Weather(this.eventemitter); }