diff --git a/jumpapp/.jump-version b/jumpapp/.jump-version index a634738..5074f6d 100644 --- a/jumpapp/.jump-version +++ b/jumpapp/.jump-version @@ -1 +1 @@ -v1.3.2 (1679526752) \ No newline at end of file +v1.3.2 (1680794802) \ No newline at end of file diff --git a/jumpapp/assets/js/index.fe3fa6c8305604e7c627.min.js b/jumpapp/assets/js/index.7f32838f57d5988f5cfa.min.js similarity index 62% rename from jumpapp/assets/js/index.fe3fa6c8305604e7c627.min.js rename to jumpapp/assets/js/index.7f32838f57d5988f5cfa.min.js index 99a7cb6..d724041 100644 --- a/jumpapp/assets/js/index.fe3fa6c8305604e7c627.min.js +++ b/jumpapp/assets/js/index.7f32838f57d5988f5cfa.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{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)}}const t=s(729);function n(e){return Array.isArray?Array.isArray(e):"[object Array]"===d(e)}const i=1/0;function r(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-i?"-0":t}(e)}function o(e){return"string"==typeof e}function c(e){return"number"==typeof e}function h(e){return!0===e||!1===e||function(e){return a(e)&&null!==e}(e)&&"[object Boolean]"==d(e)}function a(e){return"object"==typeof e}function l(e){return null!=e}function u(e){return!e.trim().length}function d(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const g=e=>`Invalid value for key ${e}`,f=e=>`Pattern length exceeds max of ${e}.`,m=e=>`Missing ${e} property in key`,p=e=>`Property 'weight' in key '${e}' must be a positive integer`,v=Object.prototype.hasOwnProperty;class y{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let s=w(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 w(e){let t=null,s=null,i=null,r=1,c=null;if(o(e)||n(e))i=e,t=M(e),s=_(e);else{if(!v.call(e,"name"))throw new Error(m("name"));const n=e.name;if(i=n,v.call(e,"weight")&&(r=e.weight,r<=0))throw new Error(p(n));t=M(n),s=_(n),c=e.getFn}return{path:t,id:s,weight:r,src:i,getFn:c}}function M(e){return n(e)?e:e.split(".")}function _(e){return n(e)?e.join("."):e}var x={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(l(e))if(t[u]){const d=e[t[u]];if(!l(d))return;if(u===t.length-1&&(o(d)||c(d)||h(d)))s.push(r(d));else if(n(d)){i=!0;for(let e=0,s=d.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,o(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();o(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 r=t.getFn?t.getFn(e):this.getFn(e,t.path);if(l(r))if(n(r)){let e=[];const t=[{nestedArrIndex:-1,value:r}];for(;t.length;){const{nestedArrIndex:s,value:i}=t.pop();if(l(i))if(o(i)&&!u(i)){let t={v:i,i:s,n:this.norm.get(i)};e.push(t)}else n(i)&&i.forEach(((e,s)=>{t.push({nestedArrIndex:s,value:e})}))}s.$[i]=e}else if(o(r)&&!u(r)){let e={v:r,n:this.norm.get(r)};s.$[i]=e}})),this.records.push(s)}toJSON(){return{keys:this.keys,records:this.records}}}function k(e,t,{getFn:s=x.getFn,fieldNormWeight:n=x.fieldNormWeight}={}){const i=new b({getFn:s,fieldNormWeight:n});return i.setKeys(e.map(w)),i.setSources(t),i.create(),i}function S(e,{errors:t=0,currentLocation:s=0,expectedLocation:n=0,distance:i=x.distance,ignoreLocation:r=x.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 C=32;function E(e,t,s,{location:n=x.location,distance:i=x.distance,threshold:r=x.threshold,findAllMatches:o=x.findAllMatches,minMatchCharLength:c=x.minMatchCharLength,includeMatches:h=x.includeMatches,ignoreLocation:a=x.ignoreLocation}={}){if(t.length>C)throw new Error(f(C));const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let g=r,m=d;const p=c>1||h,v=p?Array(u):[];let y;for(;(y=e.indexOf(t,m))>-1;){let e=S(t,{currentLocation:y,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),m=y+l,p){let e=0;for(;e=h;r-=1){let o=r-1,c=s[e.charAt(o)];if(p&&(v[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]&L&&(M=S(t,{errors:n,currentLocation:o,expectedLocation:d,distance:i,ignoreLocation:a}),M<=g)){if(g=M,m=o,m<=d)break;h=Math.max(1,2*d-m)}}if(S(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;w=y}const b={isMatch:m>=0,score:Math.max(.001,M)};if(p){const e=function(e=[],t=x.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}(v,c);e.length?h&&(b.indices=e):b.isMatch=!1}return b}function A(e){let t={};for(let s=0,n=e.length;s{this.chunks.push({pattern:e,alphabet:A(e),startIndex:t})},l=this.pattern.length;if(l>C){let e=0;const t=l%C,s=l-t;for(;e{const{isMatch:f,score:m,indices:p}=E(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 N{constructor(e){this.pattern=e}static isMultiMatch(e){return q(e,this.multiRegex)}static isSingleMatch(e){return q(e,this.singleRegex)}search(){}}function q(e,t){const s=e.match(t);return s?s[1]:null}class P extends N{constructor(e,{location:t=x.location,threshold:s=x.threshold,distance:n=x.distance,includeMatches:i=x.includeMatches,findAllMatches:r=x.findAllMatches,minMatchCharLength:o=x.minMatchCharLength,isCaseSensitive:c=x.isCaseSensitive,ignoreLocation:h=x.ignoreLocation}={}){super(e),this._bitapSearch=new I(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 O extends N{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 $=[class extends N{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]}}},O,class extends N{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 N{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 N{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 N{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 N{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]}}},P],R=$.length,F=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,J="|";const U=new Set([P.type,O.type]);class j{constructor(e,{isCaseSensitive:t=x.isCaseSensitive,includeMatches:s=x.includeMatches,minMatchCharLength:n=x.minMatchCharLength,ignoreLocation:i=x.ignoreLocation,findAllMatches:r=x.findAllMatches,location:o=x.location,threshold:c=x.threshold,distance:h=x.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(J).map((e=>{let s=e.trim().split(F).filter((e=>e&&!!e.trim())),n=[];for(let e=0,i=s.length;e!(!e[W]&&!e[z]),V=e=>!!e[D.PATH],B=e=>!n(e)&&a(e)&&!K(e),Q=e=>({[W]:Object.keys(e).map((t=>({[t]:e[t]})))});function G(e,t,{auto:s=!0}={}){const i=e=>{let r=Object.keys(e);const c=V(e);if(!c&&r.length>1&&!K(e))return i(Q(e));if(B(e)){const n=c?e[D.PATH]:r[0],i=c?e[D.PATTERN]:e[n];if(!o(i))throw new Error(g(n));const h={keyId:_(n),pattern:i};return s&&(h.searcher=H(i,t)),h}let h={children:[],operator:r[0]};return r.forEach((t=>{const s=e[t];n(s)&&s.forEach((e=>{h.children.push(i(e))}))})),h};return K(e)||(e=Q(e)),i(e)}function X(e,t){const s=e.matches;t.matches=[],l(s)&&s.forEach((e=>{if(!l(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 Y(e,t){t.score=e.score}class Z{constructor(e,t={},s){this.options={...x,...t},this.options.useExtendedSearch,this._keyStore=new y(this.options.keys),this.setCollection(e,s)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof b))throw new Error("Incorrect 'index' type");this._myIndex=t||k(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){l(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(r),c(t)&&t>-1&&(a=a.slice(0,t)),function(e,t,{includeMatches:s=x.includeMatches,includeScore:n=x.includeScore}={}){const i=[];return s&&i.push(X),n&&i.push(Y),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=H(e,this.options),{records:s}=this._myIndex,n=[];return s.forEach((({v:e,i:s,n:i})=>{if(!l(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=G(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(l(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=H(e,this.options),{keys:s,records:n}=this._myIndex,i=[];return n.forEach((({$:e,i:n})=>{if(!l(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(!l(t))return[];let i=[];if(n(t))t.forEach((({v:t,i:n,n:r})=>{if(!l(t))return;const{isMatch:o,score:c,indices:h}=s.searchIn(t);o&&i.push({score:c,key:e,value:t,idx:n,norm:r,indices:h})}));else{const{v:n,n:r}=t,{isMatch:o,score:c,indices:h}=s.searchIn(n);o&&i.push({score:c,key:e,value:n,norm:r,indices:h})}return i}}Z.version="6.6.2",Z.createIndex=k,Z.parseIndex=function(e,{getFn:t=x.getFn,fieldNormWeight:s=x.fieldNormWeight}={}){const{keys:n,records:i}=e,r=new b({getFn:t,fieldNormWeight:s});return r.setKeys(n),r.setIndexRecords(i),r},Z.config=x,Z.parseQuery=G,function(...e){T.push(...e)}(j);class ee{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 te{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 se{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=>{if(401===e.status)return console.error("JUMP ERROR: The OWM API key is invalid, check config.php"),void this.eventemitter.emit("weather-error");e.json().then((e=>{if(e.error)return console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error),void this.eventemitter.emit("weather-error");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)}))}JUMP.checkstatus&&fetch(JUMP.wwwurl+"/api/status/"+JUMP.token+"/").then((e=>e.json())).then((e=>{for(const[t,s]of Object.entries(e)){const e=document.querySelector("#"+t);if(e&&(e.classList.add(s),"online"!==s)){const t=e.querySelector("a");t.title="(Status: "+s+") "+t.title}}})),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("weather-error",(e=>{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 ee(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 te(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{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)}}const t=s(729);function n(e){return Array.isArray?Array.isArray(e):"[object Array]"===d(e)}const i=1/0;function r(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-i?"-0":t}(e)}function o(e){return"string"==typeof e}function c(e){return"number"==typeof e}function h(e){return!0===e||!1===e||function(e){return a(e)&&null!==e}(e)&&"[object Boolean]"==d(e)}function a(e){return"object"==typeof e}function l(e){return null!=e}function u(e){return!e.trim().length}function d(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const g=e=>`Invalid value for key ${e}`,f=e=>`Pattern length exceeds max of ${e}.`,m=e=>`Missing ${e} property in key`,p=e=>`Property 'weight' in key '${e}' must be a positive integer`,v=Object.prototype.hasOwnProperty;class y{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let s=w(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 w(e){let t=null,s=null,i=null,r=1,c=null;if(o(e)||n(e))i=e,t=M(e),s=_(e);else{if(!v.call(e,"name"))throw new Error(m("name"));const n=e.name;if(i=n,v.call(e,"weight")&&(r=e.weight,r<=0))throw new Error(p(n));t=M(n),s=_(n),c=e.getFn}return{path:t,id:s,weight:r,src:i,getFn:c}}function M(e){return n(e)?e:e.split(".")}function _(e){return n(e)?e.join("."):e}var x={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(l(e))if(t[u]){const d=e[t[u]];if(!l(d))return;if(u===t.length-1&&(o(d)||c(d)||h(d)))s.push(r(d));else if(n(d)){i=!0;for(let e=0,s=d.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,o(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();o(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 r=t.getFn?t.getFn(e):this.getFn(e,t.path);if(l(r))if(n(r)){let e=[];const t=[{nestedArrIndex:-1,value:r}];for(;t.length;){const{nestedArrIndex:s,value:i}=t.pop();if(l(i))if(o(i)&&!u(i)){let t={v:i,i:s,n:this.norm.get(i)};e.push(t)}else n(i)&&i.forEach(((e,s)=>{t.push({nestedArrIndex:s,value:e})}))}s.$[i]=e}else if(o(r)&&!u(r)){let e={v:r,n:this.norm.get(r)};s.$[i]=e}})),this.records.push(s)}toJSON(){return{keys:this.keys,records:this.records}}}function k(e,t,{getFn:s=x.getFn,fieldNormWeight:n=x.fieldNormWeight}={}){const i=new b({getFn:s,fieldNormWeight:n});return i.setKeys(e.map(w)),i.setSources(t),i.create(),i}function S(e,{errors:t=0,currentLocation:s=0,expectedLocation:n=0,distance:i=x.distance,ignoreLocation:r=x.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 C=32;function E(e,t,s,{location:n=x.location,distance:i=x.distance,threshold:r=x.threshold,findAllMatches:o=x.findAllMatches,minMatchCharLength:c=x.minMatchCharLength,includeMatches:h=x.includeMatches,ignoreLocation:a=x.ignoreLocation}={}){if(t.length>C)throw new Error(f(C));const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let g=r,m=d;const p=c>1||h,v=p?Array(u):[];let y;for(;(y=e.indexOf(t,m))>-1;){let e=S(t,{currentLocation:y,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),m=y+l,p){let e=0;for(;e=h;r-=1){let o=r-1,c=s[e.charAt(o)];if(p&&(v[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]&L&&(M=S(t,{errors:n,currentLocation:o,expectedLocation:d,distance:i,ignoreLocation:a}),M<=g)){if(g=M,m=o,m<=d)break;h=Math.max(1,2*d-m)}}if(S(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;w=y}const b={isMatch:m>=0,score:Math.max(.001,M)};if(p){const e=function(e=[],t=x.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}(v,c);e.length?h&&(b.indices=e):b.isMatch=!1}return b}function A(e){let t={};for(let s=0,n=e.length;s{this.chunks.push({pattern:e,alphabet:A(e),startIndex:t})},l=this.pattern.length;if(l>C){let e=0;const t=l%C,s=l-t;for(;e{const{isMatch:f,score:m,indices:p}=E(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 I{constructor(e){this.pattern=e}static isMultiMatch(e){return q(e,this.multiRegex)}static isSingleMatch(e){return q(e,this.singleRegex)}search(){}}function q(e,t){const s=e.match(t);return s?s[1]:null}class P extends I{constructor(e,{location:t=x.location,threshold:s=x.threshold,distance:n=x.distance,includeMatches:i=x.includeMatches,findAllMatches:r=x.findAllMatches,minMatchCharLength:o=x.minMatchCharLength,isCaseSensitive:c=x.isCaseSensitive,ignoreLocation:h=x.ignoreLocation}={}){super(e),this._bitapSearch=new N(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 O extends I{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 $=[class extends I{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]}}},O,class extends I{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 I{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 I{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 I{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 I{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]}}},P],R=$.length,J=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,U="|";const F=new Set([P.type,O.type]);class j{constructor(e,{isCaseSensitive:t=x.isCaseSensitive,includeMatches:s=x.includeMatches,minMatchCharLength:n=x.minMatchCharLength,ignoreLocation:i=x.ignoreLocation,findAllMatches:r=x.findAllMatches,location:o=x.location,threshold:c=x.threshold,distance:h=x.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(U).map((e=>{let s=e.trim().split(J).filter((e=>e&&!!e.trim())),n=[];for(let e=0,i=s.length;e!(!e[W]&&!e[z]),V=e=>!!e[D.PATH],B=e=>!n(e)&&a(e)&&!K(e),Q=e=>({[W]:Object.keys(e).map((t=>({[t]:e[t]})))});function G(e,t,{auto:s=!0}={}){const i=e=>{let r=Object.keys(e);const c=V(e);if(!c&&r.length>1&&!K(e))return i(Q(e));if(B(e)){const n=c?e[D.PATH]:r[0],i=c?e[D.PATTERN]:e[n];if(!o(i))throw new Error(g(n));const h={keyId:_(n),pattern:i};return s&&(h.searcher=H(i,t)),h}let h={children:[],operator:r[0]};return r.forEach((t=>{const s=e[t];n(s)&&s.forEach((e=>{h.children.push(i(e))}))})),h};return K(e)||(e=Q(e)),i(e)}function X(e,t){const s=e.matches;t.matches=[],l(s)&&s.forEach((e=>{if(!l(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 Y(e,t){t.score=e.score}class Z{constructor(e,t={},s){this.options={...x,...t},this.options.useExtendedSearch,this._keyStore=new y(this.options.keys),this.setCollection(e,s)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof b))throw new Error("Incorrect 'index' type");this._myIndex=t||k(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){l(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(r),c(t)&&t>-1&&(a=a.slice(0,t)),function(e,t,{includeMatches:s=x.includeMatches,includeScore:n=x.includeScore}={}){const i=[];return s&&i.push(X),n&&i.push(Y),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=H(e,this.options),{records:s}=this._myIndex,n=[];return s.forEach((({v:e,i:s,n:i})=>{if(!l(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=G(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(l(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=H(e,this.options),{keys:s,records:n}=this._myIndex,i=[];return n.forEach((({$:e,i:n})=>{if(!l(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(!l(t))return[];let i=[];if(n(t))t.forEach((({v:t,i:n,n:r})=>{if(!l(t))return;const{isMatch:o,score:c,indices:h}=s.searchIn(t);o&&i.push({score:c,key:e,value:t,idx:n,norm:r,indices:h})}));else{const{v:n,n:r}=t,{isMatch:o,score:c,indices:h}=s.searchIn(n);o&&i.push({score:c,key:e,value:n,norm:r,indices:h})}return i}}Z.version="6.6.2",Z.createIndex=k,Z.parseIndex=function(e,{getFn:t=x.getFn,fieldNormWeight:s=x.fieldNormWeight}={}){const{keys:n,records:i}=e,r=new b({getFn:t,fieldNormWeight:s});return r.setKeys(n),r.setIndexRecords(i),r},Z.config=x,Z.parseQuery=G,function(...e){T.push(...e)}(j);class ee{constructor(e,t){this.hour=e,this.greetings={0:t.greetings.goodmorning,12:t.greetings.goodafternoon,16:t.greetings.goodevening,19:t.greetings.goodnight}}get_greeting(){let e=Object.keys(this.greetings).reverse();for(let t of e)if(this.hour>=t)return this.greetings[t]}}class te{constructor(e,t,s,n,i){this.containerelm=s,this.eventemitter=n,this.inputelm=t,this.suggestionslistelm=s.querySelector(".suggestion-list"),this.searchproviderlist=null,this.searchengines=e,this.strings=i}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=''+this.strings.search.searchon[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=this.strings.search.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=this.strings.search.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 se{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=>{if(401===e.status)return console.error("JUMP ERROR: The OWM API key is invalid, check config.php"),void this.eventemitter.emit("weather-error");e.json().then((e=>{if(e.error)return console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error),void this.eventemitter.emit("weather-error");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)}))}JUMP.checkstatus&&fetch(JUMP.wwwurl+"/api/status/"+JUMP.token+"/").then((e=>e.json())).then((e=>{for(const[t,s]of Object.entries(e)){const e=document.querySelector("#"+t);if(e&&(e.classList.add(s),"online"!==s)){const t=e.querySelector("a");t.title="("+this.strings.status.status+": "+this.strings.status[s]+") "+t.title}}})),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("weather-error",(e=>{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 ee(e.hour,this.strings);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 te(JSON.parse(JUMP.searchengines),e,this.showsearchbuttonelm,this.eventemitter,this.strings),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/Greeting.js b/jumpapp/assets/js/src/classes/Greeting.js index 53341aa..d4f6f52 100644 --- a/jumpapp/assets/js/src/classes/Greeting.js +++ b/jumpapp/assets/js/src/classes/Greeting.js @@ -14,13 +14,13 @@ import Clock from "./Clock"; export default class Greeting { - constructor(hour) { + constructor(hour, strings) { this.hour = hour; this.greetings = { - 0 : 'morning', - 12 : 'afternoon', - 16 : 'evening', - 19 : 'night' + 0 : strings.greetings.goodmorning, + 12 : strings.greetings.goodafternoon, + 16 : strings.greetings.goodevening, + 19 : strings.greetings.goodnight }; } diff --git a/jumpapp/assets/js/src/classes/Main.js b/jumpapp/assets/js/src/classes/Main.js index acd8ec2..6a3859b 100644 --- a/jumpapp/assets/js/src/classes/Main.js +++ b/jumpapp/assets/js/src/classes/Main.js @@ -55,6 +55,9 @@ export default class Main { keys: ['name', 'tags', 'url'] }); } + + // Parse stringsforjs JSON object. + this.strings = JSON.parse(JUMP.strings) } /** @@ -90,7 +93,7 @@ export default class Main { siteelm.classList.add(status); if (status !== 'online') { const sitelinkelm = siteelm.querySelector('a'); - sitelinkelm.title = '(Status: ' + status + ') ' + sitelinkelm.title; + sitelinkelm.title = '('+this.strings.status.status+': ' + this.strings.status[status] + ') ' + sitelinkelm.title; } } } @@ -141,7 +144,7 @@ export default class Main { this.timeelm.innerHTML = clockdata.formatted_time; } if (this.greetingelm != null) { - let greeting = new Greeting(clockdata.hour); + let greeting = new Greeting(clockdata.hour, this.strings); this.greetingelm.innerHTML = greeting.get_greeting(); } }); @@ -180,7 +183,7 @@ export default class Main { if (this.showsearchbuttonelm) { const searchinput = document.querySelector('.search-form input'); - this.searchsuggestions = new SearchSuggestions(JSON.parse(JUMP.searchengines), searchinput, this.showsearchbuttonelm, this.eventemitter); + this.searchsuggestions = new SearchSuggestions(JSON.parse(JUMP.searchengines), searchinput, this.showsearchbuttonelm, this.eventemitter, this.strings); // When the search icon is licked, show the search bar and focus on it. this.showsearchbuttonelm.addEventListener('click', e => { diff --git a/jumpapp/assets/js/src/classes/SearchSuggestions.js b/jumpapp/assets/js/src/classes/SearchSuggestions.js index f3144db..412e785 100644 --- a/jumpapp/assets/js/src/classes/SearchSuggestions.js +++ b/jumpapp/assets/js/src/classes/SearchSuggestions.js @@ -15,13 +15,14 @@ */ export default class SearchSuggestions { - constructor(searchengines, inputelm, containerelm, eventemitter) { + constructor(searchengines, inputelm, containerelm, eventemitter, strings) { this.containerelm = containerelm; this.eventemitter = eventemitter; this.inputelm = inputelm; this.suggestionslistelm = containerelm.querySelector('.suggestion-list'); this.searchproviderlist = null; this.searchengines = searchengines; + this.strings = strings; } build_searchprovider_list_elm(query) { @@ -32,7 +33,7 @@ export default class SearchSuggestions { const searchprovider = document.createElement('li'); searchprovider.setAttribute('tabindex', -1); searchprovider.innerHTML = 'Search on '+provider.name+''; + href="'+provider.url+encodeURIComponent(query)+'">'+this.strings.search.searchon[provider.name]+''; searchproviderlist.appendChild(searchprovider); }); searchproviderlist.addEventListener('keyup', e => { @@ -102,7 +103,7 @@ export default class SearchSuggestions { if (this.inputelm.value !== '') { const searchtitle = document.createElement('span'); searchtitle.classList.add('suggestiontitle'); - searchtitle.innerHTML = 'Search'; + searchtitle.innerHTML = this.strings.search.search; suggestionholder.appendChild(searchtitle); this.searchproviderlist = this.build_searchprovider_list_elm(this.inputelm.value); suggestionholder.appendChild(this.searchproviderlist); @@ -111,7 +112,7 @@ export default class SearchSuggestions { if (newsuggestionslist.childNodes.length > 0) { const suggestiontitle = document.createElement('span'); suggestiontitle.classList.add('suggestiontitle'); - suggestiontitle.innerHTML = 'Sites'; + suggestiontitle.innerHTML = this.strings.search.sites; suggestionholder.appendChild(suggestiontitle); suggestionholder.appendChild(newsuggestionslist) } diff --git a/jumpapp/classes/API/AbstractAPI.php b/jumpapp/classes/API/AbstractAPI.php index cca5f7e..46cb6b9 100644 --- a/jumpapp/classes/API/AbstractAPI.php +++ b/jumpapp/classes/API/AbstractAPI.php @@ -19,6 +19,7 @@ abstract class AbstractAPI { protected \Jump\Config $config, protected \Jump\Cache $cache, protected \Nette\Http\Session $session, + protected \Jump\Language $language, protected ?array $routeparams ){} diff --git a/jumpapp/classes/API/Unsplash.php b/jumpapp/classes/API/Unsplash.php index 718128a..8cf81bf 100644 --- a/jumpapp/classes/API/Unsplash.php +++ b/jumpapp/classes/API/Unsplash.php @@ -22,7 +22,7 @@ class Unsplash extends AbstractAPI { $unsplashdata = $this->cache->load(cachename: 'unsplash'); if ($unsplashdata == null) { - $unsplashdata = \Jump\Unsplash::load_cache_unsplash_data($this->config); + $unsplashdata = \Jump\Unsplash::load_cache_unsplash_data($this->config, $this->language); $this->cache->save(cachename: 'unsplash', data: $unsplashdata); } diff --git a/jumpapp/classes/API/Weather.php b/jumpapp/classes/API/Weather.php index 4dbad10..0fcb401 100644 --- a/jumpapp/classes/API/Weather.php +++ b/jumpapp/classes/API/Weather.php @@ -41,6 +41,7 @@ class Weather extends AbstractAPI { .'?units=' . $units .'&lat=' . trim($latlong[0]) .'&lon=' . trim($latlong[1]) + .'&lang='. substr($this->config->get('language'), 0, 2) .'&appid=' . $this->config->get('owmapikey', false); // Use the cache to store/retrieve data, make an md5 hash of latlong so it is not possible diff --git a/jumpapp/classes/Cache.php b/jumpapp/classes/Cache.php index bf3622d..cde725a 100644 --- a/jumpapp/classes/Cache.php +++ b/jumpapp/classes/Cache.php @@ -42,6 +42,13 @@ class Cache { public function __construct(private Config $config) { // Define the various caches used throughout the app. $this->caches = [ + 'languages' => [ + 'cache' => null, + 'expirationtype' => Caching\Cache::Files, + 'expirationparams' => [ + __DIR__.'/../config.php', + ] + ], 'searchengines' => [ 'cache' => null, 'expirationtype' => Caching\Cache::Files, diff --git a/jumpapp/classes/Config.php b/jumpapp/classes/Config.php index d4d7ffa..c48abc3 100644 --- a/jumpapp/classes/Config.php +++ b/jumpapp/classes/Config.php @@ -39,6 +39,7 @@ class Config { 'sitesdir' => '/sites', 'sitesfile' => '/sites/sites.json', 'templatedir' => '/templates', + 'translationsdir' => '/translations' ]; /** diff --git a/jumpapp/classes/Language.php b/jumpapp/classes/Language.php new file mode 100644 index 0000000..4c70ed2 --- /dev/null +++ b/jumpapp/classes/Language.php @@ -0,0 +1,84 @@ + + * @copyright Copyright (c) 2022, Dale Davies + * @license MIT + */ + +namespace Jump; + +use \Jump\Exceptions\ConfigException; + +/** + * Defines a class for loading language strings form available translations files, caching + * and fetching of language strings etc. Will fetch the appropriate strings based on the + * language code defined in config.php. + * + * @author Dale Davies + * @license MIT + */ +class Language { + + private \Utopia\Locale\Locale $locale; + + /** + * Automatically loads available language strings on instantiation, either from the + * cache or from available files in the translations dir. + * + * @param Config $config + * @param Cache $cache + */ + public function __construct(private Config $config, private Cache $cache) { + // Try to load the translations from cache. + $languages = $this->cache->load(cachename: 'languages'); + // If they are not there or the cache has expired, then find all language files, load them up + // again and cache them. + if ($languages == null) { + $languages = []; + // Enumerate translation files and load their content. + $languagefiles = glob($this->config->get('translationsdir').'/*.json'); + foreach ($languagefiles as $file) { + $rawjson = file_get_contents($file); + if ($rawjson === false) { + throw new ConfigException('There was a problem loading a translation file... ' . $file); + } + if ($rawjson === '') { + throw new ConfigException('The following translation file is empty... ' . $file); + } + $languages[pathinfo($file, PATHINFO_FILENAME)] = json_decode($rawjson, true); + } + // Save the content of translation files into the cache. + $this->cache->save(cachename: 'languages', data: $languages); + } + // For each translation file that has been loaded, set them as available locales. + foreach ($languages as $name => $strings) { + \Utopia\Locale\Locale::setLanguageFromArray($name, $strings); + } + // Initialise the locale defined in the config.php language setting. + try { + $locale = new \Utopia\Locale\Locale($this->config->get('language')); + } catch (\Exception) { + (new Pages\ErrorPage($this->cache, $this->config, 500, 'Provided language code has no corresponding translation file.'))->init(); + } + + $this->locale = $locale; + } + + /** + * Retrieve a language string for the given key, substituting and placeholders + * that are provided. + * + * @param string $string + * @param array $placeholders + * @return mixed + */ + public function get(string $string, array $placeholders = []): mixed { + return $this->locale->getText($string, $placeholders); + } +} diff --git a/jumpapp/classes/Main.php b/jumpapp/classes/Main.php index a1d8094..1e77267 100644 --- a/jumpapp/classes/Main.php +++ b/jumpapp/classes/Main.php @@ -13,19 +13,20 @@ namespace Jump; -use Nette\Routing\RouteList; - class Main { private Cache $cache; private Config $config; + private Language $language; private \Nette\Http\Request $request; + private \Nette\Routing\RouteList $router; private \Nette\Http\Session $session; public function __construct() { $this->config = new Config(); $this->cache = new Cache($this->config); - $this->router = new RouteList; + $this->router = new \Nette\Routing\RouteList; + $this->language = new Language($this->config, $this->cache); // Set up the routes that Jump expects. $this->router->addRoute('/', [ @@ -65,7 +66,7 @@ class Main { // Instantiate the correct class to build the requested page, get the // content and return it. - $page = new $outputclass($this->config, $this->cache, $this->session, $matchedroute ?? null); + $page = new $outputclass($this->config, $this->cache, $this->session, $this->language, $matchedroute ?? null); return $page->get_output(); } diff --git a/jumpapp/classes/Pages/AbstractPage.php b/jumpapp/classes/Pages/AbstractPage.php index 5ca0770..b1a12ea 100644 --- a/jumpapp/classes/Pages/AbstractPage.php +++ b/jumpapp/classes/Pages/AbstractPage.php @@ -29,15 +29,21 @@ abstract class AbstractPage { protected \Jump\Config $config, protected \Jump\Cache $cache, protected \Nette\Http\Session $session, + protected \Jump\Language $language, protected ?array $routeparams ){ $this->hastags = false; $this->mustache = new \Mustache_Engine([ 'loader' => new \Mustache_Loader_FilesystemLoader($this->config->get('templatedir')), // Create a urlencodde helper for use in template. E.g. using siteurl in icon.php query param. - 'helpers' => array('urlencode' => function($text, $renderer) { - return urlencode($renderer($text)); - }), + 'helpers' => [ + 'urlencode' => function($text, $renderer) { + return urlencode($renderer($text)); + }, + 'language' => function($text, $renderer) { + return $this->language->get($text); + }, + ], ]); // Get a Nette session section for CSRF data. $csrfsection = $this->session->getSection('csrf'); diff --git a/jumpapp/classes/Pages/HomePage.php b/jumpapp/classes/Pages/HomePage.php index dbe359a..28467d4 100644 --- a/jumpapp/classes/Pages/HomePage.php +++ b/jumpapp/classes/Pages/HomePage.php @@ -38,12 +38,20 @@ class HomePage extends AbstractPage { 'wwwurl' => $this->config->get_wwwurl(), 'checkstatus' => $checkstatus, ]; + $stringsforjs = \Jump\Status::get_strings_for_js($this->language); + $stringsforjs['greetings']['goodmorning'] = $this->language->get('greetings.goodmorning'); + $stringsforjs['greetings']['goodafternoon'] = $this->language->get('greetings.goodafternoon'); + $stringsforjs['greetings']['goodevening'] = $this->language->get('greetings.goodevening'); + $stringsforjs['greetings']['goodnight'] = $this->language->get('greetings.goodnight'); if ($showsearch || $checkstatus) { $templatecontext['sitesjson'] = json_encode((new \Jump\Sites($this->config, $this->cache))->get_sites_for_frontend()); if ($showsearch) { - $templatecontext['searchengines'] = json_encode((new \Jump\SearchEngines($this->config, $this->cache))->get_search_engines()); + $searchengines = new \Jump\SearchEngines($this->config, $this->cache, $this->language); + $templatecontext['searchengines'] = json_encode($searchengines->get_search_engines()); + $stringsforjs += $searchengines->get_strings_for_js(); } } + $templatecontext['stringsforjs'] = json_encode($stringsforjs); return $template->render($templatecontext); } diff --git a/jumpapp/classes/Pages/TagPage.php b/jumpapp/classes/Pages/TagPage.php index d3e2a3c..9962f35 100644 --- a/jumpapp/classes/Pages/TagPage.php +++ b/jumpapp/classes/Pages/TagPage.php @@ -38,12 +38,20 @@ class TagPage extends AbstractPage { 'wwwurl' => $this->config->get_wwwurl(), 'checkstatus' => $checkstatus, ]; + $stringsforjs = \Jump\Status::get_strings_for_js($this->language); + $stringsforjs['greetings']['goodmorning'] = $this->language->get('greetings.goodmorning'); + $stringsforjs['greetings']['goodafternoon'] = $this->language->get('greetings.goodafternoon'); + $stringsforjs['greetings']['goodevening'] = $this->language->get('greetings.goodevening'); + $stringsforjs['greetings']['goodnight'] = $this->language->get('greetings.goodnight'); if ($showsearch || $checkstatus) { $templatecontext['sitesjson'] = json_encode((new \Jump\Sites($this->config, $this->cache))->get_sites_for_frontend()); if ($showsearch) { - $templatecontext['searchengines'] = json_encode((new \Jump\SearchEngines($this->config, $this->cache))->get_search_engines()); + $searchengines = new \Jump\SearchEngines($this->config, $this->cache, $this->language); + $templatecontext['searchengines'] = json_encode($searchengines->get_search_engines()); + $stringsforjs += $searchengines->get_strings_for_js(); } } + $templatecontext['stringsforjs'] = json_encode($stringsforjs); return $template->render($templatecontext); } diff --git a/jumpapp/classes/SearchEngines.php b/jumpapp/classes/SearchEngines.php index 57ac85b..84e0a72 100644 --- a/jumpapp/classes/SearchEngines.php +++ b/jumpapp/classes/SearchEngines.php @@ -26,7 +26,7 @@ class SearchEngines { /** * Automatically load searchengines.json on instantiation. */ - public function __construct(private Config $config, private Cache $cache) { + public function __construct(private Config $config, private Cache $cache, private Language $language) { $this->config = $config; $this->loadedsearchengines = []; $this->searchfilelocation = $this->config->get('searchenginesfile'); @@ -87,4 +87,22 @@ class SearchEngines { public function get_search_engines() { return $this->loadedsearchengines; } + + /** + * Return all the strings to be used by JS on the frontend. + * + * @return array + */ + public function get_strings_for_js(): array { + $strings = [ + 'search' => [ + 'search' => $this->language->get('search.search'), + 'sites' => $this->language->get('search.sites'), + ] + ]; + foreach ($this->loadedsearchengines as $searchengine) { + $strings['search']['searchon'][$searchengine->name] = $this->language->get('search.searchon', ['searchprovider' => $searchengine->name]); + } + return $strings; + } } diff --git a/jumpapp/classes/Status.php b/jumpapp/classes/Status.php index c68afca..03ddceb 100644 --- a/jumpapp/classes/Status.php +++ b/jumpapp/classes/Status.php @@ -92,4 +92,21 @@ class Status { return self::STATUS_UNKNOWN; } } + + /** + * Return all the strings to be used by JS on the frontend. + * + * @return array + */ + public static function get_strings_for_js(Language $language): array { + return [ + 'status' => [ + 'status' => $language->get('status.status'), + 'error' => $language->get('status.error'), + 'offline' => $language->get('status.offline'), + 'online' => $language->get('status.online'), + 'unknown' => $language->get('status.unknown'), + ] + ]; + } } diff --git a/jumpapp/classes/Unsplash.php b/jumpapp/classes/Unsplash.php index 8a045f6..94e31df 100644 --- a/jumpapp/classes/Unsplash.php +++ b/jumpapp/classes/Unsplash.php @@ -15,7 +15,7 @@ namespace Jump; class Unsplash { - public static function load_cache_unsplash_data($config) { + public static function load_cache_unsplash_data($config, $language) { \Unsplash\HttpClient::init([ 'utmSource' => 'jump_startpage', 'applicationId' => $config->get('unsplashapikey'), @@ -37,14 +37,14 @@ class Unsplash { curl_setopt($ch, CURLOPT_FAILONERROR, true); $response = curl_exec($ch); // Create the response and return it. - $description = 'Photo'; + $description = $language->get('unsplash.description.photoby', ['user' => $photo->user['name']]); if ($photo->description !== null && strlen($photo->description) <= 45) { - $description = $photo->description; + $description = $language->get('unsplash.description.by', ['description' => $photo->description, 'user' => $photo->user['name']]); } $unsplashdata = new \stdClass(); $unsplashdata->color = $photo->color; - $unsplashdata->attribution = ''.$description.' by '.$photo->user['name'].''; + $unsplashdata->attribution = ''.$description.''; $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 index 1eb5a6f..798c37e 100644 --- a/jumpapp/cli/cacheunsplash.php +++ b/jumpapp/cli/cacheunsplash.php @@ -20,12 +20,13 @@ require __DIR__ .'/../vendor/autoload.php'; $config = new Jump\Config(); $cache = new Jump\Cache($config); +$language = new Jump\Language($this->config, $this->cache); // 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); + $unsplashdata = Jump\Unsplash::load_cache_unsplash_data($config, $language); $cache->save(cachename: 'unsplash', data: $unsplashdata); die('Cached data from Unsplash'); } diff --git a/jumpapp/composer.json b/jumpapp/composer.json index 674b2a3..e2be85e 100644 --- a/jumpapp/composer.json +++ b/jumpapp/composer.json @@ -19,6 +19,7 @@ "nette/http": "^3.1", "guzzlehttp/guzzle": "^7.0", "unsplash/unsplash": "3.2.1", - "divineomega/array_undot": "^4.1" + "divineomega/array_undot": "^4.1", + "utopia-php/locale": "^0.6.0" } } diff --git a/jumpapp/composer.lock b/jumpapp/composer.lock index b7d5ee0..1f24898 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#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8849ca2f3c80ed00c98055bf630ba1fb", + "content-hash": "5fcb97502e7722b9364b85cdd3a4f851", "packages": [ { "name": "arthurhoaro/favicon", @@ -1558,6 +1558,57 @@ "description": "Wrapper to access the Unsplash API and photo library", "time": "2022-01-17T20:32:58+00:00" }, + { + "name": "utopia-php/locale", + "version": "0.6.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/locale.git", + "reference": "9de05231484ab29f61e6557e7ae494cbcf31cf41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/locale/zipball/9de05231484ab29f61e6557e7ae494cbcf31cf41", + "reference": "9de05231484ab29f61e6557e7ae494cbcf31cf41", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Locale\\": "src/Locale" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "description": "A simple locale library to manage application translations", + "keywords": [ + "framework", + "locale", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/locale/issues", + "source": "https://github.com/utopia-php/locale/tree/0.6.0" + }, + "time": "2021-09-19T20:15:14+00:00" + }, { "name": "yosymfony/parser-utils", "version": "v2.0.0", diff --git a/jumpapp/config.php b/jumpapp/config.php index e4a2023..a49e277 100644 --- a/jumpapp/config.php +++ b/jumpapp/config.php @@ -21,6 +21,8 @@ return [ 'wwwroot' => getenv('WWWROOT') ?: '/var/www/html', // Site URL - might help if just is hosted in a subdirectory. 'wwwurl' => getenv('WWWURL') ?: '', + // The language Jump should use for strings, uses ISO 639-1 language codes. + 'language' => getenv('LANGUAGE') ?: 'en-gb', // Stop retrieving items from the cache, useful for testing. 'cachebypass' => getenv('CACHEBYPASS') ?: false, diff --git a/jumpapp/templates/footer.mustache b/jumpapp/templates/footer.mustache index 40f335c..d34c9f6 100644 --- a/jumpapp/templates/footer.mustache +++ b/jumpapp/templates/footer.mustache @@ -24,9 +24,9 @@ {{# hastags}}
- Tags + {{#language}}tags{{/language}}
diff --git a/jumpapp/templates/header.mustache b/jumpapp/templates/header.mustache index 6f0b8f2..3c106fe 100644 --- a/jumpapp/templates/header.mustache +++ b/jumpapp/templates/header.mustache @@ -22,7 +22,8 @@ searchengines: '{{{searchengines}}}', unsplash: '{{{unsplash}}}', unsplashcolor: '{{unsplashcolor}}', - wwwurl: '{{{wwwurl}}}' + wwwurl: '{{{wwwurl}}}', + strings: '{{{stringsforjs}}}' }; @@ -30,6 +31,6 @@