const app = new Vue({ el: '#app', data: { config: null, offline: false, filter: '', vlayout: true, isDark: null, showMenu: false }, created: async function () { let that = this; this.isDark = 'overrideDark' in localStorage ? JSON.parse(localStorage.overrideDark) : matchMedia("(prefers-color-scheme: dark)").matches; if ('vlayout' in localStorage) { this.vlayout = JSON.parse(localStorage.vlayout) } this.checkOffline(); try { this.config = await this.getConfig(); document.title = this.config.title + ' | Homer'; } catch (error) { this.offline = true; } // Look for a new message if an endpoint is provided. if (this.config.message && this.config.message.url) { this.getMessage(this.config.message.url).then(function(message){ // keep the original config value if no value is provided by the endpoint for (const prop of ['title','style','content']) { if (prop in message && message[prop] !== null) { that.config.message[prop] = message[prop]; } } }); } document.addEventListener('visibilitychange', function () { if (document.visibilityState == "visible") { that.checkOffline(); } }, false); }, methods: { checkOffline: function () { let that = this; return fetch(window.location.href + "?alive", { method: 'HEAD', cache: 'no-store' }).then(function () { that.offline = false; }).catch(function () { that.offline = true; }); }, getConfig: function (event) { return fetch('config.yml').then(function (response) { if (response.status != 200) { return } return response.text().then(function (body) { return jsyaml.load(body); }); }); }, getMessage: function (url) { return fetch(url).then(function (response) { if (response.status != 200) { return; } return response.json(); }); }, toggleTheme: function() { this.isDark = !this.isDark; localStorage.overrideDark = this.isDark; }, toggleLayout: function() { this.vlayout = !this.vlayout; localStorage.vlayout = this.vlayout; }, toggleMenu: function() { this.showMenu = !this.showMenu; }, matchesFilter: function(item) { return (item.name.toLowerCase().includes(this.filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(this.filter.toLowerCase()))) }, firstMatchingService: function() { for (group of this.config.services) { for (item of group.items) { if (this.matchesFilter(item)) { return item; } } } return null; }, navigateToFirstService: function(target) { service = this.firstMatchingService(); if (service) { window.open(service.url, target || service.target || '_self'); } } }, mounted() { function isSmallScreen() { return window.matchMedia('screen and (max-width: 1023px)').matches; } this._keyListener = function(e) { if (e.key === '/') { if (isSmallScreen()) { this.showMenu = true; } Vue.nextTick(() => { this.$refs.search.focus(); }); e.preventDefault(); } if (e.key === 'Escape') { this.filter = ''; this.$refs.search.blur(); if (isSmallScreen()) { this.showMenu = false; } } } document.addEventListener('keydown', this._keyListener.bind(this)); }, beforeDestroy() { document.removeEventListener('keydown', this._keyListener); } }); Vue.component('service', { props: ['item'], template: `

{{ item.name }}

{{ item.subtitle }}

#{{ item.tag }}
` }); if ('serviceWorker' in navigator) { window.addEventListener('load', function () { navigator.serviceWorker.register('worker.js'); }); }