* Fixed cursor clicking ToDo lists clicking to early

* Added login form to home page with focus on load
* Tags update after editing tags from title card
* Fixed uploading of images/files
* Fixed images not appearing when opening images tab
* Search hits all categories on search, like archived
* Got rid of brand icons to reduce size
* Got rid of DiffPatchMatch and Crypto from note input panel to reduce size
* Disabled animation on io events so they don't annoy the shit out of people on other computers
This commit is contained in:
Max G 2020-05-20 07:57:15 +00:00
parent 543ecf0f2d
commit 06a140e0d4
12 changed files with 210 additions and 242 deletions

View File

@ -178,10 +178,10 @@
</div> </div>
</div> </div>
<div class="menu-section" v-if="loggedIn && $store.getters.totals && $store.getters.totals['totalNotes']"> <div class="menu-section" v-if="loggedIn">
<router-link exact-active-class="active" class="menu-item menu-button" to="/notes" v-on:click.native="emitReloadEvent()"> <router-link exact-active-class="active" class="menu-item menu-button" to="/notes" v-on:click.native="emitReloadEvent()">
<i class="file outline icon"></i>Notes <i class="file outline icon"></i>Notes
<counter class="float-right" number-id="totalNotes" /> <counter v-if="$store.getters.totals && $store.getters.totals['totalNotes']" class="float-right" number-id="totalNotes" />
</router-link> </router-link>
<div> <div>
<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> --> <!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> -->

View File

@ -0,0 +1,112 @@
<template>
<div v-on:keyup.enter="submit()">
<div v-if="!thin" class="ui large form">
<div class="field">
<div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
</div>
</div>
<div class="field">
<div class="ui input">
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui massive compact fluid green submit button">Sign Up / Login</div>
</div>
<div v-if="thin" class="ui small form">
<div class="fields">
<div class="six wide field">
<div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
</div>
</div>
<div class="six wide field">
<div class="ui input">
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="four wide field">
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui fluid green submit button">Sign Up / Login</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Login',
props:[ 'thin' ],
mounted() {
//Focus on login form on desktop
if(!this.$store.getters.getIsUserOnMobile){
this.$refs.nameForm.focus()
}
},
data () {
return {
enabled: false,
username: '',
password: ''
}
},
methods: {
submit(){
//Both fields are required
if(this.username <= 0){
return false
}
if(this.password <= 0){
return false
}
let vm = this
let data = {
username: this.username,
password: this.password
}
axios.post('/api/user/login', data)
.then(response => {
if(response.data.success){
const token = response.data.token
const username = response.data.username
const masterKey = response.data.masterKey
this.$store.commit('setLoginToken', {token, username, masterKey})
//Setup socket io after user logs in
this.$io.emit('user_connect', token)
//Redirect user to notes section after login
this.$router.push('/notes')
} else {
// this.password = ''
this.$bus.$emit('notification', 'Incorrect Username or Password')
vm.$store.commit('destroyLoginToken')
}
})
.catch(error => {
this.$bus.$emit('notification', 'Incorrect Username or Password')
})
}
}
}
</script>

View File

@ -153,7 +153,7 @@
/> />
<!-- Side slide menus for colors, tags, images and other options --> <!-- Side slide menus for colors, tags, images and other options -->
<side-slide-menu v-show="colors" v-on:close="colors = false" name="colors"> <side-slide-menu v-if="colors" v-on:close="colors = false" name="colors">
<color-picker <color-picker
@changeColor="onChangeColor" @changeColor="onChangeColor"
@close="colors = false" @close="colors = false"
@ -161,13 +161,13 @@
/> />
</side-slide-menu> </side-slide-menu>
<side-slide-menu v-show="tags" v-on:close="tags = false" name="tags" :style-object="styleObject"> <side-slide-menu v-if="tags" v-on:close="tags = false" name="tags" :style-object="styleObject">
<div class="ui basic segment"> <div class="ui basic segment">
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/> <note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
</div> </div>
</side-slide-menu> </side-slide-menu>
<side-slide-menu v-show="images" v-on:close="images = false" name="images" :style-object="styleObject"> <side-slide-menu v-if="images" v-on:close="images = false" name="images" :style-object="styleObject">
<div class="ui basic segment"> <div class="ui basic segment">
<simple-attachment-note <simple-attachment-note
v-on:close="images = false" v-on:close="images = false"
@ -233,8 +233,8 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
const crypto = require('crypto') // const crypto = require('crypto')
const DiffMatchPatch = require('../../../server/helpers/DiffMatchPatch') // const DiffMatchPatch = require('../../../server/helpers/DiffMatchPatch')
export default { export default {
name: 'InputNotes', name: 'InputNotes',
@ -443,19 +443,14 @@
let el = e.target let el = e.target
//Adjust ofset by 40 px //If the offset is triggered with a negative offset, it means the before element was clicked
let correction = 40 if(e.offsetX < -5){
//Will hide keyboard if clicked on mobile
//Determine if element was clicked or area before it, before means checkbox was clicked if(this.$store.getters.getIsUserOnMobile){
if (e.offsetX > e.target.offsetLeft - correction) { this.editor.blur()
//Element was clicked }
} else {
//Will hide keyboard if clicked, much better for mobile
this.editor.blur()
//Area before element was clicked, they clicked the checkbox //Area before element was clicked, they clicked the checkbox
this.onKeyup()
if (el.className === 'active'){ if (el.className === 'active'){
el.className = 'inactive'; el.className = 'inactive';
} else { } else {
@ -869,171 +864,6 @@
console.log('Could not fetch note') console.log('Could not fetch note')
} }
}, },
diffText(){
return
// dont emit to one user
if(this.usersOnNote <= 1){
return
}
//Post latest diff to server, server will emit change event to all connected clients
// clearTimeout(this.emitChangeDebounce)
this.emitChangeDebounce = setTimeout(i => {
//caldulate text diff
let oldText = this.diffNoteText
let newText = this.getText()
if(oldText == newText){
return
}
const dmp = new DiffMatchPatch.diff_match_patch()
const diff = dmp.diff_main(oldText, newText)
// dmp.diff_cleanupSemantic(diff)
const patch_list = dmp.patch_make(oldText, newText, diff);
const patch_text = dmp.patch_toText(patch_list);
var patches = dmp.patch_fromText(patch_text);
var results = dmp.patch_apply(patches, oldText);
const computedText = results[0]
//Save computed diff text
this.noteText = computedText
this.diffNoteText = computedText
if(patch_text == ''){
return
}
// console.log(patch_text)
this.$io.emit('note_diff', {
id: this.rawTextId,
diff: patch_text
})
}, 5)
},
patchText(patch_text){
return
//
// Capture x,y of caret and position into string
//
let currentSelection = this.editor.getSelection()
let lineText = currentSelection.startContainer.textContent
// console.log(lineText)
let cursorOffset = parseInt(currentSelection.startOffset) //number of characters in
let path = this.xpath(currentSelection.commonAncestorContainer.parentElement)
// console.log(path)
//
//Set up text to process diff
//
let currentText = this.editor._getHTML()
const startingLines = (currentText.match(/<br>/g) || '').length + 1
// console.log('1')
const dmp = new DiffMatchPatch.diff_match_patch()
var patches = dmp.patch_fromText(patch_text);
var results = dmp.patch_apply(patches, currentText);
let newText = results[0]
// console.log('2')
this.noteText = newText
this.diffNoteText = newText
// console.log('3')
// this.editor._setHTML(newText)
this.editor.setHTML(newText)
// console.log('4')
//
// I user hasn't selected the document, we are done here
// @TODO add code to halt execution
//
const endingLines = (newText.match(/<br>/g) || '').length + 1
// if(this.pastFocusedNode != null || true){
setTimeout( ()=>{
var root = this.editor.getRoot()
//Get node under current x,y on dom (may break on scroll)
// let node = document.elementFromPoint(mouse.x, mouse.y)
let node = this.getElementByXPath(path)
if(node.firstChild){
node = node.firstChild
}
//If the number of lines changed
if(startingLines != endingLines){
//Line diff may be +1 or -1
let lineDiff = endingLines - startingLines
console.log('Line Diff => ', lineDiff)
//Pull out node number from path
var nodeNumber = path.match(/\d+/)
let modifyNode = null
if(nodeNumber.length == 1){
modifyNode = parseInt(nodeNumber[0])
}
path = path.replace(modifyNode, modifyNode + lineDiff )
console.log(path)
let maybeNext = this.getElementByXPath(path)
if(maybeNext && maybeNext.firstChild){
maybeNext = maybeNext.firstChild
}
if(maybeNext && maybeNext.textContent == lineText){
node = maybeNext
console.log('The Node Moved!')
}
}
// console.log('Targeting Node')
// console.log(node)
//Create and set range
let squireRange = this.editor.createRange(node, cursorOffset)
squireRange.collapse(true)
this.editor.setSelection(squireRange)
// console.log('cursor set')
}, 20)
// }
},
xpath(el) {
//Skip things we can't use
if (typeof el == "string") return document.evaluate(el, document, null, 0, null)
if (!el || el.nodeType != 1) return ''
//Anchor xpath using Ids or test-ids
const testId = el.getAttribute('test-id')
if (el.id) return "//*[@id='" + el.id + "']"
//Continue to build path
const sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName == el.tagName })
return this.xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '['+([].indexOf.call(sames, el)+1)+']' : '')
},
getElementByXPath(xpath){
return new XPathEvaluator()
.createExpression(xpath)
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue
},
onKeyup(){ onKeyup(){
this.statusText = 'modified' this.statusText = 'modified'
@ -1198,7 +1028,7 @@
//Server will hand deliver diffs from other notes to this one //Server will hand deliver diffs from other notes to this one
this.$io.on('incoming_diff', incomingDiffData => { this.$io.on('incoming_diff', incomingDiffData => {
this.patchText(incomingDiffData) // this.patchText(incomingDiffData)
}) })
}, },
titleResize(){ titleResize(){

View File

@ -235,7 +235,7 @@
this.showTagSlideMenu = state this.showTagSlideMenu = state
if(state == false){ if(state == false){
// this.$bus.$emit('update_single_note', this.note.id) this.$bus.$emit('update_single_note', this.note.id)
} }
}, },

View File

@ -87,22 +87,24 @@
}, },
mounted(){ mounted(){
this.loadImages()
axios.post('/api/attachment/search', {'attachmentType':'files', 'setSize':1000})
.then( ({data}) => {
//Sort files into two categories
data.forEach(file => {
if(file['note_id'] == this.noteId){
this.uploadedToNote.push(file)
} else {
this.files.push(file)
}
})
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Load Attachments') })
}, },
methods: { methods: {
loadImages(){
axios.post('/api/attachment/search', {'attachmentType':'files', 'setSize':1000})
.then( ({data}) => {
//Sort files into two categories
data.forEach(file => {
if(file['note_id'] == this.noteId){
this.uploadedToNote.push(file)
} else {
this.files.push(file)
}
})
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Load Attachments') })
},
onFileClick(file){ onFileClick(file){
const imageCode = `<img alt="image" src="/api/static/thumb_${file.file_location}">` const imageCode = `<img alt="image" src="/api/static/thumb_${file.file_location}">`

View File

@ -22,8 +22,8 @@
<div class="fourteen wide column"> <div class="fourteen wide column">
<h2 class="ui header"><i class="small green tags icon"></i>Tags</h2> <h2 class="ui header"><i class="small green tags icon"></i>Tags</h2>
</div> </div>
<div class="two wide middle aligned center aligned column" v-on:click="menuOpen = false"> <div class="two wide middle aligned center aligned column" v-on:click="closeMenu()">
<i class="grey close icon"></i> <i class="link grey close icon"></i>
</div> </div>
<div class="sixteen wide middle aligned column" v-if="loadedTags.length == 0"> <div class="sixteen wide middle aligned column" v-if="loadedTags.length == 0">
Tags added to Notes will appear here. Tags added to Notes will appear here.
@ -44,7 +44,7 @@
</div> </div>
</div> </div>
<div class="shade" v-if="menuOpen" v-on:click="menuOpen = false"></div> <div class="shade" v-if="menuOpen" v-on:click="closeMenu()"></div>
</div> </div>
</template> </template>
@ -63,6 +63,9 @@
components: { components: {
}, },
methods:{ methods:{
closeMenu(){
this.menuOpen = false
},
openMenu(){ openMenu(){
this.menuOpen = true this.menuOpen = true
axios.post('/api/tag/usertags') axios.post('/api/tag/usertags')

View File

@ -23,7 +23,7 @@ import 'fomantic-ui-css/components/container.css'
import 'fomantic-ui-css/components/form.css' import 'fomantic-ui-css/components/form.css'
import 'fomantic-ui-css/components/grid.css' import 'fomantic-ui-css/components/grid.css'
import 'fomantic-ui-css/components/header.css' import 'fomantic-ui-css/components/header.css'
import 'fomantic-ui-css/components/icon.css' import 'fomantic-ui-css/components/icon.css' //Modified to remove brand icons
import 'fomantic-ui-css/components/input.css' import 'fomantic-ui-css/components/input.css'
import 'fomantic-ui-css/components/segment.css' import 'fomantic-ui-css/components/segment.css'
import 'fomantic-ui-css/components/label.css' import 'fomantic-ui-css/components/label.css'

View File

@ -120,7 +120,7 @@
</h2> </h2>
<h3 class="subtext"> <h3 class="subtext">
An easy, encrypted Note App<i class="i cursor icon blinking"></i> An easy, free, secure Note App<i class="i cursor icon blinking"></i>
</h3> </h3>
</div> </div>
@ -131,14 +131,24 @@
</div> </div>
<div class="row"> <!-- Go to notes button -->
<div class="four wide center aligned column"> <div class="row" v-if="$parent.loggedIn">
<router-link class="ui huge green labeled icon button" to="/login"> <div class="sixteen wide middle algined center aligned column">
<i class="plug icon"></i>Sign Up <router-link class="ui huge green labeled icon button" to="/notes">
<i class="external alternate icon"></i>Go to Notes
</router-link> </router-link>
</div> </div>
<div class="eight wide middle aligned column"> </div>
<h2>Only a Username and Password are required.</h2>
<!-- Small login form -->
<div class="row" v-if="!$parent.loggedIn">
<div class="sixteen wide middle algined column">
<div class="ui text container">
<h2>
<i class="plug icon"></i>
Sign Up Now - Only a Username and Password are required.</h2>
<login-form :thin="true" />
</div>
</div> </div>
</div> </div>
@ -310,6 +320,9 @@
<script> <script>
export default { export default {
name: 'WelcomePage', name: 'WelcomePage',
components: {
'login-form':require('@/components/LoginFormComponent.vue').default,
},
data(){ data(){
return { return {
height: null, height: null,
@ -328,7 +341,7 @@ export default {
//Don't change hero banner on mobile //Don't change hero banner on mobile
if(!this.$store.getters.getIsUserOnMobile){ if(!this.$store.getters.getIsUserOnMobile){
let windowHeight = window.innerHeight let windowHeight = window.innerHeight
this.height = windowHeight - (windowHeight * 0.15) this.height = windowHeight - (windowHeight * 0.18)
} }
}, },

View File

@ -13,27 +13,16 @@
<div class="ui segment" v-on:keyup.enter="submit"> <div class="ui segment" v-on:keyup.enter="submit">
<h4 class="ui header"> <h4 class="ui header">
<i class="plug icon"></i> <i class="plug icon"></i>
<div class="content"> <div class="content">
To Register To Register
<div class="sub header">Choose Any Username & password</div> <div class="sub header">Choose Any Username & password</div>
</div> </div>
</h4> </h4>
<div class="ui large form"> <login-form />
<div class="field">
<div class="ui input">
<input v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
</div>
</div>
<div class="field">
<div class="ui input">
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui massive compact fluid green submit button">Login</div>
</div>
</div> </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 30 days or until you log out.</p>
@ -49,6 +38,9 @@
export default { export default {
name: 'Login', name: 'Login',
components: {
'login-form':require('@/components/LoginFormComponent.vue').default,
},
data () { data () {
return { return {
enabled: false, enabled: false,

View File

@ -35,7 +35,7 @@
v-on:tagClick="tagId => toggleTagFilter(tagId)" v-on:tagClick="tagId => toggleTagFilter(tagId)"
/> />
<div class="ui basic shrinking icon button" v-on:click="toggleTitleView()"> <div class="ui basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<i v-if="titleView" class="th icon"></i> <i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i> <i v-if="!titleView" class="bars icon"></i>
</div> </div>
@ -91,7 +91,7 @@
<!-- Note title card display --> <!-- Note title card display -->
<div class="sixteen wide column"> <div class="sixteen wide column">
<h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0"> <h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0 && fastFilters['notesHome'] == 1">
No Notes Yet. <br>Thats ok.<br><br> <br> No Notes Yet. <br>Thats ok.<br><br> <br>
<img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br> <img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br>
Create one when you feel ready. Create one when you feel ready.
@ -264,6 +264,13 @@
} }
}) })
this.$bus.$on('update_single_note', (noteId) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId)
}
})
//Update totals for app //Update totals for app
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
@ -277,10 +284,6 @@
} }
}) })
// this.$bus.$on('update_single_note', (noteId) => {
// this.updateSingleNote(noteId)
// })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
//Remove deleted note from set, its deleted //Remove deleted note from set, its deleted
@ -664,6 +667,18 @@
//Sort notes into defined sections //Sort notes into defined sections
notes.forEach(note => { notes.forEach(note => {
if(this.searchTerm.length > 0){
if(note.pinned == 1){
this.noteSections.pinned.push(note)
return
}
//Push to default note section
this.noteSections.notes.push(note)
return
}
//Display all tags in tag section
if(this.searchTags.length >= 1){ if(this.searchTags.length >= 1){
this.noteSections.tagged.push(note) this.noteSections.tagged.push(note)
return return
@ -693,9 +708,7 @@
} }
return return
} }
if(note.archived == 1){ if(note.archived == 1){ return }
return
}
//Only show sent notes section if shared is selected //Only show sent notes section if shared is selected
if(this.fastFilters.onlyShowSharedNotes == 1){ if(this.fastFilters.onlyShowSharedNotes == 1){
@ -746,6 +759,8 @@
//clear out tags //clear out tags
this.searchTags = [] this.searchTags = []
this.loadingInProgress = false this.loadingInProgress = false
this.searchTerm = ''
this.$bus.$emit('reset_fast_filters')
//A little hacky, brings user to notes page then filters on click //A little hacky, brings user to notes page then filters on click
if(this.$route.name != 'Note Page'){ if(this.$route.name != 'Note Page'){

View File

@ -57,6 +57,8 @@ Tag.addToNote = (userId, noteId, tagText) => {
.then( newTagId => { .then( newTagId => {
Tag.associateWithNote(userId, noteId, newTagId) Tag.associateWithNote(userId, noteId, newTagId)
.then( result => { .then( result => {
SocketIo.to(userId).emit('new_note_text_saved', {noteId})
resolve(result) resolve(result)
}) })
}) })

View File

@ -12,6 +12,7 @@ let userId = null
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
if(userId = req.headers.userId){ if(userId = req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey
} }
next() next()
@ -52,9 +53,7 @@ router.post('/upload', upload.single('file'), function (req, res, next) {
Attachment.processUploadedFile(userId, noteId, req.file) Attachment.processUploadedFile(userId, noteId, req.file)
.then( uploadResults => { .then( uploadResults => {
//Reindex note, attachment may have had text res.send(uploadResults)
Note.reindex(userId, noteId)
.then( data => {res.send(uploadResults)})
}) })
}) })