Compare commits

..

3 Commits

16 changed files with 627 additions and 201 deletions

View File

@ -12,6 +12,7 @@ echo '-------'
BACKUPDIR="/home/mab/databaseBackupSolidScribe" BACKUPDIR="/home/mab/databaseBackupSolidScribe"
#DEVDBPASS="Crama!Lama*Jamma###88383!!!!!345345956245i" #DEVDBPASS="Crama!Lama*Jamma###88383!!!!!345345956245i"
DEVDBPASS="RootPass1234!" DEVDBPASS="RootPass1234!"
# LazaLinga&33Can't!Do!That34
cd $BACKUPDIR cd $BACKUPDIR
@ -28,8 +29,12 @@ gunzip -dkv $LASTZIPPEDFILE
BACKUPFILE=$(ls -At *.sql | head -n1) BACKUPFILE=$(ls -At *.sql | head -n1)
#Fix to replace incompatible DB type #Fix to replace incompatible DB type
echo "Updating table name in $BACKUPFILE" echo "Updating table name in -> $BACKUPFILE"
sed -i $BACKUPFILE -e 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' #sed -i $BACKUPFILE -e 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g'
#Fix encoding for dev DB and exclude system tables
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' $BACKUPFILE
sed -r '/INSERT INTO `(sys|mysql)`/d' $BACKUPFILE > $BACKUPFILE
echo "Removing and syncing static files" echo "Removing and syncing static files"
rm -r /home/mab/ss/staticFiles/* rm -r /home/mab/ss/staticFiles/*
@ -38,8 +43,20 @@ rsync -e 'ssh -p 13328' -hazC --update mab@solidscribe.com:/home/mab/pi/staticFi
echo "Updating Database" echo "Updating Database"
mysql -u root --password="$DEVDBPASS" < $BACKUPFILE mysql -u root --password="$DEVDBPASS" < $BACKUPFILE
## Optimize Database Tables
# mysqlcheck --all-databases
mysqlcheck --all-databases -o -u root --password="$DEVDBPASS" --silent
# mysqlcheck --all-databases --auto-repair
# mysqlcheck --all-databases --analyze
# Fix an issues with DB after messing around with it
mysql_upgrade -u root --password="$DEVDBPASS"
#clean up extracted and modified SQL dumps
rm *.sql rm *.sql
echo '-------' echo '-------'
echo "Applied Prod database to Dev. LastFile: $BACKUPFILE" echo "Applied Prod database to Dev. LastFile: $BACKUPFILE"
echo '-------' echo '-------'

View File

@ -200,6 +200,11 @@ export default {
this.blockUntilNextRequest = true this.blockUntilNextRequest = true
}) })
//Track users active sessions
this.$io.on('update_active_user_count', countData => {
this.$store.commit('setActiveSessions', countData)
})
}, },
computed: { computed: {
loggedIn () { loggedIn () {

View File

@ -330,9 +330,14 @@ i.green.icon.icon.icon.icon {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
} }
.note-card-text code,
.squire-box code,
.note-card-text pre, .note-card-text pre,
.squire-box pre { .squire-box pre {
/*word-wrap: break-word;*/ /*word-wrap: break-word;*/
display: inline-block;
border-left: 2px solid var(--main-accent);
padding-left: 15px;
} }
.note-card-text p, .note-card-text p,
.squire-box p { .squire-box p {
@ -880,6 +885,14 @@ i.green.icon.icon.icon.icon {
-webkit-transform-origin: left center; -webkit-transform-origin: left center;
transform-origin: left center; transform-origin: left center;
} }
@media only screen and (max-width: 740px) {
/*hide tooltips on mobile*/
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
visibility: visible;
opacity: 0;
}
}
.glint:after { .glint:after {

View File

@ -154,16 +154,20 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.icon-button, .color-button { .icon-button, .color-button {
height: 40px; height: 40px;
width: calc(10% - 7px); width: calc(15% - 1px);
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
border: 1px solid grey; border: 1px solid grey;
text-align: center; text-align: center;
padding: 5px 0 0; padding: 5px 0px 0 0;
border-radius: 4px; border-radius: 4px;
box-shadow: 0px 1px 3px 0px #3e3e3e; box-shadow: 0px 1px 3px 0px #3e3e3e;
margin: 7px 7px 0 0; margin: 2px 2px 0 0;
box-sizing: border-box;
}
.color-button {
width: calc(10% - 4px);
} }
.rounded { .rounded {
border-radius: 5px; border-radius: 5px;

View File

@ -1,12 +1,12 @@
<style scoped> <style scoped>
.slotholder { .slotholder {
height: 100vh; height: 100vh;
width: 155px; width: 180px;
display: block; display: block;
float: left; float: left;
} }
.global-menu { .global-menu {
width: 155px; width: 180px;
/* background: #221f2b; */ /* background: #221f2b; */
background: #221f2b; background: #221f2b;
margin: 0; margin: 0;
@ -21,7 +21,7 @@
} }
.menu-logo-display { .menu-logo-display {
width: 27px; width: 27px;
margin: 5px 0 0 41px; margin: 5px 0 0 55px;
display: inline-block; display: inline-block;
height: auto; height: auto;
} }
@ -54,9 +54,6 @@
text-decoration: none; text-decoration: none;
} }
.router-link-active i {
/*color: #16ab39;*/
}
.router-link-active { .router-link-active {
background-color: #534c68; background-color: #534c68;
} }
@ -141,6 +138,17 @@
.mobile-button.active { .mobile-button.active {
background-color: transparent; background-color: transparent;
} }
.single-line-text {
width: calc(100%);
/*margin: 5px 10px;*/
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.faded {
color: var(--dark_border_color);
}
</style> </style>
@ -170,7 +178,6 @@
</span> </span>
</div> </div>
<!-- open straight to note --> <!-- open straight to note -->
<router-link <router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']" v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
@ -198,8 +205,8 @@
</router-link> </router-link>
<!-- menu --> <!-- menu -->
<div class="mobile-button"> <div class="mobile-button" v-on:click="collapseMenu">
<i class="green link bars icon" v-on:click="collapseMenu"></i> <i class="green link bars icon" ></i>
Menu Menu
</div> </div>
@ -220,12 +227,12 @@
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<div v-if="!disableNewNote" @click="createNote" class="menu-item menu-item menu-button"> <div v-if="!disableNewNote" @click="createNote" class="menu-item menu-item menu-button">
<div class="ui green button"> <div class="ui green fluid compact button">
<i class="plus icon"></i>New Note <i class="plus icon"></i>New Note
</div> </div>
</div> </div>
<div v-if="disableNewNote" class="menu-item menu-item menu-button"> <div v-if="disableNewNote" class="menu-item menu-item menu-button">
<div class="ui basic button"> <div class="ui basic fluid compact button">
<i class="plus loading icon"></i>New Note <i class="plus loading icon"></i>New Note
</div> </div>
</div> </div>
@ -238,14 +245,19 @@
</router-link> </router-link>
<div> <div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)">
<i class="grey mail outline icon"></i>Inbox <i class="grey paper plane outline icon"></i>Shared
<counter v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']" class="float-right" number-id="sharedToNotes" />
</div> </div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0">
<i class="grey archive icon"></i>Archived <i class="grey archive icon"></i>Archived
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
<counter v-if="$store.getters.totals && $store.getters.totals['archivedNotes']" class="float-right" number-id="archivedNotes" />
</div> </div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0">
<i class="grey trash alternate outline icon"></i>Trashed <i class="grey trash alternate outline icon"></i>Trashed
<counter v-if="$store.getters.totals && $store.getters.totals['trashedNotes']" class="float-right" number-id="trashedNotes" />
</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> -->
<!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> --> <!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> -->
@ -320,6 +332,25 @@
</div> </div>
</div> </div>
<!-- Tags -->
<div class="menu-section" v-if="gotTags()">
<div class="menu-item">
<i class="green tags icon"></i>
Tags
</div>
</div>
<div v-if="gotTags()">
<div class="menu-section"
v-for="(data, tag) in $store.getters.totals['tags']">
<router-link class="menu-item menu-button" :to="`/search/tags/${tag}`">
<span class="single-line-text">
<!-- <i class="small grey tag icon"></i> -->
<span class="float-right">{{ data.uses }}</span>
<span class="faded"> #</span> {{ tag }}</span>
</router-link>
</div>
</div>
<div v-on:click="reloadPage" class="version-display" v-if="version != 0" > <div v-on:click="reloadPage" class="version-display" v-if="version != 0" >
<i :class="`${getVersionIcon()} icon`"></i> {{ version }} <i :class="`${getVersionIcon()} icon`"></i> {{ version }}
</div> </div>
@ -379,6 +410,16 @@
}, },
}, },
methods: { methods: {
gotTags(){
if(this.loggedIn && this.$store.getters.totals && this.$store.getters.totals.tags
&& Object.keys(this.$store.getters.totals.tags).length
){
return true
}
return false
},
logout() { logout() {
this.$router.push('/') this.$router.push('/')

View File

@ -39,9 +39,9 @@
.loading-container { .loading-container {
text-align: center; text-align: center;
width: 100%; width: 100%;
min-height: 100px; /*min-height: 100px;*/
margin: 20px 0; margin: 20px 0;
padding: 40px; /*padding: 40px;*/
border-radius: 7px; border-radius: 7px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<div v-on:keyup.enter="login()"> <div>
<!-- thicc form display --> <!-- thicc form display -->
<div v-if="!thin" class="ui large form"> <div v-if="!thin" class="ui large form" v-on:keyup.enter="register()">
<div class="field"> <div class="field">
<div class="ui input"> <div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail"> <input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
@ -24,14 +24,14 @@
<div class="ui fluid buttons"> <div class="ui fluid buttons">
<div v-on:click="register()" class="ui button"> <div v-on:click="register()" class="ui green button" :class="{ 'disabled':(username.length == 0 || password.length == 0)}">
<i class="plug icon"></i> <i class="plug icon"></i>
Sign Up Sign Up
</div> </div>
<div class="or"></div> <div class="or"></div>
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button"> <div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui button">
<i class="power icon"></i> <i class="power icon"></i>
Login Login
</div> </div>
@ -49,7 +49,7 @@
</div> </div>
<!-- Thin form display --> <!-- Thin form display -->
<div v-if="thin" class="ui small form"> <div v-if="thin" class="ui small form" v-on:keyup.enter="login()">
<div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA --> <div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA -->
<div class="ui grid"> <div class="ui grid">
@ -228,7 +228,6 @@
<style type="text/css" scoped="true"> <style type="text/css" scoped="true">
.small-terms { .small-terms {
display: inline-block; display: inline-block;
text-align: right;
width: 100%; width: 100%;
font-size: 0.9em; font-size: 0.9em;
} }

View File

@ -7,38 +7,16 @@
> >
<!-- Giant Edit Note Menu --> <!-- Giant Edit Note Menu -->
<div class="edit-menu glint" :class="{ 'slide-out-top':(sizeDown == true) }"> <div class="edit-menu" :class="{ 'slide-out-top':(sizeDown == true) }">
<!-- edit spacer is disabled, it is helpful if menu gets bigger. It adds a left margin, starting the icons at the edge of the note --> <!-- edit spacer is disabled, it is helpful if menu gets bigger. It adds a left margin, starting the icons at the edge of the note -->
<div class="edit-spacer"></div> <div class="edit-spacer"></div>
<div class="menu-top-half"> <div class="menu-top-half">
<div class="edit-button" v-on:click=" hash=0; save() " :data-tooltip="`Save\n(CTRL + S)`" data-position="bottom center">
<i class="icons">
<i class="grey save outline icon"></i>
<i v-if="statusText == 'saved'" class="green small bottom left corner check icon"></i>
<i v-if="statusText == 'saving'" class="small purple bottom left corner double angle up icon"></i>
</i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="bottom center" :class="{'edit-active':activeToDo}">
<i class="tasks icon"></i>
</div>
<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="bottom center" :class="{'edit-active':activeList}">
<i class="list ol icon"></i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center"> <div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center">
<i class="font icon"></i> <i class="font icon"></i>
</div> <div class="font-color-bar" :style="{'background':lastUsedColor}"></div>
<div v-if="lastUsedColor" class="edit-button" v-on:click="applyLastUsedColor()" data-tooltip="Last Color" data-position="bottom center" :style="{'color':lastUsedColor}">
<i class="eye dropper icon"></i>
</div> </div>
<div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}"> <div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}">
@ -53,10 +31,22 @@
<i class="underline icon"></i> <i class="underline icon"></i>
</div> </div>
<div class="edit-button" v-on:click="modifyCode('1.4em')" data-tooltip="Quote" data-position="bottom center" :class="{'edit-active':activeCode}">
<i class="quote right icon"></i>
</div>
<div class="edit-button" v-on:click="modifyFont('0.9em')" data-tooltip="Sub Title" data-position="bottom center" :class="{'edit-active':activeSubTitle}">
<i class="small text height icon"></i>
</div>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" :class="{'edit-active':activeTitle}"> <div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" :class="{'edit-active':activeTitle}">
<i class="text height icon"></i> <i class="text height icon"></i>
</div> </div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center">
<i class="remove format icon"></i>
</div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
<div class="edit-button" v-on:click="editor.increaseQuoteLevel()" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center"> <div class="edit-button" v-on:click="editor.increaseQuoteLevel()" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center">
@ -75,17 +65,21 @@
<i class="border all icon"></i> <i class="border all icon"></i>
</div> --> </div> -->
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="bottom center" :class="{'edit-active':activeToDo}">
<i class="tasks icon"></i>
</div>
<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="bottom center" :class="{'edit-active':activeList}">
<i class="list ol icon"></i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center"> <div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center">
<i class="grip lines icon"></i> <i class="grip lines icon"></i>
</div> </div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center">
<i class="remove format icon"></i>
</div>
<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center">
<i class="undo icon"></i>
</div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
@ -114,28 +108,19 @@
<div class="edit-divide"></div> <div class="edit-divide"></div>
<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center">
<div class="edit-button" v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="bottom center"> <i class="reply icon"></i>
<i class="green eye icon"></i> {{ usersOnNote }}
</div> </div>
<!-- <div class="edit-button done-button" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
<div class="edit-button" v-on:click="simulateTyping()"> <!-- <i class="green close icon"></i> -->
<i class="purple bolt icon"></i>
</div> -->
<div class="edit-button" v-if="diffsApplied > 0" :data-tooltip="`Unsaved Changes`" data-position="bottom center">
+{{ diffsApplied }}
</div>
<div class="edit-button ui" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
<i class="green close icon"></i>
<span class="ui green text">Done</span> <span class="ui green text">Done</span>
</div> </div>
</div> </div>
</div> </div>
<!-- invisible menu item, creates BG for bottom menu -->
<div class="bottom-edit-menu"></div> <div class="bottom-edit-menu"></div>
<div class="input-container-wrapper" <div class="input-container-wrapper"
@ -146,7 +131,7 @@
<!-- Loading indicator --> <!-- Loading indicator -->
<transition name="fade"> <transition name="fade">
<div v-if="loading || forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> <div v-if="loading && forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<div class="loading-text"> <div class="loading-text">
<loading-icon :message="loadingMessage" /> <loading-icon :message="loadingMessage" />
</div> </div>
@ -173,6 +158,12 @@
<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" <div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }"
v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)"
:style="{ 'background-color':styleObject['noteBackground'] }"> :style="{ 'background-color':styleObject['noteBackground'] }">
<span>
{{ lastVisibilityState }}
</span>
<span class="add-mini-tag" v-if="allTags.length == 0"> <span class="add-mini-tag" v-if="allTags.length == 0">
<i class="tags icon"></i>Add Tags <i class="tags icon"></i>Add Tags
</span> </span>
@ -183,6 +174,28 @@
+ +
</span> </span>
<span class="status-menu" v-on:click=" hash=0; save()">
<span v-if="diffsApplied > 0">
+{{ diffsApplied }} Unsaved Changes
</span>
<span v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="left center">
<i class="green eye icon"></i> {{ usersOnNote }}
</span>
<span v-if="statusText == 'modified'" data-position="left center" data-tooltip="Modified">
<i class="grey asterisk icon"></i>
</span>
<span v-if="statusText == 'saving'" data-position="left center" data-tooltip="Saving">
<i class="grey upload icon"></i>
</span>
<span v-if="statusText == 'saved'" data-position="left center" data-tooltip="Saved">
<i class="grey check icon"></i>
</span>
</span>
</div> </div>
<!-- Squire Box --> <!-- Squire Box -->
@ -202,6 +215,7 @@
<!-- color picker --> <!-- color picker -->
<color-tooltip <color-tooltip
v-if="colorpicker" v-if="colorpicker"
:last-used-color="lastUsedColor"
v-on:color="color => modifyColor(color)" v-on:color="color => modifyColor(color)"
v-on:close="colorpicker = false" v-on:close="colorpicker = false"
/> />
@ -234,7 +248,7 @@
<side-slide-menu v-if="options" v-on:close="options = false" name="note-options"> <side-slide-menu v-if="options" v-on:close="options = false" name="note-options">
<div class="ui basic padded segment"> <div class="ui basic padded segment">
<div class="ui grid"> <div class="ui compact stackable grid">
<div class="eight wide column"> <div class="eight wide column">
<div class="ui dividing header"> <div class="ui dividing header">
@ -370,7 +384,7 @@
data(){ data(){
return { return {
loading: true, loading: true,
forceShowLoading: true, forceShowLoading: false,
loadingMessage: 'Loading Note', loadingMessage: 'Loading Note',
currentNoteId: 0, currentNoteId: 0,
modified: false, modified: false,
@ -385,6 +399,7 @@
lastNoteHash: null, lastNoteHash: null,
saveDebounce: null, //Prevent save from being called numerous times quickly saveDebounce: null, //Prevent save from being called numerous times quickly
updated: 'Never', updated: 'Never',
lastInteractionTimestamp:null, //Tracks when note was loaded and last saved/refreshed
editDebounce: null, editDebounce: null,
textChangedDebounce: null, textChangedDebounce: null,
keyPressesCounter: 0, //Determine keys pressed between saves keyPressesCounter: 0, //Determine keys pressed between saves
@ -478,12 +493,12 @@
}, },
mounted: function() { mounted: function() {
//Show loading for a minimum time //Show loading if note has not loaded in 500ms
setTimeout(()=>{ setTimeout(()=>{
this.forceShowLoading = false this.forceShowLoading = true
}, 100) }, 500)
// document.addEventListener('visibilitychange', this.checkForUpdatedNote) document.addEventListener('visibilitychange', this.checkForUpdatedNote)
//Init squire as early as possible //Init squire as early as possible
if(this.editor && this.editor.destroy){ if(this.editor && this.editor.destroy){
@ -561,7 +576,6 @@
if(!this.$store.getters.getIsUserOnMobile){ if(!this.$store.getters.getIsUserOnMobile){
this.editor.focus() this.editor.focus()
this.editor.moveCursorToEnd() this.editor.moveCursorToEnd()
} }
//Set up websockets after squire is set up //Set up websockets after squire is set up
@ -596,10 +610,6 @@
//If the offset is triggered with a negative offset, it means the before element was clicked //If the offset is triggered with a negative offset, it means the before element was clicked
if(e.offsetX < -5){ if(e.offsetX < -5){
//Will hide keyboard if clicked on mobile
if(this.$store.getters.getIsUserOnMobile){
this.editor.blur()
}
//Area before element was clicked, they clicked the checkbox //Area before element was clicked, they clicked the checkbox
if (el.className === 'active'){ if (el.className === 'active'){
@ -607,6 +617,21 @@
} else { } else {
el.className = 'active'; el.className = 'active';
} }
//Trigger keyup event to save list changes
this.onKeyup(e)
//Will hide keyboard if clicked on mobile
if(this.$store.getters.getIsUserOnMobile){
setTimeout(() => {
document.activeElement.blur()
e.preventDefault()
return
}, 25)
}
} }
@ -660,10 +685,10 @@
}) })
//Show and hide additional toolbars //Show and hide additional toolbars
this.editor.addEventListener('focus', e => { // this.editor.addEventListener('focus', e => {
}) // })
this.editor.addEventListener('blur', e => { // this.editor.addEventListener('blur', e => {
}) // })
}, },
openEditAttachment(){ openEditAttachment(){
@ -723,35 +748,8 @@
return return
} }
//Set up local data //Setup all responsive vue data
this.currentNoteId = this.noteid this.setupLoadedNoteData(response)
this.rawTextId = response.data.rawTextId
this.shareUsername = response.data.shareUsername
this.created = response.data.created
this.updated = response.data.updated
this.noteTitle = ''
if(response.data.title){
this.noteTitle = response.data.title
}
this.noteText = response.data.text
this.lastNoteHash = this.hashString( response.data.text )
// this.diffNoteText = response.data.text
//Setup note tags
this.allTags = response.data.tags ? response.data.tags.split(','):[]
//Set up note colors
if(response.data.color){
this.styleObject = JSON.parse(response.data.color)
}
if(response.data.pinned != null){
this.pinned = response.data.pinned
}
this.archived = response.data.archived
this.attachmentCount = response.data.attachment_count
this.loading = false this.loading = false
@ -768,6 +766,43 @@
console.log('Could not fetch note') console.log('Could not fetch note')
} }
}, },
setupLoadedNoteData(response){
//All the data returned by the server, setup locally in vue component
//Set up local data
this.currentNoteId = this.noteid
this.rawTextId = response.data.rawTextId
this.shareUsername = response.data.shareUsername
this.created = response.data.created
this.updated = response.data.updated
this.lastInteractionTimestamp = +new Date
this.noteTitle = ''
if(response.data.title){
this.noteTitle = response.data.title
}
this.noteText = response.data.text
this.lastNoteHash = this.hashString( response.data.text )
// this.diffNoteText = response.data.text
//Setup note tags
this.allTags = response.data.tags ? response.data.tags.split(','):[]
//Set up note colors
if(response.data.color){
this.styleObject = JSON.parse(response.data.color)
}
if(response.data.pinned != null){
this.pinned = response.data.pinned
}
this.archived = response.data.archived
this.attachmentCount = response.data.attachment_count
return true
},
//Called on squire event for keyup //Called on squire event for keyup
diffText(event){ diffText(event){
@ -846,10 +881,11 @@
//Save after x keystrokes //Save after x keystrokes
this.keyPressesCounter = (this.keyPressesCounter + 1) this.keyPressesCounter = (this.keyPressesCounter + 1)
if(this.keyPressesCounter > 25){ if(this.keyPressesCounter > 60){
this.keyPressesCounter = 0 this.keyPressesCounter = 0
this.save() this.save()
} }
}, },
save(force = false){ save(force = false){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -890,6 +926,7 @@
axios.post('/api/note/update', postData).then( response => { axios.post('/api/note/update', postData).then( response => {
this.statusText = 'saved' this.statusText = 'saved'
this.updated = +new Date this.updated = +new Date
this.lastInteractionTimestamp = +new Date
this.modified = true this.modified = true
this.diffsApplied = 0 this.diffsApplied = 0
@ -902,27 +939,35 @@
}, },
checkForUpdatedNote(){ checkForUpdatedNote(){
//Ignore visibility changes, handle this with socket IO const now = +new Date
//Just keep it always up to date if user is on note //Only check every 3 seconds
return const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (2 * 1000)
//If user leaves page then returns to page, reload the first batch //If user leaves page then returns to page, reload the first batch
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){ if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible' && checkForUpdateTimeout){
// console.log('Checking for note updates after visibility change.')
const postData = { //Focus Regained on Note, check for update
noteId:this.currentNoteId, axios.post('/api/note/get', { 'noteId': this.noteid })
text:this.getText(), .then(response => {
updated: this.updated
} const serverTextHash = this.hashString( response.data.text )
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)
}
})
console.log('Focus regained with note open.')
console.log('Attempting to fix diff text. fix this. Search spleen')
return
} }
//Track visibility state //Keep track of visibility change and last interaction time
this.lastVisibilityState = document.visibilityState this.lastVisibilityState = document.visibilityState
this.lastInteractionTimestamp = +new Date
}, },
hashString(inText){ hashString(inText){
@ -1040,6 +1085,33 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.status-menu {
position: absolute;
right: 10px;
z-index: 1019;
text-align: right;
}
.font-color-bar {
/*width: calc(100% - 8px);*/
height: 3px;
position: absolute;
bottom: 2px;
right: 4px;
left: 4px;
z-index: 0;
background: linear-gradient(
45deg,
rgba(255, 0, 0, 1) 0%,
rgba(208, 222, 33, 1) 20%,
rgba(63, 218, 216, 1) 40%,
rgba(28, 127, 238, 1) 60%,
rgba(186, 12, 248, 1) 80%,
rgba(255, 0, 0, 1) 100%
);
overflow: hidden;
}
.large-close-button { .large-close-button {
position: absolute; position: absolute;
min-height: 55px; min-height: 55px;
@ -1094,6 +1166,7 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: 1100px; max-width: 1100px;
position: relative;
} }
.add-mini-tag { .add-mini-tag {
color: var(--border_color); color: var(--border_color);
@ -1128,7 +1201,7 @@
display: block; display: block;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
z-index: 1019; z-index: 1019;
padding: 3px 5px; padding: 5px 0;
border: none; border: none;
border-bottom: 1px solid var(--menu-accent); border-bottom: 1px solid var(--menu-accent);
text-align: center; text-align: center;
@ -1140,10 +1213,24 @@
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
color: var(--menu-text); color: var(--menu-text);
display: inline-block; display: inline-block;
font-size: 1.4em;
padding: 4px 1px 5px 4px;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
font-size: 1.4em;
box-shadow: 0 0 1px 0 #c4c4c4;
margin: 0 3px 0;
padding: 6px 0 0 0;
text-align: center;
min-width: 32px;
min-height: 32px;
}
.edit-button > i {
font-size: 1em;
padding: 0;
margin: 0;
}
.edit-button > span {
padding: 0 8px;
} }
.edit-button:hover { .edit-button:hover {
background-color: var(--menu-accent); background-color: var(--menu-accent);
@ -1157,7 +1244,7 @@
background-color: var(--menu-accent); background-color: var(--menu-accent);
height: 15px; height: 15px;
width: 1px; width: 1px;
margin: 0 1px; margin: 0 8px;
padding: 0; padding: 0;
} }
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
@ -1253,7 +1340,7 @@
left: 0; left: 0;
right: 0; right: 0;
top: 35px; top: 35px;
bottom: 40px; bottom: 37px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
} }
.note-wrapper { .note-wrapper {
@ -1263,6 +1350,9 @@
.shade1, .shade2 { .shade1, .shade2 {
right: 150%; right: 150%;
} }
.edit-menu {
background-color: var(--dark_border_color);
}
/*menu overwrites */ /*menu overwrites */
.bottom-edit-menu { .bottom-edit-menu {
position: fixed; position: fixed;
@ -1271,14 +1361,19 @@
right: 0; right: 0;
width: 100%; width: 100%;
display: block; display: block;
background-color: var(--small_element_bg_color); /*background-color: var(--small_element_bg_color);*/
background-color: var(--dark_border_color);
z-index: 1012; z-index: 1012;
border: none; border: none;
border-top: 1px solid var(--menu-accent); border-top: 1px solid var(--menu-accent);
min-height: 40px; min-height: 37px;
} }
.edit-menu { .menu-top-half, .menu-bottom-half {
text-align: center; display: flex;
justify-content: space-around;
}
.edit-divide {
display: none;
} }
.menu-bottom-half { .menu-bottom-half {
z-index: 1005; z-index: 1005;
@ -1288,6 +1383,14 @@
right: 0; right: 0;
text-align: center; text-align: center;
} }
.edit-button {
flex-basis: 100%;
margin: 0 2px;
}
.done-button {
flex-basis: 150%;
font-size: 13px;
}
} }

View File

@ -1,8 +1,11 @@
<template> <template>
<div class="note-title-display-card" <div class="note-title-display-card"
:style="{'background-color':color, 'color':fontColor, 'border-color':color }" :style="{'background-color':color, 'color':fontColor, 'border-color':color }"
:class="{'currently-open':(currentlyOpen || showWorking), 'bgboy':triggerClosedAnimation, 'title-view':titleView }" :class="{
> 'currently-open':(currentlyOpen || showWorking),
'ring':triggerClosedAnimation,
'title-view':titleView
}">
<!-- Show title and snippet below it --> <!-- Show title and snippet below it -->
@ -75,8 +78,15 @@
<span class="thin-title" v-if="note.title.length > 0">{{ note.title }}</span> <span class="thin-title" v-if="note.title.length > 0">{{ note.title }}</span>
<!-- snippet --> <!-- snippet -->
<span class="thin-sub" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span> <span class="thick-sub" v-if="note.subtext.length > 0 && note.title.length == 0">
<span v-if="note.title.length == 0 && removeHtml(note.subtext).length == 0">Empty Note</span> {{ removeHtml(note.subtext) }}
</span>
<span class="thin-sub" v-else-if="note.subtext.length > 0">
{{ removeHtml(note.subtext) }}
</span>
<span v-else-if="note.title.length == 0 && removeHtml(note.subtext).length == 0">
Empty Note
</span>
<!-- tags --> <!-- tags -->
<span v-if="note.tags" class="thin-tags" > <span v-if="note.tags" class="thin-tags" >
@ -280,7 +290,7 @@
setTimeout(()=>{ setTimeout(()=>{
//After 3 seconds, hide it //After 3 seconds, hide it
this.triggerClosedAnimation = false this.triggerClosedAnimation = false
}, 3000) }, 1500)
}, 500) }, 500)
@ -472,7 +482,7 @@
max-width: none; max-width: none;
padding: 10px; padding: 10px;
margin: 0; margin: 0;
overflow: hidden; /*overflow: hidden;*/
border-radius: 0; border-radius: 0;
border: none; border: none;
/*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/ /*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/
@ -498,8 +508,17 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 1;
line-clamp: 2; line-clamp: 1;
-webkit-box-orient: vertical;
opacity: 0.85;
}
.thin-container .thick-sub {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
opacity: 0.85; opacity: 0.85;
} }
@ -700,4 +719,36 @@
animation: bgin 4s cubic-bezier(0.19, 1, 0.22, 1) 1; animation: bgin 4s cubic-bezier(0.19, 1, 0.22, 1) 1;
} }
/*switch between ring or BG boy to change save animation*/
.ring {
position: relative;
}
.ring::after {
content: '';
width: 10px;
height: 10px;
border-radius: 100%;
border: 6px solid #00FFCB;
position: absolute;
z-index: 800;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: ring 1.5s 1;
}
@keyframes ring {
0% {
width: 10px;
height: 10px;
opacity: 1;
}
100% {
width: 420px;
height: 420px;
opacity: 0;
}
}
</style> </style>

View File

@ -2,22 +2,43 @@
.colors { .colors {
position: fixed; position: fixed;
z-index: 1023; z-index: 1023;
top: 5px; top: 35px;
/*height: 100px;*/ /*height: 100px;*/
width: 400px; width: 400px;
left: 20%; left: 20%;
} }
.colors-container { .colors-container {
max-width: 370px; /*max-width: 360px;*/
display: flex;
/*flex-direction: column;*/
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
align-content: stretch;
height: 250px;
width: 100%;
} }
.dot { .dot {
display: inline-block; /*display: inline-block;*/
width: 30px;
height: 30px;
border-radius: 30px; border-radius: 30px;
box-shadow: 0px 1px 3px 0px #3e3e3e; box-shadow: 0px 0px 0px 1px inset #3e3e3e;
margin: 7px 7px 0 0; margin: 0 0 2px 2px;
cursor: pointer; cursor: pointer;
flex-basis: 9%;
height: 30px;
text-align: center;
}
.dot > i {
margin: 9px 0 0 0;
color: white;
text-shadow:
1px 1px 2px #3e3e3e,
1px -1px 2px #3e3e3e,
-1px 1px 2px #3e3e3e,
-1px -1px 2px #3e3e3e
;
} }
.shade { .shade {
position: fixed; position: fixed;
@ -30,12 +51,16 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }
.big-shadow {
box-shadow: 0px 4px 5px 1px #a8a8a8;
}
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.colors { .colors {
position: fixed; position: fixed;
left: 0; left: 5px;
right: 0; right: -5px;
top: 0; top: 5px;
width: 95%;
} }
} }
</style> </style>
@ -43,13 +68,15 @@
<template> <template>
<div> <div>
<div class="colors"> <div class="colors">
<div class="ui raised segment"> <div class="ui segment big-shadow">
<h3>Select Text Color</h3>
<div class="colors-container"> <div class="colors-container">
<span <span
v-for="(color,index) in colors" v-for="(color,index) in colors"
class="dot" class="dot"
v-on:click="onColorClick(index)" v-on:click="onColorClick(index)"
:style="`background-color: ${color};`"> :style="`background-color: ${color};`">
<i v-if="lastUsedColor == color" class="check icon"></i>
</span> </span>
</div> </div>
</div> </div>
@ -65,6 +92,7 @@
components:{ components:{
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default 'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
}, },
props: [ 'lastUsedColor' ],
data: function(){ data: function(){
return { return {
hover: false, hover: false,

View File

@ -9,6 +9,8 @@ const SquireButtonFunctions = {
activeList: false, activeList: false,
activeToDo: false, activeToDo: false,
activeColor: null, activeColor: null,
activeCode: false,
activeSubTitle: false,
// //
lastUsedColor: null, lastUsedColor: null,
} }
@ -28,6 +30,8 @@ const SquireButtonFunctions = {
this.activeToDo = false this.activeToDo = false
this.activeColor = null this.activeColor = null
this.activeUnderline = false this.activeUnderline = false
this.activeCode = false
this.activeSubTitle = false
if(e.path.indexOf('>U>') > -1 || e.path.search(/U$/) > -1){ if(e.path.indexOf('>U>') > -1 || e.path.search(/U$/) > -1){
this.activeUnderline = true this.activeUnderline = true
@ -38,15 +42,21 @@ const SquireButtonFunctions = {
if(e.path.indexOf('>I') > -1){ if(e.path.indexOf('>I') > -1){
this.activeItalics = true this.activeItalics = true
} }
if(e.path.indexOf('fontSize') > -1){ if(e.path.indexOf('fontSize=1.4em') > -1){
this.activeTitle = true this.activeTitle = true
} }
if(e.path.indexOf('fontSize=0.9em') > -1){
this.activeSubTitle = true
}
if(e.path.indexOf('OL>LI') > -1){ if(e.path.indexOf('OL>LI') > -1){
this.activeList = true this.activeList = true
} }
if(e.path.indexOf('UL>LI') > -1){ if(e.path.indexOf('UL>LI') > -1){
this.activeToDo = true this.activeToDo = true
} }
if(e.path.indexOf('CODE') > -1){
this.activeCode= true
}
const colorIndex = e.path.indexOf('color=') const colorIndex = e.path.indexOf('color=')
if(colorIndex > -1){ if(colorIndex > -1){
//Get all digigs after color index, then limit to 3 //Get all digigs after color index, then limit to 3
@ -143,6 +153,12 @@ const SquireButtonFunctions = {
this.editor.italic() this.editor.italic()
} }
}, },
modifyCode(){
this.selectLineIfNoSelect()
this.editor.toggleCode()
},
undoCustom(){ undoCustom(){
//The same as pressing CTRL + Z //The same as pressing CTRL + Z
// this.editor.focus() // this.editor.focus()

View File

@ -27,6 +27,10 @@
v-on:tagClick="tagId => toggleTagFilter(tagId)" v-on:tagClick="tagId => toggleTagFilter(tagId)"
/> />
<span>
Active Sessions {{ $store.getters.getActiveSessions }}
</span>
<div class="ui right floated basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0"> <div class="ui right floated basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<span v-if="titleView"> <span v-if="titleView">
<i class="th icon"></i> Tiles <i class="th icon"></i> Tiles
@ -51,7 +55,7 @@
</div> </div>
<div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress"> <div class="sixteen wide column" v-if="searchTerm.length > 0 && !showLoading">
<h2 class="ui header"> <h2 class="ui header">
<div class="content"> <div class="content">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}" {{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
@ -63,11 +67,15 @@
</div> </div>
<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column"> <div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
<h2>Archived Notes</h2> <h2>
<i class="green archive icon"></i>
Archived Notes</h2>
</div> </div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1"> <div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
<h2>Trash <h2>
<i class="green trash alternate outline icon"></i>
Trashed Notes
<span>({{ $store.getters.totals['trashedNotes'] }})</span> <span>({{ $store.getters.totals['trashedNotes'] }})</span>
<div class="ui right floated basic button" data-tooltip="This doesn't work yet"> <div class="ui right floated basic button" data-tooltip="This doesn't work yet">
<i class="poo storm icon"></i> <i class="poo storm icon"></i>
@ -77,7 +85,8 @@
</div> </div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1"> <div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
<h2>Shared Notes</h2> <h2><i class="green paper plane outline icon"></i>
Shared Notes</h2>
</div> </div>
<div class="sixteen wide column" v-if="tagSuggestions.length > 0"> <div class="sixteen wide column" v-if="tagSuggestions.length > 0">
@ -88,17 +97,6 @@
</div> </div>
</div> </div>
<!-- found attachments -->
<div class="sixteen wide column" v-if="foundAttachments.length > 0">
<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5>
<attachment-display
v-for="item in foundAttachments"
:item="item"
:key="item.id"
:search-params="{}"
/>
</div>
<!-- Note title card display --> <!-- Note title card display -->
<div class="sixteen wide column"> <div class="sixteen wide column">
@ -130,11 +128,25 @@
</div> </div>
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" /> <div class="loading-section" v-if="showLoading">
<loading-icon message="Decrypting Notes" />
</div>
</div> </div>
</div> </div>
<!-- found attachments -->
<div class="sixteen wide column" v-if="foundAttachments.length > 0">
<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5>
<attachment-display
v-for="item in foundAttachments"
:item="item"
:key="item.id"
:search-params="{}"
/>
</div>
</div> </div>
@ -180,10 +192,10 @@
//Load up notes in batches //Load up notes in batches
firstLoadBatchSize: 10, //First set of rapidly loaded notes firstLoadBatchSize: 10, //First set of rapidly loaded notes
batchSize: 25, //Size of batch loaded when user scrolls through current batch batchSize: 20, //Size of batch loaded when user scrolls through current batch
batchOffset: 0, //Tracks the current batch that has been loaded batchOffset: 0, //Tracks the current batch that has been loaded
loadingBatchTimeout: null, //Limit how quickly batches can be loaded loadingBatchTimeout: null, //Limit how quickly batches can be loaded
loadingInProgress: false, showLoading: false,
scrollLoadEnabled: true, scrollLoadEnabled: true,
//Clear button is not visible //Clear button is not visible
@ -329,7 +341,7 @@
//Reload page content - don't trigger if load is in progress //Reload page content - don't trigger if load is in progress
this.$bus.$on('note_reload', () => { this.$bus.$on('note_reload', () => {
if(!this.loadingInProgress){ if(!this.showLoading){
this.reset() this.reset()
} }
}) })
@ -342,6 +354,8 @@
//update note on visibility change //update note on visibility change
// document.addEventListener('visibilitychange', this.visibiltyChangeAction); // document.addEventListener('visibilitychange', this.visibiltyChangeAction);
//Find previously stored notes, cache for 20 hours, load them and compare
}, },
beforeDestroy(){ beforeDestroy(){
window.removeEventListener('scroll', this.onScroll) window.removeEventListener('scroll', this.onScroll)
@ -373,12 +387,29 @@
'$route.params.id': function(id){ '$route.params.id': function(id){
//Open note on ID, null id will close note //Open note on ID, null id will close note
this.activeNoteId1 = id this.activeNoteId1 = id
},
'$route' (to, from) {
// Reload the notes if returning to this page
if(to.fullPath == '/notes' && !from.fullPath.includes('/notes/open/')){
this.reset()
}
//Lookup tags set in URL
if(to.params.tag && this.$store.getters.totals && this.$store.getters.totals['tags'][to.params.tag]){
//Lookup tag in store by string
const tagObject = this.$store.getters.totals['tags'][to.params.tag]
//Pull key out of string and load tags for that key
this.toggleTagFilter(tagObject.id)
return
}
} }
}, },
methods: { methods: {
onScroll(e){
console.log('Scroll')
},
toggleTitleView(){ toggleTitleView(){
this.titleView = !this.titleView this.titleView = !this.titleView
}, },
@ -418,6 +449,10 @@
}, },
onScroll(e){ onScroll(e){
if(!this.scrollLoadEnabled){
return
}
clearTimeout(this.loadingBatchTimeout) clearTimeout(this.loadingBatchTimeout)
this.loadingBatchTimeout = setTimeout(() => { this.loadingBatchTimeout = setTimeout(() => {
@ -427,12 +462,12 @@
const height = document.getElementById('app').scrollHeight const height = document.getElementById('app').scrollHeight
//Load if less than 500px from the bottom //Load if less than 500px from the bottom
if(((height - scrolledDown) < 500) && this.scrollLoadEnabled && !this.loadingInProgress){ if(((height - scrolledDown) < 500) && this.scrollLoadEnabled){
this.search(false, this.batchSize, true) this.search(true, this.batchSize, true)
} }
}, 30) }, 50)
return return
@ -553,19 +588,14 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//Don't double load note batches //Don't double load note batches
if(this.loadingInProgress){ if(this.showLoading){
console.log('Loading already in progress') console.log('Loading already in progress')
return resolve(false) return resolve(false)
} }
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){ if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => { this.batchOffset = 0 // Reset batch offset if we are not merging note batches or new set will be offset from current and overwrite current set with second batch
this.noteSections[key] = []
})
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
} }
this.searchResultsCount = 0
//Remove all filter limits from previous queries //Remove all filter limits from previous queries
delete this.fastFilters.limitSize delete this.fastFilters.limitSize
@ -593,25 +623,40 @@
} }
//Perform search - or die //Perform search - or die
this.loadingInProgress = true this.showLoading = showLoading
this.scrollLoadEnabled = false
axios.post('/api/note/search', postData) axios.post('/api/note/search', postData)
.then(response => { .then(response => {
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
}
this.searchResultsCount = 0
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad) // console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
//Save the number of notes just loaded //Save the number of notes just loaded
this.batchOffset += response.data.notes.length this.batchOffset += response.data.notes.length
//Enable or disable scroll loading //Enable scroll loading if endpoint retured notes
this.scrollLoadEnabled = response.data.notes.length > 0 this.scrollLoadEnabled = response.data.notes.length > 0
if(response.data.total > 0){ if(response.data.total > 0){
this.searchResultsCount = response.data.total this.searchResultsCount = response.data.total
} }
this.loadingInProgress = false this.showLoading = false
this.generateNoteCategories(response.data.notes, mergeExisting) this.generateNoteCategories(response.data.notes, mergeExisting)
//cache initial notes for faster reloads
if(!mergeExisting && this.showClear == false){
const cachedNotesJson = JSON.stringify(response.data.notes)
localStorage.setItem('snippetCache', cachedNotesJson)
}
return resolve(true) return resolve(true)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') })
@ -726,7 +771,7 @@
//clear out tags //clear out tags
this.searchTags = [] this.searchTags = []
this.tagSuggestions = [] this.tagSuggestions = []
this.loadingInProgress = false this.showLoading = false
this.searchTerm = '' this.searchTerm = ''
this.$bus.$emit('reset_fast_filters') //Clear out search this.$bus.$emit('reset_fast_filters') //Clear out search
@ -743,9 +788,21 @@
filter[options[index]] = 1 filter[options[index]] = 1
this.fastFilters = filter this.fastFilters = filter
//If notes exist in cache, load them up
let showLoading = true
const cachedNotesJson = localStorage.getItem('snippetCache')
const cachedNotes = JSON.parse(cachedNotesJson)
if(cachedNotes && cachedNotes.length > 0 && !this.showClear){
//Load cache. do not merge existing
this.generateNoteCategories(cachedNotes, false)
showLoading = false
}
//Fetch First batch of notes with new filter //Fetch First batch of notes with new filter
this.search(true, this.firstLoadBatchSize, false) this.search(showLoading, this.batchSize, false)
.then( r => this.search(false, this.batchSize, true)) // .then( r => this.search(false, this.batchSize, true))
} }
} }
} }
@ -769,4 +826,35 @@
.note-card-section + .note-card-section { .note-card-section + .note-card-section {
padding: 15px 0 0; padding: 15px 0 0;
} }
.loading-section {
position: fixed;
bottom: 40px;
padding: 0 10px;
right: 5px;
box-shadow: 0 1px 3px 0 #656565;
border-radius: 6px;
background-color: var(--small_element_bg_color);
opacity: 0.9;
font-size: 0.7em;
}
/*html, body {
height: 100%;
}
.wrap {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}*/
</style> </style>

View File

@ -43,6 +43,12 @@ export default new Router({
meta: {title: 'Open Note'}, meta: {title: 'Open Note'},
component: NotesPage, component: NotesPage,
}, },
{
path: '/search/tags/:tag',
name: 'Search Notes',
meta: {title: 'Search Notes'},
component: NotesPage,
},
{ {
path: '/notes/open/:id/menu/:openMenu', path: '/notes/open/:id/menu/:openMenu',
name: 'Open Note Menu', name: 'Open Note Menu',

View File

@ -10,6 +10,7 @@ export default new Vuex.Store({
nightMode: false, nightMode: false,
isUserOnMobile: false, isUserOnMobile: false,
userTotals: null, userTotals: null,
activeSessions: 0,
}, },
mutations: { mutations: {
setUsername(state, username){ setUsername(state, username){
@ -24,6 +25,7 @@ export default new Vuex.Store({
localStorage.removeItem('loginToken') localStorage.removeItem('loginToken')
localStorage.removeItem('username') localStorage.removeItem('username')
localStorage.removeItem('currentVersion') localStorage.removeItem('currentVersion')
localStorage.removeItem('snippetCache')
delete axios.defaults.headers.common['authorizationtoken'] delete axios.defaults.headers.common['authorizationtoken']
state.username = null state.username = null
state.userTotals = null state.userTotals = null
@ -119,6 +121,10 @@ export default new Vuex.Store({
// Object.keys(totalsObject).forEach( key => { // Object.keys(totalsObject).forEach( key => {
// console.log(key + ' -- ' + totalsObject[key]) // console.log(key + ' -- ' + totalsObject[key])
// }) // })
},
setActiveSessions(state, countData){
//Count of the number of active socket.io sessions for this user
state.activeSessions = countData
} }
}, },
getters: { getters: {
@ -144,6 +150,9 @@ export default new Vuex.Store({
totals: state => { totals: state => {
return state.userTotals return state.userTotals
}, },
getActiveSessions: state => {
return state.activeSessions
}
}, },
actions: { actions: {
fetchAndUpdateUserTotals ({ commit }) { fetchAndUpdateUserTotals ({ commit }) {

View File

@ -51,12 +51,31 @@ io.on('connection', function(socket){
Auth.decodeToken(token) Auth.decodeToken(token)
.then(userData => { .then(userData => {
socket.join(userData.userId) socket.join(userData.userId)
//Track active logged in user accounts
const usersInRoom = io.sockets.adapter.rooms[userData.userId]
io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
}).catch(error => { }).catch(error => {
//Don't add user to room if they are not logged in //Don't add user to room if they are not logged in
// console.log(error) // console.log(error)
}) })
}) })
socket.on('get_active_user_count', token => {
Auth.decodeToken(token)
.then(userData => {
socket.join(userData.userId)
//Track active logged in user accounts
const usersInRoom = io.sockets.adapter.rooms[userData.userId]
io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
}).catch(error => {
// console.log(error)
})
})
//Renew Session tokens when users request a new one //Renew Session tokens when users request a new one
socket.on('renew_session_token', token => { socket.on('renew_session_token', token => {
@ -205,7 +224,7 @@ io.on('connection', function(socket){
} }
}) })
socket.on('disconnect', function(){ socket.on('disconnect', function(socket){
// console.log('user disconnected'); // console.log('user disconnected');
}); });
}); });

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.3.3' const version = '3.4.2'
//Login a user, if that user does not exist create them //Login a user, if that user does not exist create them
//Issues login token //Issues login token
@ -196,7 +196,9 @@ User.register = (username, password) => {
User.getCounts = (userId) => { User.getCounts = (userId) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let countTotals = {} let countTotals = {
tags: {}
}
const userHash = cs.hash(String(userId)).toString('base64') const userHash = cs.hash(String(userId)).toString('base64')
db.promise().query( db.promise().query(
@ -244,12 +246,37 @@ User.getCounts = (userId) => {
Object.assign(countTotals, rows[0][0]) //combine results Object.assign(countTotals, rows[0][0]) //combine results
//Count usages of user tags, sort by most popular
return db.promise().query(`
SELECT
tag.text, COUNT(tag_id) AS uses, tag.id
FROM note_tag
JOIN tag ON (tag.id = note_tag.tag_id)
WHERE user_id = ?
GROUP BY tag_id
ORDER BY uses DESC
LIMIT 5
`, [userId])
}).then( (rows, fields) => {
//Convert everything to an int or 0 //Convert everything to an int or 0
Object.keys(countTotals).forEach( key => { Object.keys(countTotals).forEach( key => {
const count = parseInt(countTotals[key]) const count = parseInt(countTotals[key])
countTotals[key] = count ? count : 0 countTotals[key] = count ? count : 0
}) })
//Build out tags object
let tagsObject = {}
rows[0].forEach(tagRow => {
tagsObject[tagRow['text']] = {'id':tagRow.id, 'uses':tagRow.uses}
})
//Assign after counts are updated
countTotals['tags'] = tagsObject
countTotals['currentVersion'] = version countTotals['currentVersion'] = version
resolve(countTotals) resolve(countTotals)