├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public ├── icon-128.png ├── icon-16.png ├── icon-48.png ├── index.html └── manifest.json ├── screenshot.png ├── src ├── App.vue ├── components │ ├── calendar │ │ ├── day.vue │ │ ├── event.vue │ │ └── index.vue │ ├── global │ │ ├── autosize.vue │ │ └── header.vue │ ├── note │ │ ├── index.vue │ │ └── note.vue │ └── task │ │ ├── index.vue │ │ └── task.vue ├── icons │ ├── check.vue │ ├── drag.vue │ ├── plus.vue │ ├── refresh.vue │ └── remove.vue ├── main.js ├── model │ └── index.js ├── store │ ├── dummy-data.js │ └── index.js └── utils │ ├── get-token.js │ └── http-calendar.js ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frame 2 | 3 | New-tab extension for Chrome and Firefox 4 | 5 | screenshot 6 | 7 | ## Install 8 | 9 | - [**Chrome** extension](https://chrome.google.com/webstore/detail/frame/pimalalkfhkmnlhoapdlhilkghboiimc) 10 | - [**Firefox** extension](https://addons.mozilla.org/tr/firefox/addon/new-tab-frame/) 11 | 12 | ## Contribute 13 | 14 | Suggestions and pull requests are highly encouraged! 15 | 16 | In order to make modifications to the extension you'd need to run it locally. 17 | 18 | Please follow the below steps: 19 | 20 | ```sh 21 | git clone https://github.com/ademilter/frame 22 | cd frame 23 | yarn # Install dev dependencies 24 | yarn serve # Listen for file changes and automatically rebuild 25 | yarn build:chrome # Build the extension code so it's ready for Chrome 26 | yarn build:firefox # Build the extension code so it's ready for Firefox 27 | ``` 28 | 29 | ## Maintainers 30 | 31 | - [Adem ilter](https://github.com/ademilter) 32 | - [Nazım Can Altınova](https://github.com/canaltinova) 33 | 34 | ## License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frame", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "yarn run build:chrome && yarn run build:firefox && yarn run clear", 8 | "build:chrome": "vue-cli-service build --dest dist/chrome && yarn run zip:chrome", 9 | "build:firefox": "VUE_APP_PLATFORM=firefox vue-cli-service build --dest dist/firefox && yarn run zip:firefox", 10 | "zip:chrome": "cd dist/chrome/ && bestzip ../chrome.zip **", 11 | "zip:firefox": "cd dist/firefox/ && bestzip ../firefox.zip **", 12 | "clear": "rimraf dist/chrome/ && rimraf dist/firefox/" 13 | }, 14 | "dependencies": { 15 | "autosize": "^4.0.2", 16 | "axios": "^0.18.1", 17 | "moment": "^2.22.2", 18 | "vue": "^2.5.16", 19 | "vue-quill-editor": "^3.0.6", 20 | "vuedraggable": "^2.16.0", 21 | "vuex": "^3.0.1", 22 | "vuex-persistedstate": "^2.5.4" 23 | }, 24 | "devDependencies": { 25 | "@vue/cli-plugin-babel": "^3.0.0-beta.15", 26 | "@vue/cli-plugin-eslint": "^3.0.0-beta.15", 27 | "@vue/cli-service": "^3.0.0-beta.15", 28 | "@vue/eslint-config-standard": "^3.0.0-rc.3", 29 | "bestzip": "^2.1.1", 30 | "moment-locales-webpack-plugin": "^1.0.7", 31 | "postcss-nested": "^3.0.0", 32 | "postcss-sorting": "^3.1.0", 33 | "pug": "^2.0.3", 34 | "pug-plain-loader": "^1.0.0", 35 | "vue-template-compiler": "^2.5.16" 36 | }, 37 | "eslintConfig": { 38 | "root": true, 39 | "env": { 40 | "node": true 41 | }, 42 | "extends": [ 43 | "plugin:vue/essential", 44 | "@vue/standard" 45 | ], 46 | "rules": { 47 | "vue/script-indent": [ 48 | "error", 49 | 2, 50 | { 51 | "baseIndent": 1 52 | } 53 | ], 54 | "indent": "off" 55 | }, 56 | "parserOptions": { 57 | "parser": "babel-eslint" 58 | } 59 | }, 60 | "postcss": { 61 | "plugins": { 62 | "autoprefixer": {}, 63 | "postcss-nested": {}, 64 | "postcss-sorting": { 65 | "order": [ 66 | "custom-properties", 67 | "declarations", 68 | "rules", 69 | "at-rules" 70 | ], 71 | "properties-order": "alphabetical" 72 | } 73 | } 74 | }, 75 | "browserslist": [ 76 | "> 1%", 77 | "last 2 versions", 78 | "not ie <= 8" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /public/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ademilter/Frame/61aaecf96683a5cce8d8443682fc867483a06289/public/icon-128.png -------------------------------------------------------------------------------- /public/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ademilter/Frame/61aaecf96683a5cce8d8443682fc867483a06289/public/icon-16.png -------------------------------------------------------------------------------- /public/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ademilter/Frame/61aaecf96683a5cce8d8443682fc867483a06289/public/icon-48.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Frame 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frame", 3 | "version": "1.6.2", 4 | "description": "", 5 | "manifest_version": 2, 6 | "applications": { 7 | "gecko": { 8 | "id": "{2e7abee6-b580-4813-8b57-cd2d2b7255cb}", 9 | "strict_min_version": "55.0" 10 | } 11 | }, 12 | "chrome_url_overrides": { 13 | "newtab": "index.html" 14 | }, 15 | "icons": { 16 | "16": "icon-16.png", 17 | "48": "icon-48.png", 18 | "128": "icon-128.png" 19 | }, 20 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOtWSnGxinGiZ3UZOur0f+6E1Ac29X9svVYwjzRi9AjOAcg5A6GgIkM8swJst1g6p9AojfphICj4mOMhZehb70b1oc9r9hwJxQ8al046sFg5IjFPVS5s7z9GjGlGtTdXaNaE7KZ/k8G9b945H/19tMW/HL6gWDeWQ+iXCOrMzvkMM/Iks6UbDae4awr/HiqZ2d5oAjeZlVE85SfUSnLEkEEapwY73JJD14gXXYZMhxtgfBHrVtablLo64/ujeMm53DtElrVTL2AaMirld2RjyvrpYU1AxkI81ljylQz0Tm/Lict+n3WuqYQnuSuvddFayciUo8KHeJ1J4skZa3AvXwIDAQAB", 21 | "oauth2": { 22 | "client_id_firefox": "825939795126-t9v6behgpkvj2vfffgg960gvneqi5umn.apps.googleusercontent.com", 23 | "client_id": "825939795126-mcce3h3b83llk6lid0fjds0b6age97k6.apps.googleusercontent.com", 24 | "scopes": [ 25 | "https://www.googleapis.com/auth/calendar" 26 | ] 27 | }, 28 | "content_security_policy": "script-src 'self' https://calendar.google.com; object-src 'self'", 29 | "permissions": [ 30 | "identity", 31 | "http://*/*", 32 | "https://*/*", 33 | "http://calendar.google.com/calendar/*", 34 | "https://calendar.google.com/calendar/*", 35 | "*://www.googleapis.com/*", 36 | "*://accounts.google.com/*" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ademilter/Frame/61aaecf96683a5cce8d8443682fc867483a06289/screenshot.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | 32 | 217 | -------------------------------------------------------------------------------- /src/components/calendar/day.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | 40 | 77 | -------------------------------------------------------------------------------- /src/components/calendar/event.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 38 | -------------------------------------------------------------------------------- /src/components/calendar/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 110 | 111 | 129 | -------------------------------------------------------------------------------- /src/components/global/autosize.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | -------------------------------------------------------------------------------- /src/components/global/header.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 59 | -------------------------------------------------------------------------------- /src/components/note/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 86 | 87 | 109 | 110 | 126 | -------------------------------------------------------------------------------- /src/components/note/note.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 113 | 114 | 195 | -------------------------------------------------------------------------------- /src/components/task/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 108 | 109 | 171 | -------------------------------------------------------------------------------- /src/components/task/task.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 152 | 153 | 273 | -------------------------------------------------------------------------------- /src/icons/check.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/icons/drag.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/icons/plus.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /src/icons/refresh.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/icons/remove.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import store from './store' 4 | import moment from 'moment' 5 | import 'moment/locale/tr' 6 | 7 | import Draggable from 'vuedraggable' 8 | import VueQuillEditor from 'vue-quill-editor' 9 | 10 | Vue.component('Draggable', Draggable) 11 | Vue.use(VueQuillEditor, {}) 12 | 13 | Vue.prototype.$moment = moment 14 | Vue.prototype.$isDev = process.env.NODE_ENV === 'development' 15 | Vue.config.productionTip = false 16 | 17 | new Vue({ 18 | store, 19 | render: h => h(App) 20 | }).$mount('#app') 21 | 22 | if (store.getters.hasToken) { 23 | store.commit('setToken', store.state.token) 24 | // if (store.getters.calendarDataExpire) { 25 | // store.dispatch('getEventList') 26 | // } 27 | } 28 | 29 | // if (store.state.notification === 'default') { 30 | // Notification.requestPermission(function (permission) { 31 | // store.commit('changeNotification', permission) 32 | // }) 33 | // } 34 | 35 | if (Vue.prototype.$isDev) { 36 | store.commit('calSetDummyData') 37 | } 38 | 39 | // DEV 40 | if (localStorage.getItem('vuex')) { 41 | store.commit('moveOldData') 42 | } 43 | -------------------------------------------------------------------------------- /src/model/index.js: -------------------------------------------------------------------------------- 1 | export class Note { 2 | constructor (data = {}) { 3 | this.id = data.id || new Date().getTime() 4 | this.content = data.content || '' 5 | } 6 | } 7 | 8 | export class Task { 9 | constructor (data = {}) { 10 | this.id = data.id || new Date().getTime() 11 | this.status = data.text || false 12 | this.text = data.text || '' 13 | } 14 | 15 | get isEmpty () { 16 | return this.text.trim().length === 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/store/dummy-data.js: -------------------------------------------------------------------------------- 1 | export const dummyCalendarList = () => { 2 | return [ 3 | { 4 | 'accessRole': 'owner', 5 | 'backgroundColor': '#9fc6e7', 6 | 'defaultReminders': [], 7 | 'etag': '"435465ygfdfgdf"', 8 | 'foregroundColor': '#000000', 9 | 'id': 'gdfdssfdgd544@group.calendar.google.com', 10 | 'kind': 'calendar#calendarListEntry', 11 | 'location': 'ISTANBUL', 12 | 'summary': 'Genel', 13 | 'timeZone': 'Europe/Istanbul' 14 | }, 15 | { 16 | 'accessRole': 'owner', 17 | 'backgroundColor': '#b3dc6c', 18 | 'defaultReminders': [{ 'method': 'popup', 'minutes': 30 }], 19 | 'etag': '"sdgfhggdf45"', 20 | 'foregroundColor': '#000000', 21 | 'id': '35efagsa@gmail.com', 22 | 'kind': 'calendar#calendarListEntry', 23 | 'location': 'ISTANBUL', 24 | 'selected': true, 25 | 'summary': 'Özel', 26 | 'timeZone': 'Europe/Istanbul' 27 | }, 28 | { 29 | 'accessRole': 'reader', 30 | 'backgroundColor': '#16a765', 31 | 'defaultReminders': [], 32 | 'etag': '"1537106192955000"', 33 | 'foregroundColor': '#000000', 34 | 'id': 'tr.turkish#holiday@group.v.calendar.google.com', 35 | 'kind': 'calendar#calendarListEntry', 36 | 'summary': 'Türkiye\'deki Tatiller', 37 | 'timeZone': 'Europe/Istanbul' 38 | }, 39 | { 40 | 'accessRole': 'reader', 41 | 'backgroundColor': '#fbe983', 42 | 'defaultReminders': [], 43 | 'etag': '"1537106200237000"', 44 | 'foregroundColor': '#000000', 45 | 'id': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 46 | 'kind': 'calendar#calendarListEntry', 47 | 'summary': 'Phases of the Moon', 48 | 'timeZone': 'Europe/Istanbul' 49 | } 50 | ] 51 | } 52 | export const dummyCalendarItems = () => { 53 | return [ 54 | { 55 | 'attendees': [ 56 | { 57 | 'email': 'ademilter@gmail.com', 58 | 'displayName': 'Adem Ilter', 59 | 'self': true, 60 | 'responseStatus': 'accepted' 61 | }, { 62 | 'email': 'ilyas.teker@gmail.com', 63 | 'displayName': 'ilyas teker', 64 | 'organizer': true, 65 | 'responseStatus': 'accepted' 66 | } 67 | ], 68 | 'created': '2018-09-03T20:52:25.000Z', 69 | 'creator': { 70 | 'email': 'ilyas.teker@gmail.com', 71 | 'displayName': 'ilyas teker' 72 | }, 73 | 'description': 'Saati 17:00 yapsak uyar mi abi?', 74 | 'end': { 'dateTime': '2018-09-20T18:00:00+03:00' }, 75 | 'etag': '"3074216283874000"', 76 | 'htmlLink': 'https://www.google.com/calendar/event?eid=ZzAwNHV2MzNzbTk4Ymc3NHNlM2NscTRoNGcgYWRlbWlsdGVyQG0', 77 | 'iCalUID': 'g004uv33sm98bg74se3clq4h4g@google.com', 78 | 'id': 'g004uv33sm98bg74se3clq4h4g', 79 | 'kind': 'calendar#event', 80 | 'location': 'Capitol Starbucks', 81 | 'organizer': { 82 | 'email': 'ilyas.teker@gmail.com', 83 | 'displayName': 'ilyas teker' 84 | }, 85 | 'reminders': { 'useDefault': true }, 86 | 'sequence': 0, 87 | 'start': { 'dateTime': '2018-09-20T17:00:00+03:00' }, 88 | 'status': 'confirmed', 89 | 'summary': 'Adem - İlyas Bulusma', 90 | 'updated': '2018-09-16T14:29:01.937Z' 91 | }, 92 | { 93 | 'attendees': [ 94 | { 95 | 'email': 'ademilter@gmail.com', 96 | 'displayName': 'Adem Ilter', 97 | 'self': true, 98 | 'responseStatus': 'accepted' 99 | } 100 | ], 101 | 'created': '2018-08-21T08:40:53.000Z', 102 | 'creator': { 103 | 'email': 'ademilter@gmail.com', 104 | 'displayName': 'Adem Ilter', 105 | 'self': true 106 | }, 107 | 'description': 'Bunun gibi otomatik oluşturulmuş etkinlikler hakkında ayrıntılı bilgi görmek üzere, resmî Google Takvim uygulamasını kullanın. https://g.co/calendar\n\nBu etkinlik, Gmail\'e aldığınız bir e-postan oluşturuldu. https://mail.google.com/mail?extsrc=cal&plid=ACUX6DPFcYfLJYrZOEh7QWpkJORwc7Vr-Jfxirc\n', 108 | 'end': { 'dateTime': '2018-09-23T07:00:00+03:00' }, 109 | 'etag': '"3069681706778000"', 110 | 'htmlLink': 'https://www.google.com/calendar/event?eid=XzZ0bG5hcXJsZTVwNmNwYjRkaG1qNHBocGVocW40ZDlwNmRrbjhyOWpjNHBqMnFiOWUxbDM0c3BsY29vM2dxajRkdHI2b29yNjY5cDMwbzlwZWRvbWdvajljcGs3MGNqNGN0aTNhZGoyY2hrbWUgYWRlbWlsdGVyQG0', 111 | 'iCalUID': '7kukuqrfedlm2f9tur593itm3a31iipj2s5f08jdovlcf2r0a9sqhbifhp2dgd56bdig', 112 | 'id': '_6tlnaqrle5p6cpb4dhmj4phpehqn4d9p6dkn8r9jc4pj2qb9e1l34splcoo3gqj4dtr6oor669p30o9pedomgoj9cpk70cj4cti3adj2chkme', 113 | 'kind': 'calendar#event', 114 | 'location': 'İstanbul IST', 115 | 'organizer': { 'email': 'unknownorganizer@calendar.google.com' }, 116 | 'reminders': { 'useDefault': false }, 117 | 'sequence': 0, 118 | 'start': { 'dateTime': '2018-09-23T05:15:00+03:00' }, 119 | 'status': 'confirmed', 120 | 'summary': 'Uçuş hedefi: Elazığ (TK 2642)', 121 | 'updated': '2018-08-21T08:40:53.484Z' 122 | }, 123 | { 124 | 'created': '2008-11-18T00:00:00.000Z', 125 | 'creator': { 126 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 127 | 'displayName': 'Phases of the Moon', 128 | 'self': true 129 | }, 130 | 'end': { 'date': '2018-09-18' }, 131 | 'etag': '"2453932800000000"', 132 | 'htmlLink': 'https://www.google.com/calendar/event?eid=bW9vbnBoYXNlKzE1MzcxMzk3MDAwMDAgaHQzamxmYWFjNWxmZDYyNjN1bGZoNHRxbDhAZw', 133 | 'iCalUID': 'moonphase+1537139700000@google.com', 134 | 'id': 'moonphase+1537139700000', 135 | 'kind': 'calendar#event', 136 | 'organizer': { 137 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 138 | 'displayName': 'Phases of the Moon', 139 | 'self': true 140 | }, 141 | 'sequence': 0, 142 | 'start': { 'date': '2018-09-17' }, 143 | 'status': 'confirmed', 144 | 'summary': 'İlk dördün 02:15', 145 | 'updated': '2008-11-18T00:00:00.000Z' 146 | }, 147 | { 148 | 'created': '2008-11-18T00:00:00.000Z', 149 | 'creator': { 150 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 151 | 'displayName': 'Phases of the Moon', 152 | 'self': true 153 | }, 154 | 'end': { 'date': '2018-09-26' }, 155 | 'etag': '"2453932800000000"', 156 | 'htmlLink': 'https://www.google.com/calendar/event?eid=bW9vbnBoYXNlKzE1Mzc4NDM5ODAwMDAgaHQzamxmYWFjNWxmZDYyNjN1bGZoNHRxbDhAZw', 157 | 'iCalUID': 'moonphase+1537843980000@google.com', 158 | 'id': 'moonphase+1537843980000', 159 | 'kind': 'calendar#event', 160 | 'organizer': { 161 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 162 | 'displayName': 'Phases of the Moon', 163 | 'self': true 164 | }, 165 | 'sequence': 0, 166 | 'start': { 'date': '2018-09-25' }, 167 | 'status': 'confirmed', 168 | 'summary': 'Dolunay 05:53', 169 | 'updated': '2008-11-18T00:00:00.000Z' 170 | }, 171 | { 172 | 'created': '2008-11-18T00:00:00.000Z', 173 | 'creator': { 174 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 175 | 'displayName': 'Phases of the Moon', 176 | 'self': true 177 | }, 178 | 'end': { 'date': '2018-09-20' }, 179 | 'etag': '"2453932800000000"', 180 | 'htmlLink': 'https://www.google.com/calendar/event?eid=bW9vbnBoYXNlKzE1Mzc4NDM5ODAwMDAgaHQzamxmYWFjNWxmZDYyNjN1bGZoNHRxbDhAZw', 181 | 'iCalUID': 'moonphase+1537843980000@google.com', 182 | 'id': 'moonphase+1537843980000', 183 | 'kind': 'calendar#event', 184 | 'organizer': { 185 | 'email': 'ht3jlfaac5lfd6263ulfh4tql8@group.calendar.google.com', 186 | 'displayName': 'Phases of the Moon', 187 | 'self': true 188 | }, 189 | 'sequence': 0, 190 | 'start': { 'date': '2018-09-20' }, 191 | 'status': 'confirmed', 192 | 'summary': 'Dolunay 05:53', 193 | 'updated': '2008-11-18T00:00:00.000Z' 194 | } 195 | ] 196 | } 197 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import createPersistedState from 'vuex-persistedstate' 4 | import httpCalendar from '../utils/http-calendar' 5 | import moment from 'moment' 6 | import { Note, Task } from '../model' 7 | import { dummyCalendarList, dummyCalendarItems } from './dummy-data' 8 | 9 | Vue.use(Vuex) 10 | 11 | export default new Vuex.Store({ 12 | 13 | plugins: [createPersistedState({ 14 | key: 'FrameExtension' 15 | })], 16 | 17 | state: { 18 | // data 19 | notes: [], 20 | tasks: [], 21 | calendarList: [], 22 | calendarItems: [], 23 | // config 24 | token: null, 25 | notification: 'default', 26 | calendarLastUpdate: moment().startOf('hour') 27 | }, 28 | 29 | getters: { 30 | hasToken: state => state.token !== null, 31 | calendarDataExpire: state => { 32 | return moment().startOf('hour').format('H') - moment(state.calendarLastUpdate).format('H') > 0 33 | }, 34 | allowNotification: state => state.notification === 'granted', 35 | hasNote: state => !!state.notes.length, 36 | hasActiveTasks: state => state.tasks.some(o => !o.status), 37 | hasCompletedTasks: state => state.tasks.some(o => o.status), 38 | hasCalenderItem: state => !!state.calendarItems.length, 39 | eventsSortAndGroupBy: state => { 40 | const sortDates = {} 41 | // groupBy 42 | const groupBy = state.calendarItems.reduce((acc, obj) => { 43 | const key = moment(obj.start.date || obj.start.dateTime).format('YYYY-MM-DD'); 44 | (acc[key] || (acc[key] = [])).push(obj) 45 | return acc 46 | }, {}) 47 | // sort by timestamp and move new object 48 | Object.keys(groupBy).sort((a, b) => { 49 | return moment(a).format('X') - moment(b).format('X') 50 | }).forEach(key => { 51 | sortDates[key] = groupBy[key] 52 | }) 53 | return sortDates 54 | } 55 | }, 56 | 57 | actions: { 58 | 59 | afterLogin ({ commit, dispatch }, token) { 60 | commit('changeToken', token) 61 | commit('setToken', token) 62 | dispatch('getEventList') 63 | }, 64 | 65 | async getCalendarList ({ commit }) { 66 | const response = await httpCalendar.get('users/me/calendarList') 67 | commit('setCalendarList', response.data) 68 | }, 69 | 70 | async getEventList ({ state, getters, dispatch, commit }) { 71 | await dispatch('getCalendarList') 72 | 73 | const fromDate = moment() 74 | const toDate = moment().add(14, 'days') 75 | 76 | const requestList = state.calendarList.map(cal => { 77 | const url = [ 78 | 'calendars/', 79 | encodeURIComponent(cal.id), 80 | '/events', 81 | '?timeMin=' + encodeURIComponent(fromDate.toISOString()), 82 | '&timeMax=' + encodeURIComponent(toDate.toISOString()), 83 | '&maxResults=50', 84 | '&orderBy=startTime', 85 | '&singleEvents=true' 86 | ].join('') 87 | return httpCalendar.get(url) 88 | }) 89 | 90 | const response = await Promise.all(requestList) 91 | commit('setCalendarItems', response) 92 | commit('updateCalendarLastUpdate') 93 | } 94 | 95 | }, 96 | 97 | mutations: { 98 | 99 | changeToken (state, token) { 100 | state.token = token 101 | }, 102 | 103 | setToken (state, token) { 104 | httpCalendar.defaults.headers.common.Authorization = `Bearer ${token}` 105 | }, 106 | 107 | changeNotification (state, status) { 108 | state.notification = status 109 | }, 110 | 111 | updateCalendarLastUpdate (state) { 112 | state.calendarLastUpdate = moment().startOf('hour') 113 | }, 114 | 115 | moveOldData (state) { 116 | const oldData = JSON.parse(localStorage.getItem('vuex')) 117 | localStorage.removeItem('vuex') 118 | state.notes = oldData.notes 119 | state.tasks = oldData.tasks 120 | }, 121 | 122 | // NOTE 123 | 124 | addNote (state) { 125 | state.notes.unshift(new Note()) 126 | }, 127 | changeNote (state, { note, content }) { 128 | note.content = content 129 | }, 130 | updateNotes (state, newList) { 131 | state.notes = newList 132 | }, 133 | removeNote (state, note) { 134 | const index = state.notes.indexOf(note) 135 | if (index > -1) { 136 | state.notes.splice(index, 1) 137 | } 138 | }, 139 | 140 | // TASK 141 | 142 | updateTasks (state, newList) { 143 | state.tasks = newList 144 | }, 145 | addTask (state) { 146 | state.tasks.unshift(new Task()) 147 | }, 148 | changeTextTask (state, { task, text }) { 149 | task.text = text 150 | }, 151 | changeStatusTask (state, { task, status }) { 152 | task.status = status 153 | }, 154 | removeTasks (state, task) { 155 | const index = state.tasks.indexOf(task) 156 | if (index > -1) { 157 | state.tasks.splice(index, 1) 158 | } 159 | }, 160 | 161 | // CALENDAR 162 | 163 | calSetDummyData (state) { 164 | state.calendarList = dummyCalendarList() 165 | state.calendarItems = dummyCalendarItems() 166 | }, 167 | 168 | setCalendarList (state, data) { 169 | state.calendarList = data.items 170 | }, 171 | 172 | setCalendarItems (state, items) { 173 | const data = items.map(item => item.data) 174 | const events = data.map(calendar => calendar.items) 175 | state.calendarItems = events.flat() 176 | } 177 | } 178 | 179 | }) 180 | -------------------------------------------------------------------------------- /src/utils/get-token.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import store from '../store' 3 | 4 | const authUrl = () => { 5 | const manifest = chrome.runtime.getManifest() 6 | const redirectUrl = chrome.identity.getRedirectURL() 7 | console.info('redirect url', redirectUrl) 8 | return [ 9 | 'https://accounts.google.com/o/oauth2/auth', 10 | '?client_id=' + manifest.oauth2.client_id_firefox, 11 | '&response_type=token', 12 | '&redirect_uri=' + encodeURIComponent(redirectUrl), 13 | '&scope=' + manifest.oauth2.scopes.join(',') 14 | ].join('') 15 | } 16 | 17 | const extractAccessToken = redirectUri => { 18 | const m = redirectUri.match(/[#?](.*)/) 19 | if (!m || m.length < 1) { 20 | return null 21 | } 22 | const params = new URLSearchParams(m[1].split('#')[0]) 23 | return params.get('access_token') 24 | } 25 | 26 | const getAuthToken = () => { 27 | if (process.env.VUE_APP_PLATFORM === 'firefox') { 28 | chrome.identity.launchWebAuthFlow({ 29 | interactive: true, 30 | url: authUrl() 31 | }, async redirectUri => { 32 | const token = extractAccessToken(redirectUri) 33 | store.dispatch('afterLogin', token) 34 | }) 35 | } else { 36 | chrome.identity.getAuthToken({ 37 | interactive: true 38 | }, async function (token) { 39 | store.dispatch('afterLogin', token) 40 | }) 41 | } 42 | } 43 | 44 | export default getAuthToken 45 | -------------------------------------------------------------------------------- /src/utils/http-calendar.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '../store' 3 | 4 | const httpCalendar = axios.create({ 5 | baseURL: 'https://www.googleapis.com/calendar/v3/' 6 | }) 7 | 8 | // Setting up an interceptor in case of a token 9 | // expiration or unsuccessful authentication. 10 | 11 | httpCalendar.interceptors.response.use(null, error => { 12 | if (error.response.status === 401) { 13 | console.error('token expired') 14 | if (store.getters.hasToken) { 15 | store.commit('changeToken', null) 16 | } 17 | } 18 | return Promise.reject(error) 19 | }) 20 | 21 | export default httpCalendar 22 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const MomentLocalesPlugin = require('moment-locales-webpack-plugin') 2 | 3 | module.exports = { 4 | configureWebpack: { 5 | plugins: [ 6 | new MomentLocalesPlugin({ 7 | localesToKeep: ['tr'] 8 | }) 9 | ] 10 | } 11 | } 12 | --------------------------------------------------------------------------------