* Added Much better session Management, key updating and deleting

* Force reload of JS if app numbers dont match
* Added cool tag display on side of note
* Cleaned up a bunch of code and tweaked little things to be better
This commit is contained in:
Max G
2020-06-15 09:02:20 +00:00
parent d2624628d8
commit 071aaf22cd
18 changed files with 333 additions and 270 deletions

View File

@@ -23,22 +23,76 @@ export default {
data: function(){
return {
// loggedIn:
fetchingInProgress: false, //Prevent start getting token while fetch is in progress
blockUntilNextRequest: false //If token was just renewed, don't fetch more until next request
}
},
//Axios response interceptor
// - Gets new session tokens from server and uses them in app
beforeCreate: function(){
//Before all requests going out
axios.interceptors.request.use(
(config) => {
//Enable token fetching after another request is made
if(this.blockUntilNextRequest){
this.fetchingInProgress = false
this.blockUntilNextRequest = false
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// Add a response interceptor, token can be renewed on every response
axios.interceptors.response.use(
(response) => {
if(typeof response.headers.remaininguses !== 'undefined'){
// console.log(response.headers.remaininguses)
//Look at remaining uses of token, if its less than five, request a new one
if(response.headers.remaininguses < 10 && !this.fetchingInProgress && !this.blockUntilNextRequest){
this.fetchingInProgress = true
const currentToken = localStorage.getItem('loginToken')
this.$io.emit('renew_session_token', currentToken)
}
}
return response
},
(error) => {
return Promise.reject(error)
}
)
//Puts token into state on page load
let token = localStorage.getItem('loginToken')
let username = localStorage.getItem('username')
// const socket = io({ path:'/socket' });
const socket = this.$io
socket.on('connect', () => {
//
if(token && token.length > 0){
this.$store.commit('setSocketIoSocket', socket.id)
//setup username display
this.$store.commit('setUsername', username)
this.$io.emit('user_connect', token)
})
//Set session token on every request if set
axios.defaults.headers.common['authorizationtoken'] = token
//Setup websockets into vue instance
const socket = this.$io
socket.on('connect', () => {
//Put user into personal event room for live note updates, etc
this.$io.emit('user_connect', token)
})
}
//Detect if user is on a mobile browser and set a flag in store
this.$store.commit('detectIsUserOnMobile')
@@ -49,11 +103,6 @@ export default {
this.$store.commit('toggleNightMode', themeNumber)
}
//Put user data into global store on load
if(token){
this.$store.commit('setLoginToken', {token, username})
}
},
mounted: function(){
@@ -63,6 +112,17 @@ export default {
this.$store.dispatch('fetchAndUpdateUserTotals')
})
this.$io.on('recievend_new_token', newToken => {
// console.log('Got a new token')
axios.defaults.headers.common['authorizationtoken'] = newToken
localStorage.setItem('loginToken', newToken)
//Disable getting new tokens until next request
this.blockUntilNextRequest = true
})
},
computed: {
loggedIn () {

View File

@@ -17,6 +17,10 @@
:root {
--main-accent: #16ab39;
/*theme colors */
--body_bg_color: #f5f6f7;
--small_element_bg_color: #fff;
--text_color: #3d3d3d;

View File

@@ -248,7 +248,7 @@
<div v-on:click="reloadPage" class="version-display">
<div v-on:click="reloadPage" class="version-display" v-if="version != 0" >
<i :class="`${getVersionIcon()} icon`"></i> {{ version }}
</div>
@@ -267,7 +267,7 @@
},
data: function(){
return {
version: '2.3.4',
version: '0',
username: '',
collapsed: false,
mobile: false,
@@ -277,6 +277,7 @@
}
},
beforeCreate: function(){
},
mounted: function(){
this.mobile = this.$store.getters.getIsUserOnMobile
@@ -288,6 +289,7 @@
if(this.loggedIn){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.version = localStorage.getItem('currentVersion')
}
},
@@ -347,11 +349,12 @@
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
},
destroyLoginToken() {
axios.post('/api/user/logout').then( response => {
axios.post('/api/user/logout')
setTimeout(() => {
this.$bus.$emit('notification', 'Logged Out')
this.$store.commit('destroyLoginToken')
this.$router.push('/')
})
}, 200)
},
toggleNightMode(){
this.$store.commit('toggleNightMode')

View File

@@ -98,13 +98,15 @@
//Login user if we have a valid token
if(data && data.token && data.token.length > 0){
const token = data.token
const username = this.username
//Set username to local session
this.$store.commit('setUsername', this.username)
this.$store.commit('setLoginToken', {token, username})
const token = data.token
//Setup socket io after user logs in
axios.defaults.headers.common['authorizationtoken'] = token
this.$io.emit('user_connect', token)
localStorage.setItem('loginToken', token)
//Redirect user to notes section after login
this.$router.push('/notes')
@@ -113,7 +115,7 @@
register(){
if( this.username.length == 0 || this.password.length == 0 ){
this.$bus.$emit('notification', 'Username and Password Required')
this.$bus.$emit('notification', 'Unable to Sign Up - Username and Password Required')
return
}
@@ -121,19 +123,19 @@
.then(({data}) => {
if(data == false){
this.$bus.$emit('notification', 'Username already in use')
this.$bus.$emit('notification', 'Unable to Sign Up - Username already in use')
}
this.finalizeLogin(data)
})
.catch(error => {
this.$bus.$emit('notification', 'Username already in use')
this.$bus.$emit('notification', 'Unable to Sign Up - Username already in use')
})
},
login(){
if( this.username.length == 0 || this.password.length == 0 ){
this.$bus.$emit('notification', 'Username and Password Required')
this.$bus.$emit('notification', 'Unable to Login - Username and Password Required')
return
}
@@ -141,13 +143,13 @@
.then(({data}) => {
if(data == false){
this.$bus.$emit('notification', 'Incorrect Username or Password')
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
}
this.finalizeLogin(data)
})
.catch(error => {
this.$bus.$emit('notification', 'Incorrect Username or Password')
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
})
}
}

View File

@@ -170,13 +170,18 @@
</div>
<!-- little tags on the side -->
<div class="note-mini-tag-area" v-if="allTags.length > 0" :class="{ 'slide-out-right':(sizeDown == true) }">
<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }">
<span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)">
<i class="tag icon"></i>
{{ tag.text }}
</span>
<span v-else class="subtle-tag" v-on:click="addTag(tag.text)">
<i class="plus icon"></i>
{{ tag.text }}
</span>
<span class="subtle-tag" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)">
<i class="plus icon"></i><i class="green tags icon"></i>Add Tag
</span>
</div>
<!-- color picker -->
@@ -195,7 +200,7 @@
/>
</side-slide-menu>
<side-slide-menu v-if="tags" v-on:close="tags = false" name="tags" :style-object="styleObject">
<side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags" :style-object="styleObject">
<div class="ui basic segment">
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
</div>
@@ -254,10 +259,10 @@
<!-- Show side shades if user is on desktop only -->
<div class="full-focus-shade shade1"
:class="{ 'slide-out-left':(sizeDown == true) }"
:class="{ 'slide-out-left':sizeDown }"
v-on:click="close()"></div>
<div class="full-focus-shade shade2"
:class="{ 'slide-out-right':(sizeDown == true) }"
:class="{ 'slide-out-right':sizeDown }"
v-on:click="close()"></div>
</div>
@@ -335,10 +340,6 @@
diffTextTimeout: null,
diffsApplied: null,
//Fake Caret position and visibility
caretShow: false,
caretLeft: null,
caretTop: null,
//Used to restore caret position
lastRange: null,
startOffset: 0,
@@ -541,9 +542,6 @@
if(!this.$store.getters.getIsUserOnMobile){
this.editor.focus()
this.editor.moveCursorToEnd()
this.caretShow = true
this.moveArtificialCaret()
this.fetchNoteTags() //Don't load tags on mobile
}
@@ -615,14 +613,7 @@
})
this.editor.addEventListener('keydown', event => {
setTimeout(() => {
if(event.keyCode == 32){
this.caretLeft += 3
}
if(event.keyCode == 8){
// this.caretLeft -= 3
}
}, 10)
})
//Bind event handlers
@@ -633,29 +624,10 @@
//Show and hide additional toolbars
this.editor.addEventListener('focus', e => {
// this.caretShow = true
})
this.editor.addEventListener('blur', e => {
// this.caretShow = false
})
},
moveArtificialCaret(rect = null){
//Lets not use the artificial caret for now
return
//If rect isn't present, grab by selection
if(!rect || rect.left == 0){ //Left should always be greater than 0, because of a margin
rect = this.editor.getCursorPosition()
//Another way to get range
// window.getSelection().getRangeAt(0)
}
const textArea = document.getElementById('text-box-container').getBoundingClientRect()
this.caretLeft = (rect.left - textArea.left - 1)
this.caretTop = (rect.top - textArea.top - 1 )
},
openEditAttachment(){
this.$router.push('/attachments/note/'+this.currentNoteId)
@@ -845,7 +817,7 @@
// clearTimeout(this.editDebounce)
if(this.statusText == 'saving'){
return reject(false)
return resolve(true)
}
//Don't save note if its hash doesn't change
@@ -1081,38 +1053,45 @@
.note-mini-tag-area {
position: fixed;
width: 100px;
left: calc(15% - 100px);
width: 120px;
left: calc(15% - 125px);
top: 46px;
bottom: 0;
height: 500px;
height: calc(100vh - 55px);
z-index: 1000;
overflow-y: scroll;
scrollbar-width: none;
scrollbar-color: transparent transparent;
}
.note-mini-tag-area {
scrollbar-width: auto;
scrollbar-color: inherit inherit;
}
.subtle-tag {
display: inline-block;
width: 100%;
padding: 2px 1px 2px 4px;
margin: 0 0 2px;
padding: 1px 1px 1px 5px;
margin: 0 0 0;
border: 1px solid transparent;
border-right: none;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
color: var(--text_color);
border-radius: 3px;
background-color: transparent;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color ease 0.3s, background ease 0.3s;
font-size: 12px;
font-size: 11px;
cursor: pointer;
opacity: 0;
text-transform:capitalize;
}
.note-mini-tag-area:hover .subtle-tag {
opacity: 1;
}
}
.note-mini-tag-area:hover .active-mini-tag {
background-color: var(--main-accent);
color: white;
}
.note-mini-tag-area:hover .subtle-tag:not(.active-mini-tag) {
border-right: none;
color: var(--text_color);
@@ -1120,9 +1099,9 @@
opacity: 1;
}
.active-mini-tag {
opacity: 0.6;
background-color: #16ab39;
color: white;
opacity: 0.7;
background-color: var(--small_element_bg_color);
color: var(--text_color)
}

View File

@@ -32,7 +32,7 @@
class="big-text"><p>{{ note.title }}</p></span>
<!-- Sub text display -->
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
<span v-if="note.subtext.length > 0"
class="small-text"
v-html="note.subtext"></span>
@@ -49,15 +49,6 @@
</span>
</span>
<!-- Display highlights from solr results -->
<span v-if="note.note_highlights.length > 0" class="term-usage">
<span
class="usage-row"
v-for="highlight in note.note_highlights"
:class="{ 'big-text':(highlight <= 100), 'small-text-title':(highlight >= 100) }"
v-html="cleanHighlight(highlight)"></span>
</span>
</div>
<div v-if="titleView" class="single-line-text" @click="cardClicked">
@@ -179,12 +170,6 @@
return updated
},
isShowingSearchResults(){
if(this.note.note_highlights.length > 0 || this.note.attachment_highlights.length > 0 || this.note.tag_highlights.length > 0){
return true
}
return false
},
splitTags(text){
return text.split(',')
},

View File

@@ -8,8 +8,12 @@
<div class="ui grid" v-if="shareUsername == null">
<div v-if="!isNoteShared" class="sixteen wide column">
<div class="ui button" v-on:click="makeShared()">Enable Shared</div>
<p>Shared notes are different and junk.</p>
<div class="ui button" v-on:click="makeShared()">Enable Sharing</div>
<ul>
<li>Shared notes can be read and edited by you and all shared users.</li>
<li>Shared notes can only be shared by the creator of the note.</li>
</ul>
</div>
<div v-if="isNoteShared" class="sixteen wide column">
@@ -17,14 +21,10 @@
<div class="ui button" v-on:click="removeShared()">Remove Shared</div>
<div class="ui button" v-on:click="getSharedUrl()">Get Shareable URL</div>
<div v-if="sharedUrl.length > 0">
<a target="_blank" :href="sharedUrl">{{ sharedUrl }}</a>
<div class="ui input">
<input type="text" v-model="sharedUrl">
</div>
</div>
</div>
<div class="sixteen wide column" v-if="isNoteShared && sharedUrl.length > 0">
<p>Public Link - this link can be disabled by turning off sharing</p>
<a target="_blank" :href="sharedUrl">{{ sharedUrl }}</a>
</div>
</div>

View File

@@ -117,7 +117,7 @@
:data="note"
:title-view="titleView"
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/>
</div>
</div>

View File

@@ -6,30 +6,16 @@ Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: null,
username: null,
nightMode: false,
isUserOnMobile: false,
isNoteSettingsOpen: false, //Little note settings pane
socket: null,
userTotals: null,
},
mutations: {
setLoginToken(state, userData){
const username = userData.username
const token = userData.token
localStorage.removeItem('loginToken') //We only want one login token per computer
localStorage.setItem('loginToken', token)
setUsername(state, username){
localStorage.removeItem('username') //We only want one login token per computer
localStorage.setItem('username', username)
//Set default token to axios, every request will have header
axios.defaults.headers.common['authorizationtoken'] = token
state.token = token
state.username = username
},
destroyLoginToken(state){
@@ -37,8 +23,8 @@ export default new Vuex.Store({
//Remove login token from local storage and from headers
localStorage.removeItem('loginToken')
localStorage.removeItem('username')
localStorage.removeItem('currentVersion')
delete axios.defaults.headers.common['authorizationtoken']
state.token = null
state.username = null
},
toggleNightMode(state, pastTheme){
@@ -125,6 +111,20 @@ export default new Vuex.Store({
//Save all the totals for the user
state.userTotals = totalsObject
//Set computer version from server
const currentVersion = localStorage.getItem('currentVersion')
if(currentVersion == null){
localStorage.setItem('currentVersion', totalsObject.currentVersion)
return
}
//If version is already set and it doesn't match the server, reload app
if(currentVersion != totalsObject.currentVersion){
localStorage.setItem('currentVersion', totalsObject.currentVersion)
location.reload(true)
}
// console.log('-------------')
// Object.keys(totalsObject).forEach( key => {
// console.log(key + ' -- ' + totalsObject[key])
@@ -135,11 +135,8 @@ export default new Vuex.Store({
getUsername: state => {
return state.username
},
getLoginToken: state => {
return state.token
},
getLoggedIn: state => {
let weIn = (state.token !== null && state.token != undefined && state.token.length > 0)
let weIn = (state.username && state.username.length > 0)
return weIn
},
getIsNightMode: state => {