├── .github └── workflows │ ├── gh-pages.yml │ └── ipfs.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── variables.scss ├── common ├── boardQuery.js ├── copy.js ├── gist.js └── importExport.js ├── components ├── Audio.vue ├── ClickToEdit.vue ├── IconChooser.vue ├── Info.vue ├── Recorder.vue ├── backup │ ├── Encryption.vue │ ├── Unlock.vue │ ├── aws │ │ ├── S3.vue │ │ ├── S3Export.vue │ │ └── S3Import.vue │ ├── dropbox │ │ ├── Dropbox.vue │ │ ├── DropboxExport.vue │ │ └── DropboxImport.vue │ ├── file │ │ ├── File.vue │ │ ├── FileExport.vue │ │ └── FileImport.vue │ ├── gist │ │ ├── Gist.vue │ │ ├── GistExport.vue │ │ └── GistImport.vue │ └── webdav │ │ ├── Webdav.vue │ │ ├── WebdavExport.vue │ │ └── WebdavImport.vue ├── board │ └── Form.vue ├── help │ ├── Board.vue │ ├── Encryption.vue │ ├── FirstSteps.vue │ ├── Gist.vue │ └── aws │ │ └── S3.vue ├── note │ ├── OverdueNotifications.vue │ ├── card │ │ ├── Camera.vue │ │ ├── Credentials.vue │ │ ├── Picture.vue │ │ ├── Record.vue │ │ ├── Reminder.vue │ │ ├── Template.vue │ │ ├── TemplateCopyDialogContent.vue │ │ ├── TemplateView.vue │ │ └── Text.vue │ ├── form │ │ ├── Camera.vue │ │ ├── ColorPicker.vue │ │ ├── Credentials.vue │ │ ├── Picture.vue │ │ ├── Record.vue │ │ ├── Reminder.vue │ │ ├── TagPicker.vue │ │ ├── Template.vue │ │ └── Text.vue │ └── types.js └── settings │ ├── Board.vue │ ├── DateTime.vue │ ├── Encryption.vue │ ├── Language.vue │ ├── Note.vue │ ├── TagDelimiter.vue │ └── Theme.vue ├── docs ├── .nojekyll ├── 200.html ├── _nuxt │ ├── 01a53180dd123fd69952.js │ ├── 1ed0a8b941c5c55eee7c.js │ ├── 2d96f2f8a64aef82bb2d.js │ ├── 39a05e90b433a218e77f.js │ ├── 3e1f31c08ef0831dae71.js │ ├── 3fffe9b81fc83d0e63bf.js │ ├── 43e3ea82d79fd1ec6cfc.js │ ├── 4eff3e0a36e6e7d86fde.js │ ├── 603423bf3829e610c490.js │ ├── 655a743471a88b5044bf.js │ ├── 6d8f41efad10be59e340.js │ ├── 78efa1a79b8aeb6d8ec0.js │ ├── LICENSES │ ├── a3126c052c5c02359dbb.js │ ├── aa5cd102e06d914bc83f.js │ ├── b638e1948186b0bb5c6a.js │ ├── b67a7c75c923b6655ff1.js │ ├── c97723026bd65a220dd6.js │ ├── d8625ffae1f925261e1d.js │ ├── dd6b73e5b1ab2e383d20.js │ ├── e136b597f3ea1cdf1ec9.js │ ├── f0cbe96f292463ef51e5.js │ ├── ffddf1145f59aeb5e4b7.js │ ├── fonts │ │ ├── 0509ab0.woff2 │ │ ├── 1618c77.ttf │ │ ├── 29b882f.woff │ │ ├── 927457e.woff2 │ │ ├── 96c4768.eot │ │ ├── d6e3eba.woff │ │ ├── da4ea5c.ttf │ │ └── f81583f.eot │ ├── icons │ │ ├── icon_120.2e5a11.png │ │ ├── icon_144.2e5a11.png │ │ ├── icon_152.2e5a11.png │ │ ├── icon_16.2e5a11.png │ │ ├── icon_192.2e5a11.png │ │ ├── icon_32.2e5a11.png │ │ ├── icon_384.2e5a11.png │ │ └── icon_512.2e5a11.png │ └── manifest.1a0b3aa1.json ├── avatar.jpg ├── favicon.ico ├── icon.png ├── index.html ├── logo.svg └── sw.js ├── layouts ├── default.vue └── error.vue ├── locales ├── de.json ├── en.json └── index.js ├── middleware ├── dropbox.js ├── encryption.js └── recall.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── backup │ └── index.vue ├── help │ └── index.vue ├── index.vue ├── notes │ ├── edit │ │ └── _id.vue │ └── new │ │ └── _type.vue ├── recall.vue ├── settings │ └── index.vue ├── trash │ └── index.vue └── unlock │ └── index.vue ├── plugins ├── globalComponents.js ├── i18n.js ├── init.js ├── localStore.js ├── notification.js ├── reminder.js └── style.js ├── preview ├── backups.png ├── cards_0.png ├── cards_1.png ├── cards_2.png └── new.png ├── release.sh ├── static ├── avatar.jpg ├── favicon.ico ├── icon.png └── logo.svg └── store ├── board.js ├── index.js ├── localStore.js ├── localStoreMigrations.js ├── note.js ├── secrets.js └── settings.js /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: GH-Pages 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '13.x' 15 | - name: Build and generate application 16 | run: | 17 | npm install 18 | npm run generate:gh 19 | - name: Configure git 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | run: | 23 | git config --local user.email "action@github.com" 24 | git config --local user.name "GitHub Action" 25 | git remote add target-repo https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git 26 | - name: Commit generated files 27 | env: 28 | TARGET_BRANCH: master 29 | run: | 30 | git add ${GITHUB_WORKSPACE}/docs 31 | git commit -m "build version $(git describe --tags)" 32 | git push target-repo HEAD:${TARGET_BRANCH} 33 | -------------------------------------------------------------------------------- /.github/workflows/ipfs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy on IPFS 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '13.x' 15 | - name: Build and generate application 16 | run: | 17 | npm install 18 | npm run generate:ipfs 19 | - name: Set env 20 | run: echo "RELEASE_VERSION=$(git describe --tags)" >> $GITHUB_ENV 21 | - name: Upload to IPFS 22 | uses: aquiladev/ipfs-action@v0.1.5 23 | with: 24 | path: ./docs 25 | service: pinata 26 | pinataPinName: DevNotes${{ env.RELEASE_VERSION }} 27 | pinataKey: ${{ secrets.PINATA_KEY }} 28 | pinataSecret: ${{ secrets.PINATA_SECRET }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | .editorconfig 83 | 84 | # Service worker 85 | static/sw.* 86 | 87 | # Mac OSX 88 | .DS_Store 89 | 90 | # Vim swap files 91 | *.swp 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 rainu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevNotes 2 | 3 | > A progressive web application (PWA) for notes that **is fun**! 4 | 5 | ## DEMO 6 | 7 | Try it out: 8 | * [Github](https://rainu.github.io/dev-notes/) 9 | * [cloudflare-ipfs](https://cloudflare-ipfs.com/ipns/k51qzi5uqu5dlu8cdctlw88zrzf2af1m7oq14tisjwieuc0fm4luadarc6rs00/) 10 | * [IPFS.io](http://gateway.ipfs.io/ipns/k51qzi5uqu5dlu8cdctlw88zrzf2af1m7oq14tisjwieuc0fm4luadarc6rs00/) 11 | 12 | [![types_1](preview/cards_2.png)](https://rainu.github.io/dev-notes/) [![types_2](preview/cards_1.png)](https://rainu.github.io/dev-notes/) [![types_1](preview/cards_0.png)](https://rainu.github.io/dev-notes/) 13 | 14 | [![new](preview/new.png)](https://rainu.github.io/dev-notes/#/notes/new/text) [![backups](preview/backups.png)](https://rainu.github.io/dev-notes/#/backup/) 15 | 16 | ## Features 17 | * **Serverless** note app (no server or database required) 18 | * The whole content will be **stored locally** 19 | * Support for full **encryption** 20 | * Data can be stored encrypted 21 | * Different Note-Types for better user experience: 22 | * Textual 23 | * Markdown 24 | * Reminder 25 | * Image 26 | * Photo 27 | * Record 28 | * Credentials 29 | * Template - text with customizable placeholder 30 | * Filter your notes easily by using **tags** 31 | * Customizable boards 32 | * a board is a user-predefined set of filters 33 | * **Copy** your note-content **to clipboard** 34 | * Multilingual 35 | * english 36 | * german 37 | * Responsive - looks great on mobile and desktop 38 | * 2 different themes: 39 | * dark 40 | * light 41 | * Backup-Mechanisms 42 | * Download/Upload file 43 | * Download/Upload from [AWS S3](https://aws.amazon.com/s3/) 44 | * Download/Upload from [gist](https://gist.github.com/) 45 | * Download/Upload from [dropbox](https://www.dropbox.com/) 46 | * Download/Upload via [WebDAV](http://www.webdav.org/) 47 | * [Nextcloud](https://nextcloud.com/) ¹ 48 | * [ownCloud](https://owncloud.org/) ¹ 49 | * ... 50 | 51 | ¹ These services will work if CORS is correctly configured to return the proper headers. This may not work by default. 52 | 53 | ## How to install application 54 | 55 | ### Desktop 56 | 1. Open Chrome 57 | 1. Navigate to [DevNotes](https://rainu.github.io/dev-notes/) 58 | 1. Tap Add to home screen 59 | 60 | ### Android 61 | 1. Open Chrome 62 | 1. Navigate to [DevNotes](https://rainu.github.io/dev-notes/) 63 | 1. At the top right, click More (three dots) 64 | 1. Click "Install DevNotes ..." 65 | 66 | ## Quickstart for developer 67 | 68 | ``` bash 69 | # install dependencies 70 | $ npm install 71 | 72 | # serve with hot reload at localhost:3000 73 | $ npm run dev 74 | 75 | # generate static project for production 76 | $ npm run generate 77 | ``` 78 | 79 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 80 | 81 | ## Changelog 82 | 83 | Have a look at the [changelog](CHANGELOG.md) 84 | 85 | ## License 86 | 87 | MIT, see [LICENSE](LICENSE). 88 | -------------------------------------------------------------------------------- /assets/variables.scss: -------------------------------------------------------------------------------- 1 | // Ref: https://github.com/nuxt-community/vuetify-module#customvariables 2 | // 3 | // The variables you want to modify 4 | // $font-size-root: 20px; 5 | -------------------------------------------------------------------------------- /common/boardQuery.js: -------------------------------------------------------------------------------- 1 | const qpFilter = "f_" 2 | const qBoard = "b" 3 | const qColor = "c" 4 | const inactiveValue = "0" 5 | const activeValue = "1" 6 | 7 | export const generateBoardQuery = (board) => { 8 | let q = `${qBoard}=${board.title}` 9 | 10 | if(board.color) { 11 | q += `&${qColor}=${board.color.substring(1)}` 12 | } 13 | 14 | if(board.filter.tags) for(let filterTagName of Object.keys(board.filter.tags)) { 15 | let tagValue = board.filter.tags[filterTagName].value ? activeValue : inactiveValue 16 | 17 | q += `&${qpFilter}${filterTagName}=${tagValue}` 18 | } 19 | 20 | return q 21 | } 22 | 23 | export const readBoardQuery = (queryParams) => { 24 | let tags = {} 25 | 26 | for(let queryKey of Object.keys(queryParams)) { 27 | if(queryKey.startsWith(qpFilter)) { 28 | let filterName = queryKey.substr(qpFilter.length) 29 | tags[filterName] = queryParams[queryKey] === activeValue 30 | } 31 | } 32 | 33 | return { 34 | title: queryParams[qBoard], 35 | color: queryParams[qColor] ? `#${queryParams[qColor]}` : null, 36 | tags, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/copy.js: -------------------------------------------------------------------------------- 1 | export const cloneDataObject = (src) => { 2 | return JSON.parse(JSON.stringify(src)) 3 | } 4 | -------------------------------------------------------------------------------- /common/gist.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const newGistClient = (token) => { 4 | 5 | const client = axios.create({ 6 | baseURL: 'https://api.github.com', 7 | headers: { 8 | 'Authorization': 'token ' + token, 9 | } 10 | }); 11 | 12 | return { 13 | 14 | getAll(){ 15 | return client.get('/gists') 16 | .then(resp => resp.data) 17 | }, 18 | 19 | list(username){ 20 | return client.get(`/users/${username}/gists`) 21 | .then(resp => resp.data) 22 | }, 23 | 24 | createNew(isPublic, fileName){ 25 | let data = { 26 | public: isPublic, 27 | files: {}, 28 | description: "Backup files for DevNotes - https://github.com/rainu/dev-notes" 29 | } 30 | data.files[fileName] = { 31 | content: "" 32 | } 33 | 34 | return client.post(`/gists`, data) 35 | .then(resp => resp.data) 36 | }, 37 | 38 | saveFile(gistId, filename, content) { 39 | let data = { 40 | files: {} 41 | } 42 | data.files[filename] = { 43 | content: content, 44 | } 45 | return client.patch(`/gists/${gistId}`, data) 46 | .then(resp => resp.data) 47 | }, 48 | 49 | saveFiles(gistId, files){ 50 | let gistFiles = {} 51 | for(let fileName of Object.keys(files)){ 52 | gistFiles[fileName] = { 53 | content: files[fileName] 54 | } 55 | } 56 | 57 | return client.patch(`/gists/${gistId}`, { 58 | files: gistFiles 59 | }) 60 | .then(resp => resp.data) 61 | }, 62 | 63 | getFile(gistId, filename) { 64 | return client.get(`/gists/${gistId}`) 65 | .then(resp => resp.data) 66 | .then(data => { 67 | if(!data.files[filename]) return "" 68 | if(!data.files[filename].truncated) return data.files[filename].content 69 | 70 | return axios.get(data.files[filename].raw_url) 71 | .then(resp => resp.data) 72 | }) 73 | }, 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /common/importExport.js: -------------------------------------------------------------------------------- 1 | import SimpleCrypto from "simple-crypto-js/build/SimpleCrypto"; 2 | 3 | const migrationSteps = [ 4 | (data) => data, //there is already a version 1 out there 5 | (data) => { 6 | //IN: { notes: {..}, boards: {..} } 7 | //OUT: { notes: {..}, boards: {..}, boardOrder: [..] } 8 | 9 | data.boardOrder = Object.keys(data.boards) 10 | return data 11 | }, 12 | (data) => { 13 | //IN: { notes: {..}, boards: {..}, boardOrder: [..] } 14 | //OUT: { notes: {..}, boards: {..}, boardOrder: [..], noteOrder: [...] } 15 | 16 | data.noteOrder = Object.keys(data.notes) 17 | return data 18 | }, 19 | (data) => { 20 | //IN: { notes: { 'id': { type: 'template', content: { placeholder:[{..}] }} } } 21 | //OUT: { notes: { 'id': { type: 'template', content: { placeholder:[{ type: 'text', ..}] }} } } 22 | 23 | for(let noteId of Object.keys(data.notes).filter(nId => data.notes[nId].type === 'template')){ 24 | data.notes[noteId].content.placeholder = data.notes[noteId].content.placeholder.map(p => { 25 | p.type = 'text' 26 | return p 27 | }) 28 | } 29 | 30 | return data 31 | }, 32 | (data) => { 33 | //IN: { ... } 34 | //OUT: { encryption: { enabled: false, check: null } } 35 | 36 | data.encryption = { 37 | enabled: false, 38 | check: null, 39 | } 40 | 41 | return data 42 | } 43 | ] 44 | 45 | const CURRENT_VERSION = migrationSteps.length 46 | const ENCRYPTION_CHECK = "ENCRYPTION_CHECK" 47 | 48 | export const exportAll = (password, notes, boards, boardOrder, noteOrder) => { 49 | let exportObj = { 50 | version: CURRENT_VERSION, 51 | encryption: { 52 | enabled: false, 53 | }, 54 | notes: {}, 55 | boards: {}, 56 | boardOrder: boardOrder, 57 | noteOrder: noteOrder, 58 | } 59 | 60 | let encrypt = (data) => data 61 | if(password) { 62 | exportObj.encryption.enabled = true 63 | 64 | const cryptoModule = new SimpleCrypto(password) 65 | encrypt = (data) => cryptoModule.encrypt(JSON.stringify(data)) 66 | exportObj.encryption.check = encrypt(ENCRYPTION_CHECK) 67 | } 68 | 69 | for(let note of notes) { 70 | exportObj.notes[note.id] = encrypt(note) 71 | } 72 | 73 | for(let board of boards) { 74 | exportObj.boards[board.id] = encrypt(board) 75 | } 76 | 77 | return exportObj 78 | } 79 | 80 | export const importAll = (json, pwCallback) => { 81 | let parsed = {} 82 | 83 | if(json && json.version) { 84 | parsed = json 85 | } else { 86 | try { 87 | parsed = JSON.parse(json) 88 | } catch (e) { 89 | return Promise.reject(new Error(e)) 90 | } 91 | } 92 | 93 | if(parsed.version !== CURRENT_VERSION) { 94 | parsed = migrate(parsed) 95 | } 96 | 97 | let pwPromise = Promise.resolve(null) 98 | if(parsed.encryption.enabled) { 99 | pwPromise = pwCallback 100 | } 101 | 102 | return pwPromise.then( 103 | (password) => { 104 | return new Promise((resolve, reject) => { 105 | let decrypt = (data) => data 106 | 107 | if(parsed.encryption.enabled) { 108 | try{ 109 | const cryptoModule = new SimpleCrypto(password) 110 | decrypt = (data) => JSON.parse(cryptoModule.decrypt(data)) 111 | 112 | //here we check if the user choose the right password 113 | if(decrypt(parsed.encryption.check) !== ENCRYPTION_CHECK){ 114 | reject(new Error("password is incorrect")) 115 | return 116 | } 117 | }catch(e) { 118 | reject(e) 119 | return 120 | } 121 | } 122 | 123 | let notes = [] 124 | let boards = [] 125 | 126 | if(parsed.notes) for(let noteId of Object.keys(parsed.notes)) notes.push(decrypt(parsed.notes[noteId])) 127 | if(parsed.boards) for(let boardId of Object.keys(parsed.boards)) boards.push(decrypt(parsed.boards[boardId])) 128 | 129 | resolve({ 130 | notes, 131 | boards, 132 | noteOrder: parsed.noteOrder ? parsed.noteOrder : [], 133 | boardOrder: parsed.boardOrder ? parsed.boardOrder : [], 134 | }) 135 | }) 136 | } 137 | ) 138 | } 139 | 140 | const migrate = (data) => { 141 | for(let i = data.version; i < migrationSteps.length; i++) { 142 | console.log(`Import-Migration: do Step ${i + 1} of ${migrationSteps.length}`) 143 | data = migrationSteps[i](data) 144 | data.version = i + 1 145 | } 146 | 147 | return data 148 | } 149 | -------------------------------------------------------------------------------- /components/Audio.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 91 | 92 | 97 | -------------------------------------------------------------------------------- /components/ClickToEdit.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /components/Info.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 111 | 112 | 115 | -------------------------------------------------------------------------------- /components/backup/Encryption.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 138 | 139 | 142 | -------------------------------------------------------------------------------- /components/backup/Unlock.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /components/backup/aws/S3Export.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 115 | 116 | 119 | -------------------------------------------------------------------------------- /components/backup/aws/S3Import.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 150 | 151 | 154 | -------------------------------------------------------------------------------- /components/backup/dropbox/DropboxExport.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /components/backup/dropbox/DropboxImport.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 142 | 143 | 146 | -------------------------------------------------------------------------------- /components/backup/file/File.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /components/backup/file/FileExport.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /components/backup/file/FileImport.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 108 | 109 | 112 | -------------------------------------------------------------------------------- /components/backup/gist/GistExport.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 116 | 117 | 120 | -------------------------------------------------------------------------------- /components/backup/gist/GistImport.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 131 | 132 | 135 | -------------------------------------------------------------------------------- /components/backup/webdav/Webdav.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 140 | 141 | 144 | -------------------------------------------------------------------------------- /components/backup/webdav/WebdavExport.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /components/backup/webdav/WebdavImport.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 142 | 143 | 146 | -------------------------------------------------------------------------------- /components/help/Board.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /components/help/Encryption.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /components/help/FirstSteps.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /components/help/Gist.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /components/help/aws/S3.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /components/note/OverdueNotifications.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /components/note/card/Camera.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 149 | 150 | 167 | -------------------------------------------------------------------------------- /components/note/card/Picture.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 136 | 137 | 151 | -------------------------------------------------------------------------------- /components/note/card/Record.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 149 | 150 | 167 | -------------------------------------------------------------------------------- /components/note/card/TemplateCopyDialogContent.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 134 | 135 | 138 | -------------------------------------------------------------------------------- /components/note/card/TemplateView.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 110 | 111 | 122 | -------------------------------------------------------------------------------- /components/note/card/Text.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 145 | 146 | 163 | -------------------------------------------------------------------------------- /components/note/form/ColorPicker.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /components/note/form/Picture.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 125 | 126 | 129 | -------------------------------------------------------------------------------- /components/note/form/Record.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 162 | 163 | 165 | -------------------------------------------------------------------------------- /components/note/form/TagPicker.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 118 | 119 | 122 | -------------------------------------------------------------------------------- /components/note/form/Text.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 127 | 128 | 131 | -------------------------------------------------------------------------------- /components/note/types.js: -------------------------------------------------------------------------------- 1 | 2 | export default [ 3 | { id: 'text', icon: 'notes' }, 4 | { id: 'reminder', icon: 'alarm' }, 5 | { id: 'camera', icon: 'camera' }, 6 | { id: 'record', icon: 'mic' }, 7 | { id: 'picture', icon: 'photo' }, 8 | { id: 'credentials', icon: 'fingerprint' }, 9 | { id: 'template', icon: 'ballot' }, 10 | ] 11 | -------------------------------------------------------------------------------- /components/settings/DateTime.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 80 | 81 | 84 | -------------------------------------------------------------------------------- /components/settings/Language.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /components/settings/Note.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 115 | 116 | 119 | -------------------------------------------------------------------------------- /components/settings/TagDelimiter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /components/settings/Theme.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/docs/.nojekyll -------------------------------------------------------------------------------- /docs/200.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DevNotes 5 | 6 | 7 |
Loading...
8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/_nuxt/1ed0a8b941c5c55eee7c.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(data){for(var r,n,c=data[0],d=data[1],l=data[2],i=0,h=[];i0||!h(t.scriptURL,this.t)||performance.now()>this.L+6e4?(this.W=t,this.B.removeEventListener("updatefound",this.g)):(this.O=t,this.u.resolve(t)),++this.o,t.addEventListener("statechange",this.l)},w.l=function(t){var e=this,i=t.target,n=i.state,r=i===this.W,u=r?"external":"",a={sw:i,originalEvent:t};!r&&this.p&&(a.isUpdate=!0),this.dispatchEvent(new v(u+n,a)),"installed"===n?this._=setTimeout((function(){"installed"===n&&e.B.waiting===i&&e.dispatchEvent(new v(u+"waiting",a))}),200):"activating"===n&&(clearTimeout(this._),r||this.s.resolve(i))},w.m=function(t){var e=this.O;e===navigator.serviceWorker.controller&&(this.dispatchEvent(new v("controlling",{sw:e,originalEvent:t})),this.h.resolve(e))},w.v=function(t){var e=t.data;this.dispatchEvent(new v("message",{data:e,originalEvent:t}))},l=f,(d=[{key:"active",get:function(){return this.s.promise}},{key:"controlling",get:function(){return this.h.promise}}])&&o(l.prototype,d),f}(function(){function t(){this.D={}}var e=t.prototype;return e.addEventListener=function(t,e){this.T(t).add(e)},e.removeEventListener=function(t,e){this.T(t).delete(e)},e.dispatchEvent=function(t){t.target=this,this.T(t.type).forEach((function(e){return e(t)}))},e.T=function(t){return this.D[t]=this.D[t]||new Set},t}())}}]); -------------------------------------------------------------------------------- /docs/_nuxt/e136b597f3ea1cdf1ec9.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[12],{1117:function(t,e,o){"use strict";o.r(e);o(10),o(8),o(5),o(4),o(6);var r=o(1),n=o(50),c=o(727),d=o(728),m=o(729),v=o(528),f=o(730),l=o(731),N=o(732),y=o(733);function _(object,t){var e=Object.keys(object);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(object);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(object,t).enumerable}))),e.push.apply(e,o)}return e}function O(t){for(var i=1;i 2 | 3 | 4 | DevNotes 5 | 6 | 7 |
Loading...
8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('https://cdn.jsdelivr.net/npm/workbox-cdn@4.3.1/workbox/workbox-sw.js') 2 | 3 | // -------------------------------------------------- 4 | // Configure 5 | // -------------------------------------------------- 6 | 7 | // Set workbox config 8 | workbox.setConfig({ 9 | "debug": false 10 | }) 11 | 12 | // Start controlling any existing clients as soon as it activates 13 | workbox.core.clientsClaim() 14 | 15 | // Skip over the SW waiting lifecycle stage 16 | workbox.core.skipWaiting() 17 | 18 | workbox.precaching.cleanupOutdatedCaches() 19 | 20 | // -------------------------------------------------- 21 | // Precaches 22 | // -------------------------------------------------- 23 | 24 | // Precache assets 25 | 26 | // -------------------------------------------------- 27 | // Runtime Caching 28 | // -------------------------------------------------- 29 | 30 | // Register route handlers for runtimeCaching 31 | workbox.routing.registerRoute(new RegExp('/dev-notes/_nuxt/'), new workbox.strategies.CacheFirst ({}), 'GET') 32 | workbox.routing.registerRoute(new RegExp('/dev-notes/'), new workbox.strategies.NetworkFirst ({}), 'GET') 33 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 91 | 92 | 150 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 62 | 63 | 68 | -------------------------------------------------------------------------------- /locales/index.js: -------------------------------------------------------------------------------- 1 | const localeMappings = { 2 | 'de': require('./de.json'), 3 | 'en': require('./en.json') 4 | } 5 | 6 | export default { 7 | locales: Object.keys(localeMappings), 8 | localeMappings, 9 | defaultLocale: 'en' 10 | } 11 | -------------------------------------------------------------------------------- /middleware/dropbox.js: -------------------------------------------------------------------------------- 1 | export default function ({ store }) { 2 | //this middleware is only responsible for trigger refreshing the dropbox token 3 | 4 | store.dispatch('secrets/refreshDropboxToken') 5 | } 6 | -------------------------------------------------------------------------------- /middleware/encryption.js: -------------------------------------------------------------------------------- 1 | export default function ({ isHMR, app, store, route, params, error, redirect }) { 2 | // if we have crypt enabled but not setup-ed we have to redirect to unlock page! 3 | 4 | if(!store.state.settings.encrypted) return 5 | if(store.state.settings.setupCrypto) return 6 | 7 | if(!route.path.endsWith("/unlock")) { 8 | redirect(`/unlock?from=${route.fullPath}`) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /middleware/recall.js: -------------------------------------------------------------------------------- 1 | export default function (ctx) { 2 | //we need to handle requests which cannot handle fragments (url with #) 3 | //so this middleware will look the real url and check if there is a recall query 4 | //if so, redirect to own recall-page with all the origin data 5 | 6 | if(window.location.search) { 7 | let queryParams = new URLSearchParams(window.location.search.substr(1)) 8 | 9 | let recall = queryParams.has('recall') 10 | if(recall) { 11 | window.location = `${window.location.origin}${window.location.pathname}#/recall/${window.location.search}${window.location.hash}` 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/es5/util/colors' 2 | 3 | // only add `router.base = '//'` if `DEPLOY_ENV` is `GH_PAGES` 4 | const routerBase = { 5 | router: { 6 | base: '/', 7 | middleware: ['recall', 'encryption', 'dropbox'], 8 | mode: 'hash' 9 | } 10 | } 11 | 12 | if(process.env.DEPLOY_ENV === 'GH_PAGES') { 13 | routerBase.router.base = '/dev-notes/' 14 | } else if(process.env.DEPLOY_ENV === 'IPFS') { 15 | routerBase.router.base = '/ipns/k51qzi5uqu5dlu8cdctlw88zrzf2af1m7oq14tisjwieuc0fm4luadarc6rs00/' 16 | } 17 | 18 | export default { 19 | ...routerBase, 20 | 21 | mode: 'spa', 22 | 23 | generate: { 24 | dir: 'docs' 25 | }, 26 | 27 | /* 28 | ** Headers of the page 29 | */ 30 | head: { 31 | title: process.env.npm_package_name || '', 32 | meta: [ 33 | { charset: 'utf-8' }, 34 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 35 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 36 | ], 37 | link: [ 38 | { rel: 'icon', type: 'image/x-icon', href: `${routerBase.router.base}favicon.ico` } 39 | ] 40 | }, 41 | 42 | env: { 43 | appName: process.env.npm_package_name, 44 | routerBase: routerBase.router.base, 45 | version: process.env.npm_package_version, 46 | builtDate: new Date(), 47 | revision: process.env.COMMIT_HASH || 'development', 48 | dropbox: { 49 | clientId: process.env.DROPBOX_CLIENT_ID || 'h89z3y690lntrca', 50 | redirectUrl: ( routerBase.router.base + '?recall=dropbox' ) 51 | }, 52 | }, 53 | 54 | manifest: { 55 | "name": "DevNotes", 56 | "short_name": "DevNotes", 57 | "start_url": routerBase.router.base, 58 | "scope": routerBase.router.base + ".", 59 | "display": "standalone", 60 | "orientation": "portrait-primary", 61 | "background_color": "#303030", 62 | "theme_color": "#424242", 63 | "dir": "ltr", 64 | "lang": "de-DE" 65 | }, 66 | 67 | icon: { 68 | sizes: [16, 32, 120, 144, 152, 192, 384, 512] 69 | }, 70 | 71 | /* 72 | ** Customize the progress-bar color 73 | */ 74 | loading: { color: '#4a566c' }, 75 | /* 76 | ** Global CSS 77 | */ 78 | css: [ 79 | ], 80 | /* 81 | ** Plugins to load before mounting the App 82 | */ 83 | plugins: [ 84 | '~/plugins/localStore', 85 | '~/plugins/i18n', 86 | '~/plugins/globalComponents', 87 | '~/plugins/init', 88 | '~/plugins/style', 89 | '~/plugins/notification', 90 | '~/plugins/reminder', 91 | ], 92 | /* 93 | ** Nuxt.js dev-modules 94 | */ 95 | buildModules: [ 96 | '@nuxtjs/vuetify', 97 | ], 98 | /* 99 | ** Nuxt.js modules 100 | */ 101 | modules: [ 102 | '@nuxtjs/pwa', 103 | ], 104 | /* 105 | ** vuetify module configuration 106 | ** https://github.com/nuxt-community/vuetify-module 107 | */ 108 | vuetify: { 109 | customVariables: ['~/assets/variables.scss'], 110 | theme: { 111 | themes: { 112 | dark: { 113 | primary: '#78c2d2', 114 | secondary: '#3a4253', 115 | accent: '#4a566c', 116 | info: '#88c2be', 117 | warning: '#f1cb81', 118 | error: '#cd5a68', 119 | success: '#9bbf85', 120 | } 121 | } 122 | } 123 | }, 124 | /* 125 | ** Build configuration 126 | */ 127 | build: { 128 | /* 129 | ** You can extend webpack config here 130 | */ 131 | extend (config, ctx) { 132 | //we need the compiler-included build for template-notes! 133 | config.resolve.alias['vue'] = 'vue/dist/vue.common' 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DevNotes", 3 | "version": "0.9.25", 4 | "description": "A progressive web application (PWA) for notes.", 5 | "author": "rainu", 6 | "private": true, 7 | "scripts": { 8 | "dev": "COMMIT_HASH=$(git rev-parse HEAD) nuxt", 9 | "start": "COMMIT_HASH=$(git rev-parse HEAD) nuxt start", 10 | "generate": "COMMIT_HASH=$(git rev-parse HEAD) nuxt generate", 11 | "generate:gh": "DEPLOY_ENV=GH_PAGES COMMIT_HASH=$(git rev-parse HEAD) nuxt generate", 12 | "generate:ipfs": "DEPLOY_ENV=IPFS COMMIT_HASH=$(git rev-parse HEAD) nuxt generate" 13 | }, 14 | "dependencies": { 15 | "@mdi/font": "^4.2.95", 16 | "@nuxtjs/pwa": "^3.0.0-0", 17 | "@nuxtjs/vuetify": "^1.0.0", 18 | "aws-sdk": "^2.547.0", 19 | "axios": "^0.19.0", 20 | "copy-to-clipboard": "^3.2.0", 21 | "date-fns": "^2.6.0", 22 | "dropbox": "^4.0.30", 23 | "fuse.js": "^5.0.7-beta", 24 | "localforage": "^1.7.3", 25 | "markdown-it": "^9.1.0", 26 | "material-design-icons-iconfont": "^5.0.1", 27 | "nuxt": "^2.10.2", 28 | "recordrtc": "^5.5.9", 29 | "simple-crypto-js": "^2.2.0", 30 | "uuid4": "^1.1.4", 31 | "vue-i18n": "^8.14.0", 32 | "vue-markdown": "^2.2.4", 33 | "vue-shortkey": "^3.1.7", 34 | "vue-web-cam": "^1.8.0", 35 | "vuedraggable": "^2.23.1", 36 | "wavesurfer.js": "^3.3.1", 37 | "webdav": "^2.10.0" 38 | }, 39 | "devDependencies": { 40 | "babel-runtime": "^6.26.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pages/backup/index.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 122 | 123 | 126 | -------------------------------------------------------------------------------- /pages/help/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /pages/notes/edit/_id.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 78 | -------------------------------------------------------------------------------- /pages/notes/new/_type.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 111 | 112 | 117 | -------------------------------------------------------------------------------- /pages/recall.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 62 | 63 | 66 | -------------------------------------------------------------------------------- /pages/settings/index.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 80 | 81 | 84 | -------------------------------------------------------------------------------- /pages/unlock/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /plugins/globalComponents.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import shortkey from 'vue-shortkey' 3 | import draggable from 'vuedraggable' 4 | import ClickToEdit from '../components/ClickToEdit' 5 | import Audio from '../components/Audio' 6 | import Recorder from '../components/Recorder' 7 | 8 | Vue.use(shortkey) 9 | Vue.component('draggable', draggable) 10 | Vue.component('ClickToEdit', ClickToEdit) 11 | Vue.component('Audio', Audio) 12 | Vue.component('Recorder', Recorder) 13 | -------------------------------------------------------------------------------- /plugins/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import i18n from '../locales' 4 | 5 | Vue.use(VueI18n) 6 | 7 | export default ({ app, store }) => { 8 | // Set i18n instance on app 9 | // This way we can use it in middleware and pages asyncData/fetch 10 | app.i18n = new VueI18n({ 11 | locale: store.state.settings.locale, 12 | fallbackLocale: i18n.defaultLocale, 13 | messages: i18n.localeMappings 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /plugins/init.js: -------------------------------------------------------------------------------- 1 | //init stuff here 2 | 3 | export default function ({app, store}) { 4 | return store.dispatch('settings/init').then(() => { 5 | //if there is encrypted content -> the middleware should redirect to 6 | //unlock page. And the unlock page should initialise the rest of the store! 7 | 8 | if(!store.state.settings.encrypted) { 9 | return Promise.all([ 10 | store.dispatch('note/init'), 11 | store.dispatch('board/init'), 12 | store.dispatch('secrets/init'), 13 | ]) 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /plugins/localStore.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as LocalStore from '../store/localStore' 3 | 4 | const localStore = LocalStore.newLocalStore() 5 | 6 | Vue.prototype.$localStore = localStore 7 | 8 | //we have to wait for our indexdb(localforage) instances to be ready 9 | export default function ({app, store}) { 10 | app.$localStore = localStore 11 | store.$localStore = localStore 12 | 13 | return localStore.ready() 14 | } 15 | -------------------------------------------------------------------------------- /plugins/notification.js: -------------------------------------------------------------------------------- 1 | export default function ({ isHMR, app, store, route, params, error, redirect }) { 2 | // check if the browser supports notifications 3 | if ("Notification" in window) { 4 | store.commit('settings/setNotificationSupported', true) 5 | } else { 6 | //notification are not supported! 7 | return 8 | } 9 | 10 | if (Notification.permission === "granted"){ 11 | store.commit('settings/setNotificationGranted', true) 12 | } else if (Notification.permission !== 'denied') { 13 | Notification.requestPermission((permission) => { 14 | store.commit('settings/setNotificationGranted', permission === "granted") 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugins/reminder.js: -------------------------------------------------------------------------------- 1 | const TICKER_TIME=60000 //1min 2 | 3 | const activeNotifications = {} 4 | 5 | const checkOverdue = (store, env) => { 6 | store.dispatch('note/checkOverdueNotes') 7 | .then(() => { 8 | if(store.getters['settings/isNotificationEnabled']){ 9 | for(let overdueNote of store.getters['note/getOverdueNotes']){ 10 | if(!activeNotifications[overdueNote.id]) { 11 | activeNotifications[overdueNote.id] = new Notification(overdueNote.title, { 12 | icon: `${env.routerBase}icon.png`, 13 | tag: overdueNote.id, 14 | requireInteraction: true, 15 | }) 16 | activeNotifications[overdueNote.id].onclick = () => { 17 | store.dispatch('note/removeOverdueAlarm', overdueNote.id) 18 | delete activeNotifications[overdueNote.id] 19 | } 20 | } 21 | } 22 | } 23 | }) 24 | 25 | setTimeout(() => { 26 | checkOverdue(store, env); 27 | }, TICKER_TIME); 28 | } 29 | 30 | export default function ({store, env}) { 31 | //check but dont blocking! 32 | setTimeout(() => { 33 | checkOverdue(store, env) 34 | }, 1) 35 | } 36 | -------------------------------------------------------------------------------- /plugins/style.js: -------------------------------------------------------------------------------- 1 | import 'material-design-icons-iconfont/dist/material-design-icons.css' 2 | import '@mdi/font/css/materialdesignicons.min.css' 3 | -------------------------------------------------------------------------------- /preview/backups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/preview/backups.png -------------------------------------------------------------------------------- /preview/cards_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/preview/cards_0.png -------------------------------------------------------------------------------- /preview/cards_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/preview/cards_1.png -------------------------------------------------------------------------------- /preview/cards_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/preview/cards_2.png -------------------------------------------------------------------------------- /preview/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/preview/new.png -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_PATH=$(realpath "$0") 3 | SCRIPT_NAME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")" 4 | SCRIPT_HOME=${SCRIPT_PATH%$SCRIPT_NAME} 5 | 6 | CURRENT_VERSION=$(cat ${SCRIPT_HOME}/package.json | grep '"version":' | cut -d\" -f4) 7 | 8 | git tag -a "v${CURRENT_VERSION}" -m "release version: ${CURRENT_VERSION}" 9 | git push origin "v${CURRENT_VERSION}" 10 | -------------------------------------------------------------------------------- /static/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/static/avatar.jpg -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/static/favicon.ico -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rainu/dev-notes/8aebdcfa3a41215f8ec8441d5a666b4a0d30d79c/static/icon.png -------------------------------------------------------------------------------- /store/board.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const state = () => ({ 4 | boards: [], 5 | boardOrder: [] 6 | }) 7 | 8 | export const mutations = { 9 | loadBoards(state, {boards, order}) { 10 | state.boards.push(...boards) 11 | state.boardOrder.push(...order) 12 | }, 13 | setBoardOrder(state, order) { 14 | if(state.boards.length !== order.length) { 15 | console.log("Try to set invalid board order!") 16 | return false 17 | } 18 | 19 | state.boardOrder = order 20 | }, 21 | addBoard(state, board) { 22 | if(!board || !board.id || !board.title || !board.icon || !board.filter) { 23 | console.log("Try to add invalid board: " + JSON.stringify(board)) 24 | return false 25 | } 26 | 27 | state.boards.push(board) 28 | state.boardOrder.push(board.id) 29 | }, 30 | editBoard(state, board) { 31 | let index = state.boards.findIndex(r => r.id === board.id) 32 | if(index === -1) { 33 | console.log("Try to edit unknown board: " + JSON.stringify(board)) 34 | return false 35 | } 36 | 37 | Vue.set(state.boards, index, board) 38 | }, 39 | deleteBoard(state, boardId) { 40 | let index = state.boards.findIndex(r => r.id === boardId) 41 | state.boards.splice(index, 1) 42 | 43 | index = state.boardOrder.findIndex(bId => bId === boardId) 44 | state.boardOrder.splice(index, 1) 45 | }, 46 | clearBoards(state){ 47 | state.boards = [] 48 | state.boardOrder = [] 49 | }, 50 | } 51 | 52 | export const getters = { 53 | getBoardById(state, id) { 54 | return state.boards.filter(n => n.id === id)[0] 55 | }, 56 | getAvailableTags(state) { 57 | let tagMap = {} 58 | for(let board of state.boards) { 59 | if(board.filter.tags) { 60 | for(let curTag of Object.keys(board.filter.tags)) { 61 | tagMap[curTag] = true 62 | } 63 | } 64 | } 65 | 66 | return Object.keys(tagMap).sort() 67 | }, 68 | getUsedColors(state) { 69 | let colors = {} 70 | for(let board of state.boards) { 71 | if(board.color) { 72 | colors[board.color.toUpperCase()] = "" 73 | } 74 | } 75 | 76 | return Object.keys(colors).sort() 77 | } 78 | } 79 | 80 | export const actions = { 81 | init(ctx) { 82 | return Promise.all([this.$localStore.getBoards(), this.$localStore.getBoardOrder()]) 83 | .then(([boards, order]) => ctx.commit('loadBoards', {boards, order})) 84 | }, 85 | 86 | setBoardOrder(ctx, order) { 87 | ctx.commit('setBoardOrder', order) 88 | return this.$localStore.setBoardOrder(ctx.state.boardOrder) 89 | }, 90 | addBoard(ctx, board) { 91 | ctx.commit('addBoard', board) 92 | 93 | return Promise.all([ 94 | this.$localStore.setBoard(board), 95 | this.$localStore.setBoardOrder(ctx.state.boardOrder) 96 | ]) 97 | }, 98 | editBoard(ctx, board) { 99 | ctx.commit('editBoard', board) 100 | return this.$localStore.setBoard(board) 101 | }, 102 | deleteBoard(ctx, boardId) { 103 | ctx.commit('deleteBoard', boardId) 104 | 105 | return Promise.all([ 106 | this.$localStore.removeBoard(boardId), 107 | this.$localStore.setBoardOrder(ctx.state.boardOrder) 108 | ]) 109 | }, 110 | clearBoards(ctx){ 111 | ctx.commit('clearBoards') 112 | return this.$localStore.clearBoards() 113 | }, 114 | } 115 | 116 | export default { 117 | namespaced: true, 118 | state, 119 | mutations, 120 | getters, 121 | actions 122 | } 123 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vuex from "vuex" 2 | import settings from './settings' 3 | import note from './note' 4 | import board from './board' 5 | import secrets from './secrets' 6 | 7 | const createStore = () => { 8 | return new Vuex.Store({ 9 | modules: { 10 | settings, note, board, secrets 11 | }, 12 | }) 13 | } 14 | 15 | export default createStore; 16 | -------------------------------------------------------------------------------- /store/localStoreMigrations.js: -------------------------------------------------------------------------------- 1 | 2 | export default [ 3 | (store, crypto) => new Promise((resolve, reject) => { 4 | //check if there are some boards 5 | store.boards.keys() 6 | .then(keys => keys.map(id => store.boards.getItem(id))) 7 | .then(promises => Promise.all(promises)) 8 | .then(boards => { 9 | //create a entry which contains the board order 10 | return store.boards.setItem('__order', boards.map(b => b.id)) 11 | }) 12 | .then(resolve) 13 | .catch(reject) 14 | }), 15 | (store, crypto) => new Promise((resolve, reject) => { 16 | //check if there are some notes 17 | store.notes.keys() 18 | .then(keys => keys.map(id => store.notes.getItem(id))) 19 | .then(promises => Promise.all(promises)) 20 | .then(encryptedNotes => encryptedNotes.map(n => crypto.decrypt(n))) 21 | .then(notes => { 22 | //create a entry which contains the note order 23 | return store.notes.setItem('__order', notes.map(n => n.id)) 24 | }) 25 | .then(resolve) 26 | .catch(reject) 27 | }), 28 | (store, crypto) => new Promise((resolve, reject) => { 29 | //check if there are some notes 30 | store.notes.keys() 31 | .then(keys => keys.filter(k => k !== '__order')) 32 | .then(keys => keys.map(id => store.notes.getItem(id))) 33 | .then(promises => Promise.all(promises)) 34 | .then(encryptedNotes => encryptedNotes.map(n => crypto.decrypt(n))) 35 | .then(notes => notes.filter(n => n.type === 'template')) 36 | .then(notes => notes.map(n => { 37 | n.content.placeholder = n.content.placeholder.map(p => { 38 | p.type = 'text' 39 | return p 40 | }) 41 | return n 42 | })) 43 | .then(notes => { 44 | //save the new notes 45 | return notes.forEach(n => store.notes.setItem(n.id, crypto.encrypt(n))) 46 | }) 47 | .then(resolve) 48 | .catch(reject) 49 | }), 50 | ] 51 | -------------------------------------------------------------------------------- /store/secrets.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import {Dropbox} from "dropbox"; 3 | 4 | export const state = () => ({ 5 | secrets: { 6 | 'dropbox.user': {} 7 | } 8 | }) 9 | 10 | export const mutations = { 11 | applySecret(state, {key, value}){ 12 | Vue.set(state.secrets, key, value) 13 | }, 14 | setSecret(state, {key, value}){ 15 | Vue.set(state.secrets, key, value) 16 | }, 17 | removeSecret(state, key){ 18 | Vue.delete(state.secrets, key) 19 | }, 20 | setDropboxUser(state, user){ 21 | Vue.set(state.secrets, 'dropbox.user', user) 22 | } 23 | } 24 | 25 | export const getters = { 26 | getBackupPassword(state) { 27 | return state.secrets['backup.password'] 28 | }, 29 | getAWSS3Settings(state) { 30 | return state.secrets['aws.s3'] 31 | }, 32 | getGistSettings(state) { 33 | return state.secrets['gist'] 34 | }, 35 | getWebdavSettings(state) { 36 | return state.secrets['webdav'] 37 | }, 38 | getDropboxAuth(state) { 39 | return state.secrets['dropbox.auth'] 40 | }, 41 | getDropboxUser(state) { 42 | return state.secrets['dropbox.user'] 43 | }, 44 | getDropboxSettings(state) { 45 | return state.secrets['dropbox'] 46 | }, 47 | } 48 | 49 | export const actions = { 50 | init(ctx) { 51 | return this.$localStore.getSecrets() 52 | .then(secrets => { 53 | if(!secrets) return; 54 | 55 | return ctx.dispatch('applySecrets', secrets) 56 | }) 57 | }, 58 | 59 | applySecrets(store, secrets) { 60 | for(let secret of secrets) { 61 | store.commit('applySecret', secret) 62 | } 63 | }, 64 | 65 | setSecret(store, {key, value}){ 66 | store.commit('setSecret', {key, value}) 67 | return this.$localStore.setSecret(key, value) 68 | }, 69 | removeSecret(store, key){ 70 | store.commit('removeSecret', key) 71 | return this.$localStore.removeSecret(key) 72 | }, 73 | 74 | setBackupPassword(store, password) { 75 | return store.dispatch('setSecret', {key: 'backup.password', value: password}) 76 | }, 77 | removeBackupPassword(store) { 78 | return store.dispatch('removeSecret', 'backup.password') 79 | }, 80 | 81 | setAWSS3Settings(store, settings) { 82 | return store.dispatch('setSecret', {key: 'aws.s3', value: settings}) 83 | }, 84 | 85 | setGistSettings(store, settings) { 86 | return store.dispatch('setSecret', {key: 'gist', value: settings}) 87 | }, 88 | 89 | setWebdavSettings(store, settings) { 90 | return store.dispatch('setSecret', {key: 'webdav', value: settings}) 91 | }, 92 | 93 | setDropboxAuth(store, settings) { 94 | return store.dispatch('setSecret', {key: 'dropbox.auth', value: settings}) 95 | }, 96 | setDropboxSettings(store, settings) { 97 | return store.dispatch('setSecret', {key: 'dropbox', value: settings}) 98 | }, 99 | removeDropboxAuth(store){ 100 | return Promise.all([ 101 | store.dispatch('setDropboxAuth', {}), 102 | store.commit('setDropboxUser', {}) 103 | ]) 104 | }, 105 | refreshDropboxToken(store){ 106 | let dpxAuth = store.getters.getDropboxAuth 107 | if(dpxAuth && dpxAuth.access_token) { 108 | let dbx = new Dropbox({ 109 | fetch: fetch, 110 | accessToken: dpxAuth.access_token, 111 | clientId: process.env.dropbox.clientId, 112 | }) 113 | 114 | return dbx.usersGetCurrentAccount() 115 | .then(user => store.commit('setDropboxUser', user)) 116 | .catch(() => store.dispatch('removeDropboxAuth')) 117 | } 118 | } 119 | } 120 | 121 | export default { 122 | namespaced: true, 123 | state, 124 | mutations, 125 | getters, 126 | actions 127 | } 128 | --------------------------------------------------------------------------------