* Delete Crunch Menu Component
* Disabled Quick Note * Note crunches over when menu is open * Added a cool loader * Remomoved locked notes * Added full note encryption * Added encrypted search index * Added encrypted shared notes * Made search bar have a clear and search button * Tags only loade when clicking on the tags menu * Tweaked home page to be a little more sane * built out some gigantic test cases * simplified a lot of things to make entire app easier to maintain
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
<!-- change class to .master-note-edit to have it popup on the screen -->
|
||||
<div
|
||||
id="InputNotes"
|
||||
class="master-note-edit"
|
||||
class="master-note-edit full-focus"
|
||||
@keyup.esc="close()"
|
||||
:class="[{ 'full-focus':(fullFocusEditor) }, 'position-'+position ]"
|
||||
:class="[ 'position-'+position ]"
|
||||
>
|
||||
|
||||
<!-- Main Menu -->
|
||||
@@ -100,6 +100,8 @@
|
||||
<div class="edit-button" v-on:click="openEditAttachment" data-tooltip="Files" data-position="bottom center" data-inverted>
|
||||
<i class="folder icon"></i>
|
||||
</div>
|
||||
|
||||
<span>{{ statusText }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -110,19 +112,20 @@
|
||||
|
||||
<div class="bottom-edit-menu"></div>
|
||||
|
||||
<div class="input-container-wrapper" :class="{ 'size-down':(sizeDown == true)}" >
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div v-if="loading" class="loading-note">
|
||||
<div class="loading-text">
|
||||
Decrypting Note &
|
||||
{{loadingMessage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true)}" >
|
||||
|
||||
<!-- Squire box grows -->
|
||||
<div class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<transition name="fade">
|
||||
<div v-if="loading || forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
<div class="loading-text">
|
||||
<loading-icon :message="loadingMessage" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Title input area -->
|
||||
<textarea
|
||||
ref="titleTextarea"
|
||||
@@ -134,45 +137,8 @@
|
||||
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input">
|
||||
</textarea>
|
||||
|
||||
<!-- Squire Box - only appears if decrypted -->
|
||||
<div v-show="isDecrypted" id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
|
||||
<!-- Decrypt note prompt -->
|
||||
<div v-if="isEncrypted && !isDecrypted" class="ui basic padded segment">
|
||||
<div class="ui raised segment">
|
||||
<h3 class="ui center aligned icon header">
|
||||
<i class="green lock alternate icon"></i>
|
||||
|
||||
<span v-if="!lockedOut">
|
||||
This note is encrypted and requires a password to be opened.
|
||||
</span>
|
||||
|
||||
<!-- note is locked for 5 minutes -->
|
||||
<span v-if="lockedOut">
|
||||
To many unlock attempts. Note is locked for 5 minutes.
|
||||
</span>
|
||||
</h3>
|
||||
<!-- Decrypt note -->
|
||||
<div class="ui form" v-if="!lockedOut">
|
||||
<h5 class="ui horizontal divider header" v-if="passwordHint && passwordHint.length > 0">
|
||||
Hint: {{ passwordHint }}
|
||||
</h5>
|
||||
<div class="field">
|
||||
<input :name="`randomThing-${noteid}`" :id="`yupper-${noteid}`"type="password" v-model="password" placeholder="Note Password" v-on:keyup.enter="decryptNote" autofocus ref="decryptNotePrompt">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div v-on:click="decryptNote" class="ui green fluid button" v-if="password.length >= 3">
|
||||
Unlock Note
|
||||
</div>
|
||||
<div class="ui disabled fluid button" v-if="password.length < 3">
|
||||
Unlock Note
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Squire Box -->
|
||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -287,11 +253,13 @@
|
||||
'share-note-component': () => import('@/components/ShareNoteComponent.vue'),
|
||||
|
||||
'color-tooltip':require('@/components/TextColorTooltipComponent.vue').default,
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default,
|
||||
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
loading: true,
|
||||
forceShowLoading: true,
|
||||
loadingMessage: 'Loading Note',
|
||||
currentNoteId: 0,
|
||||
modified: false,
|
||||
@@ -315,13 +283,8 @@
|
||||
styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
|
||||
|
||||
sizeDown: false, //Used to animate close state
|
||||
|
||||
colorPickerLocation: null,
|
||||
|
||||
fullFocusEditor: true, //Initialized editor instance
|
||||
|
||||
//Settings vars
|
||||
showAllSettings: true,
|
||||
lastVisibilityState: null,
|
||||
|
||||
//All the squire settings
|
||||
@@ -329,23 +292,12 @@
|
||||
// pastFocusedNode: null,
|
||||
usersOnNote: 0,
|
||||
|
||||
sideMenuOpen: false,
|
||||
tags: false,
|
||||
colors: false,
|
||||
images: false,
|
||||
options: false,
|
||||
colorpicker: false,
|
||||
|
||||
//Encryption options
|
||||
passwordHint: '',
|
||||
password: '', //Field Variables, only for form
|
||||
passwordConfirm: '', //Only a form variable
|
||||
hashedPass: '', //sha-256 password hash, sends to server for decryption
|
||||
isEncrypted: false,
|
||||
isDecrypted: false,
|
||||
passwordprotect: false,
|
||||
decryptAttempts: 0,
|
||||
lockedOut: false,
|
||||
autoLockTimeout: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -372,9 +324,9 @@
|
||||
}
|
||||
|
||||
//Reset all note menus on URL change
|
||||
this.sideMenuOpen = false
|
||||
this.colors = false
|
||||
this.tags = false
|
||||
this.passwordprotect = false
|
||||
this.options = false
|
||||
this.images = false
|
||||
|
||||
@@ -382,7 +334,7 @@
|
||||
if(newVal.openMenu){
|
||||
//Only modify menu boolean if its defined
|
||||
if(typeof this[newVal.openMenu] == 'boolean'){
|
||||
|
||||
this.sideMenuOpen = true
|
||||
this[newVal.openMenu] = true
|
||||
}
|
||||
}
|
||||
@@ -399,22 +351,23 @@
|
||||
},
|
||||
beforeDestroy(){
|
||||
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.hashedPass = ''
|
||||
clearTimeout(this.autoLockTimeout)
|
||||
|
||||
// this.$io.emit('leave_room', this.rawTextId)
|
||||
|
||||
this.$bus.$off('new_file_upload')
|
||||
|
||||
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
this.editor.destroy()
|
||||
if(this.editor){
|
||||
this.editor.destroy()
|
||||
}
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
setTimeout(()=>{
|
||||
this.forceShowLoading = false
|
||||
}, 500)
|
||||
|
||||
document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -429,6 +382,9 @@
|
||||
this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
|
||||
this.setText(this.noteText)
|
||||
|
||||
this.lastNoteHash = this.hashString(this.getText())
|
||||
console.log('hash on load', this.lastNoteHash)
|
||||
|
||||
//focus on open, not on mobile, thats annoying
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
// this.editor.focus()
|
||||
@@ -826,12 +782,12 @@
|
||||
|
||||
//Component is activated with NoteId in place, lookup text with associated ID
|
||||
if(this.$store.getters.getLoggedIn){
|
||||
axios.post('/api/note/get', { 'noteId': this.noteid, 'password':this.hashedPass })
|
||||
axios.post('/api/note/get', { 'noteId': this.noteid })
|
||||
.then(response => {
|
||||
|
||||
//Block notes you don't have access to from opening
|
||||
if(response.data === false){
|
||||
this.$bus.$emit('notification', 'Invalid Note')
|
||||
this.$bus.$emit('notification', 'Error opening Note')
|
||||
this.close(true)
|
||||
return
|
||||
}
|
||||
@@ -840,7 +796,6 @@
|
||||
this.currentNoteId = this.noteid
|
||||
this.rawTextId = response.data.rawTextId
|
||||
this.shareUsername = response.data.shareUsername
|
||||
this.passwordHint = response.data.password_hint
|
||||
|
||||
this.created = response.data.created
|
||||
this.updated = response.data.updated
|
||||
@@ -852,7 +807,6 @@
|
||||
this.noteText = response.data.text
|
||||
this.diffNoteText = response.data.text
|
||||
|
||||
this.lastNoteHash = this.hashString(response.data.text)
|
||||
//Set up note colors
|
||||
if(response.data.color){
|
||||
this.styleObject = JSON.parse(response.data.color)
|
||||
@@ -866,29 +820,12 @@
|
||||
|
||||
this.loading = false
|
||||
|
||||
this.isDecrypted = response.data.decrypted
|
||||
this.isEncrypted = response.data.encrypted == 1
|
||||
this.decryptAttempts = response.data.decrypt_attempts_count
|
||||
this.lockedOut = response.data.lockedOut
|
||||
|
||||
|
||||
//If password is required, display a prompt and focus on it
|
||||
if(this.password.length == 0 && this.isEncrypted && !this.isDecrypted){
|
||||
this.$nextTick(() => {
|
||||
if(this.$refs.decryptNotePrompt){
|
||||
// this.editor.moveCursorToEnd()
|
||||
this.$refs.decryptNotePrompt.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
|
||||
//Adjust note title size after load
|
||||
this.titleResize()
|
||||
|
||||
this.setupWebSockets()
|
||||
this.initSquire()
|
||||
this.startAutolockTimer()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1064,7 +1001,7 @@
|
||||
},
|
||||
onKeyup(){
|
||||
|
||||
this.statusText = 'Save'
|
||||
this.statusText = ''
|
||||
|
||||
this.diffText()
|
||||
|
||||
@@ -1088,23 +1025,16 @@
|
||||
|
||||
// return resolve(true)
|
||||
|
||||
|
||||
//Encrypted notes that are not decrypted should not be saved
|
||||
if(this.isEncrypted && !this.isDecrypted){
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
//Don't save note if its hash doesn't change
|
||||
const currentNoteText = this.getText()
|
||||
if( this.lastNoteHash == this.hashString( currentNoteText )){
|
||||
const currentHash = this.hashString( currentNoteText )
|
||||
if( this.lastNoteHash == currentHash){
|
||||
this.statusText = 'Saved'
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
//If user accidentally clears note, it won't delete it
|
||||
if(currentNoteText == ''){
|
||||
this.statusText = 'Empty'
|
||||
console.log('Prevented from saving empty note.')
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
@@ -1115,10 +1045,11 @@
|
||||
'color': JSON.stringify(this.styleObject), //Save little json color object
|
||||
'pinned': this.pinned,
|
||||
'archived': this.archived,
|
||||
'password': this.hashedPass,
|
||||
'hint': this.passwordHint,
|
||||
'hash': currentHash,
|
||||
}
|
||||
|
||||
console.log('Save Hash', currentHash)
|
||||
|
||||
this.statusText = 'Saving'
|
||||
axios.post('/api/note/update', postData).then( response => {
|
||||
this.statusText = 'Saved'
|
||||
@@ -1126,8 +1057,8 @@
|
||||
this.modified = true
|
||||
|
||||
//Update last saved note hash
|
||||
this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.startAutolockTimer()
|
||||
// this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.lastNoteHash = currentHash
|
||||
return resolve(true)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') })
|
||||
@@ -1135,7 +1066,9 @@
|
||||
},
|
||||
checkForUpdatedNote(){
|
||||
|
||||
// return
|
||||
//Ignore visibility changes, handle this with socket IO
|
||||
//Just keep it always up to date if user is on note
|
||||
return
|
||||
|
||||
//If user leaves page then returns to page, reload the first batch
|
||||
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
|
||||
@@ -1169,18 +1102,15 @@
|
||||
//Track visibility state
|
||||
this.lastVisibilityState = document.visibilityState
|
||||
},
|
||||
hashString(text){
|
||||
hashString(inText){
|
||||
|
||||
text = this.noteTitle + text
|
||||
let text = this.noteTitle + inText
|
||||
|
||||
var hash = 0;
|
||||
let hash = 0;
|
||||
if (text == null || text.length == 0) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
//Simplified for speed
|
||||
// return text.length
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let char = text.charCodeAt(i);
|
||||
hash = ((hash<<5)-hash)+char;
|
||||
@@ -1217,6 +1147,11 @@
|
||||
},
|
||||
setupWebSockets(){
|
||||
|
||||
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
|
||||
console.log('Current hash', this.lastNoteHash)
|
||||
console.log('Incoming Hash', hash)
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
//Tell server to push this note into a room
|
||||
@@ -1231,62 +1166,6 @@
|
||||
this.patchText(incomingDiffData)
|
||||
})
|
||||
},
|
||||
decryptNote(){
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||
//Remove plaintext password
|
||||
this.hashedPass = hashed
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
|
||||
this.loadNote()
|
||||
},
|
||||
lockNote(){
|
||||
this.save().then(results => {
|
||||
this.isDecrypted = false
|
||||
this.password = ''
|
||||
this.hashedPass = ''
|
||||
this.passwordprotect = false
|
||||
this.setText('')
|
||||
})
|
||||
},
|
||||
enableEncryption(){
|
||||
|
||||
if(this.noteText == ''){
|
||||
this.noteText = 'Text Typed here is encrypted.'
|
||||
}
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||
//Remove plaintext password
|
||||
this.hashedPass = hashed
|
||||
|
||||
this.lastNoteHash = 0
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.passwordprotect = false
|
||||
|
||||
this.save()
|
||||
.then(results => {
|
||||
this.$bus.$emit('notification', 'Password Protection Enabled')
|
||||
this.loadNote()
|
||||
})
|
||||
},
|
||||
disableEncryption(){
|
||||
|
||||
this.lastNoteHash = 0
|
||||
this.isEncrypted = false
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.hashedPass = ''
|
||||
this.passwordprotect = false
|
||||
|
||||
//Reload Note
|
||||
this.save()
|
||||
.then(results => {
|
||||
this.loadNote()
|
||||
this.$bus.$emit('notification', 'Password Protection Removed')
|
||||
})
|
||||
},
|
||||
titleResize(){
|
||||
//Resize the title field
|
||||
let element = this.$refs.titleTextarea
|
||||
@@ -1295,15 +1174,6 @@
|
||||
element.style.height = 'auto';
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
},
|
||||
startAutolockTimer(){
|
||||
//Start autolock timer on encrypted notes that are encrypted and in a decrypted state
|
||||
if(this.isEncrypted && this.isDecrypted){
|
||||
clearTimeout(this.autoLockTimeout)
|
||||
this.autoLockTimeout = setTimeout(() => {
|
||||
this.lockNote()
|
||||
}, (60 * 1000 * 20) ) //Autolock after 20 min
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1343,6 +1213,7 @@
|
||||
background-color: var(--background_color);
|
||||
border: 1px solid var(--menu-accent);;
|
||||
margin: 45px 0 45px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1438,18 +1309,18 @@
|
||||
}
|
||||
.loading-note {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
bottom: 20%;
|
||||
background: transparent;
|
||||
color: #5e6268;;
|
||||
font-size: 1.3em;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
background: var(--background_color);
|
||||
/*opacity: 0.;*/
|
||||
z-index: 1;
|
||||
}
|
||||
.loading-text {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
top: 200px;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@@ -1464,6 +1335,10 @@
|
||||
left: 15%;
|
||||
right: 15%;
|
||||
}
|
||||
.side-menu-open {
|
||||
left: calc(50% + 10px) !important;
|
||||
right: calc(0% + 10px) !important;
|
||||
}
|
||||
@media only screen and (max-width: 740px) {
|
||||
.input-container-wrapper {
|
||||
left: 0;
|
||||
@@ -1580,6 +1455,24 @@
|
||||
right: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade out transition animation */
|
||||
.fade-enter {
|
||||
/*opacity: 0;*/
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
/*transition: opacity 0.7s;*/
|
||||
}
|
||||
|
||||
.fade-leave {
|
||||
/* opacity: 0; */
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.7s;
|
||||
opacity: 0;
|
||||
}
|
||||
/* animations END */
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user