Added privacy policy
Updated marketing Added some keyboard shortcuts Added settings page Added accent theming Added beta 2FA
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -110,7 +110,7 @@
|
||||
</h2>
|
||||
|
||||
<h3 class="subtext">
|
||||
A free, secure Note App<i class="i cursor icon blinking"></i>
|
||||
A free, secure, online note taking application<i class="i cursor icon blinking"></i>
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
@@ -210,18 +210,7 @@
|
||||
<i class="bottom left corner blue pen icon"></i>
|
||||
</i>
|
||||
Document Editing Tools
|
||||
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables Color Text, Color Background and more.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
<i class="grey tags icon"></i>
|
||||
<i class="bottom left corner purple plus icon"></i>
|
||||
</i>
|
||||
Tag Notes
|
||||
<div class="sub header">Easily add and edit tags on notes then sort notes by tag.</div>
|
||||
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
@@ -236,6 +225,17 @@
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
<i class="grey tags icon"></i>
|
||||
<i class="bottom left corner purple plus icon"></i>
|
||||
</i>
|
||||
Tag Notes
|
||||
<div class="sub header">Easily add and edit tags on notes then sort notes by tag.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
@@ -254,7 +254,7 @@
|
||||
<i class="bottom left corner share icon"></i>
|
||||
</i>
|
||||
Share Encrypted Notes
|
||||
<div class="sub header">Share notes with friends without compromising security. And its easy to disable sharing.</div>
|
||||
<div class="sub header">Share encrypted notes with friends without compromising security.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
@@ -290,6 +290,17 @@
|
||||
<div class="sub header">Add text to Images and links than can be searched.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
<i class="grey tv icon"></i>
|
||||
<i class="bottom left corner blue mobile icon"></i>
|
||||
</i>
|
||||
Two Factor Authentication
|
||||
<div class="sub header">Enable two factor authentication for added peace of mind.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
</div>
|
||||
<div class="four wide column">
|
||||
|
@@ -25,7 +25,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<p>You will remain logged in on this browser, for 30 days or until you log out.</p>
|
||||
<p>You will remain logged in on this browser, for 20 days or until you log out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -31,7 +31,7 @@
|
||||
<i v-if="titleView" class="th icon"></i>
|
||||
<i v-if="!titleView" class="bars icon"></i>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="eight wide column" v-if="showClear">
|
||||
@@ -117,7 +117,7 @@
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:title-view="titleView"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:currently-open="activeNoteId1 == note.id"
|
||||
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
|
||||
/>
|
||||
</div>
|
||||
@@ -132,13 +132,12 @@
|
||||
</div>
|
||||
|
||||
|
||||
<input-notes
|
||||
<note-input-panel
|
||||
v-if="activeNoteId1 != null"
|
||||
:key="'active_note_'+activeNoteId1"
|
||||
:key="activeNoteId1"
|
||||
:noteid="activeNoteId1"
|
||||
:position="activeNote1Position"
|
||||
:url-data="$route.params"
|
||||
ref="note1" />
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -151,7 +150,7 @@
|
||||
name: 'SearchBar',
|
||||
components: {
|
||||
|
||||
'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
|
||||
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
|
||||
|
||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||
// 'fast-filters': require('@/components/FastFilters.vue').default,
|
||||
@@ -247,7 +246,7 @@
|
||||
|
||||
//Do not update note if its open
|
||||
if(this.activeNoteId1 != noteId){
|
||||
this.updateSingleNote(noteId, false)
|
||||
this.updateSingleNote(noteId, true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -264,10 +263,18 @@
|
||||
//Close note event
|
||||
this.$bus.$on('close_active_note', ({noteId, modified}) => {
|
||||
|
||||
this.closeNote()
|
||||
if(modified){
|
||||
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
|
||||
}
|
||||
|
||||
//A note has been closed
|
||||
if(this.$route.fullPath != '/notes'){
|
||||
this.$router.push('/notes')
|
||||
}
|
||||
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
//Focus and animate if modified
|
||||
this.updateSingleNote(parseInt(noteId), modified)
|
||||
this.updateSingleNote(noteId, modified)
|
||||
})
|
||||
|
||||
this.$bus.$on('note_deleted', (noteId) => {
|
||||
@@ -312,35 +319,25 @@
|
||||
})
|
||||
})
|
||||
|
||||
//New note button pushes open note event
|
||||
this.$bus.$on('open_note', noteId => {
|
||||
this.openNote(noteId)
|
||||
})
|
||||
|
||||
//Reload page content
|
||||
//Reload page content - don't trigger if load is in progress
|
||||
this.$bus.$on('note_reload', () => {
|
||||
this.reset()
|
||||
if(!this.loadingInProgress){
|
||||
this.reset()
|
||||
}
|
||||
})
|
||||
|
||||
//Mount notes on load if note ID is set
|
||||
if(this.$route.params && this.$route.params.id){
|
||||
const id = this.$route.params.id
|
||||
this.openNote(id)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', this.onScroll)
|
||||
|
||||
//Close notes when back button is pressed
|
||||
window.addEventListener('hashchange', this.hashChangeAction)
|
||||
// window.addEventListener('hashchange', this.hashChangeAction)
|
||||
|
||||
//update note on visibility change
|
||||
document.addEventListener('visibilitychange', this.visibiltyChangeAction);
|
||||
// document.addEventListener('visibilitychange', this.visibiltyChangeAction);
|
||||
|
||||
},
|
||||
beforeDestroy(){
|
||||
window.removeEventListener('scroll', this.onScroll)
|
||||
window.removeEventListener('hashchange', this.hashChangeAction)
|
||||
document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
|
||||
// document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
|
||||
|
||||
this.$bus.$off('note_reload')
|
||||
this.$bus.$off('close_active_note')
|
||||
@@ -348,7 +345,6 @@
|
||||
this.$bus.$off('note_deleted')
|
||||
this.$bus.$off('update_fast_filters')
|
||||
this.$bus.$off('update_search_term')
|
||||
this.$bus.$off('open_note')
|
||||
|
||||
//We want to remove event listeners, but something here is messing them up and preventing ALL event listeners from working
|
||||
// this.$off() // Remove all event listeners
|
||||
@@ -356,11 +352,20 @@
|
||||
},
|
||||
mounted() {
|
||||
|
||||
//Open note on load if ID is set
|
||||
if(this.$route.params.id > 1){
|
||||
this.activeNoteId1 = this.$route.params.id
|
||||
}
|
||||
|
||||
//Loads initial batch and tags
|
||||
this.reset()
|
||||
|
||||
// this.search(true, this.firstLoadBatchSize, false)
|
||||
// .then( r => this.search(false, this.batchSize, true))
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id': function(id){
|
||||
//Open note on ID, null id will close note
|
||||
this.activeNoteId1 = id
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleTitleView(){
|
||||
@@ -382,17 +387,9 @@
|
||||
if(nodeClick == 'A'){ return }
|
||||
}
|
||||
|
||||
//1 note open
|
||||
if(this.activeNoteId1 == null){
|
||||
this.activeNoteId1 = id
|
||||
this.activeNote1Position = 0 //Middel of page
|
||||
this.$router.push('/notes/open/'+this.activeNoteId1).catch(e => { console.log(e) })
|
||||
return
|
||||
}
|
||||
},
|
||||
closeNote(position){
|
||||
this.activeNoteId1 = null
|
||||
this.$router.push('/notes')
|
||||
//Open note if a link was not clicked
|
||||
this.$router.push('/notes/open/'+id)
|
||||
return
|
||||
},
|
||||
toggleTagFilter(tagId){
|
||||
|
||||
@@ -429,34 +426,6 @@
|
||||
|
||||
return
|
||||
},
|
||||
//Try to close notes on URL hash change /notes/open/123 to /notes - parse 123, close note id 123
|
||||
hashChangeAction(event){
|
||||
|
||||
//Clean up path of hash change
|
||||
let path = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.hash
|
||||
let newPath = event.newURL.replace(path,'')
|
||||
let oldPath = event.oldURL.replace(path,'')
|
||||
|
||||
// console.log(this.$route.params)
|
||||
// console.log(this.$router)
|
||||
|
||||
//Open note if user goes forward to a note id
|
||||
if(this.$route.params && this.$route.params.id){
|
||||
this.openNote(this.$route.params.id)
|
||||
}
|
||||
|
||||
//If we go from open note ID to no note ID, close the note
|
||||
if(newPath == '' && oldPath.indexOf('/open/') != -1){
|
||||
//Pull note ID out of URL
|
||||
const noteIdToClose = oldPath.split('/').pop()
|
||||
|
||||
// console.log(noteIdToClose)
|
||||
|
||||
if(this.$refs.note1 && this.$refs.note1.currentNoteId == noteIdToClose){
|
||||
// this.$refs.note1.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
visibiltyChangeAction(event){
|
||||
|
||||
//Fuck this shit, just use web sockets
|
||||
@@ -484,8 +453,11 @@
|
||||
let note = null
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
|
||||
note = this.$refs['note-'+noteId][0].note
|
||||
//Show that note is working on updating
|
||||
this.$refs['note-'+noteId][0].showWorking = true
|
||||
}
|
||||
|
||||
|
||||
//Lookup one note using passed in ID
|
||||
const postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
@@ -508,18 +480,13 @@
|
||||
|
||||
if(note && newNote){
|
||||
|
||||
//Don't move notes that were not changed
|
||||
if(note.updated == newNote.updated){
|
||||
// return
|
||||
}
|
||||
|
||||
//go through each prop and update it with new values
|
||||
Object.keys(newNote).forEach(prop => {
|
||||
note[prop] = newNote[prop]
|
||||
})
|
||||
|
||||
//Push new note to front if its modified
|
||||
if(focuseAndAnimate){
|
||||
//Push new note to front if its modified or we want it to
|
||||
if( focuseAndAnimate || note.updated != newNote.updated ){
|
||||
|
||||
// Find note, in section, move to front
|
||||
Object.keys(this.noteSections).forEach( key => {
|
||||
@@ -536,6 +503,7 @@
|
||||
this.$nextTick( () => {
|
||||
//Trigger close animation on note
|
||||
this.$refs['note-'+noteId][0].justClosed()
|
||||
this.$refs['note-'+noteId][0].showWorking = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -547,9 +515,14 @@
|
||||
//Trigger close animation on note
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
|
||||
this.$refs['note-'+noteId][0].justClosed()
|
||||
this.$refs['note-'+noteId][0].showWorking = false
|
||||
}
|
||||
}
|
||||
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
|
||||
this.$refs['note-'+noteId][0].showWorking = false
|
||||
}
|
||||
|
||||
//Trigger section rebuild
|
||||
this.rebuildNoteCategorise()
|
||||
})
|
||||
@@ -610,7 +583,6 @@
|
||||
|
||||
//Perform search - or die
|
||||
this.loadingInProgress = true
|
||||
// console.time('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
axios.post('/api/note/search', postData)
|
||||
.then(response => {
|
||||
|
||||
@@ -735,7 +707,6 @@
|
||||
this.fastFilters = {}
|
||||
this.foundAttachments = [] //Remove all attachments
|
||||
|
||||
this.$bus.$emit('reset_fast_filters')
|
||||
this.updateFastFilters(5) //This loads notes
|
||||
|
||||
},
|
||||
|
@@ -14,6 +14,11 @@
|
||||
|
||||
<div class="sixteen wide middle aligned column" v-if="quickNoteId > 0">
|
||||
|
||||
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui compact basic button">
|
||||
<i class="file outline icon"></i>
|
||||
Open Note
|
||||
</div>
|
||||
|
||||
<div class="ui compact basic right floated button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
|
||||
<i class="sync alternate reload icon"></i>
|
||||
New Scratch Pad
|
||||
@@ -46,10 +51,6 @@
|
||||
<i class="folder open outline icon"></i>
|
||||
Files
|
||||
</div>
|
||||
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui right floated basic button">
|
||||
<i class="file outline icon"></i>
|
||||
Open Note
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
182
client/src/pages/SettingsPage.vue
Normal file
182
client/src/pages/SettingsPage.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="ui grid">
|
||||
<div class="row"></div>
|
||||
<!-- spacer column -->
|
||||
<div class="sixteen wide column">
|
||||
<h2 class="ui dividing header">
|
||||
<i class="cog icon"></i>
|
||||
Settings
|
||||
</h2>
|
||||
|
||||
<!-- Accent Color -->
|
||||
<div class="ui segment">
|
||||
<div class="ui grid">
|
||||
<div class="sixteen wide column">
|
||||
<h3 class="ui header">
|
||||
Accent Color
|
||||
</h3>
|
||||
<div
|
||||
v-for="color in themeColors"
|
||||
class="ui compact basic button"
|
||||
:style="`background: linear-gradient(0deg, ${color} 4%, rgba(0,0,0,0) 5%);`"
|
||||
v-on:click="setAccentColor(color)">
|
||||
<logo style="width: 33px; height: auto;" :color="color" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable Two Factor -->
|
||||
<div class="ui segment">
|
||||
<h3>Two Factor Authentication</h3>
|
||||
<div class="ui stackable grid">
|
||||
<div class="four wide column">
|
||||
<p>1. Enter Password and get QR</p>
|
||||
<div class="ui fluid action input">
|
||||
<input type="password" placeholder="Current Password" v-model="password">
|
||||
|
||||
<div v-if="password.length == 0" class="ui disabled button">
|
||||
Get QR code
|
||||
</div>
|
||||
<div v-if="password.length > 0" class="ui green button" v-on:click="getQrCode()">
|
||||
Get QR code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="five wide column">
|
||||
<p>2. Scan QR Code</p>
|
||||
<p v-if="qrCode == ''">QR Code Will appear here.</p>
|
||||
<img v-if="qrCode != ''" :src="qrCode" alt="QR Code">
|
||||
</div>
|
||||
<div class="four wide column">
|
||||
<p>3. Verify with code</p>
|
||||
<div class="ui input" v-if="qrCode != ''">
|
||||
<input type="text" placeholder="Verification Code" v-model="verificationToken" v-on:keyup.enter="verifyQrCode()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- change password -->
|
||||
<div class="ui segment">
|
||||
<h3>Change Password</h3>
|
||||
<div class="ui grid">
|
||||
<div class="five wide column">
|
||||
<label>Current Password</label>
|
||||
<div class="ui fluid input">
|
||||
<input type="text" placeholder="Current Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="five wide column">
|
||||
<label>New Password</label>
|
||||
<div class="ui fluid input">
|
||||
<input type="text" placeholder="New Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<label>Rereat New Password</label>
|
||||
<div class="ui fluid action input">
|
||||
<input type="text" placeholder="Repeat Password">
|
||||
<div class="ui green button">
|
||||
Change it!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- log out -->
|
||||
<div class="ui segment">
|
||||
<div class="ui grid">
|
||||
<div class="sixteen wide column">
|
||||
<h3>Log Out</h3>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui button" v-on:click="logout()">
|
||||
Log Out this browser
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui button">
|
||||
Log Out all other browsers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'SettingsPage',
|
||||
components: {
|
||||
'logo':require('@/components/LogoComponent.vue').default,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
password: '',
|
||||
qrCode: '',
|
||||
verificationToken: '',
|
||||
|
||||
themeColors: [
|
||||
'#21BA45', //Green
|
||||
'#b5cc18', //Lime
|
||||
'#00b5ad', //Teal
|
||||
'#2185d0', //Blue
|
||||
'#7128b9', //Violet
|
||||
'#a333c8', // "Purple"
|
||||
'#e03997', //Pink
|
||||
'#db2828', //Red
|
||||
'#f2711c', //Orange
|
||||
'#fbbd08', //Yellow
|
||||
'#767676', //Grey
|
||||
'#303030', //Black-almost
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
this.$store.commit('destroyLoginToken')
|
||||
this.$router.push('/')
|
||||
axios.post('/api/user/logout')
|
||||
setTimeout(() => {
|
||||
this.$bus.$emit('notification', 'Logged Out')
|
||||
}, 200)
|
||||
},
|
||||
setAccentColor(color){
|
||||
|
||||
let root = document.documentElement
|
||||
root.style.setProperty('--main-accent', color)
|
||||
localStorage.setItem('main-accent', color)
|
||||
|
||||
if(!color || color == '#21BA45'){
|
||||
localStorage.removeItem('main-accent')
|
||||
}
|
||||
},
|
||||
getQrCode(){
|
||||
|
||||
axios.post('/api/user/twofactorsetup', { password:this.password })
|
||||
.then(({data}) => {
|
||||
this.qrCode = data
|
||||
})
|
||||
},
|
||||
verifyQrCode(){
|
||||
|
||||
axios.post('/api/user/verifytwofactorsetuptoken', { password:this.password, token: this.verificationToken })
|
||||
.then(({data}) => {
|
||||
if(data == true){
|
||||
//Two FA is set up
|
||||
} else {
|
||||
//It failed
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Reference in New Issue
Block a user