From c3c464a5a02382298583aebe5d834d936804e3e2 Mon Sep 17 00:00:00 2001 From: rainu Date: Tue, 12 Nov 2019 15:04:47 +0100 Subject: [PATCH] [#21] implements the notification when notes are overdue --- components/note/OverdueNotifications.vue | 36 ++++++++++++++++++++++++ components/note/form/Reminder.vue | 1 + layouts/default.vue | 4 ++- nuxt.config.js | 5 ++-- package-lock.json | 13 +++++++++ plugins/notification.js | 17 +++++++++++ plugins/reminder.js | 35 +++++++++++++++++++++++ store/note.js | 31 +++++++++++++++++++- store/settings.js | 17 +++++++++++ 9 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 components/note/OverdueNotifications.vue create mode 100644 plugins/notification.js create mode 100644 plugins/reminder.js diff --git a/components/note/OverdueNotifications.vue b/components/note/OverdueNotifications.vue new file mode 100644 index 0000000..df6a514 --- /dev/null +++ b/components/note/OverdueNotifications.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/components/note/form/Reminder.vue b/components/note/form/Reminder.vue index 1dcbb78..feeda32 100644 --- a/components/note/form/Reminder.vue +++ b/components/note/form/Reminder.vue @@ -185,6 +185,7 @@ data.tags = this.note.tags data.content = {} data.content.date = this.note.date + data.content.noticed = false if(this.note.markdown) data.content.markdown = this.note.content else data.content.text = this.note.content diff --git a/layouts/default.vue b/layouts/default.vue index 00f3734..305e26e 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -73,6 +73,7 @@ + @@ -82,9 +83,10 @@ import { mapState } from 'vuex'; import { generateBoardQuery, readBoardQuery } from '../common/boardQuery' import Info from "../components/Info"; +import OverdueNotifications from "../components/note/OverdueNotifications"; export default { - components: {Info}, + components: {OverdueNotifications, Info}, data () { return { clipped: false, diff --git a/nuxt.config.js b/nuxt.config.js index edcc1e1..66cdfb6 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -3,15 +3,14 @@ import colors from 'vuetify/es5/util/colors' // only add `router.base = '//'` if `DEPLOY_ENV` is `GH_PAGES` const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? { router: { - middleware: ['encryption', 'dropbox'], base: '/dev-notes/' } } : { router: { - middleware: ['encryption', 'dropbox'], base: '/' } } +routerBase.router.middleware = ['encryption', 'dropbox'] export default { ...routerBase, @@ -82,6 +81,8 @@ export default { '~/plugins/i18n', '~/plugins/init', '~/plugins/style', + '~/plugins/notification', + '~/plugins/reminder', ], /* ** Nuxt.js dev-modules diff --git a/package-lock.json b/package-lock.json index 700c745..7d481f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1142,6 +1142,14 @@ "workbox-cdn": "^4.3.1" } }, + "@nuxtjs/toast": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/toast/-/toast-3.3.0.tgz", + "integrity": "sha512-HZAhWMQqgQ8m2XtQS4CpvoJtiZw2kmdqFg0GJv8upNEXoP7/nATW2CPIFemMtiparevLZwpCAAcMIVqcSW9l1g==", + "requires": { + "vue-toasted": "^1.1.27" + } + }, "@nuxtjs/vuetify": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@nuxtjs/vuetify/-/vuetify-1.8.2.tgz", @@ -8692,6 +8700,11 @@ "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" }, + "vue-toasted": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/vue-toasted/-/vue-toasted-1.1.27.tgz", + "integrity": "sha512-GVbwInwnqkVxQ4GU/XYeQt1e0dAXL8sF5Hr1H/coCBbYUan5xP0G2mEz/HRDf1lt73rFQAN/bJcLTOKkqiM6tg==" + }, "vue-web-cam": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/vue-web-cam/-/vue-web-cam-1.8.0.tgz", diff --git a/plugins/notification.js b/plugins/notification.js new file mode 100644 index 0000000..e00a2d5 --- /dev/null +++ b/plugins/notification.js @@ -0,0 +1,17 @@ +export default function ({ isHMR, app, store, route, params, error, redirect }) { + // check if the browser supports notifications + if ("Notification" in window) { + store.commit('settings/setNotificationSupported', true) + } else { + //notification are not supported! + return + } + + if (Notification.permission === "granted"){ + store.commit('settings/setNotificationGranted', true) + } else if (Notification.permission !== 'denied') { + Notification.requestPermission((permission) => { + store.commit('settings/setNotificationGranted', permission === "granted") + }) + } +} diff --git a/plugins/reminder.js b/plugins/reminder.js new file mode 100644 index 0000000..3312a42 --- /dev/null +++ b/plugins/reminder.js @@ -0,0 +1,35 @@ +const TICKER_TIME=60000 //1min + +const activeNotifications = {} + +const checkOverdue = (store, env) => { + store.dispatch('note/checkOverdueNotes') + .then(() => { + if(store.getters['settings/isNotificationEnabled']){ + for(let overdueNote of store.getters['note/getOverdueNotes']){ + if(!activeNotifications[overdueNote.id]) { + activeNotifications[overdueNote.id] = new Notification(overdueNote.title, { + icon: `${env.routerBase}icon.png`, + tag: overdueNote.id, + requireInteraction: true, + }) + activeNotifications[overdueNote.id].onclick = () => { + store.commit('note/removeOverdueAlarm', overdueNote.id) + delete activeNotifications[overdueNote.id] + } + } + } + } + }) + + setTimeout(() => { + checkOverdue(store, env); + }, TICKER_TIME); +} + +export default function ({store, env}) { + //check but dont blocking! + setTimeout(() => { + checkOverdue(store, env) + }, 1) +} diff --git a/store/note.js b/store/note.js index 95974fe..dd4e2e5 100644 --- a/store/note.js +++ b/store/note.js @@ -2,7 +2,8 @@ import Vue from 'vue' export const state = () => ({ notes: [], - noteOrder: [] + noteOrder: [], + overdueAlarm: [] }) export const mutations = { @@ -56,6 +57,21 @@ export const mutations = { this.$localStore.clearNotes() }, + triggerOverdueAlarm(state, noteId) { + if(!state.overdueAlarm.includes(noteId)) { + state.overdueAlarm.push(noteId) + } + }, + removeOverdueAlarm(state, noteId) { + let index = state.overdueAlarm.findIndex(n => n.id === noteId) + if(index) { + state.overdueAlarm.splice(index, 1) + } + + let note = state.notes.find(n => n.id === noteId) + note.content.noticed = true + this.$localStore.setNote(note) + } } export const getters = { @@ -73,6 +89,9 @@ export const getters = { } return Object.keys(tagMap).sort() + }, + getOverdueNotes(state) { + return state.overdueAlarm.map(nId => state.notes.find(n => n.id === nId)) } } @@ -80,6 +99,16 @@ export const actions = { init(ctx) { return Promise.all([this.$localStore.getNotes(), this.$localStore.getNoteOrder()]) .then(([notes, order]) => ctx.commit('loadNotes', {notes, order})) + }, + checkOverdueNotes(ctx) { + ctx.state.notes + .filter(n => n.type === 'reminder') + .filter(n => n.content.date) + .filter(n => new Date() > n.content.date) + .filter(n => !n.content.noticed) + .forEach(n => { + ctx.commit('triggerOverdueAlarm', n.id) + }) } } diff --git a/store/settings.js b/store/settings.js index 1af3f0e..2f6bec3 100644 --- a/store/settings.js +++ b/store/settings.js @@ -15,6 +15,10 @@ export const state = () => ({ }, theme: { dark: true, + }, + notification: { + supported: false, + granted: false, } }) @@ -32,6 +36,12 @@ export const mutations = { this.$localStore.setLanguage(lang) } }, + setNotificationSupported(state, supported) { + state.notification.supported = supported + }, + setNotificationGranted(state, granted) { + state.notification.granted = granted + }, setNoteSize(state, {fixed, size}) { state.notes.fixed = fixed state.notes.size = size @@ -53,6 +63,12 @@ export const mutations = { } } +export const getters = { + isNotificationEnabled(state){ + return state.notification.supported && state.notification.granted + } +} + export const actions = { init(ctx) { return Promise.all([ @@ -130,5 +146,6 @@ export default { namespaced: true, state, mutations, + getters, actions }