diff --git a/backupDatabase.sh b/backupDatabase.sh index b5d1372..5be9541 100755 --- a/backupDatabase.sh +++ b/backupDatabase.sh @@ -16,6 +16,9 @@ gzip "backup-$NOW.sql" echo "Database Backup Complete on $NOW" +# Delete all but last 8 files +ls -tp | grep -v '/$' | tail -n +9 | tr '\n' '\0' | xargs -0 rm -- + ## # Restore DB ## diff --git a/client/src/components/AttachmentDisplayCard.vue b/client/src/components/AttachmentDisplayCard.vue index a9778fb..fe7d0a0 100644 --- a/client/src/components/AttachmentDisplayCard.vue +++ b/client/src/components/AttachmentDisplayCard.vue @@ -117,11 +117,16 @@ {{linkText}} -
+
Open Note
-
+ + Pushed from Web +
+ +
Note Files diff --git a/client/src/components/LoginFormComponent.vue b/client/src/components/LoginFormComponent.vue index 70e3e03..6617c5f 100644 --- a/client/src/components/LoginFormComponent.vue +++ b/client/src/components/LoginFormComponent.vue @@ -4,7 +4,7 @@
-
+
@@ -15,6 +15,11 @@
+
+
+ +
+
@@ -24,17 +29,10 @@
-
+
Sign Up
- -
- -
- - Login -
@@ -49,12 +47,12 @@
-
+
-
+
Sign Up Now!
@@ -87,7 +85,7 @@
-
+
Login
@@ -128,6 +126,7 @@ enabled: false, username: '', password: '', + password2: '', authToken: '', require2FA: false, } @@ -160,13 +159,21 @@ }, register(){ - if( this.username.length == 0 || this.password.length == 0 ){ + let error = false - if(this.$route.name == 'LoginPage'){ - this.$bus.$emit('notification', 'Both a Username and Password are Required') - return - } + if( this.username.length == 0 || this.password.length == 0 || this.password2.length == 0 ){ + this.$bus.$emit('notification', 'All fields are required.') + error = true + } + + if( this.password !== this.password2 ){ + + this.$bus.$emit('notification', 'Passwords must be identical.') + error = true + } + + if(error){ //Login section this.$router.push('/login') return diff --git a/client/src/components/NoteInputPanel.vue b/client/src/components/NoteInputPanel.vue index c7109cd..bd635b3 100644 --- a/client/src/components/NoteInputPanel.vue +++ b/client/src/components/NoteInputPanel.vue @@ -172,6 +172,10 @@ + + + + +{{ diffsApplied }} @@ -347,6 +351,10 @@ :class="{ 'fade-me-out':sizeDown }" v-on:click="closeButtonAction()">
--> +
+ +
+
@@ -404,13 +412,11 @@ pinned: 0, archived: 0, attachmentCount: 0, + attachments: [], styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges sizeDown: false, //Used to animate close state - //Settings vars - lastVisibilityState: null, - //All the squire settings editor: null, usersOnNote: 0, @@ -426,6 +432,9 @@ //Diff text/sync text variables diffTextTimeout: null, diffsApplied: null, + idleNote: true, // If note is idle, get updates from server + idleNoteTimeout: null, + reloadNoteDebounce: null, //Used to restore caret position lastRange: null, @@ -486,9 +495,12 @@ this.$bus.$off('new_file_upload') + this.destroyAttachmentStyles() + this.destroyWebSockets() - document.removeEventListener('visibilitychange', this.checkForUpdatedNote) + window.removeEventListener('blur', this.windowBlurEvent) + window.removeEventListener('focus', this.windowFocusEvent) //Obliterate squire instance this.editor.destroy() @@ -504,7 +516,9 @@ this.forceShowLoading = true }, 500) - document.addEventListener('visibilitychange', this.checkForUpdatedNote) + window.addEventListener('blur', this.windowBlurEvent) + window.addEventListener('focus', this.windowFocusEvent) + // this.logNoteInteraction() //Init squire as early as possible if(this.editor && this.editor.destroy){ @@ -685,13 +699,16 @@ this.editor.addEventListener('keyup', event => { this.onKeyup(event) this.diffText(event) + this.logNoteInteraction() }) this.editor.addEventListener('focus', e => { + this.logNoteInteraction() // this.diffText(e) }) this.editor.addEventListener('blur', e => { + this.idleNote = true this.diffText(e) }) @@ -811,6 +828,12 @@ this.pinned = response.data.pinned } this.archived = response.data.archived + + // Fetch attachmets if the count changed + if(response.data.attachment_count > 0){ + this.getAttachments() + } + this.attachmentCount = response.data.attachment_count return true @@ -967,6 +990,9 @@ // save editor HTML after change for future comparisons rawNoteText = editorElement.innerHTML + // update hash on patch + this.lastNoteHash = this.hashString( rawNoteText ) + this.$nextTick(() => { return resolve(true) }) @@ -975,12 +1001,13 @@ onKeyup(event){ this.statusText = 'modified' + this.idleNote = false //Save after x seconds clearTimeout(this.editDebounce) this.editDebounce = setTimeout(() => { this.save() - }, 5 * 1000) + }, 4 * 1000) //Save after x keystrokes this.keyPressesCounter = (this.keyPressesCounter + 1) @@ -1013,6 +1040,7 @@ } //tell websockets to truncate history at this save + this.lastNoteHash = currentHash //Update last saved note hash this.$io.emit('truncate_diffs_at_save', {'rawTextId':this.rawTextId, 'hash':currentHash }) const postData = { @@ -1033,44 +1061,54 @@ this.modified = true this.diffsApplied = 0 - //Update last saved note hash - this.lastNoteHash = currentHash return resolve(true) }) .catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') }) }) }, - checkForUpdatedNote(){ + loadNoteNextFromServer(){ - const now = +new Date - //Only check every 3 seconds - const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (2 * 1000) + clearTimeout(this.reloadNoteDebounce) + this.reloadNoteDebounce = setTimeout(() => { + + // flash note text to show the update + // this.setText('') - //If user leaves page then returns to page, reload the first batch - if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible' && checkForUpdateTimeout){ - //Focus Regained on Note, check for update axios.post('/api/note/get', { 'noteId': this.noteid }) .then(response => { - const serverTextHash = this.hashString( response.data.text ) + this.setupLoadedNoteData(response) - if(this.lastNoteHash != serverTextHash){ - // console.log('note was changed UPDATE THAT BITCH!!!!') - this.setupLoadedNoteData(response) - - //Manually set squire text to show - this.setText(this.noteText) - } + //Manually set squire text to show + this.setText(this.noteText) + }) - } + }, 200) - //Keep track of visibility change and last interaction time - this.lastVisibilityState = document.visibilityState - this.lastInteractionTimestamp = +new Date + }, + windowFocusEvent(){ + + //Only check if its been greater than a few seconds + const now = +new Date + const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (3 * 1000) + + //If user leaves page then returns to page, reload the first batch + if(checkForUpdateTimeout){ + + this.loadNoteNextFromServer() + this.lastInteractionTimestamp = now + } + + }, + windowBlurEvent(){ + + this.idleNote = true + this.lastInteractionTimestamp = +new Date + }, hashString(inText){ @@ -1127,6 +1165,9 @@ this.$io.removeListener('past_diffs') this.$io.removeListener('update_user_count') this.$io.removeListener('incoming_diff') + this.$io.removeListener('update_note_attachments') + + clearTimeout(this.idleNoteTimeout) }, initWebsocketEvents(){ @@ -1159,6 +1200,31 @@ }) }) + this.$io.on('new_note_text_saved', ({noteId, hash}) => { + + const sameIdCheck = (this.idleNote && this.noteid == noteId) + const differentHashCheck = (hash != this.lastNoteHash) + + // if hashes do not match, reload text from server + if(sameIdCheck && differentHashCheck){ + this.loadNoteNextFromServer() + } + + }) + + this.$io.on('update_note_attachments', () => { + this.getAttachments() + }) + + }, + logNoteInteraction(){ + + this.idleNote = false + clearTimeout(this.idleNoteTimeout) + this.idleNoteTimeout = setTimeout(() => { + this.idleNote = true + }, 5000) + }, saveCaretPosition(event){ @@ -1202,6 +1268,88 @@ element.style.height = (element.scrollHeight) +'px' } }, + destroyAttachmentStyles(){ + // Remove attachment preview styles + var head = document.head + var styleElement = document.getElementById('attachmentGeneratedStyles'+this.noteid) + if(styleElement){ + head.removeChild(styleElement) + } + }, + getAttachments(){ + + axios.post('/api/attachment/search', {'noteId':this.noteid}) + .then( results => { + + + // generate new style group + var style = document.createElement('style') + style.id = 'attachmentGeneratedStyles'+this.noteid + style.type = 'text/css' + + // iterate attachments and build unique style for each + let attachmentStyles = [] + results.data.forEach(attachment => { + + // thumbnail location + const bgurl = `/api/static/thumb_${attachment.file_location}` + let padding = '2px 0 0' + + // increase padding if there is a valid file + if(attachment.file_location){ + padding = '13px 0 13px 155px' + } + + // unescaped characters will break content attribute + const strippedText = attachment.text + .replace(/'/g, "\\'") //Escape ' s + .replace(/\n/g, '\\A') //Escape new lines + + // strip down URL, *= matches anywhere is string + const substringsToRemove = ['https://','http://','www.'] + var pattern = new RegExp(substringsToRemove.join('|'), 'g'); + const strippedurl = attachment.url + .replace(pattern, '') // remove url protocol + .replace(/&.*/, '') // remove anything after & + + const cleanStyle = ` + .squire-box a[href*="${strippedurl}" i]::before { + content: '${strippedText}'; + display: inline-block; + padding: ${padding}; + pointer-events: none; + font-size: 1.3em !important; + width: 100%; + background-position: left center; + background-repeat: no-repeat; + background-size: 140px auto; + background-image: url(${bgurl}); + border-bottom: solid 1px var(--main-accent); + margin-bottom: -10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .squire-box a[href*="${strippedurl}" i] { + font-size: 0.8em; + } + ` + .replace(/\t|\r|\n/gm, "") // Remove tabs, new lines, returns + .replace(/\s+/g, ' ') // remove double spaces + + attachmentStyles.push(cleanStyle) + }) + + // Destroy just before creating new to prevent page jumping + this.destroyAttachmentStyles() + + // push new styles into + style.innerHTML = attachmentStyles.join(' ') + document.head.appendChild(style) + + }) + .catch(error => { console.log(error);this.$bus.$emit('notification', 'Failed to Search Attachments') }) + }, } } diff --git a/client/src/components/PasteButton.vue b/client/src/components/PasteButton.vue index 701c7d8..be5dc53 100644 --- a/client/src/components/PasteButton.vue +++ b/client/src/components/PasteButton.vue @@ -1,7 +1,7 @@